| 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 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 65 | 65 |
| 66 /** Constant to define the threshold of RTL directionality. */ | 66 /** Constant to define the threshold of RTL directionality. */ |
| 67 num _RTL_DETECTION_THRESHOLD = 0.40; | 67 num _RTL_DETECTION_THRESHOLD = 0.40; |
| 68 | 68 |
| 69 /** | 69 /** |
| 70 * Practical patterns to identify strong LTR and RTL characters, respectively. | 70 * Practical patterns to identify strong LTR and RTL characters, respectively. |
| 71 * These patterns are not completely correct according to the Unicode | 71 * These patterns are not completely correct according to the Unicode |
| 72 * standard. They are simplified for performance and small code size. | 72 * standard. They are simplified for performance and small code size. |
| 73 */ | 73 */ |
| 74 const String _LTR_CHARS = | 74 const String _LTR_CHARS = |
| 75 @'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590' | 75 r'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590' |
| 76 @'\u0800-\u1FFF\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF'; | 76 r'\u0800-\u1FFF\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF'; |
| 77 const String _RTL_CHARS = @'\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC'; | 77 const String _RTL_CHARS = r'\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC'; |
| 78 | 78 |
| 79 /** | 79 /** |
| 80 * Returns the input [text] with spaces instead of HTML tags or HTML escapes, | 80 * Returns the input [text] with spaces instead of HTML tags or HTML escapes, |
| 81 * which is helpful for text directionality estimation. | 81 * which is helpful for text directionality estimation. |
| 82 * Note: This function should not be used in other contexts. | 82 * Note: This function should not be used in other contexts. |
| 83 * It does not deal well with many things: comments, script, | 83 * It does not deal well with many things: comments, script, |
| 84 * elements, style elements, dir attribute,`>` in quoted attribute values, | 84 * elements, style elements, dir attribute,`>` in quoted attribute values, |
| 85 * etc. But it does handle well enough the most common use cases. | 85 * 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 | 86 * 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 | 87 * the wrong directionality will be estimated, we have not invested in |
| 88 * improving this. | 88 * improving this. |
| 89 */ | 89 */ |
| 90 String stripHtmlIfNeeded(String text) { | 90 String stripHtmlIfNeeded(String text) { |
| 91 // The regular expression is simplified for an HTML tag (opening or | 91 // 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 | 92 // closing) or an HTML escape. We might want to skip over such expressions |
| 93 // when estimating the text directionality. | 93 // when estimating the text directionality. |
| 94 return text.replaceAll(const RegExp(@'<[^>]*>|&[^;]+;'), ' '); | 94 return text.replaceAll(const RegExp(r'<[^>]*>|&[^;]+;'), ' '); |
| 95 } | 95 } |
| 96 | 96 |
| 97 /** | 97 /** |
| 98 * Determines if the first character in [text] with strong directionality is | 98 * Determines if the first character in [text] with strong directionality is |
| 99 * LTR. If [isHtml] is true, the text is HTML or HTML-escaped. | 99 * LTR. If [isHtml] is true, the text is HTML or HTML-escaped. |
| 100 */ | 100 */ |
| 101 bool startsWithLtr(String text, [isHtml=false]) { | 101 bool startsWithLtr(String text, [isHtml=false]) { |
| 102 return const RegExp('^[^$_RTL_CHARS]*[$_LTR_CHARS]').hasMatch( | 102 return const RegExp('^[^$_RTL_CHARS]*[$_LTR_CHARS]').hasMatch( |
| 103 isHtml? stripHtmlIfNeeded(text) : text); | 103 isHtml? stripHtmlIfNeeded(text) : text); |
| 104 } | 104 } |
| (...skipping 25 matching lines...) Expand all Loading... |
| 130 bool endsWithRtl(String text, [isHtml=false]) { | 130 bool endsWithRtl(String text, [isHtml=false]) { |
| 131 return const RegExp('[$_RTL_CHARS][^$_LTR_CHARS]*\$').hasMatch( | 131 return const RegExp('[$_RTL_CHARS][^$_LTR_CHARS]*\$').hasMatch( |
| 132 isHtml? stripHtmlIfNeeded(text) : text); | 132 isHtml? stripHtmlIfNeeded(text) : text); |
| 133 } | 133 } |
| 134 | 134 |
| 135 /** | 135 /** |
| 136 * Determines if the given [text] has any LTR characters in it. | 136 * Determines if the given [text] has any LTR characters in it. |
| 137 * If [isHtml] is true, the text is HTML or HTML-escaped. | 137 * If [isHtml] is true, the text is HTML or HTML-escaped. |
| 138 */ | 138 */ |
| 139 bool hasAnyLtr(String text, [isHtml=false]) { | 139 bool hasAnyLtr(String text, [isHtml=false]) { |
| 140 return const RegExp(@'[' '$_LTR_CHARS' @']').hasMatch( | 140 return const RegExp(r'[' '$_LTR_CHARS' r']').hasMatch( |
| 141 isHtml? stripHtmlIfNeeded(text) : text); | 141 isHtml? stripHtmlIfNeeded(text) : text); |
| 142 } | 142 } |
| 143 | 143 |
| 144 /** | 144 /** |
| 145 * Determines if the given [text] has any RTL characters in it. | 145 * Determines if the given [text] has any RTL characters in it. |
| 146 * If [isHtml] is true, the text is HTML or HTML-escaped. | 146 * If [isHtml] is true, the text is HTML or HTML-escaped. |
| 147 */ | 147 */ |
| 148 bool hasAnyRtl(String text, [isHtml=false]) { | 148 bool hasAnyRtl(String text, [isHtml=false]) { |
| 149 return const RegExp(@'[' '$_RTL_CHARS' @']').hasMatch( | 149 return const RegExp(r'[' '$_RTL_CHARS' r']').hasMatch( |
| 150 isHtml? stripHtmlIfNeeded(text) : text); | 150 isHtml? stripHtmlIfNeeded(text) : text); |
| 151 } | 151 } |
| 152 | 152 |
| 153 /** | 153 /** |
| 154 * Check if a BCP 47 / III [languageString] indicates an RTL language. | 154 * Check if a BCP 47 / III [languageString] indicates an RTL language. |
| 155 * | 155 * |
| 156 * i.e. either: | 156 * i.e. either: |
| 157 * - a language code explicitly specifying one of the right-to-left scripts, | 157 * - a language code explicitly specifying one of the right-to-left scripts, |
| 158 * e.g. "az-Arab", or | 158 * e.g. "az-Arab", or |
| 159 * - a language code specifying one of the languages normally written in a | 159 * - a language code specifying one of the languages normally written in a |
| 160 * right-to-left script, e.g. "fa" (Farsi), except ones explicitly | 160 * right-to-left script, e.g. "fa" (Farsi), except ones explicitly |
| 161 * specifying Latin or Cyrillic script (which are the usual LTR | 161 * specifying Latin or Cyrillic script (which are the usual LTR |
| 162 * alternatives). | 162 * alternatives). |
| 163 * | 163 * |
| 164 * The list of right-to-left scripts appears in the 100-199 range in | 164 * 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 | 165 * 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 | 166 * 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, | 167 * Tifinagh, which also have significant modern usage. The rest (Syriac, |
| 168 * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage | 168 * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage |
| 169 * and are not recognized. | 169 * and are not recognized. |
| 170 * The languages usually written in a right-to-left script are taken as those | 170 * The languages usually written in a right-to-left script are taken as those |
| 171 * with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng in | 171 * with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng in |
| 172 * http://www.iana.org/assignments/language-subtag-registry, | 172 * http://www.iana.org/assignments/language-subtag-registry, |
| 173 * as well as Sindhi (sd) and Uyghur (ug). | 173 * as well as Sindhi (sd) and Uyghur (ug). |
| 174 * The presence of other subtags of the language code, e.g. regions like EG | 174 * The presence of other subtags of the language code, e.g. regions like EG |
| 175 * (Egypt), is ignored. | 175 * (Egypt), is ignored. |
| 176 */ | 176 */ |
| 177 bool isRtlLanguage(String languageString) { | 177 bool isRtlLanguage(String languageString) { |
| 178 return const RegExp(@'^(ar|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|.*[-_]' | 178 return const RegExp(r'^(ar|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|.*[-_]' |
| 179 @'(Arab|Hebr|Thaa|Nkoo|Tfng))(?!.*[-_](Latn|Cyrl)($|-|_))' | 179 r'(Arab|Hebr|Thaa|Nkoo|Tfng))(?!.*[-_](Latn|Cyrl)($|-|_))' |
| 180 @'($|-|_)', ignoreCase : true).hasMatch(languageString); | 180 r'($|-|_)', ignoreCase : true).hasMatch(languageString); |
| 181 } | 181 } |
| 182 | 182 |
| 183 /** | 183 /** |
| 184 * Enforce the [html] snippet in RTL directionality regardless of overall | 184 * 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 | 185 * 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. | 186 * 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 | 187 * 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 | 188 * 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 | 189 * will override existing one in behavior (should work on Chrome, FF, and IE |
| 190 * since this was ported directly from the Closure version). | 190 * since this was ported directly from the Closure version). |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 247 | 247 |
| 248 /** | 248 /** |
| 249 * Apply bracket guard to [str] using html span tag. This is to address the | 249 * 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. | 250 * 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 | 251 * If [isRtlContext] is true, then we explicitly want to wrap in a span of RTL |
| 252 * directionality, regardless of the estimated directionality. | 252 * directionality, regardless of the estimated directionality. |
| 253 */ | 253 */ |
| 254 String guardBracketInHtml(String str, [bool isRtlContext]) { | 254 String guardBracketInHtml(String str, [bool isRtlContext]) { |
| 255 var useRtl = isRtlContext == null ? hasAnyRtl(str) : isRtlContext; | 255 var useRtl = isRtlContext == null ? hasAnyRtl(str) : isRtlContext; |
| 256 RegExp matchingBrackets = | 256 RegExp matchingBrackets = |
| 257 const RegExp(@'(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?(>)+)'); | 257 const RegExp(r'(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?(>)+)'); |
| 258 return _guardBracketHelper(str, matchingBrackets, | 258 return _guardBracketHelper(str, matchingBrackets, |
| 259 '<span dir=${useRtl? "rtl" : "ltr"}>', '</span>'); | 259 '<span dir=${useRtl? "rtl" : "ltr"}>', '</span>'); |
| 260 } | 260 } |
| 261 | 261 |
| 262 /** | 262 /** |
| 263 * Apply bracket guard to [str] using LRM and RLM. This is to address the | 263 * 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. | 264 * 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 | 265 * This version works for both plain text and html, but in some cases is not |
| 266 * as good as guardBracketInHtml. | 266 * as good as guardBracketInHtml. |
| 267 * If [isRtlContext] is true, then we explicitly want to wrap in a span of RTL | 267 * If [isRtlContext] is true, then we explicitly want to wrap in a span of RTL |
| 268 * directionality, regardless of the estimated directionality. | 268 * directionality, regardless of the estimated directionality. |
| 269 */ | 269 */ |
| 270 String guardBracketInText(String str, [bool isRtlContext]) { | 270 String guardBracketInText(String str, [bool isRtlContext]) { |
| 271 var useRtl = isRtlContext == null ? hasAnyRtl(str) : isRtlContext; | 271 var useRtl = isRtlContext == null ? hasAnyRtl(str) : isRtlContext; |
| 272 var mark = useRtl ? RLM : LRM; | 272 var mark = useRtl ? RLM : LRM; |
| 273 return _guardBracketHelper(str, | 273 return _guardBracketHelper(str, |
| 274 const RegExp(@'(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)'), mark, mark); | 274 const RegExp(r'(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)'), mark, mark); |
| 275 } | 275 } |
| 276 | 276 |
| 277 /** | 277 /** |
| 278 * (Mostly) reimplements the $& functionality of "replace" in JavaScript. | 278 * (Mostly) reimplements the $& functionality of "replace" in JavaScript. |
| 279 * Given a [str] and the [regexp] to match with, optionally supply a string to | 279 * 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, | 280 * be inserted [before] the match and/or [after]. For example, |
| 281 * `_guardBracketHelper('firetruck', const RegExp('truck'), 'hydrant', '!')` | 281 * `_guardBracketHelper('firetruck', const RegExp('truck'), 'hydrant', '!')` |
| 282 * would return 'firehydrant!'. | 282 * would return 'firehydrant!'. |
| 283 */ | 283 */ |
| 284 // TODO(efortuna): Get rid of this once this is implemented in Dart. | 284 // TODO(efortuna): Get rid of this once this is implemented in Dart. |
| (...skipping 23 matching lines...) Expand all Loading... |
| 308 * Otherwise, returns UNKNOWN, which is used to mean `neutral`. | 308 * Otherwise, returns UNKNOWN, which is used to mean `neutral`. |
| 309 * Numbers and URLs are counted as weakly LTR. | 309 * Numbers and URLs are counted as weakly LTR. |
| 310 */ | 310 */ |
| 311 TextDirection estimateDirectionOfText(String text, [bool isHtml=false]) { | 311 TextDirection estimateDirectionOfText(String text, [bool isHtml=false]) { |
| 312 text = isHtml? stripHtmlIfNeeded(text) : text; | 312 text = isHtml? stripHtmlIfNeeded(text) : text; |
| 313 var rtlCount = 0; | 313 var rtlCount = 0; |
| 314 var total = 0; | 314 var total = 0; |
| 315 var hasWeaklyLtr = false; | 315 var hasWeaklyLtr = false; |
| 316 // Split a string into 'words' for directionality estimation based on | 316 // Split a string into 'words' for directionality estimation based on |
| 317 // relative word counts. | 317 // relative word counts. |
| 318 for (String token in text.split(const RegExp(@'\s+'))) { | 318 for (String token in text.split(const RegExp(r'\s+'))) { |
| 319 if (startsWithRtl(token)) { | 319 if (startsWithRtl(token)) { |
| 320 rtlCount++; | 320 rtlCount++; |
| 321 total++; | 321 total++; |
| 322 } else if (const RegExp(@'^http://').hasMatch(token)) { | 322 } else if (const RegExp(r'^http://').hasMatch(token)) { |
| 323 // Checked if token looks like something that must always be LTR even in | 323 // Checked if token looks like something that must always be LTR even in |
| 324 // RTL text, such as a URL. | 324 // RTL text, such as a URL. |
| 325 hasWeaklyLtr = true; | 325 hasWeaklyLtr = true; |
| 326 } else if (hasAnyLtr(token)) { | 326 } else if (hasAnyLtr(token)) { |
| 327 total++; | 327 total++; |
| 328 } else if (const RegExp(@'\d').hasMatch(token)) { | 328 } else if (const RegExp(r'\d').hasMatch(token)) { |
| 329 // Checked if token contains any numerals. | 329 // Checked if token contains any numerals. |
| 330 hasWeaklyLtr = true; | 330 hasWeaklyLtr = true; |
| 331 } | 331 } |
| 332 } | 332 } |
| 333 | 333 |
| 334 if (total == 0) { | 334 if (total == 0) { |
| 335 return hasWeaklyLtr ? TextDirection.LTR : TextDirection.UNKNOWN; | 335 return hasWeaklyLtr ? TextDirection.LTR : TextDirection.UNKNOWN; |
| 336 } else if (rtlCount > _RTL_DETECTION_THRESHOLD * total) { | 336 } else if (rtlCount > _RTL_DETECTION_THRESHOLD * total) { |
| 337 return TextDirection.RTL; | 337 return TextDirection.RTL; |
| 338 } else { | 338 } else { |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 382 } | 382 } |
| 383 | 383 |
| 384 /** | 384 /** |
| 385 * Check the estimated directionality of [str], return true if the piece of | 385 * 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 | 386 * text should be laid out in RTL direction. If [isHtml] is true, the string |
| 387 * is HTML or HTML-escaped. | 387 * is HTML or HTML-escaped. |
| 388 */ | 388 */ |
| 389 bool detectRtlDirectionality(String str, [bool isHtml]) { | 389 bool detectRtlDirectionality(String str, [bool isHtml]) { |
| 390 return estimateDirectionOfText(str, isHtml) == TextDirection.RTL; | 390 return estimateDirectionOfText(str, isHtml) == TextDirection.RTL; |
| 391 } | 391 } |
| OLD | NEW |