Chromium Code Reviews| 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 273 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 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 atleast 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(); | |
|
karandeepb
2016/12/07 03:57:47
Why do we remove newline segments?
The change he
msw
2016/12/13 03:04:55
I don't know if/why this was necessary; perhaps we
karandeepb
2016/12/16 02:58:38
See my comment at https://codereview.chromium.org/
| |
| 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 502 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 808 Size RenderTextHarfBuzz::GetStringSize() { | 815 Size RenderTextHarfBuzz::GetStringSize() { |
| 809 const SizeF size_f = GetStringSizeF(); | 816 const SizeF size_f = GetStringSizeF(); |
| 810 return Size(std::ceil(size_f.width()), size_f.height()); | 817 return Size(std::ceil(size_f.width()), size_f.height()); |
| 811 } | 818 } |
| 812 | 819 |
| 813 SizeF RenderTextHarfBuzz::GetStringSizeF() { | 820 SizeF RenderTextHarfBuzz::GetStringSizeF() { |
| 814 EnsureLayout(); | 821 EnsureLayout(); |
| 815 return total_size_; | 822 return total_size_; |
| 816 } | 823 } |
| 817 | 824 |
| 818 SelectionModel RenderTextHarfBuzz::FindCursorPosition(const Point& point) { | 825 SelectionModel RenderTextHarfBuzz::FindCursorPosition(const Point& view_point) { |
| 819 EnsureLayout(); | 826 EnsureLayout(); |
| 827 DCHECK(!lines().empty()); | |
| 820 | 828 |
| 821 int x = ToTextPoint(point).x(); | 829 int line_index = GetLineContainingYCoord((view_point - GetLineOffset(0)).y()); |
| 822 float offset = 0; | 830 // Clip line index to a valid value in case kDragToEndIfOutsideVerticalBounds |
| 823 size_t run_index = GetRunContainingXCoord(x, &offset); | 831 // is false. Else, drag to end. |
| 832 if (line_index < 0) { | |
| 833 if (RenderText::kDragToEndIfOutsideVerticalBounds) | |
| 834 return EdgeSelectionModel(GetVisualDirectionOfLogicalBeginning()); | |
| 835 else | |
| 836 line_index++; | |
| 837 } | |
| 838 if (line_index >= (int)lines().size()) { | |
| 839 if (RenderText::kDragToEndIfOutsideVerticalBounds) | |
| 840 return EdgeSelectionModel(GetVisualDirectionOfLogicalEnd()); | |
| 841 else | |
| 842 line_index--; | |
| 843 } | |
| 844 const internal::Line& line = lines()[line_index]; | |
| 824 | 845 |
| 825 internal::TextRunList* run_list = GetRunList(); | 846 float point_offset_relative_segment = 0; |
| 826 if (run_index >= run_list->size()) | 847 const int segment_index = GetLineSegmentContainingXCoord( |
| 827 return EdgeSelectionModel((x < 0) ? CURSOR_LEFT : CURSOR_RIGHT); | 848 line, (view_point - GetLineOffset(line_index)).x(), |
| 828 const internal::TextRunHarfBuzz& run = *run_list->runs()[run_index]; | 849 &point_offset_relative_segment); |
| 850 if (segment_index < 0) | |
| 851 return LineSelectionModel(line, CURSOR_LEFT); | |
|
msw
2016/12/13 03:04:55
Perhaps we need to replace CURSOR_LEFT with GetVis
karandeepb
2016/12/16 02:58:38
Think this is correct, segment_index < 0, indicate
msw
2016/12/19 22:27:42
Acknowledged.
| |
| 852 if (segment_index >= (int)line.segments.size()) | |
| 853 return LineSelectionModel(line, CURSOR_RIGHT); | |
| 854 const internal::LineSegment& segment = line.segments[segment_index]; | |
| 855 | |
| 856 const internal::TextRunHarfBuzz& run = *GetRunList()->runs()[segment.run]; | |
| 857 const Range segment_glyph_range = | |
| 858 run.CharRangeToGlyphRange(segment.char_range); | |
| 859 const float segment_offset_relative_run = | |
| 860 segment_glyph_range.GetMin() != 0 | |
| 861 ? run.positions[segment_glyph_range.GetMin()].x() | |
| 862 : 0; | |
| 863 const float point_offset_relative_run = | |
| 864 point_offset_relative_segment + segment_offset_relative_run; | |
| 865 | |
| 829 for (size_t i = 0; i < run.glyph_count; ++i) { | 866 for (size_t i = 0; i < run.glyph_count; ++i) { |
| 830 const SkScalar end = | 867 const SkScalar end = |
| 831 i + 1 == run.glyph_count ? run.width : run.positions[i + 1].x(); | 868 i + 1 == run.glyph_count ? run.width : run.positions[i + 1].x(); |
| 832 const SkScalar middle = (end + run.positions[i].x()) / 2; | 869 const SkScalar middle = (end + run.positions[i].x()) / 2; |
| 833 | 870 |
| 834 if (offset < middle) { | 871 if (point_offset_relative_run < middle) { |
| 835 return SelectionModel(DisplayIndexToTextIndex( | 872 return SelectionModel(DisplayIndexToTextIndex( |
| 836 run.glyph_to_char[i] + (run.is_rtl ? 1 : 0)), | 873 run.glyph_to_char[i] + (run.is_rtl ? 1 : 0)), |
| 837 (run.is_rtl ? CURSOR_BACKWARD : CURSOR_FORWARD)); | 874 (run.is_rtl ? CURSOR_BACKWARD : CURSOR_FORWARD)); |
| 838 } | 875 } |
| 839 if (offset < end) { | 876 if (point_offset_relative_run < end) { |
| 840 return SelectionModel(DisplayIndexToTextIndex( | 877 return SelectionModel(DisplayIndexToTextIndex( |
| 841 run.glyph_to_char[i] + (run.is_rtl ? 0 : 1)), | 878 run.glyph_to_char[i] + (run.is_rtl ? 0 : 1)), |
| 842 (run.is_rtl ? CURSOR_FORWARD : CURSOR_BACKWARD)); | 879 (run.is_rtl ? CURSOR_FORWARD : CURSOR_BACKWARD)); |
| 843 } | 880 } |
| 844 } | 881 } |
| 845 return EdgeSelectionModel(CURSOR_RIGHT); | 882 |
| 883 return LineSelectionModel(line, CURSOR_RIGHT); | |
| 846 } | 884 } |
| 847 | 885 |
| 848 bool RenderTextHarfBuzz::IsSelectionSupported() const { | 886 bool RenderTextHarfBuzz::IsSelectionSupported() const { |
| 849 // TODO(karandeepb): Support multi-line text selection. | 887 return true; |
| 850 return !multiline(); | |
| 851 } | 888 } |
| 852 | 889 |
| 853 std::vector<RenderText::FontSpan> RenderTextHarfBuzz::GetFontSpansForTesting() { | 890 std::vector<RenderText::FontSpan> RenderTextHarfBuzz::GetFontSpansForTesting() { |
| 854 EnsureLayout(); | 891 EnsureLayout(); |
| 855 | 892 |
| 856 internal::TextRunList* run_list = GetRunList(); | 893 internal::TextRunList* run_list = GetRunList(); |
| 857 std::vector<RenderText::FontSpan> spans; | 894 std::vector<RenderText::FontSpan> spans; |
| 858 for (auto* run : run_list->runs()) { | 895 for (auto* run : run_list->runs()) { |
| 859 spans.push_back(RenderText::FontSpan( | 896 spans.push_back(RenderText::FontSpan( |
| 860 run->font, Range(DisplayIndexToTextIndex(run->range.start()), | 897 run->font, Range(DisplayIndexToTextIndex(run->range.start()), |
| (...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1005 if (is_forward ? iter.IsEndOfWord(cursor) : iter.IsStartOfWord(cursor)) | 1042 if (is_forward ? iter.IsEndOfWord(cursor) : iter.IsStartOfWord(cursor)) |
| 1006 break; | 1043 break; |
| 1007 } | 1044 } |
| 1008 return cur; | 1045 return cur; |
| 1009 #endif | 1046 #endif |
| 1010 } | 1047 } |
| 1011 | 1048 |
| 1012 std::vector<Rect> RenderTextHarfBuzz::GetSubstringBounds(const Range& range) { | 1049 std::vector<Rect> RenderTextHarfBuzz::GetSubstringBounds(const Range& range) { |
| 1013 DCHECK(!update_display_run_list_); | 1050 DCHECK(!update_display_run_list_); |
| 1014 DCHECK(Range(0, text().length()).Contains(range)); | 1051 DCHECK(Range(0, text().length()).Contains(range)); |
| 1015 Range layout_range(TextIndexToDisplayIndex(range.start()), | 1052 Range layout_range(TextIndexToDisplayIndex(range.start()), |
|
msw
2016/12/13 03:04:55
optional nit: rename this |display_range|
karandeepb
2016/12/16 02:58:38
Done.
| |
| 1016 TextIndexToDisplayIndex(range.end())); | 1053 TextIndexToDisplayIndex(range.end())); |
| 1017 DCHECK(Range(0, GetDisplayText().length()).Contains(layout_range)); | 1054 DCHECK(Range(0, GetDisplayText().length()).Contains(layout_range)); |
| 1018 | 1055 |
| 1019 std::vector<Rect> rects; | 1056 std::vector<Rect> rects; |
| 1020 if (layout_range.is_empty()) | 1057 if (layout_range.is_empty()) |
| 1021 return rects; | 1058 return rects; |
| 1022 std::vector<Range> bounds; | |
| 1023 | 1059 |
| 1024 internal::TextRunList* run_list = GetRunList(); | 1060 internal::TextRunList* run_list = GetRunList(); |
| 1061 for (size_t line_index = 0; line_index < lines().size(); ++line_index) { | |
| 1062 const internal::Line& line = lines()[line_index]; | |
| 1063 if (line.segments.empty()) { | |
| 1064 DCHECK_EQ(lines().size() - 1, line_index); | |
| 1065 continue; | |
| 1066 } | |
| 1025 | 1067 |
| 1026 // Add a Range for each run/selection intersection. | 1068 float line_x = 0; |
| 1027 for (size_t i = 0; i < run_list->size(); ++i) { | 1069 for (const internal::LineSegment& segment : line.segments) { |
| 1028 internal::TextRunHarfBuzz* run = | 1070 const internal::TextRunHarfBuzz& run = *run_list->runs()[segment.run]; |
| 1029 run_list->runs()[run_list->visual_to_logical(i)]; | 1071 const Range intersection = segment.char_range.Intersect(layout_range); |
| 1030 Range intersection = run->range.Intersect(layout_range); | 1072 DCHECK(!intersection.is_reversed()); |
| 1031 if (!intersection.IsValid()) | 1073 if (!intersection.is_empty()) { |
|
karandeepb
2016/12/07 03:57:47
The earlier code used GetGraphemeBounds so as to c
msw
2016/12/13 03:04:55
Perhaps that was done to handle invalid ranges (th
karandeepb
2016/12/16 02:58:38
The code was indeed incorrect for the cases when t
| |
| 1032 continue; | 1074 SkScalar width = run.GetGlyphWidthForCharRange(intersection); |
| 1033 DCHECK(!intersection.is_reversed()); | 1075 SkScalar x; |
| 1034 const size_t left_index = | 1076 if (run.is_rtl) { |
| 1035 run->is_rtl ? intersection.end() - 1 : intersection.start(); | 1077 x = line_x + SkScalarToFloat(run.GetGlyphWidthForCharRange(gfx::Range( |
| 1036 const Range leftmost_character_x = | 1078 intersection.end(), segment.char_range.end()))); |
| 1037 run->GetGraphemeBounds(this, left_index).Round(); | 1079 } else { |
| 1038 const size_t right_index = | 1080 x = line_x + SkScalarToFloat(run.GetGlyphWidthForCharRange(gfx::Range( |
| 1039 run->is_rtl ? intersection.start() : intersection.end() - 1; | 1081 segment.char_range.start(), intersection.start()))); |
| 1040 const Range rightmost_character_x = | 1082 } |
| 1041 run->GetGraphemeBounds(this, right_index).Round(); | 1083 int end_x = std::ceil(x + width); |
| 1042 Range range_x(leftmost_character_x.start(), rightmost_character_x.end()); | 1084 int start_x = std::ceil(x); |
| 1043 DCHECK(!range_x.is_reversed()); | 1085 gfx::Rect rect(start_x, 0, end_x - start_x, line.size.height()); |
| 1044 if (range_x.is_empty()) | 1086 rects.push_back(rect + GetLineOffset(line_index)); |
| 1045 continue; | 1087 } |
| 1046 | 1088 line_x += segment.width(); |
| 1047 // Union this with the last range if they're adjacent. | |
| 1048 DCHECK(bounds.empty() || bounds.back().GetMax() <= range_x.GetMin()); | |
| 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 } | 1089 } |
| 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 } | 1090 } |
| 1059 return rects; | 1091 return rects; |
| 1060 } | 1092 } |
| 1061 | 1093 |
| 1062 size_t RenderTextHarfBuzz::TextIndexToDisplayIndex(size_t index) { | 1094 size_t RenderTextHarfBuzz::TextIndexToDisplayIndex(size_t index) { |
| 1063 return TextIndexToGivenTextIndex(GetDisplayText(), index); | 1095 return TextIndexToGivenTextIndex(GetDisplayText(), index); |
| 1064 } | 1096 } |
| 1065 | 1097 |
| 1066 size_t RenderTextHarfBuzz::DisplayIndexToTextIndex(size_t index) { | 1098 size_t RenderTextHarfBuzz::DisplayIndexToTextIndex(size_t index) { |
| 1067 if (!obscured()) | 1099 if (!obscured()) |
| (...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1222 LogicalCursorDirection affinity = caret.caret_affinity(); | 1254 LogicalCursorDirection affinity = caret.caret_affinity(); |
| 1223 internal::TextRunList* run_list = GetRunList(); | 1255 internal::TextRunList* run_list = GetRunList(); |
| 1224 for (size_t i = 0; i < run_list->size(); ++i) { | 1256 for (size_t i = 0; i < run_list->size(); ++i) { |
| 1225 internal::TextRunHarfBuzz* run = run_list->runs()[i]; | 1257 internal::TextRunHarfBuzz* run = run_list->runs()[i]; |
| 1226 if (RangeContainsCaret(run->range, layout_position, affinity)) | 1258 if (RangeContainsCaret(run->range, layout_position, affinity)) |
| 1227 return i; | 1259 return i; |
| 1228 } | 1260 } |
| 1229 return run_list->size(); | 1261 return run_list->size(); |
| 1230 } | 1262 } |
| 1231 | 1263 |
| 1232 size_t RenderTextHarfBuzz::GetRunContainingXCoord(float x, | 1264 int RenderTextHarfBuzz::GetLineContainingYCoord(float text_y) { |
| 1233 float* offset) const { | 1265 if (text_y < 0) |
| 1234 DCHECK(!update_display_run_list_); | 1266 return -1; |
| 1235 const internal::TextRunList* run_list = GetRunList(); | 1267 |
| 1236 if (x < 0) | 1268 for (size_t i = 0; i < lines().size(); i++) { |
| 1237 return run_list->size(); | 1269 const internal::Line& line = lines()[i]; |
| 1238 // Find the text run containing the argument point (assumed already offset). | 1270 |
| 1239 float current_x = 0; | 1271 if (text_y <= line.size.height()) |
| 1240 for (size_t i = 0; i < run_list->size(); ++i) { | 1272 return i; |
| 1241 size_t run = run_list->visual_to_logical(i); | 1273 text_y -= line.size.height(); |
| 1242 current_x += run_list->runs()[run]->width; | 1274 } |
| 1243 if (x < current_x) { | 1275 |
| 1244 *offset = x - (current_x - run_list->runs()[run]->width); | 1276 return lines().size(); |
| 1245 return run; | 1277 } |
| 1278 | |
| 1279 int RenderTextHarfBuzz::GetLineSegmentContainingXCoord( | |
| 1280 const internal::Line& line, | |
| 1281 float line_x, | |
| 1282 float* offset_relative_segment) { | |
| 1283 DCHECK(offset_relative_segment); | |
| 1284 | |
| 1285 *offset_relative_segment = 0; | |
| 1286 if (line_x < 0) | |
| 1287 return -1; | |
| 1288 for (size_t i = 0; i < line.segments.size(); i++) { | |
| 1289 const internal::LineSegment& segment = line.segments[i]; | |
| 1290 | |
| 1291 // segment.x_range is not used because it is text space. | |
| 1292 if (line_x <= segment.width()) { | |
| 1293 *offset_relative_segment = line_x; | |
| 1294 return i; | |
| 1246 } | 1295 } |
| 1296 line_x -= segment.width(); | |
| 1247 } | 1297 } |
| 1248 return run_list->size(); | 1298 return line.segments.size(); |
| 1249 } | 1299 } |
| 1250 | 1300 |
| 1251 SelectionModel RenderTextHarfBuzz::FirstSelectionModelInsideRun( | 1301 SelectionModel RenderTextHarfBuzz::FirstSelectionModelInsideRun( |
| 1252 const internal::TextRunHarfBuzz* run) { | 1302 const internal::TextRunHarfBuzz* run) { |
| 1253 size_t position = DisplayIndexToTextIndex(run->range.start()); | 1303 size_t position = DisplayIndexToTextIndex(run->range.start()); |
| 1254 position = IndexOfAdjacentGrapheme(position, CURSOR_FORWARD); | 1304 position = IndexOfAdjacentGrapheme(position, CURSOR_FORWARD); |
| 1255 return SelectionModel(position, CURSOR_BACKWARD); | 1305 return SelectionModel(position, CURSOR_BACKWARD); |
| 1256 } | 1306 } |
| 1257 | 1307 |
| 1258 SelectionModel RenderTextHarfBuzz::LastSelectionModelInsideRun( | 1308 SelectionModel RenderTextHarfBuzz::LastSelectionModelInsideRun( |
| (...skipping 367 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1626 | 1676 |
| 1627 attribute.strike = run.strike; | 1677 attribute.strike = run.strike; |
| 1628 attribute.diagonal_strike = run.diagonal_strike; | 1678 attribute.diagonal_strike = run.diagonal_strike; |
| 1629 decorated_text->attributes.push_back(attribute); | 1679 decorated_text->attributes.push_back(attribute); |
| 1630 } | 1680 } |
| 1631 } | 1681 } |
| 1632 return true; | 1682 return true; |
| 1633 } | 1683 } |
| 1634 | 1684 |
| 1635 } // namespace gfx | 1685 } // namespace gfx |
| OLD | NEW |