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

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

Issue 2541313002: RenderTextHarfBuzz: Add support for multi line text selection. (Closed)
Patch Set: Nits. Created 4 years 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
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 273 matching lines...) Expand 10 before | Expand all | Expand 10 after
284 const Range word_range = words_->GetRange(iter); 284 const Range word_range = words_->GetRange(iter);
285 std::vector<internal::LineSegment> word_segments; 285 std::vector<internal::LineSegment> word_segments;
286 SkScalar word_width = GetWordWidth(word_range, &word_segments); 286 SkScalar word_width = GetWordWidth(word_range, &word_segments);
287 287
288 // If the last word is '\n', we should advance a new line after adding 288 // If the last word is '\n', we should advance a new line after adding
289 // the word to the current line. 289 // the word to the current line.
290 bool new_line = false; 290 bool new_line = false;
291 if (!word_segments.empty() && 291 if (!word_segments.empty() &&
292 text_[word_segments.back().char_range.start()] == '\n') { 292 text_[word_segments.back().char_range.start()] == '\n') {
293 new_line = true; 293 new_line = true;
294 word_width -= word_segments.back().width(); 294
295 word_segments.pop_back(); 295 // Since the line should at least contain some information regarding the
296 // text range it corresponds to, don't pop the newline segment, if it's
297 // the only segment in the line. This ensures that every line has a non-
298 // empty segments vector (except the last in some cases).
299 if (word_segments.size() != 1u || available_width_ != max_width_) {
300 word_width -= word_segments.back().width();
301 word_segments.pop_back();
302 }
296 } 303 }
297 304
298 // If the word is not the first word in the line and it can't fit into 305 // If the word is not the first word in the line and it can't fit into
299 // the current line, advance a new line. 306 // the current line, advance a new line.
300 if (word_width > available_width_ && available_width_ != max_width_) 307 if (word_width > available_width_ && available_width_ != max_width_)
301 AdvanceLine(); 308 AdvanceLine();
302 if (!word_segments.empty()) 309 if (!word_segments.empty())
303 AddWordToLine(word_segments); 310 AddWordToLine(word_segments);
304 if (new_line) 311 if (new_line)
305 AdvanceLine(); 312 AdvanceLine();
(...skipping 334 matching lines...) Expand 10 before | Expand all | Expand 10 after
640 GetClusterAtImpl(pos, range, glyph_to_char.rbegin(), glyph_to_char.rend(), 647 GetClusterAtImpl(pos, range, glyph_to_char.rbegin(), glyph_to_char.rend(),
641 true, chars, glyphs); 648 true, chars, glyphs);
642 return; 649 return;
643 } 650 }
644 651
645 GetClusterAtImpl(pos, range, glyph_to_char.begin(), glyph_to_char.end(), 652 GetClusterAtImpl(pos, range, glyph_to_char.begin(), glyph_to_char.end(),
646 false, chars, glyphs); 653 false, chars, glyphs);
647 } 654 }
648 655
649 RangeF TextRunHarfBuzz::GetGraphemeBounds(RenderTextHarfBuzz* render_text, 656 RangeF TextRunHarfBuzz::GetGraphemeBounds(RenderTextHarfBuzz* render_text,
650 size_t text_index) { 657 size_t text_index) const {
651 DCHECK_LT(text_index, range.end()); 658 DCHECK_LT(text_index, range.end());
652 if (glyph_count == 0) 659 if (glyph_count == 0)
653 return RangeF(preceding_run_widths, preceding_run_widths + width); 660 return RangeF(preceding_run_widths, preceding_run_widths + width);
654 661
655 Range chars; 662 Range chars;
656 Range glyphs; 663 Range glyphs;
657 GetClusterAt(text_index, &chars, &glyphs); 664 GetClusterAt(text_index, &chars, &glyphs);
658 const float cluster_begin_x = positions[glyphs.start()].x(); 665 const float cluster_begin_x = positions[glyphs.start()].x();
659 const float cluster_end_x = glyphs.end() < glyph_count ? 666 const float cluster_end_x = glyphs.end() < glyph_count ?
660 positions[glyphs.end()].x() : SkFloatToScalar(width); 667 positions[glyphs.end()].x() : SkFloatToScalar(width);
(...skipping 29 matching lines...) Expand all
690 cluster_width * (before + 1) / static_cast<float>(total)); 697 cluster_width * (before + 1) / static_cast<float>(total));
691 return RangeF(preceding_run_widths + grapheme_begin_x, 698 return RangeF(preceding_run_widths + grapheme_begin_x,
692 preceding_run_widths + grapheme_end_x); 699 preceding_run_widths + grapheme_end_x);
693 } 700 }
694 } 701 }
695 702
696 return RangeF(preceding_run_widths + cluster_begin_x, 703 return RangeF(preceding_run_widths + cluster_begin_x,
697 preceding_run_widths + cluster_end_x); 704 preceding_run_widths + cluster_end_x);
698 } 705 }
699 706
707 float TextRunHarfBuzz::GetGraphemeWidthForCharRange(
708 RenderTextHarfBuzz* render_text,
709 const Range& char_range) const {
710 if (char_range.is_empty())
711 return 0;
712 DCHECK(!char_range.is_reversed());
713 DCHECK(range.Contains(char_range));
714 size_t left_index = is_rtl ? char_range.end() - 1 : char_range.start();
msw 2016/12/19 22:27:43 Is checking the first/last char always sufficient?
karandeepb 2016/12/21 14:23:43 This should be sufficient. In the example you cite
msw 2016/12/21 20:29:11 Acknowledged.
715 size_t right_index = is_rtl ? char_range.start() : char_range.end() - 1;
716 return GetGraphemeBounds(render_text, right_index).GetMax() -
717 GetGraphemeBounds(render_text, left_index).GetMin();
718 }
719
700 SkScalar TextRunHarfBuzz::GetGlyphWidthForCharRange( 720 SkScalar TextRunHarfBuzz::GetGlyphWidthForCharRange(
701 const Range& char_range) const { 721 const Range& char_range) const {
702 if (char_range.is_empty()) 722 if (char_range.is_empty())
703 return 0; 723 return 0;
704 724
705 DCHECK(range.Contains(char_range)); 725 DCHECK(range.Contains(char_range));
706 Range glyph_range = CharRangeToGlyphRange(char_range); 726 Range glyph_range = CharRangeToGlyphRange(char_range);
707 727
708 // The |glyph_range| might be empty or invalid on Windows if a multi-character 728 // The |glyph_range| might be empty or invalid on Windows if a multi-character
709 // grapheme is divided into different runs (e.g., there are two font sizes or 729 // grapheme is divided into different runs (e.g., there are two font sizes or
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after
808 Size RenderTextHarfBuzz::GetStringSize() { 828 Size RenderTextHarfBuzz::GetStringSize() {
809 const SizeF size_f = GetStringSizeF(); 829 const SizeF size_f = GetStringSizeF();
810 return Size(std::ceil(size_f.width()), size_f.height()); 830 return Size(std::ceil(size_f.width()), size_f.height());
811 } 831 }
812 832
813 SizeF RenderTextHarfBuzz::GetStringSizeF() { 833 SizeF RenderTextHarfBuzz::GetStringSizeF() {
814 EnsureLayout(); 834 EnsureLayout();
815 return total_size_; 835 return total_size_;
816 } 836 }
817 837
818 SelectionModel RenderTextHarfBuzz::FindCursorPosition(const Point& point) { 838 SelectionModel RenderTextHarfBuzz::FindCursorPosition(const Point& view_point) {
819 EnsureLayout(); 839 EnsureLayout();
840 DCHECK(!lines().empty());
820 841
821 int x = ToTextPoint(point).x(); 842 int line_index = GetLineContainingYCoord((view_point - GetLineOffset(0)).y());
822 float offset = 0; 843 // Clip line index to a valid value in case kDragToEndIfOutsideVerticalBounds
823 size_t run_index = GetRunContainingXCoord(x, &offset); 844 // is false. Else, drag to end.
845 if (line_index < 0) {
846 if (RenderText::kDragToEndIfOutsideVerticalBounds)
847 return EdgeSelectionModel(GetVisualDirectionOfLogicalBeginning());
848 else
849 line_index = 0;
850 }
851 if (line_index >= static_cast<int>(lines().size())) {
852 if (RenderText::kDragToEndIfOutsideVerticalBounds)
853 return EdgeSelectionModel(GetVisualDirectionOfLogicalEnd());
854 else
855 line_index = lines().size() - 1;
856 }
857 const internal::Line& line = lines()[line_index];
824 858
825 internal::TextRunList* run_list = GetRunList(); 859 float point_offset_relative_segment = 0;
826 if (run_index >= run_list->size()) 860 const int segment_index = GetLineSegmentContainingXCoord(
827 return EdgeSelectionModel((x < 0) ? CURSOR_LEFT : CURSOR_RIGHT); 861 line, (view_point - GetLineOffset(line_index)).x(),
828 const internal::TextRunHarfBuzz& run = *run_list->runs()[run_index]; 862 &point_offset_relative_segment);
863 if (segment_index < 0)
864 return LineSelectionModel(line_index, CURSOR_LEFT);
865 if (segment_index >= static_cast<int>(line.segments.size()))
866 return LineSelectionModel(line_index, CURSOR_RIGHT);
867 const internal::LineSegment& segment = line.segments[segment_index];
868
869 const internal::TextRunHarfBuzz& run = *GetRunList()->runs()[segment.run];
870 const size_t segment_min_glyph_index =
871 run.CharRangeToGlyphRange(segment.char_range).GetMin();
872 const float segment_offset_relative_run =
873 segment_min_glyph_index != 0
874 ? SkScalarToFloat(run.positions[segment_min_glyph_index].x())
875 : 0;
876 const float point_offset_relative_run =
877 point_offset_relative_segment + segment_offset_relative_run;
878
829 for (size_t i = 0; i < run.glyph_count; ++i) { 879 for (size_t i = 0; i < run.glyph_count; ++i) {
830 const SkScalar end = 880 const float end = i + 1 == run.glyph_count
831 i + 1 == run.glyph_count ? run.width : run.positions[i + 1].x(); 881 ? run.width
832 const SkScalar middle = (end + run.positions[i].x()) / 2; 882 : SkScalarToFloat(run.positions[i + 1].x());
833 883 const float middle = (end + SkScalarToFloat(run.positions[i].x())) / 2;
834 if (offset < middle) { 884 const size_t index = DisplayIndexToTextIndex(run.glyph_to_char[i]);
835 return SelectionModel(DisplayIndexToTextIndex( 885 if (point_offset_relative_run < middle) {
836 run.glyph_to_char[i] + (run.is_rtl ? 1 : 0)), 886 return run.is_rtl ? SelectionModel(
837 (run.is_rtl ? CURSOR_BACKWARD : CURSOR_FORWARD)); 887 IndexOfAdjacentGrapheme(index, CURSOR_FORWARD),
888 CURSOR_BACKWARD)
889 : SelectionModel(index, CURSOR_FORWARD);
838 } 890 }
839 if (offset < end) { 891 if (point_offset_relative_run < end) {
840 return SelectionModel(DisplayIndexToTextIndex( 892 return run.is_rtl ? SelectionModel(index, CURSOR_FORWARD)
841 run.glyph_to_char[i] + (run.is_rtl ? 0 : 1)), 893 : SelectionModel(
842 (run.is_rtl ? CURSOR_FORWARD : CURSOR_BACKWARD)); 894 IndexOfAdjacentGrapheme(index, CURSOR_FORWARD),
895 CURSOR_BACKWARD);
843 } 896 }
844 } 897 }
845 return EdgeSelectionModel(CURSOR_RIGHT); 898
899 return LineSelectionModel(line_index, CURSOR_RIGHT);
846 } 900 }
847 901
848 bool RenderTextHarfBuzz::IsSelectionSupported() const { 902 bool RenderTextHarfBuzz::IsSelectionSupported() const {
849 // TODO(karandeepb): Support multi-line text selection. 903 return true;
850 return !multiline();
851 } 904 }
852 905
853 std::vector<RenderText::FontSpan> RenderTextHarfBuzz::GetFontSpansForTesting() { 906 std::vector<RenderText::FontSpan> RenderTextHarfBuzz::GetFontSpansForTesting() {
854 EnsureLayout(); 907 EnsureLayout();
855 908
856 internal::TextRunList* run_list = GetRunList(); 909 internal::TextRunList* run_list = GetRunList();
857 std::vector<RenderText::FontSpan> spans; 910 std::vector<RenderText::FontSpan> spans;
858 for (auto* run : run_list->runs()) { 911 for (auto* run : run_list->runs()) {
859 spans.push_back(RenderText::FontSpan( 912 spans.push_back(RenderText::FontSpan(
860 run->font, Range(DisplayIndexToTextIndex(run->range.start()), 913 run->font, Range(DisplayIndexToTextIndex(run->range.start()),
(...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after
1005 if (is_forward ? iter.IsEndOfWord(cursor) : iter.IsStartOfWord(cursor)) 1058 if (is_forward ? iter.IsEndOfWord(cursor) : iter.IsStartOfWord(cursor))
1006 break; 1059 break;
1007 } 1060 }
1008 return cur; 1061 return cur;
1009 #endif 1062 #endif
1010 } 1063 }
1011 1064
1012 std::vector<Rect> RenderTextHarfBuzz::GetSubstringBounds(const Range& range) { 1065 std::vector<Rect> RenderTextHarfBuzz::GetSubstringBounds(const Range& range) {
1013 DCHECK(!update_display_run_list_); 1066 DCHECK(!update_display_run_list_);
1014 DCHECK(Range(0, text().length()).Contains(range)); 1067 DCHECK(Range(0, text().length()).Contains(range));
1015 Range layout_range(TextIndexToDisplayIndex(range.start()), 1068 const size_t start =
1016 TextIndexToDisplayIndex(range.end())); 1069 IsValidCursorIndex(range.GetMin())
1017 DCHECK(Range(0, GetDisplayText().length()).Contains(layout_range)); 1070 ? range.GetMin()
1071 : IndexOfAdjacentGrapheme(range.GetMin(), CURSOR_BACKWARD);
1072 const size_t end =
1073 IsValidCursorIndex(range.GetMax())
1074 ? range.GetMax()
1075 : IndexOfAdjacentGrapheme(range.GetMax(), CURSOR_FORWARD);
1076 Range display_range(TextIndexToDisplayIndex(start),
1077 TextIndexToDisplayIndex(end));
1078 DCHECK(Range(0, GetDisplayText().length()).Contains(display_range));
1018 1079
1019 std::vector<Rect> rects; 1080 std::vector<Rect> rects;
1020 if (layout_range.is_empty()) 1081 if (display_range.is_empty())
1021 return rects; 1082 return rects;
1022 std::vector<Range> bounds;
1023 1083
1024 internal::TextRunList* run_list = GetRunList(); 1084 internal::TextRunList* run_list = GetRunList();
1085 for (size_t line_index = 0; line_index < lines().size(); ++line_index) {
1086 const internal::Line& line = lines()[line_index];
1087 // Only the last line can be empty.
1088 DCHECK(!line.segments.empty() || (line_index == lines().size() - 1));
1025 1089
1026 // Add a Range for each run/selection intersection. 1090 float line_x = 0;
1027 for (size_t i = 0; i < run_list->size(); ++i) { 1091 for (const internal::LineSegment& segment : line.segments) {
1028 internal::TextRunHarfBuzz* run = 1092 const internal::TextRunHarfBuzz& run = *run_list->runs()[segment.run];
1029 run_list->runs()[run_list->visual_to_logical(i)]; 1093 const Range intersection = segment.char_range.Intersect(display_range);
1030 Range intersection = run->range.Intersect(layout_range); 1094 DCHECK(!intersection.is_reversed());
1031 if (!intersection.IsValid()) 1095 if (!intersection.is_empty()) {
1032 continue; 1096 float width = SkScalarToFloat(
1033 DCHECK(!intersection.is_reversed()); 1097 run.GetGraphemeWidthForCharRange(this, intersection));
1034 const size_t left_index = 1098 float x = line_x;
1035 run->is_rtl ? intersection.end() - 1 : intersection.start(); 1099 if (run.is_rtl) {
1036 const Range leftmost_character_x = 1100 x += SkScalarToFloat(run.GetGraphemeWidthForCharRange(
1037 run->GetGraphemeBounds(this, left_index).Round(); 1101 this, gfx::Range(intersection.end(), segment.char_range.end())));
1038 const size_t right_index = 1102 } else {
1039 run->is_rtl ? intersection.start() : intersection.end() - 1; 1103 x += SkScalarToFloat(run.GetGraphemeWidthForCharRange(
1040 const Range rightmost_character_x = 1104 this,
1041 run->GetGraphemeBounds(this, right_index).Round(); 1105 gfx::Range(segment.char_range.start(), intersection.start())));
1042 Range range_x(leftmost_character_x.start(), rightmost_character_x.end()); 1106 }
1043 DCHECK(!range_x.is_reversed()); 1107 int end_x = std::ceil(x + width);
1044 if (range_x.is_empty()) 1108 int start_x = std::ceil(x);
1045 continue; 1109 gfx::Rect rect(start_x, 0, end_x - start_x, line.size.height());
1046 1110 rects.push_back(rect + GetLineOffset(line_index));
1047 // Union this with the last range if they're adjacent. 1111 }
1048 DCHECK(bounds.empty() || bounds.back().GetMax() <= range_x.GetMin()); 1112 line_x += segment.width();
1049 if (!bounds.empty() && bounds.back().GetMax() == range_x.GetMin()) {
1050 range_x = Range(bounds.back().GetMin(), range_x.GetMax());
1051 bounds.pop_back();
1052 } 1113 }
1053 bounds.push_back(range_x);
1054 }
1055 for (Range& bound : bounds) {
1056 std::vector<Rect> current_rects = TextBoundsToViewBounds(bound);
1057 rects.insert(rects.end(), current_rects.begin(), current_rects.end());
1058 } 1114 }
1059 return rects; 1115 return rects;
1060 } 1116 }
1061 1117
1062 size_t RenderTextHarfBuzz::TextIndexToDisplayIndex(size_t index) { 1118 size_t RenderTextHarfBuzz::TextIndexToDisplayIndex(size_t index) {
1063 return TextIndexToGivenTextIndex(GetDisplayText(), index); 1119 return TextIndexToGivenTextIndex(GetDisplayText(), index);
1064 } 1120 }
1065 1121
1066 size_t RenderTextHarfBuzz::DisplayIndexToTextIndex(size_t index) { 1122 size_t RenderTextHarfBuzz::DisplayIndexToTextIndex(size_t index) {
1067 if (!obscured()) 1123 if (!obscured())
(...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after
1222 LogicalCursorDirection affinity = caret.caret_affinity(); 1278 LogicalCursorDirection affinity = caret.caret_affinity();
1223 internal::TextRunList* run_list = GetRunList(); 1279 internal::TextRunList* run_list = GetRunList();
1224 for (size_t i = 0; i < run_list->size(); ++i) { 1280 for (size_t i = 0; i < run_list->size(); ++i) {
1225 internal::TextRunHarfBuzz* run = run_list->runs()[i]; 1281 internal::TextRunHarfBuzz* run = run_list->runs()[i];
1226 if (RangeContainsCaret(run->range, layout_position, affinity)) 1282 if (RangeContainsCaret(run->range, layout_position, affinity))
1227 return i; 1283 return i;
1228 } 1284 }
1229 return run_list->size(); 1285 return run_list->size();
1230 } 1286 }
1231 1287
1232 size_t RenderTextHarfBuzz::GetRunContainingXCoord(float x, 1288 int RenderTextHarfBuzz::GetLineContainingYCoord(float text_y) {
1233 float* offset) const { 1289 if (text_y < 0)
1234 DCHECK(!update_display_run_list_); 1290 return -1;
1235 const internal::TextRunList* run_list = GetRunList(); 1291
1236 if (x < 0) 1292 for (size_t i = 0; i < lines().size(); i++) {
1237 return run_list->size(); 1293 const internal::Line& line = lines()[i];
1238 // Find the text run containing the argument point (assumed already offset). 1294
1239 float current_x = 0; 1295 if (text_y <= line.size.height())
1240 for (size_t i = 0; i < run_list->size(); ++i) { 1296 return i;
1241 size_t run = run_list->visual_to_logical(i); 1297 text_y -= line.size.height();
1242 current_x += run_list->runs()[run]->width; 1298 }
1243 if (x < current_x) { 1299
1244 *offset = x - (current_x - run_list->runs()[run]->width); 1300 return lines().size();
1245 return run; 1301 }
1302
1303 int RenderTextHarfBuzz::GetLineSegmentContainingXCoord(
1304 const internal::Line& line,
1305 float line_x,
1306 float* offset_relative_segment) {
1307 DCHECK(offset_relative_segment);
1308
1309 *offset_relative_segment = 0;
1310 if (line_x < 0)
1311 return -1;
1312 for (size_t i = 0; i < line.segments.size(); i++) {
1313 const internal::LineSegment& segment = line.segments[i];
1314
1315 // segment.x_range is not used because it is in text space.
1316 if (line_x < segment.width()) {
1317 *offset_relative_segment = line_x;
1318 return i;
1246 } 1319 }
1320 line_x -= segment.width();
1247 } 1321 }
1248 return run_list->size(); 1322 return line.segments.size();
1249 } 1323 }
1250 1324
1251 SelectionModel RenderTextHarfBuzz::FirstSelectionModelInsideRun( 1325 SelectionModel RenderTextHarfBuzz::FirstSelectionModelInsideRun(
1252 const internal::TextRunHarfBuzz* run) { 1326 const internal::TextRunHarfBuzz* run) {
1253 size_t position = DisplayIndexToTextIndex(run->range.start()); 1327 size_t position = DisplayIndexToTextIndex(run->range.start());
1254 position = IndexOfAdjacentGrapheme(position, CURSOR_FORWARD); 1328 position = IndexOfAdjacentGrapheme(position, CURSOR_FORWARD);
1255 return SelectionModel(position, CURSOR_BACKWARD); 1329 return SelectionModel(position, CURSOR_BACKWARD);
1256 } 1330 }
1257 1331
1258 SelectionModel RenderTextHarfBuzz::LastSelectionModelInsideRun( 1332 SelectionModel RenderTextHarfBuzz::LastSelectionModelInsideRun(
(...skipping 367 matching lines...) Expand 10 before | Expand all | Expand 10 after
1626 1700
1627 attribute.strike = run.strike; 1701 attribute.strike = run.strike;
1628 attribute.diagonal_strike = run.diagonal_strike; 1702 attribute.diagonal_strike = run.diagonal_strike;
1629 decorated_text->attributes.push_back(attribute); 1703 decorated_text->attributes.push_back(attribute);
1630 } 1704 }
1631 } 1705 }
1632 return true; 1706 return true;
1633 } 1707 }
1634 1708
1635 } // namespace gfx 1709 } // namespace gfx
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698