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

Side by Side Diff: ui/gfx/render_text_harfbuzz.cc

Issue 1070223004: Stop combining text runs which are connected by 'COMMON' blocks. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: small glitches Created 5 years, 7 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
« no previous file with comments | « ui/gfx/render_text_harfbuzz.h ('k') | ui/gfx/render_text_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "ui/gfx/render_text_harfbuzz.h" 5 #include "ui/gfx/render_text_harfbuzz.h"
6 6
7 #include <limits> 7 #include <limits>
8 #include <set> 8 #include <set>
9 9
10 #include "base/i18n/bidi_line_iterator.h" 10 #include "base/i18n/bidi_line_iterator.h"
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
42 // The maximum number of scripts a Unicode character can belong to. This value 42 // The maximum number of scripts a Unicode character can belong to. This value
43 // is arbitrarily chosen to be a good limit because it is unlikely for a single 43 // is arbitrarily chosen to be a good limit because it is unlikely for a single
44 // character to belong to more scripts. 44 // character to belong to more scripts.
45 const size_t kMaxScripts = 5; 45 const size_t kMaxScripts = 5;
46 46
47 // Returns true if characters of |block_code| may trigger font fallback. 47 // Returns true if characters of |block_code| may trigger font fallback.
48 // Dingbats and emoticons can be rendered through the color emoji font file, 48 // Dingbats and emoticons can be rendered through the color emoji font file,
49 // therefore it needs to be trigerred as fallbacks. See crbug.com/448909 49 // therefore it needs to be trigerred as fallbacks. See crbug.com/448909
50 bool IsUnusualBlockCode(UBlockCode block_code) { 50 bool IsUnusualBlockCode(UBlockCode block_code) {
51 return block_code == UBLOCK_GEOMETRIC_SHAPES || 51 return block_code == UBLOCK_GEOMETRIC_SHAPES ||
52 block_code == UBLOCK_MISCELLANEOUS_SYMBOLS || 52 block_code == UBLOCK_MISCELLANEOUS_SYMBOLS;
53 block_code == UBLOCK_DINGBATS ||
54 block_code == UBLOCK_EMOTICONS;
55 } 53 }
56 54
57 bool IsBracket(UChar32 character) { 55 bool IsBracket(UChar32 character) {
58 static const char kBrackets[] = { '(', ')', '{', '}', '<', '>', }; 56 static const char kBrackets[] = { '(', ')', '{', '}', '<', '>', };
59 static const char* kBracketsEnd = kBrackets + arraysize(kBrackets); 57 static const char* kBracketsEnd = kBrackets + arraysize(kBrackets);
60 return std::find(kBrackets, kBracketsEnd, character) != kBracketsEnd; 58 return std::find(kBrackets, kBracketsEnd, character) != kBracketsEnd;
61 } 59 }
62 60
63 // Returns the boundary between a special and a regular character. Special 61 // Returns the boundary between a special and a regular character. Special
64 // characters are brackets or characters that satisfy |IsUnusualBlockCode|. 62 // characters are brackets or characters that satisfy |IsUnusualBlockCode|.
(...skipping 18 matching lines...) Expand all
83 const bool block_break = current_block != first_block && 81 const bool block_break = current_block != first_block &&
84 (first_block_unusual || IsUnusualBlockCode(current_block)); 82 (first_block_unusual || IsUnusualBlockCode(current_block));
85 if (block_break || current_char == '\n' || 83 if (block_break || current_char == '\n' ||
86 first_bracket != IsBracket(current_char)) { 84 first_bracket != IsBracket(current_char)) {
87 return run_start + iter.array_pos(); 85 return run_start + iter.array_pos();
88 } 86 }
89 } 87 }
90 return run_break; 88 return run_break;
91 } 89 }
92 90
93 // If the given scripts match, returns the one that isn't USCRIPT_COMMON or 91 // If the given scripts match, returns the one that isn't USCRIPT_INHERITED,
94 // USCRIPT_INHERITED, i.e. the more specific one. Otherwise returns 92 // i.e. the more specific one. Otherwise returns
95 // USCRIPT_INVALID_CODE. 93 // USCRIPT_INVALID_CODE.
96 UScriptCode ScriptIntersect(UScriptCode first, UScriptCode second) { 94 UScriptCode ScriptIntersect(UScriptCode first, UScriptCode second) {
97 if (first == second || 95 if (first == second || second == USCRIPT_INHERITED)
98 (second > USCRIPT_INVALID_CODE && second <= USCRIPT_INHERITED)) {
99 return first; 96 return first;
100 } 97 if (first == USCRIPT_INHERITED)
101 if (first > USCRIPT_INVALID_CODE && first <= USCRIPT_INHERITED)
102 return second; 98 return second;
103 return USCRIPT_INVALID_CODE; 99 return USCRIPT_INVALID_CODE;
104 } 100 }
105 101
106 // Writes the script and the script extensions of the character with the 102 // Writes the script and the script extensions of the character with the
107 // Unicode |codepoint|. Returns the number of written scripts. 103 // Unicode |codepoint|. Returns the number of written scripts.
108 int GetScriptExtensions(UChar32 codepoint, UScriptCode* scripts) { 104 int GetScriptExtensions(UChar32 codepoint, UScriptCode* scripts) {
109 UErrorCode icu_error = U_ZERO_ERROR; 105 UErrorCode icu_error = U_ZERO_ERROR;
110 // ICU documentation incorrectly states that the result of 106 // ICU documentation incorrectly states that the result of
111 // |uscript_getScriptExtensions| will contain the regular script property. 107 // |uscript_getScriptExtensions| will contain the regular script property.
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
159 UScriptCode* script) { 155 UScriptCode* script) {
160 DCHECK_GT(length, 0U); 156 DCHECK_GT(length, 0U);
161 157
162 UScriptCode scripts[kMaxScripts] = { USCRIPT_INVALID_CODE }; 158 UScriptCode scripts[kMaxScripts] = { USCRIPT_INVALID_CODE };
163 159
164 base::i18n::UTF16CharIterator char_iterator(text.c_str() + start, length); 160 base::i18n::UTF16CharIterator char_iterator(text.c_str() + start, length);
165 size_t scripts_size = GetScriptExtensions(char_iterator.get(), scripts); 161 size_t scripts_size = GetScriptExtensions(char_iterator.get(), scripts);
166 *script = scripts[0]; 162 *script = scripts[0];
167 163
168 while (char_iterator.Advance()) { 164 while (char_iterator.Advance()) {
165 // Special handling to merge white space into the previous run.
166 if (u_isUWhiteSpace(char_iterator.get()))
167 continue;
169 ScriptSetIntersect(char_iterator.get(), scripts, &scripts_size); 168 ScriptSetIntersect(char_iterator.get(), scripts, &scripts_size);
170 if (scripts_size == 0U) 169 if (scripts_size == 0U)
171 return char_iterator.array_pos(); 170 return char_iterator.array_pos();
172 *script = scripts[0]; 171 *script = scripts[0];
173 } 172 }
174 173
175 return length; 174 return length;
176 } 175 }
177 176
178 // A port of hb_icu_script_to_script because harfbuzz on CrOS is built without 177 // A port of hb_icu_script_to_script because harfbuzz on CrOS is built without
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
229 const BreakList<size_t>* words, 228 const BreakList<size_t>* words,
230 const internal::TextRunList& run_list) 229 const internal::TextRunList& run_list)
231 : max_width_((max_width == 0) ? SK_ScalarMax : SkIntToScalar(max_width)), 230 : max_width_((max_width == 0) ? SK_ScalarMax : SkIntToScalar(max_width)),
232 min_baseline_(min_baseline), 231 min_baseline_(min_baseline),
233 min_height_(min_height), 232 min_height_(min_height),
234 multiline_(multiline), 233 multiline_(multiline),
235 word_wrap_behavior_(word_wrap_behavior), 234 word_wrap_behavior_(word_wrap_behavior),
236 text_(text), 235 text_(text),
237 words_(words), 236 words_(words),
238 run_list_(run_list), 237 run_list_(run_list),
238 max_descent_(0),
239 max_ascent_(0),
239 text_x_(0), 240 text_x_(0),
240 line_x_(0), 241 available_width_(max_width_) {
241 max_descent_(0),
242 max_ascent_(0) {
243 DCHECK_EQ(multiline_, (words_ != nullptr)); 242 DCHECK_EQ(multiline_, (words_ != nullptr));
244 AdvanceLine(); 243 RunLineBreaking();
245 } 244 }
246 245
247 // Breaks the run at given |run_index| into Line structs. 246 std::vector<internal::Line> GetLines() const { return lines_; }
Jun Mukai 2015/05/15 20:16:01 Do not do this. This copies all of the line struct
xdai1 2015/05/15 23:41:17 Removed it and passed values through FinalizeLines
248 void AddRun(int run_index) { 247 SizeF GetTotalSize() const { return total_size_; }
249 const internal::TextRunHarfBuzz* run = run_list_.runs()[run_index];
250 base::char16 first_char = text_[run->range.start()];
251 if (multiline_ && first_char == '\n') {
252 AdvanceLine();
253 } else if (multiline_ && (line_x_ + SkFloatToScalar(run->width)) >
254 max_width_) {
255 BreakRun(run_index);
256 } else {
257 AddSegment(run_index, run->range, run->width);
258 }
259 }
260
261 // Finishes line breaking and outputs the results. Can be called at most once.
262 void Finalize(std::vector<internal::Line>* lines, SizeF* size) {
263 DCHECK(!lines_.empty());
264 // Add an empty line to finish the line size calculation and remove it.
265 AdvanceLine();
266 lines_.pop_back();
267 *size = total_size_;
268 lines->swap(lines_);
269 }
270 248
271 private: 249 private:
272 // A (line index, segment index) pair that specifies a segment in |lines_|. 250 // A (line index, segment index) pair that specifies a segment in |lines_|.
273 typedef std::pair<size_t, size_t> SegmentHandle; 251 typedef std::pair<size_t, size_t> SegmentHandle;
274 252
275 internal::LineSegment* SegmentFromHandle(const SegmentHandle& handle) { 253 internal::LineSegment* SegmentFromHandle(const SegmentHandle& handle) {
276 return &lines_[handle.first].segments[handle.second]; 254 return &lines_[handle.first].segments[handle.second];
277 } 255 }
278 256
279 // Breaks a run into segments that fit in the last line in |lines_| and adds 257 // Runs line breaking algorithm and break |text_| into lines if applicable.
280 // them. Adds a new Line to the back of |lines_| whenever a new segment can't 258 void RunLineBreaking() {
Jun Mukai 2015/05/15 20:16:00 I don't think this method is necessary. - revert
xdai1 2015/05/15 23:41:17 Done.
281 // be added without the Line's width exceeding |max_width_|. 259 AdvanceLine();
282 void BreakRun(int run_index) { 260 if (!multiline_)
283 const internal::TextRunHarfBuzz& run = *(run_list_.runs()[run_index]); 261 ConstructSingleLine();
284 SkScalar width = 0; 262 else
285 size_t next_char = run.range.start(); 263 ConstructMultiLines();
264 // Finishes line breaking and calculates the line size.
265 FinalizeLines();
266 }
286 267
287 // Break the run until it fits the current line. 268 // Constructs a single line for |text_| using |run_list_|.
288 while (next_char < run.range.end()) { 269 void ConstructSingleLine() {
289 const size_t current_char = next_char; 270 for (auto iter = run_list_.runs().begin(); iter != run_list_.runs().end();
Jun Mukai 2015/05/15 20:16:00 It's better to use 'for (size_t i = 0; i < run_lis
xdai1 2015/05/15 23:41:17 Done. but I don't know why...
290 size_t end_char = next_char; 271 iter++) {
291 const bool skip_line = 272 internal::LineSegment segment;
292 BreakRunAtWidth(run, current_char, &width, &end_char, &next_char); 273 segment.run = std::distance(run_list_.runs().begin(), iter);
293 AddSegment(run_index, Range(current_char, end_char), 274 segment.char_range = (*iter)->range;
294 SkScalarToFloat(width)); 275 segment.x_range = Range(SkScalarCeilToInt(text_x_),
295 if (skip_line) 276 SkScalarCeilToInt(text_x_ + (*iter)->width));
277 segment.width = (*iter)->width;
278 AddLineSegment(segment);
279 }
280 }
281
282 // Constructs multiple lines for |text_| based on words iteration approach.
283 void ConstructMultiLines() {
284 for (auto iter = words_->breaks().begin(); iter != words_->breaks().end();
285 iter++) {
Jun Mukai 2015/05/15 20:16:00 for (const auto& word : words_->breaks()) { ... }
xdai1 2015/05/15 23:41:18 Done.
286 std::vector<internal::LineSegment> word_segments;
287 SkScalar word_width = GetWordWidth(iter, word_segments);
288
289 bool new_line = false;
290 if (!word_segments.empty()) {
291 // If the word is end with '\n', we should advance a new line after
292 // adding the word to current line.
293 internal::LineSegment last_segment = word_segments.back();
Jun Mukai 2015/05/15 20:15:59 const internal::LineSegment&
xdai1 2015/05/15 23:41:17 Done.
294 base::char16 last_char = text_[last_segment.char_range.start()];
Jun Mukai 2015/05/15 20:16:00 const base::char16
xdai1 2015/05/15 23:41:18 Done.
295 if (last_char == '\n') {
296 new_line = true;
297 word_width -= last_segment.width;
298 word_segments.pop_back();
299 }
300 }
301
302 // If the word is not the first word in the line and it can't fit into
303 // the current line, advance a new line.
304 if (word_width > available_width_ && available_width_ != max_width_)
305 AdvanceLine();
Jun Mukai 2015/05/15 20:16:00 I think this is not necessary, and if behavior is
xdai1 2015/05/15 23:41:18 As we discussed offline, this logic is still neede
306 AddWordToLine(word_segments);
307 if (new_line)
296 AdvanceLine(); 308 AdvanceLine();
297 } 309 }
298 } 310 }
299 311
300 // Starting from |start_char|, finds a suitable line break position at or
301 // before available width using word break. If the current position is at the
302 // beginning of a line, this function will not roll back to |start_char| and
303 // |*next_char| will be greater than |start_char| (to avoid constructing empty
304 // lines). It stores the end of the segment range to |end_char|, which can be
305 // smaller than |*next_char| for certain word wrapping behavior.
306 // Returns whether to skip the line before |*next_char|.
307 // TODO(ckocagil): We might have to reshape after breaking at ligatures.
308 // See whether resolving the TODO above resolves this too.
309 // TODO(ckocagil): Do not reserve width for whitespace at the end of lines.
310 bool BreakRunAtWidth(const internal::TextRunHarfBuzz& run,
311 size_t start_char,
312 SkScalar* width,
313 size_t* end_char,
314 size_t* next_char) {
315 DCHECK(words_);
316 DCHECK(run.range.Contains(Range(start_char, start_char + 1)));
317 SkScalar available_width = max_width_ - line_x_;
318 BreakList<size_t>::const_iterator word = words_->GetBreak(start_char);
319 BreakList<size_t>::const_iterator next_word = word + 1;
320 // Width from |std::max(word->first, start_char)| to the current character.
321 SkScalar word_width = 0;
322 *width = 0;
323
324 Range char_range;
325 SkScalar truncated_width = 0;
326 for (size_t i = start_char; i < run.range.end(); i += char_range.length()) {
327 // |word| holds the word boundary at or before |i|, and |next_word| holds
328 // the word boundary right after |i|. Advance both |word| and |next_word|
329 // when |i| reaches |next_word|.
330 if (next_word != words_->breaks().end() && i >= next_word->first) {
331 if (*width > available_width) {
332 DCHECK_NE(WRAP_LONG_WORDS, word_wrap_behavior_);
333 *next_char = i;
334 if (word_wrap_behavior_ != TRUNCATE_LONG_WORDS)
335 *end_char = *next_char;
336 else
337 *width = truncated_width;
338 return true;
339 }
340 word = next_word++;
341 word_width = 0;
342 }
343
344 Range glyph_range;
345 run.GetClusterAt(i, &char_range, &glyph_range);
346 DCHECK_LT(0U, char_range.length());
347
348 SkScalar char_width = ((glyph_range.end() >= run.glyph_count)
349 ? SkFloatToScalar(run.width)
350 : run.positions[glyph_range.end()].x()) -
351 run.positions[glyph_range.start()].x();
352
353 *width += char_width;
354 word_width += char_width;
355
356 // TODO(mukai): implement ELIDE_LONG_WORDS.
357 if (*width > available_width) {
358 if (line_x_ != 0 || word_width < *width) {
359 // Roll back one word.
360 *width -= word_width;
361 *next_char = std::max(word->first, start_char);
362 *end_char = *next_char;
363 return true;
364 } else if (word_wrap_behavior_ == WRAP_LONG_WORDS) {
365 if (char_width < *width) {
366 // Roll back one character.
367 *width -= char_width;
368 *next_char = i;
369 } else {
370 // Continue from the next character.
371 *next_char = i + char_range.length();
372 }
373 *end_char = *next_char;
374 return true;
375 }
376 } else {
377 *end_char = char_range.end();
378 truncated_width = *width;
379 }
380 }
381
382 if (word_wrap_behavior_ == TRUNCATE_LONG_WORDS)
383 *width = truncated_width;
384 *end_char = *next_char = run.range.end();
385 return false;
386 }
387
388 // RTL runs are broken in logical order but displayed in visual order. To find
389 // the text-space coordinate (where it would fall in a single-line text)
390 // |x_range| of RTL segments, segment widths are applied in reverse order.
391 // e.g. {[5, 10], [10, 40]} will become {[35, 40], [5, 35]}.
392 void UpdateRTLSegmentRanges() {
393 if (rtl_segments_.empty())
394 return;
395 float x = SegmentFromHandle(rtl_segments_[0])->x_range.start();
396 for (size_t i = rtl_segments_.size(); i > 0; --i) {
397 internal::LineSegment* segment = SegmentFromHandle(rtl_segments_[i - 1]);
398 const float segment_width = segment->width;
399 segment->x_range = Range(x, x + segment_width);
400 x += segment_width;
401 }
402 rtl_segments_.clear();
403 }
404
405 // Finishes the size calculations of the last Line in |lines_|. Adds a new 312 // Finishes the size calculations of the last Line in |lines_|. Adds a new
406 // Line to the back of |lines_|. 313 // Line to the back of |lines_|.
407 void AdvanceLine() { 314 void AdvanceLine() {
408 if (!lines_.empty()) { 315 if (!lines_.empty()) {
409 internal::Line* line = &lines_.back(); 316 internal::Line* line = &lines_.back();
410 std::sort(line->segments.begin(), line->segments.end(), 317 std::sort(line->segments.begin(), line->segments.end(),
411 [this](const internal::LineSegment& s1, 318 [this](const internal::LineSegment& s1,
412 const internal::LineSegment& s2) -> bool { 319 const internal::LineSegment& s2) -> bool {
413 return run_list_.logical_to_visual(s1.run) < 320 return run_list_.logical_to_visual(s1.run) <
414 run_list_.logical_to_visual(s2.run); 321 run_list_.logical_to_visual(s2.run);
415 }); 322 });
416 line->size.set_height(std::max(min_height_, max_descent_ + max_ascent_)); 323 line->size.set_height(std::max(min_height_, max_descent_ + max_ascent_));
417 line->baseline = 324 line->baseline = std::max(min_baseline_, SkScalarRoundToInt(max_ascent_));
418 std::max(min_baseline_, SkScalarRoundToInt(max_ascent_));
419 line->preceding_heights = std::ceil(total_size_.height()); 325 line->preceding_heights = std::ceil(total_size_.height());
420 total_size_.set_height(total_size_.height() + line->size.height()); 326 total_size_.set_height(total_size_.height() + line->size.height());
421 total_size_.set_width(std::max(total_size_.width(), line->size.width())); 327 total_size_.set_width(std::max(total_size_.width(), line->size.width()));
422 } 328 }
423 max_descent_ = 0; 329 max_descent_ = 0;
424 max_ascent_ = 0; 330 max_ascent_ = 0;
425 line_x_ = 0; 331 available_width_ = max_width_;
426 lines_.push_back(internal::Line()); 332 lines_.push_back(internal::Line());
427 } 333 }
428 334
429 // Adds a new segment with the given properties to |lines_.back()|. 335 // Adds word to the current line. A word may contain multiple segments. If the
430 void AddSegment(int run_index, Range char_range, float width) { 336 // word is the first word in line and its width exceeds |available_width_|,
431 if (char_range.is_empty()) { 337 // ignore/truncate/wrap it according to |word_wrap_behavior_|.
432 DCHECK_EQ(0, width); 338 void AddWordToLine(std::vector<internal::LineSegment>& segments) {
Jun Mukai 2015/05/15 20:16:00 const std::vector<internal::LineSegment>&
xdai1 2015/05/15 23:41:18 This can't be const since the element inside of th
Jun Mukai 2015/05/16 00:21:56 If you really need to modify this parameter, pass
xdai1 2015/05/18 17:41:26 Done.
339 if (segments.empty() || lines_.empty())
Jun Mukai 2015/05/15 20:16:00 lines_ shouldn't be empty, because AdvanceLine() m
xdai1 2015/05/15 23:41:17 Done.
433 return; 340 return;
341
342 bool has_truncated = false;
343 for (auto iter = segments.begin(); iter != segments.end(); iter++) {
Jun Mukai 2015/05/15 20:16:00 for (const internal::LineSegment& segment : segmen
xdai1 2015/05/15 23:41:17 Same as previous one, segment can't be const since
344 if (has_truncated)
345 break;
346 internal::LineSegment segment = *iter;
347 if (segment.width <= available_width_ ||
348 word_wrap_behavior_ == IGNORE_LONG_WORDS) {
349 AddLineSegment(segment);
350 } else {
351 DCHECK(word_wrap_behavior_ != IGNORE_LONG_WORDS);
Jun Mukai 2015/05/15 20:16:01 DCHECK(word_wrap_behavior_ == TRUNCATE_LONG_WORDS
xdai1 2015/05/15 23:41:17 Done.
352 has_truncated = (word_wrap_behavior_ == TRUNCATE_LONG_WORDS);
353
354 while (!segment.char_range.is_empty()) {
355 size_t cutoff_pos;
Jun Mukai 2015/05/15 20:16:00 size_t cutoff_pos = 0;
xdai1 2015/05/15 23:41:17 Done.
356 SkScalar width =
357 GetCutoffWidth(segment, segment.char_range.start(), cutoff_pos);
358 if (width == 0 && available_width_ == max_width_) {
359 // |max_width_| might be smaller than a single character. In this
360 // case we need to put at least one character in the line.
361 // See RenderTextTest.Multiline_MinWidth for example.
362 cutoff_pos = segment.char_range.start() + 1;
363 const internal::TextRunHarfBuzz& run =
364 *(run_list_.runs()[segment.run]);
365 Range char_range = Range(segment.char_range.start(), cutoff_pos);
366 Range glyph_range = run.CharRangeToGlyphRange(char_range);
367 width = ((glyph_range.end() >= run.glyph_count)
368 ? SkFloatToScalar(run.width)
369 : run.positions[glyph_range.end()].x()) -
370 run.positions[glyph_range.start()].x();
Jun Mukai 2015/05/15 20:16:01 Looks like it's better to add a new method to Harf
xdai1 2015/05/15 23:41:17 Done. Also adjusted some functions' sequence to co
371 }
372 if (width > 0) {
373 internal::LineSegment cut_segment;
374 cut_segment.run = segment.run;
375 cut_segment.char_range =
376 Range(segment.char_range.start(), cutoff_pos);
377 cut_segment.width = width;
378 cut_segment.x_range = Range(segment.x_range.start(),
379 SkScalarCeilToInt(text_x_ + width));
380 AddLineSegment(cut_segment);
381 // Updates old segment.
382 segment.char_range.set_start(cutoff_pos);
383 segment.x_range.set_start(SkScalarCeilToInt(text_x_));
384 segment.width -= width;
385 }
386 if (has_truncated)
387 break;
388 if (!segment.char_range.is_empty())
389 AdvanceLine();
390 }
391 }
434 } 392 }
435 const internal::TextRunHarfBuzz& run = *(run_list_.runs()[run_index]); 393 }
436 394
437 internal::LineSegment segment; 395 // Add line segment to current line. Note in order to keep the visual order
438 segment.run = run_index; 396 // correct for ltr and rtl language, we need to merge segments that belong to
439 segment.char_range = char_range; 397 // a same run.
440 segment.x_range = Range( 398 void AddLineSegment(internal::LineSegment& segment) {
Jun Mukai 2015/05/15 20:16:00 const internal::LineSegment&
xdai1 2015/05/15 23:41:18 It can't be const since it might be modified in li
Jun Mukai 2015/05/16 00:21:56 In that case please pass as the pointer. Our codi
441 SkScalarCeilToInt(text_x_), 399 DCHECK(!lines_.empty());
442 SkScalarCeilToInt(text_x_ + SkFloatToScalar(width)));
443 segment.width = width;
444
445 internal::Line* line = &lines_.back(); 400 internal::Line* line = &lines_.back();
401 const internal::TextRunHarfBuzz& run = *(run_list_.runs()[segment.run]);
402 if (!line->segments.empty()) {
403 internal::LineSegment last_segment = line->segments.back();
Jun Mukai 2015/05/15 20:16:01 internal::LineSegment& last_segment = line_segment
xdai1 2015/05/15 23:41:17 Done. And also make last_segment const.
Jun Mukai 2015/05/16 00:21:56 No. My intention was, if the last_segment and seg
xdai1 2015/05/18 17:41:26 You're right. We can modify last_segment and keep
404 // Merge segments that belong to the same run.
405 if (last_segment.run == segment.run) {
406 segment.char_range.set_start(last_segment.char_range.start());
407 segment.width += last_segment.width;
408 segment.x_range.set_start(
409 SkScalarCeilToInt(text_x_ - last_segment.width));
410 line->segments.pop_back();
411 if (run.is_rtl)
412 rtl_segments_.pop_back();
413 line->size.set_width(line->size.width() - last_segment.width);
414 text_x_ -= last_segment.width;
415 available_width_ += last_segment.width;
416 }
417 }
446 line->segments.push_back(segment); 418 line->segments.push_back(segment);
447 419
448 SkPaint paint; 420 SkPaint paint;
449 paint.setTypeface(run.skia_face.get()); 421 paint.setTypeface(run.skia_face.get());
450 paint.setTextSize(SkIntToScalar(run.font_size)); 422 paint.setTextSize(SkIntToScalar(run.font_size));
451 paint.setAntiAlias(run.render_params.antialiasing); 423 paint.setAntiAlias(run.render_params.antialiasing);
452 SkPaint::FontMetrics metrics; 424 SkPaint::FontMetrics metrics;
453 paint.getFontMetrics(&metrics); 425 paint.getFontMetrics(&metrics);
454 426
455 line->size.set_width(line->size.width() + width); 427 line->size.set_width(line->size.width() + segment.width);
456 // TODO(dschuyler): Account for stylized baselines in string sizing. 428 // TODO(dschuyler): Account for stylized baselines in string sizing.
457 max_descent_ = std::max(max_descent_, metrics.fDescent); 429 max_descent_ = std::max(max_descent_, metrics.fDescent);
458 // fAscent is always negative. 430 // fAscent is always negative.
459 max_ascent_ = std::max(max_ascent_, -metrics.fAscent); 431 max_ascent_ = std::max(max_ascent_, -metrics.fAscent);
460 432
461 if (run.is_rtl) { 433 if (run.is_rtl) {
462 rtl_segments_.push_back( 434 rtl_segments_.push_back(
463 SegmentHandle(lines_.size() - 1, line->segments.size() - 1)); 435 SegmentHandle(lines_.size() - 1, line->segments.size() - 1));
464 // If this is the last segment of an RTL run, reprocess the text-space x 436 // If this is the last segment of an RTL run, reprocess the text-space x
465 // ranges of all segments from the run. 437 // ranges of all segments from the run.
466 if (char_range.end() == run.range.end()) 438 if (segment.char_range.end() == run.range.end())
467 UpdateRTLSegmentRanges(); 439 UpdateRTLSegmentRanges();
468 } 440 }
469 text_x_ += SkFloatToScalar(width); 441 text_x_ += segment.width;
470 line_x_ += SkFloatToScalar(width); 442 available_width_ -= segment.width;
443 }
444
445 // Starts from |st_pos|, find the end position |en_pos| that the preceding
446 // width is no larger than |available_width_|, and return the preceding width.
447 SkScalar GetCutoffWidth(internal::LineSegment segment,
Jun Mukai 2015/05/15 20:16:00 const internal::LineSegment&
xdai1 2015/05/15 23:41:18 Done.
448 size_t st_pos,
Jun Mukai 2015/05/15 20:16:00 Remove st_pos. It always starts from segment.char_
xdai1 2015/05/15 23:41:18 Done.
449 size_t& en_pos) {
Jun Mukai 2015/05/15 20:16:00 Return value is passed by a pointer, do not use no
xdai1 2015/05/15 23:41:17 I see. Thanks! Done.
450 DCHECK(segment.char_range.Contains(Range(st_pos, st_pos + 1)));
451 const internal::TextRunHarfBuzz& run = *(run_list_.runs()[segment.run]);
452 en_pos = st_pos;
453 SkScalar width = 0;
454 while (en_pos < segment.char_range.end()) {
455 Range char_range;
456 Range glyph_range;
457 run.GetClusterAt(en_pos, &char_range, &glyph_range);
458 SkScalar char_width = ((glyph_range.end() >= run.glyph_count)
Jun Mukai 2015/05/15 20:16:00 const SkScalar
xdai1 2015/05/15 23:41:17 Done.
459 ? SkFloatToScalar(run.width)
460 : run.positions[glyph_range.end()].x()) -
461 run.positions[glyph_range.start()].x();
462 if (width + char_width > available_width_)
463 break;
464 width += char_width;
465 en_pos++;
466 }
467 return width;
468 }
469
470 // Gets the glyph width for |word|, and splits the |word| into different
471 // segments based on its runs.
472 SkScalar GetWordWidth(BreakList<size_t>::const_iterator word,
473 std::vector<internal::LineSegment>& segments) {
Jun Mukai 2015/05/15 20:16:00 std::vector<internal::LineSegment>* Also make this
xdai1 2015/05/15 23:41:17 Done.
474 if (word == words_->breaks().end())
475 return 0;
476 int word_st = word->first;
477 int word_en = (word + 1 == words_->breaks().end()) ? text_.size() - 1
478 : (word + 1)->first - 1;
479 if (word_en < 0 || word_en < word_st)
480 return 0;
481 size_t run_st_idx = run_list_.GetRunIndexAt(word_st);
482 size_t run_en_idx = run_list_.GetRunIndexAt(word_en);
483 SkScalar width = 0;
484 for (size_t idx = run_st_idx; idx <= run_en_idx; idx++) {
485 const internal::TextRunHarfBuzz& run = *(run_list_.runs()[idx]);
486 Range char_range = run.range.Intersect(Range(word_st, word_en + 1));
487 Range glyph_range = run.CharRangeToGlyphRange(char_range);
488 SkScalar char_width = ((glyph_range.end() >= run.glyph_count)
489 ? SkFloatToScalar(run.width)
490 : run.positions[glyph_range.end()].x()) -
491 run.positions[glyph_range.start()].x();
492 width += char_width;
493
494 internal::LineSegment segment;
495 segment.run = idx;
496 segment.char_range = char_range;
497 segment.width = char_width;
498 segment.x_range = Range(SkScalarCeilToInt(text_x_ + width - char_width),
499 SkScalarCeilToInt(text_x_ + width));
500 segments.push_back(segment);
501 }
502 return width;
503 }
504
505 // Finishes line breaking algorithm.
506 void FinalizeLines() {
507 DCHECK(!lines_.empty());
508 // Add an empty line to finish the line size calculation and remove it.
509 AdvanceLine();
510 lines_.pop_back();
511 }
512
513 // RTL runs are broken in logical order but displayed in visual order. To find
514 // the text-space coordinate (where it would fall in a single-line text)
515 // |x_range| of RTL segments, segment widths are applied in reverse order.
516 // e.g. {[5, 10], [10, 40]} will become {[35, 40], [5, 35]}.
517 void UpdateRTLSegmentRanges() {
518 if (rtl_segments_.empty())
519 return;
520 float x = SegmentFromHandle(rtl_segments_[0])->x_range.start();
521 for (size_t i = rtl_segments_.size(); i > 0; --i) {
522 internal::LineSegment* segment = SegmentFromHandle(rtl_segments_[i - 1]);
523 const float segment_width = segment->width;
524 segment->x_range = Range(x, x + segment_width);
525 x += segment_width;
526 }
527 rtl_segments_.clear();
471 } 528 }
472 529
473 const SkScalar max_width_; 530 const SkScalar max_width_;
474 const int min_baseline_; 531 const int min_baseline_;
475 const float min_height_; 532 const float min_height_;
476 const bool multiline_; 533 const bool multiline_;
477 const WordWrapBehavior word_wrap_behavior_; 534 const WordWrapBehavior word_wrap_behavior_;
478 const base::string16& text_; 535 const base::string16& text_;
479 const BreakList<size_t>* const words_; 536 const BreakList<size_t>* const words_;
480 const internal::TextRunList& run_list_; 537 const internal::TextRunList& run_list_;
481 538
482 // Stores the resulting lines. 539 // Stores the resulting lines.
483 std::vector<internal::Line> lines_; 540 std::vector<internal::Line> lines_;
484 541
485 // Text space and line space x coordinates of the next segment to be added.
486 SkScalar text_x_;
487 SkScalar line_x_;
488
489 float max_descent_; 542 float max_descent_;
490 float max_ascent_; 543 float max_ascent_;
491 544
545 // Text space x coordinates of the next segment to be added.
546 SkScalar text_x_;
547 // Stores available width in the current line.
548 SkScalar available_width_;
549
492 // Size of the multiline text, not including the currently processed line. 550 // Size of the multiline text, not including the currently processed line.
493 SizeF total_size_; 551 SizeF total_size_;
494 552
495 // The current RTL run segments, to be applied by |UpdateRTLSegmentRanges()|. 553 // The current RTL run segments, to be applied by |UpdateRTLSegmentRanges()|.
496 std::vector<SegmentHandle> rtl_segments_; 554 std::vector<SegmentHandle> rtl_segments_;
497 555
498 DISALLOW_COPY_AND_ASSIGN(HarfBuzzLineBreaker); 556 DISALLOW_COPY_AND_ASSIGN(HarfBuzzLineBreaker);
499 }; 557 };
500 558
501 // Function object for case insensitive string comparison. 559 // Function object for case insensitive string comparison.
(...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after
653 void TextRunList::ComputePrecedingRunWidths() { 711 void TextRunList::ComputePrecedingRunWidths() {
654 // Precalculate run width information. 712 // Precalculate run width information.
655 width_ = 0.0f; 713 width_ = 0.0f;
656 for (size_t i = 0; i < runs_.size(); ++i) { 714 for (size_t i = 0; i < runs_.size(); ++i) {
657 TextRunHarfBuzz* run = runs_[visual_to_logical_[i]]; 715 TextRunHarfBuzz* run = runs_[visual_to_logical_[i]];
658 run->preceding_run_widths = width_; 716 run->preceding_run_widths = width_;
659 width_ += run->width; 717 width_ += run->width;
660 } 718 }
661 } 719 }
662 720
721 size_t TextRunList::GetRunIndexAt(size_t position) const {
722 for (size_t i = 0; i < runs_.size(); ++i) {
723 if (runs_[i]->range.start() <= position && runs_[i]->range.end() > position)
724 return i;
725 }
726 return runs_.size();
727 }
728
663 } // namespace internal 729 } // namespace internal
664 730
665 RenderTextHarfBuzz::RenderTextHarfBuzz() 731 RenderTextHarfBuzz::RenderTextHarfBuzz()
666 : RenderText(), 732 : RenderText(),
667 update_layout_run_list_(false), 733 update_layout_run_list_(false),
668 update_display_run_list_(false), 734 update_display_run_list_(false),
669 update_grapheme_iterator_(false), 735 update_grapheme_iterator_(false),
670 update_display_text_(false), 736 update_display_text_(false),
671 glyph_width_for_test_(0u) { 737 glyph_width_for_test_(0u) {
672 set_truncate_length(kMaxTextLength); 738 set_truncate_length(kMaxTextLength);
(...skipping 334 matching lines...) Expand 10 before | Expand all | Expand 10 after
1007 1073
1008 internal::TextRunList* run_list = GetRunList(); 1074 internal::TextRunList* run_list = GetRunList();
1009 HarfBuzzLineBreaker line_breaker( 1075 HarfBuzzLineBreaker line_breaker(
1010 display_rect().width(), font_list().GetBaseline(), 1076 display_rect().width(), font_list().GetBaseline(),
1011 std::max(font_list().GetHeight(), min_line_height()), multiline(), 1077 std::max(font_list().GetHeight(), min_line_height()), multiline(),
1012 word_wrap_behavior(), GetDisplayText(), 1078 word_wrap_behavior(), GetDisplayText(),
1013 multiline() ? &GetLineBreaks() : nullptr, *run_list); 1079 multiline() ? &GetLineBreaks() : nullptr, *run_list);
1014 1080
1015 tracking_profile.reset(); 1081 tracking_profile.reset();
1016 1082
1017 for (size_t i = 0; i < run_list->size(); ++i) 1083 std::vector<internal::Line> lines = line_breaker.GetLines();
1018 line_breaker.AddRun(i);
1019 std::vector<internal::Line> lines;
1020 line_breaker.Finalize(&lines, &total_size_);
1021 set_lines(&lines); 1084 set_lines(&lines);
1085 total_size_ = line_breaker.GetTotalSize();
1022 } 1086 }
1023 } 1087 }
1024 1088
1025 void RenderTextHarfBuzz::DrawVisualText(Canvas* canvas) { 1089 void RenderTextHarfBuzz::DrawVisualText(Canvas* canvas) {
1026 internal::SkiaTextRenderer renderer(canvas); 1090 internal::SkiaTextRenderer renderer(canvas);
1027 DrawVisualTextInternal(&renderer); 1091 DrawVisualTextInternal(&renderer);
1028 } 1092 }
1029 1093
1030 void RenderTextHarfBuzz::DrawVisualTextInternal( 1094 void RenderTextHarfBuzz::DrawVisualTextInternal(
1031 internal::SkiaTextRenderer* renderer) { 1095 internal::SkiaTextRenderer* renderer) {
(...skipping 447 matching lines...) Expand 10 before | Expand all | Expand 10 after
1479 DCHECK(!update_layout_run_list_); 1543 DCHECK(!update_layout_run_list_);
1480 DCHECK(!update_display_run_list_); 1544 DCHECK(!update_display_run_list_);
1481 return text_elided() ? display_run_list_.get() : &layout_run_list_; 1545 return text_elided() ? display_run_list_.get() : &layout_run_list_;
1482 } 1546 }
1483 1547
1484 const internal::TextRunList* RenderTextHarfBuzz::GetRunList() const { 1548 const internal::TextRunList* RenderTextHarfBuzz::GetRunList() const {
1485 return const_cast<RenderTextHarfBuzz*>(this)->GetRunList(); 1549 return const_cast<RenderTextHarfBuzz*>(this)->GetRunList();
1486 } 1550 }
1487 1551
1488 } // namespace gfx 1552 } // namespace gfx
OLDNEW
« no previous file with comments | « ui/gfx/render_text_harfbuzz.h ('k') | ui/gfx/render_text_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698