Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * Bidi stands for Bi-directional text. | 6 * Bidi stands for Bi-directional text. |
| 7 * According to http://en.wikipedia.org/wiki/Bi-directional_text: | 7 * According to http://en.wikipedia.org/wiki/Bi-directional_text: |
| 8 * Bi-directional text is text containing text in both text directionalities, | 8 * Bi-directional text is text containing text in both text directionalities, |
| 9 * both right-to-left (RTL) and left-to-right (LTR). It generally involves text | 9 * both right-to-left (RTL) and left-to-right (LTR). It generally involves text |
| 10 * containing different types of alphabets, but may also refer to boustrophedon, | 10 * containing different types of alphabets, but may also refer to boustrophedon, |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 21 * bidi_utils.dart directly. | 21 * bidi_utils.dart directly. |
| 22 */ | 22 */ |
| 23 class TextDirection { | 23 class TextDirection { |
| 24 static const LTR = const TextDirection._('LTR', 'ltr'); | 24 static const LTR = const TextDirection._('LTR', 'ltr'); |
| 25 static const RTL = const TextDirection._('RTL', 'rtl'); | 25 static const RTL = const TextDirection._('RTL', 'rtl'); |
| 26 // If the directionality of the text cannot be determined and we are not using | 26 // If the directionality of the text cannot be determined and we are not using |
| 27 // the context direction (or if the context direction is unknown), then the | 27 // the context direction (or if the context direction is unknown), then the |
| 28 // text falls back on the more common ltr direction. | 28 // text falls back on the more common ltr direction. |
| 29 static const UNKNOWN = const TextDirection._('UNKNOWN', 'ltr'); | 29 static const UNKNOWN = const TextDirection._('UNKNOWN', 'ltr'); |
| 30 | 30 |
| 31 /** | 31 /** |
| 32 * Textual representation of the directionality constant. One of | 32 * Textual representation of the directionality constant. One of |
| 33 * 'LTR', 'RTL', or 'UNKNOWN'. | 33 * 'LTR', 'RTL', or 'UNKNOWN'. |
| 34 */ | 34 */ |
| 35 final String value; | 35 final String value; |
| 36 | 36 |
| 37 /** Textual representation of the directionality when used in span tag. */ | 37 /** Textual representation of the directionality when used in span tag. */ |
| 38 final String spanText; | 38 final String spanText; |
| 39 | 39 |
| 40 const TextDirection._(this.value, this.spanText); | 40 const TextDirection._(this.value, this.spanText); |
| 41 | 41 |
| 42 /** | 42 /** |
| 43 * Returns true if [otherDirection] is known to be different from this | 43 * Returns true if [otherDirection] is known to be different from this |
| 44 * direction. | 44 * direction. |
| 45 */ | 45 */ |
| 46 bool isDirectionChange(TextDirection otherDirection) { | 46 bool isDirectionChange(TextDirection otherDirection) { |
| 47 return otherDirection != TextDirection.UNKNOWN && this != otherDirection; | 47 return otherDirection != TextDirection.UNKNOWN && this != otherDirection; |
| 48 } | 48 } |
| 49 } | 49 } |
| 50 | 50 |
| 51 /** | |
| 52 * This provides a number of utility methods for working with bidirectional | |
| 53 * text. ####### | |
|
Emily Fortuna
2012/10/04 18:37:55
is this a marker for more documentation suggested?
Alan Knight
2012/10/04 19:34:22
Oops. That was a temporary marker that I should ha
| |
| 54 */ | |
| 55 class Bidi { | |
| 56 | |
| 51 /** Unicode "Left-To-Right Embedding" (LRE) character. */ | 57 /** Unicode "Left-To-Right Embedding" (LRE) character. */ |
|
Emily Fortuna
2012/10/04 18:37:55
can you fix the indentation of all of these lines
Alan Knight
2012/10/04 19:34:22
Done.
| |
| 52 const LRE = '\u202A'; | 58 static const LRE = '\u202A'; |
| 53 | 59 |
| 54 /** Unicode "Right-To-Left Embedding" (RLE) character. */ | 60 /** Unicode "Right-To-Left Embedding" (RLE) character. */ |
| 55 const RLE = '\u202B'; | 61 static const RLE = '\u202B'; |
| 56 | 62 |
| 57 /** Unicode "Pop Directional Formatting" (PDF) character. */ | 63 /** Unicode "Pop Directional Formatting" (PDF) character. */ |
| 58 const PDF = '\u202C'; | 64 static const PDF = '\u202C'; |
| 59 | 65 |
| 60 /** Unicode "Left-To-Right Mark" (LRM) character. */ | 66 /** Unicode "Left-To-Right Mark" (LRM) character. */ |
| 61 const LRM = '\u200E'; | 67 static const LRM = '\u200E'; |
| 62 | 68 |
| 63 /** Unicode "Right-To-Left Mark" (RLM) character. */ | 69 /** Unicode "Right-To-Left Mark" (RLM) character. */ |
| 64 const RLM = '\u200F'; | 70 static const RLM = '\u200F'; |
| 65 | 71 |
| 66 /** Constant to define the threshold of RTL directionality. */ | 72 /** Constant to define the threshold of RTL directionality. */ |
| 67 num _RTL_DETECTION_THRESHOLD = 0.40; | 73 static num _RTL_DETECTION_THRESHOLD = 0.40; |
| 68 | 74 |
| 69 /** | 75 /** |
| 70 * Practical patterns to identify strong LTR and RTL characters, respectively. | 76 * Practical patterns to identify strong LTR and RTL characters, respectively. |
| 71 * These patterns are not completely correct according to the Unicode | 77 * These patterns are not completely correct according to the Unicode |
| 72 * standard. They are simplified for performance and small code size. | 78 * standard. They are simplified for performance and small code size. |
| 73 */ | 79 */ |
| 74 const String _LTR_CHARS = | 80 static const String _LTR_CHARS = |
| 75 r'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590' | 81 r'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590' |
| 76 r'\u0800-\u1FFF\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF'; | 82 r'\u0800-\u1FFF\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF'; |
| 77 const String _RTL_CHARS = r'\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC'; | 83 static const String _RTL_CHARS = r'\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC'; |
| 78 | 84 |
| 79 /** | 85 /** |
| 80 * Returns the input [text] with spaces instead of HTML tags or HTML escapes, | 86 * Returns the input [text] with spaces instead of HTML tags or HTML escapes, |
| 81 * which is helpful for text directionality estimation. | 87 * which is helpful for text directionality estimation. |
| 82 * Note: This function should not be used in other contexts. | 88 * Note: This function should not be used in other contexts. |
| 83 * It does not deal well with many things: comments, script, | 89 * It does not deal well with many things: comments, script, |
| 84 * elements, style elements, dir attribute,`>` in quoted attribute values, | 90 * elements, style elements, dir attribute,`>` in quoted attribute values, |
| 85 * etc. But it does handle well enough the most common use cases. | 91 * etc. But it does handle well enough the most common use cases. |
| 86 * Since the worst that can happen as a result of these shortcomings is that | 92 * Since the worst that can happen as a result of these shortcomings is that |
| 87 * the wrong directionality will be estimated, we have not invested in | 93 * the wrong directionality will be estimated, we have not invested in |
| 88 * improving this. | 94 * improving this. |
| 89 */ | 95 */ |
| 90 String stripHtmlIfNeeded(String text) { | 96 static String stripHtmlIfNeeded(String text) { |
| 91 // The regular expression is simplified for an HTML tag (opening or | 97 // The regular expression is simplified for an HTML tag (opening or |
| 92 // closing) or an HTML escape. We might want to skip over such expressions | 98 // closing) or an HTML escape. We might want to skip over such expressions |
| 93 // when estimating the text directionality. | 99 // when estimating the text directionality. |
| 94 return text.replaceAll(const RegExp(r'<[^>]*>|&[^;]+;'), ' '); | 100 return text.replaceAll(const RegExp(r'<[^>]*>|&[^;]+;'), ' '); |
| 95 } | 101 } |
| 96 | 102 |
| 97 /** | 103 /** |
| 98 * Determines if the first character in [text] with strong directionality is | 104 * Determines if the first character in [text] with strong directionality is |
| 99 * LTR. If [isHtml] is true, the text is HTML or HTML-escaped. | 105 * LTR. If [isHtml] is true, the text is HTML or HTML-escaped. |
| 100 */ | 106 */ |
| 101 bool startsWithLtr(String text, [isHtml=false]) { | 107 static bool startsWithLtr(String text, [isHtml=false]) { |
| 102 return const RegExp('^[^$_RTL_CHARS]*[$_LTR_CHARS]').hasMatch( | 108 return const RegExp('^[^$_RTL_CHARS]*[$_LTR_CHARS]').hasMatch( |
| 103 isHtml? stripHtmlIfNeeded(text) : text); | 109 isHtml? stripHtmlIfNeeded(text) : text); |
| 104 } | 110 } |
| 105 | 111 |
| 106 /** | 112 /** |
| 107 * Determines if the first character in [text] with strong directionality is | 113 * Determines if the first character in [text] with strong directionality is |
| 108 * RTL. If [isHtml] is true, the text is HTML or HTML-escaped. | 114 * RTL. If [isHtml] is true, the text is HTML or HTML-escaped. |
| 109 */ | 115 */ |
| 110 bool startsWithRtl(String text, [isHtml=false]) { | 116 static bool startsWithRtl(String text, [isHtml=false]) { |
| 111 return const RegExp('^[^$_LTR_CHARS]*[$_RTL_CHARS]').hasMatch( | 117 return const RegExp('^[^$_LTR_CHARS]*[$_RTL_CHARS]').hasMatch( |
| 112 isHtml? stripHtmlIfNeeded(text) : text); | 118 isHtml? stripHtmlIfNeeded(text) : text); |
| 113 } | 119 } |
| 114 | 120 |
| 115 /** | 121 /** |
| 116 * Determines if the exit directionality (ie, the last strongly-directional | 122 * Determines if the exit directionality (ie, the last strongly-directional |
| 117 * character in [text] is LTR. If [isHtml] is true, the text is HTML or | 123 * character in [text] is LTR. If [isHtml] is true, the text is HTML or |
| 118 * HTML-escaped. | 124 * HTML-escaped. |
| 119 */ | 125 */ |
| 120 bool endsWithLtr(String text, [isHtml=false]) { | 126 static bool endsWithLtr(String text, [isHtml=false]) { |
| 121 return const RegExp('[$_LTR_CHARS][^$_RTL_CHARS]*\$').hasMatch( | 127 return const RegExp('[$_LTR_CHARS][^$_RTL_CHARS]*\$').hasMatch( |
| 122 isHtml? stripHtmlIfNeeded(text) : text); | 128 isHtml? stripHtmlIfNeeded(text) : text); |
| 123 } | 129 } |
| 124 | 130 |
| 125 /** | 131 /** |
| 126 * Determines if the exit directionality (ie, the last strongly-directional | 132 * Determines if the exit directionality (ie, the last strongly-directional |
| 127 * character in [text] is RTL. If [isHtml] is true, the text is HTML or | 133 * character in [text] is RTL. If [isHtml] is true, the text is HTML or |
| 128 * HTML-escaped. | 134 * HTML-escaped. |
| 129 */ | 135 */ |
| 130 bool endsWithRtl(String text, [isHtml=false]) { | 136 static bool endsWithRtl(String text, [isHtml=false]) { |
| 131 return const RegExp('[$_RTL_CHARS][^$_LTR_CHARS]*\$').hasMatch( | 137 return const RegExp('[$_RTL_CHARS][^$_LTR_CHARS]*\$').hasMatch( |
| 132 isHtml? stripHtmlIfNeeded(text) : text); | 138 isHtml? stripHtmlIfNeeded(text) : text); |
| 133 } | 139 } |
| 134 | 140 |
| 135 /** | 141 /** |
| 136 * Determines if the given [text] has any LTR characters in it. | 142 * Determines if the given [text] has any LTR characters in it. |
| 137 * If [isHtml] is true, the text is HTML or HTML-escaped. | 143 * If [isHtml] is true, the text is HTML or HTML-escaped. |
| 138 */ | 144 */ |
| 139 bool hasAnyLtr(String text, [isHtml=false]) { | 145 static bool hasAnyLtr(String text, [isHtml=false]) { |
| 140 return const RegExp(r'[' '$_LTR_CHARS' r']').hasMatch( | 146 return const RegExp(r'[' '$_LTR_CHARS' r']').hasMatch( |
| 141 isHtml? stripHtmlIfNeeded(text) : text); | 147 isHtml? stripHtmlIfNeeded(text) : text); |
| 142 } | 148 } |
| 143 | 149 |
| 144 /** | 150 /** |
| 145 * Determines if the given [text] has any RTL characters in it. | 151 * Determines if the given [text] has any RTL characters in it. |
| 146 * If [isHtml] is true, the text is HTML or HTML-escaped. | 152 * If [isHtml] is true, the text is HTML or HTML-escaped. |
| 147 */ | 153 */ |
| 148 bool hasAnyRtl(String text, [isHtml=false]) { | 154 static bool hasAnyRtl(String text, [isHtml=false]) { |
| 149 return const RegExp(r'[' '$_RTL_CHARS' r']').hasMatch( | 155 return const RegExp(r'[' '$_RTL_CHARS' r']').hasMatch( |
| 150 isHtml? stripHtmlIfNeeded(text) : text); | 156 isHtml? stripHtmlIfNeeded(text) : text); |
| 151 } | 157 } |
| 152 | 158 |
| 153 /** | 159 /** |
| 154 * Check if a BCP 47 / III [languageString] indicates an RTL language. | 160 * Check if a BCP 47 / III [languageString] indicates an RTL language. |
| 155 * | 161 * |
| 156 * i.e. either: | 162 * i.e. either: |
| 157 * - a language code explicitly specifying one of the right-to-left scripts, | 163 * - a language code explicitly specifying one of the right-to-left scripts, |
| 158 * e.g. "az-Arab", or | 164 * e.g. "az-Arab", or |
| 159 * - a language code specifying one of the languages normally written in a | 165 * - a language code specifying one of the languages normally written in a |
| 160 * right-to-left script, e.g. "fa" (Farsi), except ones explicitly | 166 * right-to-left script, e.g. "fa" (Farsi), except ones explicitly |
| 161 * specifying Latin or Cyrillic script (which are the usual LTR | 167 * specifying Latin or Cyrillic script (which are the usual LTR |
| 162 * alternatives). | 168 * alternatives). |
| 163 * | 169 * |
| 164 * The list of right-to-left scripts appears in the 100-199 range in | 170 * The list of right-to-left scripts appears in the 100-199 range in |
| 165 * http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and | 171 * http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and |
| 166 * Hebrew are by far the most widely used. We also recognize Thaana, N'Ko, and | 172 * Hebrew are by far the most widely used. We also recognize Thaana, N'Ko, and |
| 167 * Tifinagh, which also have significant modern usage. The rest (Syriac, | 173 * Tifinagh, which also have significant modern usage. The rest (Syriac, |
| 168 * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage | 174 * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage |
| 169 * and are not recognized. | 175 * and are not recognized. |
| 170 * The languages usually written in a right-to-left script are taken as those | 176 * The languages usually written in a right-to-left script are taken as those |
| 171 * with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng in | 177 * with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng in |
| 172 * http://www.iana.org/assignments/language-subtag-registry, | 178 * http://www.iana.org/assignments/language-subtag-registry, |
| 173 * as well as Sindhi (sd) and Uyghur (ug). | 179 * as well as Sindhi (sd) and Uyghur (ug). |
| 174 * The presence of other subtags of the language code, e.g. regions like EG | 180 * The presence of other subtags of the language code, e.g. regions like EG |
| 175 * (Egypt), is ignored. | 181 * (Egypt), is ignored. |
| 176 */ | 182 */ |
| 177 bool isRtlLanguage(String languageString) { | 183 static bool isRtlLanguage(String languageString) { |
| 178 return const RegExp(r'^(ar|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|.*[-_]' | 184 return const RegExp(r'^(ar|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|.*[-_]' |
| 179 r'(Arab|Hebr|Thaa|Nkoo|Tfng))(?!.*[-_](Latn|Cyrl)($|-|_))' | 185 r'(Arab|Hebr|Thaa|Nkoo|Tfng))(?!.*[-_](Latn|Cyrl)($|-|_))' |
| 180 r'($|-|_)', ignoreCase : true).hasMatch(languageString); | 186 r'($|-|_)', ignoreCase : true).hasMatch(languageString); |
| 181 } | 187 } |
| 182 | 188 |
| 183 /** | 189 /** |
| 184 * Enforce the [html] snippet in RTL directionality regardless of overall | 190 * Enforce the [html] snippet in RTL directionality regardless of overall |
| 185 * context. If the html piece was enclosed by a tag, the direction will be | 191 * context. If the html piece was enclosed by a tag, the direction will be |
| 186 * applied to existing tag, otherwise a span tag will be added as wrapper. | 192 * applied to existing tag, otherwise a span tag will be added as wrapper. |
| 187 * For this reason, if html snippet start with with tag, this tag must enclose | 193 * For this reason, if html snippet start with with tag, this tag must enclose |
| 188 * the whole piece. If the tag already has a direction specified, this new one | 194 * the whole piece. If the tag already has a direction specified, this new one |
| 189 * will override existing one in behavior (should work on Chrome, FF, and IE | 195 * will override existing one in behavior (should work on Chrome, FF, and IE |
| 190 * since this was ported directly from the Closure version). | 196 * since this was ported directly from the Closure version). |
| 191 */ | 197 */ |
| 192 String enforceRtlInHtml(String html) { | 198 static String enforceRtlInHtml(String html) { |
| 193 return _enforceInHtmlHelper(html, 'rtl'); | 199 return _enforceInHtmlHelper(html, 'rtl'); |
| 194 } | 200 } |
| 195 | 201 |
| 196 /** | 202 /** |
| 197 * Enforce RTL on both end of the given [text] using unicode BiDi formatting | 203 * Enforce RTL on both end of the given [text] using unicode BiDi formatting |
| 198 * characters RLE and PDF. | 204 * characters RLE and PDF. |
| 199 */ | 205 */ |
| 200 String enforceRtlInText(String text) { | 206 static String enforceRtlInText(String text) { |
| 201 return '$RLE$text$PDF'; | 207 return '$RLE$text$PDF'; |
| 202 } | 208 } |
| 203 | 209 |
| 204 /** | 210 /** |
| 205 * Enforce the [html] snippet in LTR directionality regardless of overall | 211 * Enforce the [html] snippet in LTR directionality regardless of overall |
| 206 * context. If the html piece was enclosed by a tag, the direction will be | 212 * context. If the html piece was enclosed by a tag, the direction will be |
| 207 * applied to existing tag, otherwise a span tag will be added as wrapper. | 213 * applied to existing tag, otherwise a span tag will be added as wrapper. |
| 208 * For this reason, if html snippet start with with tag, this tag must enclose | 214 * For this reason, if html snippet start with with tag, this tag must enclose |
| 209 * the whole piece. If the tag already has a direction specified, this new one | 215 * the whole piece. If the tag already has a direction specified, this new one |
| 210 * will override existing one in behavior (tested on FF and IE). | 216 * will override existing one in behavior (tested on FF and IE). |
| 211 */ | 217 */ |
| 212 String enforceLtrInHtml(String html) { | 218 static String enforceLtrInHtml(String html) { |
| 213 return _enforceInHtmlHelper(html, 'ltr'); | 219 return _enforceInHtmlHelper(html, 'ltr'); |
| 214 } | 220 } |
| 215 | 221 |
| 216 /** | 222 /** |
| 217 * Enforce LTR on both end of the given [text] using unicode BiDi formatting | 223 * Enforce LTR on both end of the given [text] using unicode BiDi formatting |
| 218 * characters LRE and PDF. | 224 * characters LRE and PDF. |
| 219 */ | 225 */ |
| 220 String enforceLtrInText(String text) { | 226 static String enforceLtrInText(String text) { |
| 221 return '$LRE$text$PDF'; | 227 return '$LRE$text$PDF'; |
| 222 } | 228 } |
| 223 | 229 |
| 224 /** | 230 /** |
| 225 * Enforce the [html] snippet in the desired [direction] regardless of overall | 231 * Enforce the [html] snippet in the desired [direction] regardless of overall |
| 226 * context. If the html piece was enclosed by a tag, the direction will be | 232 * context. If the html piece was enclosed by a tag, the direction will be |
| 227 * applied to existing tag, otherwise a span tag will be added as wrapper. | 233 * applied to existing tag, otherwise a span tag will be added as wrapper. |
| 228 * For this reason, if html snippet start with with tag, this tag must enclose | 234 * For this reason, if html snippet start with with tag, this tag must enclose |
| 229 * the whole piece. If the tag already has a direction specified, this new one | 235 * the whole piece. If the tag already has a direction specified, this new one |
| 230 * will override existing one in behavior (tested on FF and IE). | 236 * will override existing one in behavior (tested on FF and IE). |
| 231 */ | 237 */ |
| 232 String _enforceInHtmlHelper(String html, String direction) { | 238 static String _enforceInHtmlHelper(String html, String direction) { |
| 233 if (html.startsWith('<')) { | 239 if (html.startsWith('<')) { |
| 234 StringBuffer buffer = new StringBuffer(); | 240 StringBuffer buffer = new StringBuffer(); |
| 235 var startIndex = 0; | 241 var startIndex = 0; |
| 236 Match match = const RegExp('<\\w+').firstMatch(html); | 242 Match match = const RegExp('<\\w+').firstMatch(html); |
| 237 if (match != null) { | 243 if (match != null) { |
| 238 buffer.add(html.substring( | 244 buffer.add(html.substring( |
| 239 startIndex, match.end())).add(' dir=$direction'); | 245 startIndex, match.end())).add(' dir=$direction'); |
| 240 startIndex = match.end(); | 246 startIndex = match.end(); |
| 241 } | 247 } |
| 242 return buffer.add(html.substring(startIndex)).toString(); | 248 return buffer.add(html.substring(startIndex)).toString(); |
| 243 } | 249 } |
| 244 // '\n' is important for FF so that it won't incorrectly merge span groups. | 250 // '\n' is important for FF so that it won't incorrectly merge span groups. |
| 245 return '\n<span dir=$direction>$html</span>'; | 251 return '\n<span dir=$direction>$html</span>'; |
| 246 } | 252 } |
| 247 | 253 |
| 248 /** | 254 /** |
| 249 * Apply bracket guard to [str] using html span tag. This is to address the | 255 * Apply bracket guard to [str] using html span tag. This is to address the |
| 250 * problem of messy bracket display that frequently happens in RTL layout. | 256 * problem of messy bracket display that frequently happens in RTL layout. |
| 251 * If [isRtlContext] is true, then we explicitly want to wrap in a span of RTL | 257 * If [isRtlContext] is true, then we explicitly want to wrap in a span of RTL |
| 252 * directionality, regardless of the estimated directionality. | 258 * directionality, regardless of the estimated directionality. |
| 253 */ | 259 */ |
| 254 String guardBracketInHtml(String str, [bool isRtlContext]) { | 260 static String guardBracketInHtml(String str, [bool isRtlContext]) { |
| 255 var useRtl = isRtlContext == null ? hasAnyRtl(str) : isRtlContext; | 261 var useRtl = isRtlContext == null ? hasAnyRtl(str) : isRtlContext; |
| 256 RegExp matchingBrackets = | 262 RegExp matchingBrackets = |
| 257 const RegExp(r'(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?(>)+)'); | 263 const RegExp(r'(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?(>)+)'); |
| 258 return _guardBracketHelper(str, matchingBrackets, | 264 return _guardBracketHelper(str, matchingBrackets, |
| 259 '<span dir=${useRtl? "rtl" : "ltr"}>', '</span>'); | 265 '<span dir=${useRtl? "rtl" : "ltr"}>', '</span>'); |
| 260 } | 266 } |
| 261 | 267 |
| 262 /** | 268 /** |
| 263 * Apply bracket guard to [str] using LRM and RLM. This is to address the | 269 * Apply bracket guard to [str] using LRM and RLM. This is to address the |
| 264 * problem of messy bracket display that frequently happens in RTL layout. | 270 * problem of messy bracket display that frequently happens in RTL layout. |
| 265 * This version works for both plain text and html, but in some cases is not | 271 * This version works for both plain text and html, but in some cases is not |
| 266 * as good as guardBracketInHtml. | 272 * as good as guardBracketInHtml. |
| 267 * If [isRtlContext] is true, then we explicitly want to wrap in a span of RTL | 273 * If [isRtlContext] is true, then we explicitly want to wrap in a span of RTL |
| 268 * directionality, regardless of the estimated directionality. | 274 * directionality, regardless of the estimated directionality. |
| 269 */ | 275 */ |
| 270 String guardBracketInText(String str, [bool isRtlContext]) { | 276 static String guardBracketInText(String str, [bool isRtlContext]) { |
| 271 var useRtl = isRtlContext == null ? hasAnyRtl(str) : isRtlContext; | 277 var useRtl = isRtlContext == null ? hasAnyRtl(str) : isRtlContext; |
| 272 var mark = useRtl ? RLM : LRM; | 278 var mark = useRtl ? RLM : LRM; |
| 273 return _guardBracketHelper(str, | 279 return _guardBracketHelper(str, |
| 274 const RegExp(r'(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)'), mark, mark); | 280 const RegExp(r'(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)'), mark, mark); |
| 275 } | 281 } |
| 276 | 282 |
| 277 /** | 283 /** |
| 278 * (Mostly) reimplements the $& functionality of "replace" in JavaScript. | 284 * (Mostly) reimplements the $& functionality of "replace" in JavaScript. |
| 279 * Given a [str] and the [regexp] to match with, optionally supply a string to | 285 * Given a [str] and the [regexp] to match with, optionally supply a string to |
| 280 * be inserted [before] the match and/or [after]. For example, | 286 * be inserted [before] the match and/or [after]. For example, |
| 281 * `_guardBracketHelper('firetruck', const RegExp('truck'), 'hydrant', '!')` | 287 * `_guardBracketHelper('firetruck', const RegExp('truck'), 'hydrant', '!')` |
| 282 * would return 'firehydrant!'. | 288 * would return 'firehydrant!'. |
| 283 */ | 289 */ |
| 284 // TODO(efortuna): Get rid of this once this is implemented in Dart. | 290 // TODO(efortuna): Get rid of this once this is implemented in Dart. |
| 285 // See Issue 2979. | 291 // See Issue 2979. |
| 286 String _guardBracketHelper(String str, RegExp regexp, [String before, | 292 static String _guardBracketHelper(String str, RegExp regexp, [String before, |
| 287 String after]) { | 293 String after]) { |
| 288 StringBuffer buffer = new StringBuffer(); | 294 StringBuffer buffer = new StringBuffer(); |
| 289 var startIndex = 0; | 295 var startIndex = 0; |
| 290 Iterable matches = regexp.allMatches(str); | 296 Iterable matches = regexp.allMatches(str); |
| 291 for (Match match in matches) { | 297 for (Match match in matches) { |
| 292 buffer.add(str.substring(startIndex, match.start())).add(before); | 298 buffer.add(str.substring(startIndex, match.start())).add(before); |
| 293 buffer.add(str.substring(match.start(), match.end())).add(after); | 299 buffer.add(str.substring(match.start(), match.end())).add(after); |
| 294 startIndex = match.end(); | 300 startIndex = match.end(); |
| 295 } | 301 } |
| 296 return buffer.add(str.substring(startIndex)).toString(); | 302 return buffer.add(str.substring(startIndex)).toString(); |
| 297 } | 303 } |
| 298 | 304 |
| 299 /** | 305 /** |
| 300 * Estimates the directionality of [text] using the best known | 306 * Estimates the directionality of [text] using the best known |
| 301 * general-purpose method (using relative word counts). A | 307 * general-purpose method (using relative word counts). A |
| 302 * TextDirection.UNKNOWN return value indicates completely neutral input. | 308 * TextDirection.UNKNOWN return value indicates completely neutral input. |
| 303 * [isHtml] is true if [text] HTML or HTML-escaped. | 309 * [isHtml] is true if [text] HTML or HTML-escaped. |
| 304 * | 310 * |
| 305 * If the number of RTL words is above a certain percentage of the total | 311 * If the number of RTL words is above a certain percentage of the total |
| 306 * number of strongly directional words, returns RTL. | 312 * number of strongly directional words, returns RTL. |
| 307 * Otherwise, if any words are strongly or weakly LTR, returns LTR. | 313 * Otherwise, if any words are strongly or weakly LTR, returns LTR. |
| 308 * Otherwise, returns UNKNOWN, which is used to mean `neutral`. | 314 * Otherwise, returns UNKNOWN, which is used to mean `neutral`. |
| 309 * Numbers and URLs are counted as weakly LTR. | 315 * Numbers and URLs are counted as weakly LTR. |
| 310 */ | 316 */ |
| 311 TextDirection estimateDirectionOfText(String text, [bool isHtml=false]) { | 317 static TextDirection estimateDirectionOfText(String text, [bool isHtml=false]) { |
| 312 text = isHtml? stripHtmlIfNeeded(text) : text; | 318 text = isHtml? stripHtmlIfNeeded(text) : text; |
| 313 var rtlCount = 0; | 319 var rtlCount = 0; |
| 314 var total = 0; | 320 var total = 0; |
| 315 var hasWeaklyLtr = false; | 321 var hasWeaklyLtr = false; |
| 316 // Split a string into 'words' for directionality estimation based on | 322 // Split a string into 'words' for directionality estimation based on |
| 317 // relative word counts. | 323 // relative word counts. |
| 318 for (String token in text.split(const RegExp(r'\s+'))) { | 324 for (String token in text.split(const RegExp(r'\s+'))) { |
| 319 if (startsWithRtl(token)) { | 325 if (startsWithRtl(token)) { |
| 320 rtlCount++; | 326 rtlCount++; |
| 321 total++; | 327 total++; |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 337 return TextDirection.RTL; | 343 return TextDirection.RTL; |
| 338 } else { | 344 } else { |
| 339 return TextDirection.LTR; | 345 return TextDirection.LTR; |
| 340 } | 346 } |
| 341 } | 347 } |
| 342 | 348 |
| 343 /** | 349 /** |
| 344 * Find the first index in [str] of the first closing parenthesis that does | 350 * Find the first index in [str] of the first closing parenthesis that does |
| 345 * not match an opening parenthesis. | 351 * not match an opening parenthesis. |
| 346 */ | 352 */ |
| 347 int _unmatchedParenIndex(String str) { | 353 static int _unmatchedParenIndex(String str) { |
| 348 int sum = 0; | 354 int sum = 0; |
| 349 int index = 0; | 355 int index = 0; |
| 350 while (sum >= 0 || index > str.length) { | 356 while (sum >= 0 || index > str.length) { |
| 351 int char = str.charCodeAt(index); | 357 int char = str.charCodeAt(index); |
| 352 if (char == '('.charCodeAt(0)) sum++; | 358 if (char == '('.charCodeAt(0)) sum++; |
| 353 else if (char == ')'.charCodeAt(0)) sum--; | 359 else if (char == ')'.charCodeAt(0)) sum--; |
| 354 index++; | 360 index++; |
| 355 } | 361 } |
| 356 return index; | 362 return index; |
| 357 } | 363 } |
| 358 | 364 |
| 359 /** | 365 /** |
| 360 * Replace the double and single quote directly after a Hebrew character in | 366 * Replace the double and single quote directly after a Hebrew character in |
| 361 * [str] with GERESH and GERSHAYIM. This is most likely the user's intention. | 367 * [str] with GERESH and GERSHAYIM. This is most likely the user's intention. |
| 362 */ | 368 */ |
| 363 String normalizeHebrewQuote(String str) { | 369 static String normalizeHebrewQuote(String str) { |
| 364 StringBuffer buf = new StringBuffer(); | 370 StringBuffer buf = new StringBuffer(); |
| 365 if (str.length > 0) { | 371 if (str.length > 0) { |
| 366 buf.add(str.substring(0, 1)); | 372 buf.add(str.substring(0, 1)); |
| 367 } | 373 } |
| 368 // Start at 1 because we're looking for the patterns [\u0591-\u05f2])" or | 374 // Start at 1 because we're looking for the patterns [\u0591-\u05f2])" or |
| 369 // [\u0591-\u05f2]'. | 375 // [\u0591-\u05f2]'. |
| 370 for (int i = 1; i < str.length; i++) { | 376 for (int i = 1; i < str.length; i++) { |
| 371 if (str.substring(i, i+1) == '"' | 377 if (str.substring(i, i+1) == '"' |
| 372 && const RegExp('[\u0591-\u05f2]').hasMatch(str.substring(i-1, i))) { | 378 && const RegExp('[\u0591-\u05f2]').hasMatch(str.substring(i-1, i))) { |
| 373 buf.add('\u05f4'); | 379 buf.add('\u05f4'); |
| 374 } else if (str.substring(i, i+1) == "'" | 380 } else if (str.substring(i, i+1) == "'" |
| 375 && const RegExp('[\u0591-\u05f2]').hasMatch(str.substring(i-1, i))) { | 381 && const RegExp('[\u0591-\u05f2]').hasMatch(str.substring(i-1, i))) { |
| 376 buf.add('\u05f3'); | 382 buf.add('\u05f3'); |
| 377 } else { | 383 } else { |
| 378 buf.add(str.substring(i, i+1)); | 384 buf.add(str.substring(i, i+1)); |
| 379 } | 385 } |
| 380 } | 386 } |
| 381 return buf.toString(); | 387 return buf.toString(); |
| 382 } | 388 } |
| 383 | 389 |
| 384 /** | 390 /** |
| 385 * Check the estimated directionality of [str], return true if the piece of | 391 * Check the estimated directionality of [str], return true if the piece of |
| 386 * text should be laid out in RTL direction. If [isHtml] is true, the string | 392 * text should be laid out in RTL direction. If [isHtml] is true, the string |
| 387 * is HTML or HTML-escaped. | 393 * is HTML or HTML-escaped. |
| 388 */ | 394 */ |
| 389 bool detectRtlDirectionality(String str, [bool isHtml]) { | 395 static bool detectRtlDirectionality(String str, [bool isHtml]) { |
| 390 return estimateDirectionOfText(str, isHtml) == TextDirection.RTL; | 396 return estimateDirectionOfText(str, isHtml) == TextDirection.RTL; |
| 391 } | 397 } |
| 398 | |
|
Emily Fortuna
2012/10/04 18:37:55
You're going to love this... delete this extra lin
Alan Knight
2012/10/04 19:34:22
Done.
| |
| 399 } | |
| OLD | NEW |