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

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

Issue 351963002: RenderTextHarfBuzz: Allow mid-glyph cursors in multi-grapheme clusters (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: comments addressed Created 6 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 | Annotate | Revision Log
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 <map> 7 #include <map>
8 8
9 #include "base/debug/leak_annotations.h" 9 #include "base/debug/leak_annotations.h"
10 #include "base/i18n/bidi_line_iterator.h" 10 #include "base/i18n/bidi_line_iterator.h"
(...skipping 382 matching lines...) Expand 10 before | Expand all | Expand 10 after
393 } 393 }
394 394
395 // A port of hb_icu_script_to_script because harfbuzz on CrOS is built without 395 // A port of hb_icu_script_to_script because harfbuzz on CrOS is built without
396 // hb-icu. See http://crbug.com/356929 396 // hb-icu. See http://crbug.com/356929
397 inline hb_script_t ICUScriptToHBScript(UScriptCode script) { 397 inline hb_script_t ICUScriptToHBScript(UScriptCode script) {
398 if (script == USCRIPT_INVALID_CODE) 398 if (script == USCRIPT_INVALID_CODE)
399 return HB_SCRIPT_INVALID; 399 return HB_SCRIPT_INVALID;
400 return hb_script_from_string(uscript_getShortName(script), -1); 400 return hb_script_from_string(uscript_getShortName(script), -1);
401 } 401 }
402 402
403 template <class Iterator>
404 void GetClusterAtImpl(size_t pos,
msw 2014/07/25 00:37:01 nit: Add a simple comment.
ckocagil 2014/07/29 23:13:41 Done.
405 Range range,
406 Iterator elements_begin,
407 Iterator elements_end,
408 bool reversed,
409 Range* chars,
410 Range* glyphs) {
411 Iterator element = std::upper_bound(elements_begin, elements_end, pos);
412 chars->set_end(element == elements_end ? range.end() : *element);
413 glyphs->set_end(reversed ? elements_end - element : element - elements_begin);
414
415 DCHECK(element != elements_begin);
416 while (--element != elements_begin && *element == *(element - 1));
417 chars->set_start(*element);
418 glyphs->set_start(
419 reversed ? elements_end - element : element - elements_begin);
420 if (reversed)
421 *glyphs = Range(glyphs->end(), glyphs->start());
422
423 DCHECK(!chars->is_reversed());
424 DCHECK(!chars->is_empty());
425 DCHECK(!glyphs->is_reversed());
426 DCHECK(!glyphs->is_empty());
427 }
428
403 } // namespace 429 } // namespace
404 430
405 namespace internal { 431 namespace internal {
406 432
407 TextRunHarfBuzz::TextRunHarfBuzz() 433 TextRunHarfBuzz::TextRunHarfBuzz()
408 : width(0), 434 : width(0),
409 preceding_run_widths(0), 435 preceding_run_widths(0),
410 is_rtl(false), 436 is_rtl(false),
411 level(0), 437 level(0),
412 script(USCRIPT_INVALID_CODE), 438 script(USCRIPT_INVALID_CODE),
413 glyph_count(static_cast<size_t>(-1)), 439 glyph_count(static_cast<size_t>(-1)),
414 font_size(0), 440 font_size(0),
415 font_style(0), 441 font_style(0),
416 strike(false), 442 strike(false),
417 diagonal_strike(false), 443 diagonal_strike(false),
418 underline(false) {} 444 underline(false) {}
419 445
420 TextRunHarfBuzz::~TextRunHarfBuzz() {} 446 TextRunHarfBuzz::~TextRunHarfBuzz() {}
421 447
422 size_t TextRunHarfBuzz::CharToGlyph(size_t pos) const { 448 void TextRunHarfBuzz::GetClusterAt(size_t pos,
423 DCHECK(range.start() <= pos && pos < range.end()); 449 Range* chars,
450 Range* glyphs) const {
451 DCHECK(range.Contains(Range(pos, pos + 1)));
424 452
425 if (!is_rtl) { 453 Range temp_range;
msw 2014/07/25 00:37:01 optional nit: The only NULL being sent in is |char
ckocagil 2014/07/29 23:13:41 Done.
426 size_t cluster_start = 0; 454 if (!chars)
427 for (size_t i = 1; i < glyph_count && pos >= glyph_to_char[i]; ++i) 455 chars = &temp_range;
428 if (glyph_to_char[i] != glyph_to_char[i - 1]) 456 if (!glyphs)
429 cluster_start = i; 457 glyphs = &temp_range;
430 return cluster_start; 458
459 if (is_rtl) {
msw 2014/07/25 00:37:01 optional nit: instead do: Iterator begin = glyph_
ckocagil 2014/07/29 23:13:41 .begin() and .rbegin() return incompatible types (
460 GetClusterAtImpl(pos, range, glyph_to_char.rbegin(), glyph_to_char.rend(),
461 true, chars, glyphs);
462 return;
431 } 463 }
432 464
433 for (size_t i = 0; i < glyph_count; ++i) { 465 GetClusterAtImpl(pos, range, glyph_to_char.begin(), glyph_to_char.end(),
434 if (pos >= glyph_to_char[i]) 466 false, chars, glyphs);
435 return i;
436 }
437 NOTREACHED();
438 return 0;
439 } 467 }
440 468
441 Range TextRunHarfBuzz::CharRangeToGlyphRange(const Range& char_range) const { 469 Range TextRunHarfBuzz::CharRangeToGlyphRange(const Range& char_range) const {
442 DCHECK(range.Contains(char_range)); 470 DCHECK(range.Contains(char_range));
443 DCHECK(!char_range.is_reversed()); 471 DCHECK(!char_range.is_reversed());
444 DCHECK(!char_range.is_empty()); 472 DCHECK(!char_range.is_empty());
445 473
446 size_t first = 0; 474 Range start_glyphs;
447 size_t last = 0; 475 Range end_glyphs;
476 GetClusterAt(char_range.start(), NULL, &start_glyphs);
477 GetClusterAt(char_range.end() - 1, NULL, &end_glyphs);
448 478
449 if (is_rtl) { 479 return is_rtl ? Range(end_glyphs.start(), start_glyphs.end()) :
450 // For RTL runs, we subtract 1 from |char_range| to get the leading edges. 480 Range(start_glyphs.start(), end_glyphs.end());
451 last = CharToGlyph(char_range.end() - 1);
452 // Loop until we find a non-empty glyph range. For multi-character clusters,
453 // the loop is needed to find the cluster end. Do the same for LTR below.
454 for (size_t i = char_range.start(); i > range.start(); --i) {
455 first = CharToGlyph(i - 1);
456 if (first != last)
457 return Range(last, first);
458 }
459 return Range(last, glyph_count);
460 }
461
462 first = CharToGlyph(char_range.start());
463 for (size_t i = char_range.end(); i < range.end(); ++i) {
464 last = CharToGlyph(i);
465 if (first != last)
466 return Range(first, last);
467 }
468 return Range(first, glyph_count);
469 } 481 }
470 482
471 size_t TextRunHarfBuzz::CountMissingGlyphs() const { 483 size_t TextRunHarfBuzz::CountMissingGlyphs() const {
472 static const int kMissingGlyphId = 0; 484 static const int kMissingGlyphId = 0;
473 size_t missing = 0; 485 size_t missing = 0;
474 for (size_t i = 0; i < glyph_count; ++i) 486 for (size_t i = 0; i < glyph_count; ++i)
475 missing += (glyphs[i] == kMissingGlyphId) ? 1 : 0; 487 missing += (glyphs[i] == kMissingGlyphId) ? 1 : 0;
476 return missing; 488 return missing;
477 } 489 }
478 490
479 int TextRunHarfBuzz::GetGlyphXBoundary(size_t text_index, bool trailing) const { 491 Range TextRunHarfBuzz::GetGraphemeBounds(const base::string16& text,
msw 2014/07/25 00:37:01 Remove |text|, it's no longer used here.
ckocagil 2014/07/29 23:13:41 Done.
480 if (text_index == range.end()) { 492 base::i18n::BreakIterator* grapheme_iterator,
481 trailing = true; 493 size_t text_index) {
482 --text_index; 494 DCHECK_LT(text_index, range.end());
495
496 Range chars;
497 Range glyphs;
498 GetClusterAt(text_index, &chars, &glyphs);
499 const int cluster_begin_x = SkScalarRoundToInt(positions[glyphs.start()].x());
500 const int cluster_end_x = glyphs.end() < glyph_count ?
501 SkScalarRoundToInt(positions[glyphs.end()].x()) : width;
502
503 // A cluster consists of a number of code points and corresponds to a number
504 // of glyphs that should be drawn together. A cluster can contain multiple
505 // graphemes. In order to place the cursor at a grapheme boundary inside the
506 // cluster, we simple divide the cluster width by the number of graphemes.
msw 2014/07/25 00:37:00 nit: s/simple/simply
ckocagil 2014/07/29 23:13:41 Done.
507 if (chars.length() != 1 && grapheme_iterator) {
msw 2014/07/25 00:37:01 nit: explicitly check |chars.length() > 1|.
ckocagil 2014/07/29 23:13:41 Done.
508 int before = 0;
509 int total = 0;
510 for (size_t i = chars.start(); i < chars.end(); ++i) {
511 if (grapheme_iterator->IsGraphemeBoundary(i)) {
512 if (i < text_index)
513 ++before;
514 ++total;
515 }
516 }
517 DCHECK_GT(total, 0);
518 if (total > 1) {
519 if (is_rtl)
520 before = total - before - 1;
521 DCHECK_GE(before, 0);
522 DCHECK_LT(before, total);
523 const int cluster_width = cluster_end_x - cluster_begin_x;
524 const int grapheme_begin_x = cluster_begin_x + static_cast<int>(0.5f +
525 cluster_width * before / static_cast<float>(total));
526 const int grapheme_end_x = cluster_begin_x + static_cast<int>(0.5f +
527 cluster_width * (before + 1) / static_cast<float>(total));
528 return Range(preceding_run_widths + grapheme_begin_x,
529 preceding_run_widths + grapheme_end_x);
530 }
483 } 531 }
484 Range glyph_range = CharRangeToGlyphRange(Range(text_index, text_index + 1)); 532
485 const size_t glyph_pos = (is_rtl == trailing) ? 533 return Range(preceding_run_widths + cluster_begin_x,
486 glyph_range.start() : glyph_range.end(); 534 preceding_run_widths + cluster_end_x);
487 const int x = glyph_pos < glyph_count ?
488 SkScalarRoundToInt(positions[glyph_pos].x()) : width;
489 return preceding_run_widths + x;
490 } 535 }
491 536
492 } // namespace internal 537 } // namespace internal
493 538
494 RenderTextHarfBuzz::RenderTextHarfBuzz() 539 RenderTextHarfBuzz::RenderTextHarfBuzz()
495 : RenderText(), 540 : RenderText(),
496 needs_layout_(false) {} 541 needs_layout_(false) {}
497 542
498 RenderTextHarfBuzz::~RenderTextHarfBuzz() {} 543 RenderTextHarfBuzz::~RenderTextHarfBuzz() {}
499 544
(...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after
625 break; 670 break;
626 } 671 }
627 pos = iter.pos() - iter.GetString().length(); 672 pos = iter.pos() - iter.GetString().length();
628 } 673 }
629 } 674 }
630 } 675 }
631 return SelectionModel(pos, CURSOR_FORWARD); 676 return SelectionModel(pos, CURSOR_FORWARD);
632 } 677 }
633 678
634 Range RenderTextHarfBuzz::GetGlyphBounds(size_t index) { 679 Range RenderTextHarfBuzz::GetGlyphBounds(size_t index) {
680 EnsureLayout();
635 const size_t run_index = 681 const size_t run_index =
636 GetRunContainingCaret(SelectionModel(index, CURSOR_FORWARD)); 682 GetRunContainingCaret(SelectionModel(index, CURSOR_FORWARD));
637 // Return edge bounds if the index is invalid or beyond the layout text size. 683 // Return edge bounds if the index is invalid or beyond the layout text size.
638 if (run_index >= runs_.size()) 684 if (run_index >= runs_.size())
639 return Range(GetStringSize().width()); 685 return Range(GetStringSize().width());
640 const size_t layout_index = TextIndexToLayoutIndex(index); 686 const size_t layout_index = TextIndexToLayoutIndex(index);
641 return Range(runs_[run_index]->GetGlyphXBoundary(layout_index, false), 687 internal::TextRunHarfBuzz* run = runs_[run_index];
642 runs_[run_index]->GetGlyphXBoundary(layout_index, true)); 688 Range bounds = run->GetGraphemeBounds(GetLayoutText(), grapheme_iterator(),
689 layout_index);
690 return run->is_rtl ? Range(bounds.end(), bounds.start()) : bounds;
643 } 691 }
644 692
645 std::vector<Rect> RenderTextHarfBuzz::GetSubstringBounds(const Range& range) { 693 std::vector<Rect> RenderTextHarfBuzz::GetSubstringBounds(const Range& range) {
646 DCHECK(!needs_layout_); 694 DCHECK(!needs_layout_);
647 DCHECK(Range(0, text().length()).Contains(range)); 695 DCHECK(Range(0, text().length()).Contains(range));
648 Range layout_range(TextIndexToLayoutIndex(range.start()), 696 Range layout_range(TextIndexToLayoutIndex(range.start()),
649 TextIndexToLayoutIndex(range.end())); 697 TextIndexToLayoutIndex(range.end()));
650 DCHECK(Range(0, GetLayoutText().length()).Contains(layout_range)); 698 DCHECK(Range(0, GetLayoutText().length()).Contains(layout_range));
651 699
652 std::vector<Rect> rects; 700 std::vector<Rect> rects;
653 if (layout_range.is_empty()) 701 if (layout_range.is_empty())
654 return rects; 702 return rects;
655 std::vector<Range> bounds; 703 std::vector<Range> bounds;
656 704
657 // Add a Range for each run/selection intersection. 705 // Add a Range for each run/selection intersection.
658 // TODO(msw): The bounds should probably not always be leading the range ends. 706 // TODO(msw): The bounds should probably not always be leading the range ends.
msw 2014/07/25 00:37:00 nit: I think you're resolving this TODO here by ge
ckocagil 2014/07/29 23:13:41 I don't quite understand this TODO, I will remove
msw 2014/07/29 23:41:13 Yeah, you can remove this TODO (at worst the bug w
ckocagil 2014/07/30 00:15:25 Done.
659 for (size_t i = 0; i < runs_.size(); ++i) { 707 for (size_t i = 0; i < runs_.size(); ++i) {
660 const internal::TextRunHarfBuzz* run = runs_[visual_to_logical_[i]]; 708 internal::TextRunHarfBuzz* run = runs_[visual_to_logical_[i]];
661 Range intersection = run->range.Intersect(layout_range); 709 Range intersection = run->range.Intersect(layout_range);
662 if (intersection.IsValid()) { 710 if (!intersection.IsValid())
663 DCHECK(!intersection.is_reversed()); 711 continue;
664 Range range_x(run->GetGlyphXBoundary(intersection.start(), false), 712 DCHECK(!intersection.is_reversed());
665 run->GetGlyphXBoundary(intersection.end(), false)); 713 const Range leftmost_character_x = run->GetGraphemeBounds(GetLayoutText(),
666 if (range_x.is_empty()) 714 grapheme_iterator(),
667 continue; 715 run->is_rtl ? intersection.end() - 1 : intersection.start());
668 range_x = Range(range_x.GetMin(), range_x.GetMax()); 716 const Range rightmost_character_x = run->GetGraphemeBounds(GetLayoutText(),
669 // Union this with the last range if they're adjacent. 717 grapheme_iterator(),
670 DCHECK(bounds.empty() || bounds.back().GetMax() <= range_x.GetMin()); 718 run->is_rtl ? intersection.start() : intersection.end() - 1);
671 if (!bounds.empty() && bounds.back().GetMax() == range_x.GetMin()) { 719 Range range_x(leftmost_character_x.start(), rightmost_character_x.end());
msw 2014/07/25 00:37:01 nit: It looks like the revised logic should always
ckocagil 2014/07/29 23:13:41 Done.
672 range_x = Range(bounds.back().GetMin(), range_x.GetMax()); 720
673 bounds.pop_back(); 721 if (range_x.is_empty())
674 } 722 continue;
675 bounds.push_back(range_x); 723 range_x = Range(range_x.GetMin(), range_x.GetMax());
724 // Union this with the last range if they're adjacent.
725 DCHECK(bounds.empty() || bounds.back().GetMax() <= range_x.GetMin());
726 if (!bounds.empty() && bounds.back().GetMax() == range_x.GetMin()) {
727 range_x = Range(bounds.back().GetMin(), range_x.GetMax());
728 bounds.pop_back();
676 } 729 }
730 bounds.push_back(range_x);
677 } 731 }
678 for (size_t i = 0; i < bounds.size(); ++i) { 732 for (size_t i = 0; i < bounds.size(); ++i) {
679 std::vector<Rect> current_rects = TextBoundsToViewBounds(bounds[i]); 733 std::vector<Rect> current_rects = TextBoundsToViewBounds(bounds[i]);
680 rects.insert(rects.end(), current_rects.begin(), current_rects.end()); 734 rects.insert(rects.end(), current_rects.begin(), current_rects.end());
681 } 735 }
682 return rects; 736 return rects;
683 } 737 }
684 738
685 size_t RenderTextHarfBuzz::TextIndexToLayoutIndex(size_t index) const { 739 size_t RenderTextHarfBuzz::TextIndexToLayoutIndex(size_t index) const {
686 DCHECK_LE(index, text().length()); 740 DCHECK_LE(index, text().length());
(...skipping 12 matching lines...) Expand all
699 DCHECK_LE(text_index, text().length()); 753 DCHECK_LE(text_index, text().length());
700 return text_index; 754 return text_index;
701 } 755 }
702 756
703 bool RenderTextHarfBuzz::IsValidCursorIndex(size_t index) { 757 bool RenderTextHarfBuzz::IsValidCursorIndex(size_t index) {
704 if (index == 0 || index == text().length()) 758 if (index == 0 || index == text().length())
705 return true; 759 return true;
706 if (!IsValidLogicalIndex(index)) 760 if (!IsValidLogicalIndex(index))
707 return false; 761 return false;
708 EnsureLayout(); 762 EnsureLayout();
709 // Disallow indices amid multi-character graphemes by checking glyph bounds. 763 internal::TextRunHarfBuzz* run =
710 // These characters are not surrogate-pairs, but may yield a single glyph: 764 runs_[GetRunContainingCaret(SelectionModel(index, CURSOR_BACKWARD))];
711 // \x0915\x093f - (ki) - one of many Devanagari biconsonantal conjuncts. 765 return !grapheme_iterator() || grapheme_iterator()->IsGraphemeBoundary(index);
712 // \x0e08\x0e33 - (cho chan + sara am) - a Thai consonant and vowel pair.
713 return GetGlyphBounds(index) != GetGlyphBounds(index - 1);
714 } 766 }
715 767
716 void RenderTextHarfBuzz::ResetLayout() { 768 void RenderTextHarfBuzz::ResetLayout() {
717 needs_layout_ = true; 769 needs_layout_ = true;
718 } 770 }
719 771
720 void RenderTextHarfBuzz::EnsureLayout() { 772 void RenderTextHarfBuzz::EnsureLayout() {
721 if (needs_layout_) { 773 if (needs_layout_) {
722 runs_.clear(); 774 runs_.clear();
723 775
(...skipping 292 matching lines...) Expand 10 before | Expand all | Expand 10 after
1016 // Shape the text. 1068 // Shape the text.
1017 hb_shape(harfbuzz_font, buffer, NULL, 0); 1069 hb_shape(harfbuzz_font, buffer, NULL, 0);
1018 1070
1019 // Populate the run fields with the resulting glyph data in the buffer. 1071 // Populate the run fields with the resulting glyph data in the buffer.
1020 unsigned int glyph_count = 0; 1072 unsigned int glyph_count = 0;
1021 hb_glyph_info_t* infos = hb_buffer_get_glyph_infos(buffer, &glyph_count); 1073 hb_glyph_info_t* infos = hb_buffer_get_glyph_infos(buffer, &glyph_count);
1022 run->glyph_count = glyph_count; 1074 run->glyph_count = glyph_count;
1023 hb_glyph_position_t* hb_positions = 1075 hb_glyph_position_t* hb_positions =
1024 hb_buffer_get_glyph_positions(buffer, NULL); 1076 hb_buffer_get_glyph_positions(buffer, NULL);
1025 run->glyphs.reset(new uint16[run->glyph_count]); 1077 run->glyphs.reset(new uint16[run->glyph_count]);
1026 run->glyph_to_char.reset(new uint32[run->glyph_count]); 1078 run->glyph_to_char.resize(run->glyph_count);
1027 run->positions.reset(new SkPoint[run->glyph_count]); 1079 run->positions.reset(new SkPoint[run->glyph_count]);
1028 run->width = 0; 1080 run->width = 0;
1029 for (size_t i = 0; i < run->glyph_count; ++i) { 1081 for (size_t i = 0; i < run->glyph_count; ++i) {
1030 run->glyphs[i] = infos[i].codepoint; 1082 run->glyphs[i] = infos[i].codepoint;
1031 run->glyph_to_char[i] = infos[i].cluster; 1083 run->glyph_to_char[i] = infos[i].cluster;
1032 const int x_offset = 1084 const int x_offset =
1033 SkScalarRoundToInt(SkFixedToScalar(hb_positions[i].x_offset)); 1085 SkScalarRoundToInt(SkFixedToScalar(hb_positions[i].x_offset));
1034 const int y_offset = 1086 const int y_offset =
1035 SkScalarRoundToInt(SkFixedToScalar(hb_positions[i].y_offset)); 1087 SkScalarRoundToInt(SkFixedToScalar(hb_positions[i].y_offset));
1036 run->positions[i].set(run->width + x_offset, y_offset); 1088 run->positions[i].set(run->width + x_offset, y_offset);
1037 run->width += 1089 run->width +=
1038 SkScalarRoundToInt(SkFixedToScalar(hb_positions[i].x_advance)); 1090 SkScalarRoundToInt(SkFixedToScalar(hb_positions[i].x_advance));
1039 } 1091 }
1040 1092
1041 hb_buffer_destroy(buffer); 1093 hb_buffer_destroy(buffer);
1042 hb_font_destroy(harfbuzz_font); 1094 hb_font_destroy(harfbuzz_font);
1043 } 1095 }
1044 1096
1045 } // namespace gfx 1097 } // namespace gfx
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698