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 <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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 |
OLD | NEW |