Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(125)

Side by Side Diff: packages/intl/lib/src/intl/bidi_utils.dart

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

Powered by Google App Engine
This is Rietveld 408576698