 Chromium Code Reviews
 Chromium Code Reviews Issue 1070223004:
  Stop combining text runs which are connected by 'COMMON' blocks.  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master
    
  
    Issue 1070223004:
  Stop combining text runs which are connected by 'COMMON' blocks.  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master| OLD | NEW | 
|---|---|
| 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 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 83 const bool block_break = current_block != first_block && | 83 const bool block_break = current_block != first_block && | 
| 84 (first_block_unusual || IsUnusualBlockCode(current_block)); | 84 (first_block_unusual || IsUnusualBlockCode(current_block)); | 
| 85 if (block_break || current_char == '\n' || | 85 if (block_break || current_char == '\n' || | 
| 86 first_bracket != IsBracket(current_char)) { | 86 first_bracket != IsBracket(current_char)) { | 
| 87 return run_start + iter.array_pos(); | 87 return run_start + iter.array_pos(); | 
| 88 } | 88 } | 
| 89 } | 89 } | 
| 90 return run_break; | 90 return run_break; | 
| 91 } | 91 } | 
| 92 | 92 | 
| 93 // If the given scripts match, returns the one that isn't USCRIPT_COMMON or | 93 // If the given scripts match, returns the one that isn't USCRIPT_INHERITED, | 
| 94 // USCRIPT_INHERITED, i.e. the more specific one. Otherwise returns | 94 // i.e. the more specific one. Otherwise returns USCRIPT_INVALID_CODE. | 
| 95 // USCRIPT_INVALID_CODE. | |
| 96 UScriptCode ScriptIntersect(UScriptCode first, UScriptCode second) { | 95 UScriptCode ScriptIntersect(UScriptCode first, UScriptCode second) { | 
| 97 if (first == second || | 96 if (first == second || second == USCRIPT_INHERITED) | 
| 98 (second > USCRIPT_INVALID_CODE && second <= USCRIPT_INHERITED)) { | |
| 99 return first; | 97 return first; | 
| 100 } | 98 if (first == USCRIPT_INHERITED) | 
| 101 if (first > USCRIPT_INVALID_CODE && first <= USCRIPT_INHERITED) | |
| 102 return second; | 99 return second; | 
| 103 return USCRIPT_INVALID_CODE; | 100 return USCRIPT_INVALID_CODE; | 
| 104 } | 101 } | 
| 105 | 102 | 
| 106 // Writes the script and the script extensions of the character with the | 103 // Writes the script and the script extensions of the character with the | 
| 107 // Unicode |codepoint|. Returns the number of written scripts. | 104 // Unicode |codepoint|. Returns the number of written scripts. | 
| 108 int GetScriptExtensions(UChar32 codepoint, UScriptCode* scripts) { | 105 int GetScriptExtensions(UChar32 codepoint, UScriptCode* scripts) { | 
| 109 UErrorCode icu_error = U_ZERO_ERROR; | 106 UErrorCode icu_error = U_ZERO_ERROR; | 
| 110 // ICU documentation incorrectly states that the result of | 107 // ICU documentation incorrectly states that the result of | 
| 111 // |uscript_getScriptExtensions| will contain the regular script property. | 108 // |uscript_getScriptExtensions| will contain the regular script property. | 
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 160 DCHECK_GT(length, 0U); | 157 DCHECK_GT(length, 0U); | 
| 161 | 158 | 
| 162 UScriptCode scripts[kMaxScripts] = { USCRIPT_INVALID_CODE }; | 159 UScriptCode scripts[kMaxScripts] = { USCRIPT_INVALID_CODE }; | 
| 163 | 160 | 
| 164 base::i18n::UTF16CharIterator char_iterator(text.c_str() + start, length); | 161 base::i18n::UTF16CharIterator char_iterator(text.c_str() + start, length); | 
| 165 size_t scripts_size = GetScriptExtensions(char_iterator.get(), scripts); | 162 size_t scripts_size = GetScriptExtensions(char_iterator.get(), scripts); | 
| 166 *script = scripts[0]; | 163 *script = scripts[0]; | 
| 167 | 164 | 
| 168 while (char_iterator.Advance()) { | 165 while (char_iterator.Advance()) { | 
| 169 ScriptSetIntersect(char_iterator.get(), scripts, &scripts_size); | 166 ScriptSetIntersect(char_iterator.get(), scripts, &scripts_size); | 
| 167 // Special handling to merge white space into the previous run. | |
| 168 if (u_isUWhiteSpace(char_iterator.get())) { | |
| 
Jun Mukai
2015/04/28 01:57:36
I am not so sure about other whitespace variations
 
xdai1
2015/04/29 18:23:27
I added a test case for fullwidth space in RenderT
 | |
| 169 scripts[0] = *script; | |
| 170 scripts_size = 1U; | |
| 171 } | |
| 170 if (scripts_size == 0U) | 172 if (scripts_size == 0U) | 
| 171 return char_iterator.array_pos(); | 173 return char_iterator.array_pos(); | 
| 172 *script = scripts[0]; | 174 *script = scripts[0]; | 
| 173 } | 175 } | 
| 174 | 176 | 
| 175 return length; | 177 return length; | 
| 176 } | 178 } | 
| 177 | 179 | 
| 178 // A port of hb_icu_script_to_script because harfbuzz on CrOS is built without | 180 // A port of hb_icu_script_to_script because harfbuzz on CrOS is built without | 
| 179 // hb-icu. See http://crbug.com/356929 | 181 // hb-icu. See http://crbug.com/356929 | 
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 232 min_baseline_(min_baseline), | 234 min_baseline_(min_baseline), | 
| 233 min_height_(min_height), | 235 min_height_(min_height), | 
| 234 multiline_(multiline), | 236 multiline_(multiline), | 
| 235 word_wrap_behavior_(word_wrap_behavior), | 237 word_wrap_behavior_(word_wrap_behavior), | 
| 236 text_(text), | 238 text_(text), | 
| 237 words_(words), | 239 words_(words), | 
| 238 run_list_(run_list), | 240 run_list_(run_list), | 
| 239 text_x_(0), | 241 text_x_(0), | 
| 240 line_x_(0), | 242 line_x_(0), | 
| 241 max_descent_(0), | 243 max_descent_(0), | 
| 242 max_ascent_(0) { | 244 max_ascent_(0), | 
| 245 next_word_start_pos_(0) { | |
| 243 DCHECK_EQ(multiline_, (words_ != nullptr)); | 246 DCHECK_EQ(multiline_, (words_ != nullptr)); | 
| 244 AdvanceLine(); | 247 AdvanceLine(); | 
| 245 } | 248 } | 
| 246 | 249 | 
| 247 // Breaks the run at given |run_index| into Line structs. | 250 // Breaks the run at given |run_index| into Line structs. | 
| 248 void AddRun(int run_index) { | 251 void AddRun(int run_index) { | 
| 249 const internal::TextRunHarfBuzz* run = run_list_.runs()[run_index]; | 252 const internal::TextRunHarfBuzz* run = run_list_.runs()[run_index]; | 
| 250 base::char16 first_char = text_[run->range.start()]; | 253 base::char16 first_char = text_[run->range.start()]; | 
| 254 | |
| 255 if (word_wrap_behavior_ == TRUNCATE_LONG_WORDS && words_) { | |
| 256 // If the run has been processed, skip it. | |
| 257 size_t current_word_start_pos = | |
| 258 words_->GetBreak(run->range.start())->first; | |
| 259 if (current_word_start_pos < next_word_start_pos_) | |
| 260 return; | |
| 261 } | |
| 262 | |
| 251 if (multiline_ && first_char == '\n') { | 263 if (multiline_ && first_char == '\n') { | 
| 252 AdvanceLine(); | 264 AdvanceLine(); | 
| 253 } else if (multiline_ && (line_x_ + SkFloatToScalar(run->width)) > | 265 } else if (multiline_ && (line_x_ + SkFloatToScalar(run->width)) > | 
| 254 max_width_) { | 266 max_width_) { | 
| 255 BreakRun(run_index); | 267 BreakRun(run_index); | 
| 256 } else { | 268 } else { | 
| 257 AddSegment(run_index, run->range, run->width); | 269 AddSegment(run_index, run->range, run->width); | 
| 258 } | 270 } | 
| 259 } | 271 } | 
| 260 | 272 | 
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 310 bool BreakRunAtWidth(const internal::TextRunHarfBuzz& run, | 322 bool BreakRunAtWidth(const internal::TextRunHarfBuzz& run, | 
| 311 size_t start_char, | 323 size_t start_char, | 
| 312 SkScalar* width, | 324 SkScalar* width, | 
| 313 size_t* end_char, | 325 size_t* end_char, | 
| 314 size_t* next_char) { | 326 size_t* next_char) { | 
| 315 DCHECK(words_); | 327 DCHECK(words_); | 
| 316 DCHECK(run.range.Contains(Range(start_char, start_char + 1))); | 328 DCHECK(run.range.Contains(Range(start_char, start_char + 1))); | 
| 317 SkScalar available_width = max_width_ - line_x_; | 329 SkScalar available_width = max_width_ - line_x_; | 
| 318 BreakList<size_t>::const_iterator word = words_->GetBreak(start_char); | 330 BreakList<size_t>::const_iterator word = words_->GetBreak(start_char); | 
| 319 BreakList<size_t>::const_iterator next_word = word + 1; | 331 BreakList<size_t>::const_iterator next_word = word + 1; | 
| 320 // Width from |std::max(word->first, start_char)| to the current character. | 332 next_word_start_pos_ = | 
| 321 SkScalar word_width = 0; | 333 (next_word == words_->breaks().end()) ? text_.size() : next_word->first; | 
| 334 | |
| 335 // Glyph width of text_[word->first, start_char - 1]. | |
| 336 SkScalar word_width = | |
| 337 start_char == 0 ? 0 : GetGlyphWidth(word->first, start_char - 1); | |
| 
Jun Mukai
2015/04/28 01:57:36
I think you don't have to add a method like 'GetGl
 
xdai1
2015/04/29 18:23:27
Done.
 | |
| 322 *width = 0; | 338 *width = 0; | 
| 323 | 339 | 
| 324 Range char_range; | 340 Range char_range; | 
| 325 SkScalar truncated_width = 0; | 341 SkScalar truncated_width = 0; | 
| 326 for (size_t i = start_char; i < run.range.end(); i += char_range.length()) { | 342 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 | 343 // |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| | 344 // the word boundary right after |i|. Advance both |word| and |next_word| | 
| 329 // when |i| reaches |next_word|. | 345 // when |i| reaches |next_word|. | 
| 330 if (next_word != words_->breaks().end() && i >= next_word->first) { | 346 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++; | 347 word = next_word++; | 
| 348 next_word_start_pos_ = (next_word == words_->breaks().end()) | |
| 349 ? text_.size() | |
| 350 : next_word->first; | |
| 341 word_width = 0; | 351 word_width = 0; | 
| 342 } | 352 } | 
| 343 | 353 | 
| 344 Range glyph_range; | 354 Range glyph_range; | 
| 345 run.GetClusterAt(i, &char_range, &glyph_range); | 355 run.GetClusterAt(i, &char_range, &glyph_range); | 
| 346 DCHECK_LT(0U, char_range.length()); | 356 DCHECK_LT(0U, char_range.length()); | 
| 347 | 357 | 
| 348 SkScalar char_width = ((glyph_range.end() >= run.glyph_count) | 358 SkScalar char_width = ((glyph_range.end() >= run.glyph_count) | 
| 349 ? SkFloatToScalar(run.width) | 359 ? SkFloatToScalar(run.width) | 
| 350 : run.positions[glyph_range.end()].x()) - | 360 : run.positions[glyph_range.end()].x()) - | 
| 351 run.positions[glyph_range.start()].x(); | 361 run.positions[glyph_range.start()].x(); | 
| 352 | 362 | 
| 353 *width += char_width; | 363 *width += char_width; | 
| 354 word_width += char_width; | 364 word_width += char_width; | 
| 355 | 365 | 
| 356 // TODO(mukai): implement ELIDE_LONG_WORDS. | 366 // TODO(mukai): implement ELIDE_LONG_WORDS. | 
| 357 if (*width > available_width) { | 367 if (*width > available_width) { | 
| 358 if (line_x_ != 0 || word_width < *width) { | 368 if ((line_x_ != 0 && word_width <= *width) || | 
| 369 (line_x_ == 0 && word_width < *width)) { | |
| 
Jun Mukai
2015/04/28 01:57:36
I don't get these conditions.  If line_x_ is 0 (i.
 
xdai1
2015/04/29 18:23:27
For example, "I am a student", suppose the line wi
 | |
| 359 // Roll back one word. | 370 // Roll back one word. | 
| 360 *width -= word_width; | 371 *width -= word_width; | 
| 361 *next_char = std::max(word->first, start_char); | 372 *next_char = std::max(word->first, start_char); | 
| 362 *end_char = *next_char; | 373 *end_char = *next_char; | 
| 363 return true; | 374 return true; | 
| 364 } else if (word_wrap_behavior_ == WRAP_LONG_WORDS) { | 375 } else if (word_wrap_behavior_ == WRAP_LONG_WORDS) { | 
| 365 if (char_width < *width) { | 376 if (char_width < *width) { | 
| 366 // Roll back one character. | 377 // Roll back one character. | 
| 367 *width -= char_width; | 378 *width -= char_width; | 
| 368 *next_char = i; | 379 *next_char = i; | 
| 369 } else { | 380 } else { | 
| 370 // Continue from the next character. | 381 // Continue from the next character. | 
| 371 *next_char = i + char_range.length(); | 382 *next_char = i + char_range.length(); | 
| 372 } | 383 } | 
| 373 *end_char = *next_char; | 384 *end_char = *next_char; | 
| 374 return true; | 385 return true; | 
| 386 } else if (word_wrap_behavior_ == TRUNCATE_LONG_WORDS) { | |
| 387 *width = truncated_width; | |
| 388 *next_char = next_word_start_pos_; | |
| 389 return true; | |
| 375 } | 390 } | 
| 376 } else { | 391 } else { | 
| 377 *end_char = char_range.end(); | 392 *end_char = char_range.end(); | 
| 378 truncated_width = *width; | 393 truncated_width = *width; | 
| 379 } | 394 } | 
| 380 } | 395 } | 
| 381 | 396 | 
| 382 if (word_wrap_behavior_ == TRUNCATE_LONG_WORDS) | 397 if (word_wrap_behavior_ == TRUNCATE_LONG_WORDS) | 
| 383 *width = truncated_width; | 398 *width = truncated_width; | 
| 384 *end_char = *next_char = run.range.end(); | 399 *end_char = *next_char = run.range.end(); | 
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 463 SegmentHandle(lines_.size() - 1, line->segments.size() - 1)); | 478 SegmentHandle(lines_.size() - 1, line->segments.size() - 1)); | 
| 464 // If this is the last segment of an RTL run, reprocess the text-space x | 479 // If this is the last segment of an RTL run, reprocess the text-space x | 
| 465 // ranges of all segments from the run. | 480 // ranges of all segments from the run. | 
| 466 if (char_range.end() == run.range.end()) | 481 if (char_range.end() == run.range.end()) | 
| 467 UpdateRTLSegmentRanges(); | 482 UpdateRTLSegmentRanges(); | 
| 468 } | 483 } | 
| 469 text_x_ += SkFloatToScalar(width); | 484 text_x_ += SkFloatToScalar(width); | 
| 470 line_x_ += SkFloatToScalar(width); | 485 line_x_ += SkFloatToScalar(width); | 
| 471 } | 486 } | 
| 472 | 487 | 
| 488 // Returns the index for the run that contains text_[pos] using binary search. | |
| 489 size_t GetRunIndexAt(size_t pos) const { | |
| 490 pos = (pos < text_.size()) ? pos : text_.size() - 1; | |
| 491 size_t low = 0, high = run_list_.size() - 1; | |
| 492 while (low <= high) { | |
| 493 size_t mid = low + (high - low) / 2; | |
| 494 if (run_list_.runs()[mid]->range.start() <= pos && | |
| 495 run_list_.runs()[mid]->range.end() > pos) | |
| 496 return mid; | |
| 497 else if (run_list_.runs()[mid]->range.start() > pos) | |
| 498 high = mid - 1; | |
| 499 else | |
| 500 low = mid + 1; | |
| 501 } | |
| 502 return 0; | |
| 503 } | |
| 504 | |
| 505 // Returns the glyph width for |text_| between [start_pos, end_pos]. | |
| 506 SkScalar GetGlyphWidth(size_t start_pos, size_t end_pos) const { | |
| 507 end_pos = (end_pos < text_.size()) ? end_pos : text_.size() - 1; | |
| 508 if (start_pos > end_pos) | |
| 509 return 0; | |
| 510 | |
| 511 // Finding the runs that containing |start_char| and |end_char|. | |
| 512 size_t start_run_idx = GetRunIndexAt(start_pos); | |
| 513 size_t end_run_idx = GetRunIndexAt(end_pos); | |
| 514 const internal::TextRunHarfBuzz* start_run = | |
| 515 run_list_.runs()[start_run_idx]; | |
| 516 const internal::TextRunHarfBuzz* end_run = run_list_.runs()[end_run_idx]; | |
| 517 | |
| 518 SkScalar width = 0; | |
| 519 for (size_t i = start_run_idx; i <= end_run_idx; ++i) { | |
| 520 width += run_list_.runs()[i]->width; | |
| 521 } | |
| 522 | |
| 523 Range char_range; | |
| 524 Range glyph_range; | |
| 525 for (size_t pos = start_run->range.start(); pos < start_pos; ++pos) { | |
| 526 start_run->GetClusterAt(pos, &char_range, &glyph_range); | |
| 527 SkScalar char_width = | |
| 528 ((glyph_range.end() >= start_run->glyph_count) | |
| 529 ? SkFloatToScalar(start_run->width) | |
| 530 : start_run->positions[glyph_range.end()].x()) - | |
| 531 start_run->positions[glyph_range.start()].x(); | |
| 532 width -= char_width; | |
| 533 } | |
| 534 | |
| 535 for (size_t pos = end_pos + 1; pos < end_run->range.end(); ++pos) { | |
| 536 end_run->GetClusterAt(pos, &char_range, &glyph_range); | |
| 537 SkScalar char_width = ((glyph_range.end() >= end_run->glyph_count) | |
| 538 ? SkFloatToScalar(end_run->width) | |
| 539 : end_run->positions[glyph_range.end()].x()) - | |
| 540 end_run->positions[glyph_range.start()].x(); | |
| 541 width -= char_width; | |
| 542 } | |
| 543 | |
| 544 return width; | |
| 545 } | |
| 546 | |
| 473 const SkScalar max_width_; | 547 const SkScalar max_width_; | 
| 474 const int min_baseline_; | 548 const int min_baseline_; | 
| 475 const float min_height_; | 549 const float min_height_; | 
| 476 const bool multiline_; | 550 const bool multiline_; | 
| 477 const WordWrapBehavior word_wrap_behavior_; | 551 const WordWrapBehavior word_wrap_behavior_; | 
| 478 const base::string16& text_; | 552 const base::string16& text_; | 
| 479 const BreakList<size_t>* const words_; | 553 const BreakList<size_t>* const words_; | 
| 480 const internal::TextRunList& run_list_; | 554 const internal::TextRunList& run_list_; | 
| 481 | 555 | 
| 482 // Stores the resulting lines. | 556 // Stores the resulting lines. | 
| 483 std::vector<internal::Line> lines_; | 557 std::vector<internal::Line> lines_; | 
| 484 | 558 | 
| 485 // Text space and line space x coordinates of the next segment to be added. | 559 // Text space and line space x coordinates of the next segment to be added. | 
| 486 SkScalar text_x_; | 560 SkScalar text_x_; | 
| 487 SkScalar line_x_; | 561 SkScalar line_x_; | 
| 488 | 562 | 
| 489 float max_descent_; | 563 float max_descent_; | 
| 490 float max_ascent_; | 564 float max_ascent_; | 
| 491 | 565 | 
| 566 // Stores the start position of next word that need to be processed. | |
| 567 size_t next_word_start_pos_; | |
| 568 | |
| 492 // Size of the multiline text, not including the currently processed line. | 569 // Size of the multiline text, not including the currently processed line. | 
| 493 SizeF total_size_; | 570 SizeF total_size_; | 
| 494 | 571 | 
| 495 // The current RTL run segments, to be applied by |UpdateRTLSegmentRanges()|. | 572 // The current RTL run segments, to be applied by |UpdateRTLSegmentRanges()|. | 
| 496 std::vector<SegmentHandle> rtl_segments_; | 573 std::vector<SegmentHandle> rtl_segments_; | 
| 497 | 574 | 
| 498 DISALLOW_COPY_AND_ASSIGN(HarfBuzzLineBreaker); | 575 DISALLOW_COPY_AND_ASSIGN(HarfBuzzLineBreaker); | 
| 499 }; | 576 }; | 
| 500 | 577 | 
| 501 // Function object for case insensitive string comparison. | 578 // Function object for case insensitive string comparison. | 
| (...skipping 977 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1479 DCHECK(!update_layout_run_list_); | 1556 DCHECK(!update_layout_run_list_); | 
| 1480 DCHECK(!update_display_run_list_); | 1557 DCHECK(!update_display_run_list_); | 
| 1481 return text_elided() ? display_run_list_.get() : &layout_run_list_; | 1558 return text_elided() ? display_run_list_.get() : &layout_run_list_; | 
| 1482 } | 1559 } | 
| 1483 | 1560 | 
| 1484 const internal::TextRunList* RenderTextHarfBuzz::GetRunList() const { | 1561 const internal::TextRunList* RenderTextHarfBuzz::GetRunList() const { | 
| 1485 return const_cast<RenderTextHarfBuzz*>(this)->GetRunList(); | 1562 return const_cast<RenderTextHarfBuzz*>(this)->GetRunList(); | 
| 1486 } | 1563 } | 
| 1487 | 1564 | 
| 1488 } // namespace gfx | 1565 } // namespace gfx | 
| OLD | NEW |