OLD | NEW |
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2011, 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 // TODO(jacobr): handle splitting lines on symbols such as '-' that aren't | 5 // TODO(jacobr): handle splitting lines on symbols such as '-' that aren't |
6 // whitespace but are valid word breaking points. | 6 // whitespace but are valid word breaking points. |
7 /** | 7 /** |
8 * Utility class to efficiently word break and measure text without requiring | 8 * Utility class to efficiently word break and measure text without requiring |
9 * access to the DOM. | 9 * access to the DOM. |
10 */ | 10 */ |
11 class MeasureText { | 11 class MeasureText { |
12 static CanvasRenderingContext2D _context; | 12 static CanvasRenderingContext2D _context; |
13 | 13 |
14 final String font; | 14 final String font; |
15 num _spaceLength; | 15 num _spaceLength; |
16 num _typicalCharLength; | 16 num _typicalCharLength; |
17 | 17 |
18 static const String ELLIPSIS = '...'; | 18 static const String ELLIPSIS = '...'; |
19 | 19 |
20 MeasureText(this.font) { | 20 MeasureText(this.font) { |
21 if (_context === null) { | 21 if (_context == null) { |
22 CanvasElement canvas = new Element.tag('canvas'); | 22 CanvasElement canvas = new Element.tag('canvas'); |
23 _context = canvas.getContext('2d'); | 23 _context = canvas.getContext('2d'); |
24 } | 24 } |
25 if (_spaceLength === null) { | 25 if (_spaceLength == null) { |
26 _context.font = font; | 26 _context.font = font; |
27 _spaceLength = _context.measureText(' ').width; | 27 _spaceLength = _context.measureText(' ').width; |
28 _typicalCharLength = _context.measureText('k').width; | 28 _typicalCharLength = _context.measureText('k').width; |
29 } | 29 } |
30 } | 30 } |
31 | 31 |
32 // TODO(jacobr): we are DOA for i18N... | 32 // TODO(jacobr): we are DOA for i18N... |
33 // the right solution is for the server to send us text perparsed into words | 33 // the right solution is for the server to send us text perparsed into words |
34 // perhaps even with hints on the guess for the correct breaks so on the | 34 // perhaps even with hints on the guess for the correct breaks so on the |
35 // client all we have to do is verify and fix errors rather than perform the | 35 // client all we have to do is verify and fix errors rather than perform the |
(...skipping 16 matching lines...) Expand all Loading... |
52 if (targetLength < text.length) { | 52 if (targetLength < text.length) { |
53 return '${text.substring(0, targetLength)}$ELLIPSIS'; | 53 return '${text.substring(0, targetLength)}$ELLIPSIS'; |
54 } else { | 54 } else { |
55 return text; | 55 return text; |
56 } | 56 } |
57 } | 57 } |
58 | 58 |
59 /** | 59 /** |
60 * Add line broken text as html separated by <br> elements. | 60 * Add line broken text as html separated by <br> elements. |
61 * Returns the number of lines in the output. | 61 * Returns the number of lines in the output. |
62 * This function is safe to call with [:sb === null:] in which case just the | 62 * This function is safe to call with [:sb == null:] in which case just the |
63 * line count is returned. | 63 * line count is returned. |
64 */ | 64 */ |
65 int addLineBrokenText(StringBuffer sb, String text, num lineWidth, | 65 int addLineBrokenText(StringBuffer sb, String text, num lineWidth, |
66 int maxLines) { | 66 int maxLines) { |
67 // Strip surrounding whitespace. This ensures we create zero lines if there | 67 // Strip surrounding whitespace. This ensures we create zero lines if there |
68 // is no visible text. | 68 // is no visible text. |
69 text = text.trim(); | 69 text = text.trim(); |
70 | 70 |
71 // We can often avoid performing a full line break calculation when only | 71 // We can often avoid performing a full line break calculation when only |
72 // the number of lines and not the actual linebreaks is required. | 72 // the number of lines and not the actual linebreaks is required. |
73 if (sb === null) { | 73 if (sb == null) { |
74 _context.font = font; | 74 _context.font = font; |
75 int textWidth = _context.measureText(text).width.toInt(); | 75 int textWidth = _context.measureText(text).width.toInt(); |
76 // By the pigeon hole principle, the resulting text will require at least | 76 // By the pigeon hole principle, the resulting text will require at least |
77 // maxLines if the raw text is longer than the amount of text that will | 77 // maxLines if the raw text is longer than the amount of text that will |
78 // fit on maxLines - 1. We add the length of a whitespace | 78 // fit on maxLines - 1. We add the length of a whitespace |
79 // character to the lineWidth as each line is separated by a whitespace | 79 // character to the lineWidth as each line is separated by a whitespace |
80 // character. We assume all whitespace characters have the same length. | 80 // character. We assume all whitespace characters have the same length. |
81 if (textWidth >= (lineWidth + _spaceLength) * (maxLines - 1)) { | 81 if (textWidth >= (lineWidth + _spaceLength) * (maxLines - 1)) { |
82 return maxLines; | 82 return maxLines; |
83 } else if (textWidth == 0) { | 83 } else if (textWidth == 0) { |
84 return 0; | 84 return 0; |
85 } else if (textWidth < lineWidth) { | 85 } else if (textWidth < lineWidth) { |
86 return 1; | 86 return 1; |
87 } | 87 } |
88 // Fall through to the regular line breaking calculation as the number | 88 // Fall through to the regular line breaking calculation as the number |
89 // of lines required is unclear. | 89 // of lines required is unclear. |
90 } | 90 } |
91 int lines = 0; | 91 int lines = 0; |
92 lineBreak(text, lineWidth, maxLines, (int start, int end, num width) { | 92 lineBreak(text, lineWidth, maxLines, (int start, int end, num width) { |
93 lines++; | 93 lines++; |
94 if (lines == maxLines) { | 94 if (lines == maxLines) { |
95 // Overflow case... there may be more lines of text than we can handle. | 95 // Overflow case... there may be more lines of text than we can handle. |
96 // Add a few characters to the last line so that the browser will | 96 // Add a few characters to the last line so that the browser will |
97 // render ellipses correctly. | 97 // render ellipses correctly. |
98 // TODO(jacobr): make this optional and only add characters until | 98 // TODO(jacobr): make this optional and only add characters until |
99 // the first whitespace character encountered. | 99 // the first whitespace character encountered. |
100 end = Math.min(end + 50, text.length); | 100 end = Math.min(end + 50, text.length); |
101 } | 101 } |
102 if (sb !== null) { | 102 if (sb != null) { |
103 if (lines > 1) { | 103 if (lines > 1) { |
104 sb.add('<br>'); | 104 sb.add('<br>'); |
105 } | 105 } |
106 // TODO(jacobr): HTML escape this text. | 106 // TODO(jacobr): HTML escape this text. |
107 sb.add(text.substring(start, end)); | 107 sb.add(text.substring(start, end)); |
108 } | 108 } |
109 }); | 109 }); |
110 return lines; | 110 return lines; |
111 } | 111 } |
112 | 112 |
(...skipping 16 matching lines...) Expand all Loading... |
129 num wordLength = _context.measureText(text.substring( | 129 num wordLength = _context.measureText(text.substring( |
130 wordStartIndex, i)).width; | 130 wordStartIndex, i)).width; |
131 // TODO(jimhug): Replace the line above with this one to workaround | 131 // TODO(jimhug): Replace the line above with this one to workaround |
132 // dartium bug - error: unimplemented code | 132 // dartium bug - error: unimplemented code |
133 // num wordLength = (i - wordStartIndex) * 17; | 133 // num wordLength = (i - wordStartIndex) * 17; |
134 currentLength += wordLength; | 134 currentLength += wordLength; |
135 if (currentLength > lineWidth) { | 135 if (currentLength > lineWidth) { |
136 // Edge case: | 136 // Edge case: |
137 // It could be the very first word we ran into was too long for a | 137 // It could be the very first word we ran into was too long for a |
138 // line in which case we let it have its own line. | 138 // line in which case we let it have its own line. |
139 if (lastWordEndIndex !== null) { | 139 if (lastWordEndIndex != null) { |
140 lines++; | 140 lines++; |
141 callback(startIndex, lastWordEndIndex, currentLength - wordLength); | 141 callback(startIndex, lastWordEndIndex, currentLength - wordLength); |
142 } | 142 } |
143 if (lines == maxLines) { | 143 if (lines == maxLines) { |
144 return; | 144 return; |
145 } | 145 } |
146 startIndex = wordStartIndex; | 146 startIndex = wordStartIndex; |
147 currentLength = wordLength; | 147 currentLength = wordLength; |
148 } | 148 } |
149 lastWordEndIndex = i; | 149 lastWordEndIndex = i; |
150 currentLength += _spaceLength; | 150 currentLength += _spaceLength; |
151 wordStartIndex = null; | 151 wordStartIndex = null; |
152 } else if (wordStartIndex === null && !whitespace) { | 152 } else if (wordStartIndex == null && !whitespace) { |
153 wordStartIndex = i; | 153 wordStartIndex = i; |
154 } | 154 } |
155 lastWhitespace = whitespace; | 155 lastWhitespace = whitespace; |
156 } | 156 } |
157 if (currentLength > 0) { | 157 if (currentLength > 0) { |
158 callback(startIndex, text.length, currentLength); | 158 callback(startIndex, text.length, currentLength); |
159 } | 159 } |
160 } | 160 } |
161 } | 161 } |
OLD | NEW |