diff --git a/lib/ui/text/paragraph.cc b/lib/ui/text/paragraph.cc index 032ee1af4d9bd..1821bf9dd3a7a 100644 --- a/lib/ui/text/paragraph.cc +++ b/lib/ui/text/paragraph.cc @@ -85,8 +85,9 @@ void Paragraph::paint(Canvas* canvas, double x, double y) { } std::vector Paragraph::getRectsForRange(unsigned start, unsigned end) { - return m_paragraphImpl->getRectsForRange(start, end, - txt::Paragraph::RectStyle::kTight); + return m_paragraphImpl->getRectsForRange( + start, end, txt::Paragraph::RectHeightStyle::kTight, + txt::Paragraph::RectWidthStyle::kTight); } Dart_Handle Paragraph::getPositionForOffset(double dx, double dy) { diff --git a/lib/ui/text/paragraph_impl.h b/lib/ui/text/paragraph_impl.h index cf71897d250e4..30ce8a080070d 100644 --- a/lib/ui/text/paragraph_impl.h +++ b/lib/ui/text/paragraph_impl.h @@ -36,7 +36,8 @@ class ParagraphImpl { virtual std::vector getRectsForRange( unsigned start, unsigned end, - txt::Paragraph::RectStyle rect_style) = 0; + txt::Paragraph::RectHeightStyle rect_height_style, + txt::Paragraph::RectWidthStyle rect_width_style) = 0; virtual Dart_Handle getPositionForOffset(double dx, double dy) = 0; diff --git a/lib/ui/text/paragraph_impl_txt.cc b/lib/ui/text/paragraph_impl_txt.cc index cc55638db2680..9f31a3896985d 100644 --- a/lib/ui/text/paragraph_impl_txt.cc +++ b/lib/ui/text/paragraph_impl_txt.cc @@ -64,10 +64,11 @@ void ParagraphImplTxt::paint(Canvas* canvas, double x, double y) { std::vector ParagraphImplTxt::getRectsForRange( unsigned start, unsigned end, - txt::Paragraph::RectStyle rect_style) { + txt::Paragraph::RectHeightStyle rect_height_style, + txt::Paragraph::RectWidthStyle rect_width_style) { std::vector result; - std::vector boxes = - m_paragraph->GetRectsForRange(start, end, rect_style); + std::vector boxes = m_paragraph->GetRectsForRange( + start, end, rect_height_style, rect_width_style); for (const txt::Paragraph::TextBox& box : boxes) { result.emplace_back(box.rect, static_cast(box.direction)); diff --git a/lib/ui/text/paragraph_impl_txt.h b/lib/ui/text/paragraph_impl_txt.h index 665d65b15cce2..eabdcc114b198 100644 --- a/lib/ui/text/paragraph_impl_txt.h +++ b/lib/ui/text/paragraph_impl_txt.h @@ -31,7 +31,8 @@ class ParagraphImplTxt : public ParagraphImpl { std::vector getRectsForRange( unsigned start, unsigned end, - txt::Paragraph::RectStyle rect_style) override; + txt::Paragraph::RectHeightStyle rect_height_style, + txt::Paragraph::RectWidthStyle rect_width_style) override; Dart_Handle getPositionForOffset(double dx, double dy) override; Dart_Handle getWordBoundary(unsigned offset) override; diff --git a/third_party/txt/src/txt/paragraph.cc b/third_party/txt/src/txt/paragraph.cc index 8f6dfd596eab7..270a094a0d947 100644 --- a/third_party/txt/src/txt/paragraph.cc +++ b/third_party/txt/src/txt/paragraph.cc @@ -446,6 +446,11 @@ void Paragraph::Layout(double width, bool force) { line_baselines_.clear(); glyph_lines_.clear(); code_unit_runs_.clear(); + line_max_spacings_.clear(); + line_max_descent_.clear(); + line_max_ascent_.clear(); + max_right_ = FLT_MIN; + min_left_ = FLT_MAX; minikin::Layout layout; SkTextBlobBuilder builder; @@ -703,6 +708,9 @@ void Paragraph::Layout(double width, bool force) { Range(glyph_positions.front().x_pos.start, glyph_positions.back().x_pos.end), line_number, metrics, run.direction()); + + min_left_ = std::min(min_left_, glyph_positions.front().x_pos.start); + max_right_ = std::max(max_right_, glyph_positions.back().x_pos.end); } // for each in glyph_blobs run_x_offset += layout.getAdvance(); @@ -729,8 +737,12 @@ void Paragraph::Layout(double width, bool force) { double max_line_spacing = 0; double max_descent = 0; + SkScalar max_unscaled_ascent = 0; auto update_line_metrics = [&](const SkPaint::FontMetrics& metrics, const TextStyle& style) { + // TODO(garyq): Multipling in the style.height on the first line is + // probably wrong. Figure out how paragraph and line heights are supposed + // to work and fix it. double line_spacing = (line_number == 0) ? -metrics.fAscent * style.height @@ -747,6 +759,8 @@ void Paragraph::Layout(double width, bool force) { double descent = metrics.fDescent * style.height; max_descent = std::max(descent, max_descent); + + max_unscaled_ascent = std::max(-metrics.fAscent, max_unscaled_ascent); }; for (const PaintRecord& paint_record : paint_records) { update_line_metrics(paint_record.metrics(), paint_record.style()); @@ -762,12 +776,20 @@ void Paragraph::Layout(double width, bool force) { update_line_metrics(metrics, style); } + // TODO(garyq): Remove rounding of line heights because it is irrelevant in + // a world of high DPI devices. line_heights_.push_back((line_heights_.empty() ? 0 : line_heights_.back()) + round(max_line_spacing + max_descent)); line_baselines_.push_back(line_heights_.back() - max_descent); y_offset += round(max_line_spacing + prev_max_descent); prev_max_descent = max_descent; + // The max line spacing and ascent have been multiplied by -1 to make math + // in GetRectsForRange more logical/readable. + line_max_spacings_.push_back(max_line_spacing); + line_max_descent_.push_back(max_descent); + line_max_ascent_.push_back(max_unscaled_ascent); + for (PaintRecord& paint_record : paint_records) { paint_record.SetOffset( SkPoint::Make(paint_record.offset().x() + line_x_offset, y_offset)); @@ -1093,10 +1115,30 @@ void Paragraph::PaintShadow(SkCanvas* canvas, std::vector Paragraph::GetRectsForRange( size_t start, size_t end, - RectStyle rect_style) const { - std::map> line_boxes; - + RectHeightStyle rect_height_style, + RectWidthStyle rect_width_style) const { + // Struct that holds calculated metrics for each line. + struct LineBoxMetrics { + std::vector boxes; + // Per-line metrics for max and min coordinates for left and right boxes. + // These metrics cannot be calculated in layout generically because of + // selections that do not cover the whole line. + SkScalar max_right = FLT_MIN; + SkScalar min_left = FLT_MAX; + }; + + std::map line_metrics; + // Text direction of the first line so we can extend the correct side for + // RectWidthStyle::kMax. + TextDirection first_line_dir = TextDirection::ltr; + + // Lines that are actually in the requested range. + size_t max_line = 0; + size_t min_line = INT_MAX; + + // Generate initial boxes and calculate metrics. for (const CodeUnitRun& run : code_unit_runs_) { + // Check to see if we are finished. if (run.code_units.start >= end) break; if (run.code_units.end <= start) @@ -1106,6 +1148,10 @@ std::vector Paragraph::GetRectsForRange( SkScalar top = baseline + run.font_metrics.fAscent; SkScalar bottom = baseline + run.font_metrics.fDescent; + max_line = std::max(run.line_number, max_line); + min_line = std::min(run.line_number, min_line); + + // Calculate left and right. SkScalar left, right; if (run.code_units.start >= start && run.code_units.end <= end) { left = run.x_pos.start; @@ -1122,7 +1168,18 @@ std::vector Paragraph::GetRectsForRange( if (left == SK_ScalarMax || right == SK_ScalarMin) continue; } - line_boxes[run.line_number].emplace_back( + // Keep track of the min and max horizontal coordinates over all lines. Not + // needed for kTight. + if (rect_width_style == RectWidthStyle::kMax) { + line_metrics[run.line_number].max_right = + std::max(line_metrics[run.line_number].max_right, right); + line_metrics[run.line_number].min_left = + std::min(line_metrics[run.line_number].min_left, left); + if (min_line == run.line_number) { + first_line_dir = run.direction; + } + } + line_metrics[run.line_number].boxes.emplace_back( SkRect::MakeLTRB(left, top, right, bottom), run.direction); } @@ -1135,34 +1192,105 @@ std::vector Paragraph::GetRectsForRange( break; if (line.end_including_newline <= start) continue; - if (line_boxes.find(line_number) == line_boxes.end()) { + if (line_metrics.find(line_number) == line_metrics.end()) { if (line.end != line.end_including_newline && line.end >= start && line.end_including_newline <= end) { SkScalar x = line_widths_[line_number]; SkScalar top = (line_number > 0) ? line_heights_[line_number - 1] : 0; SkScalar bottom = line_heights_[line_number]; - line_boxes[line_number].emplace_back( + line_metrics[line_number].boxes.emplace_back( SkRect::MakeLTRB(x, top, x, bottom), TextDirection::ltr); } } } + // "Post-process" metrics and aggregate final rects to return. std::vector boxes; - for (const auto& kv : line_boxes) { - if (rect_style & RectStyle::kTight) { + for (const auto& kv : line_metrics) { + // Handle rect_width_styles. We skip the last line because not everything is + // selected. + if (rect_width_style == RectWidthStyle::kMax && kv.first != max_line) { + if (line_metrics[kv.first].min_left > min_left_ && + (kv.first != min_line || first_line_dir == TextDirection::rtl)) { + line_metrics[kv.first].boxes.emplace_back( + SkRect::MakeLTRB( + min_left_, + line_baselines_[kv.first] - line_max_ascent_[kv.first], + line_metrics[kv.first].min_left, + line_baselines_[kv.first] + line_max_descent_[kv.first]), + TextDirection::rtl); + } + if (line_metrics[kv.first].max_right < max_right_ && + (kv.first != min_line || first_line_dir == TextDirection::ltr)) { + line_metrics[kv.first].boxes.emplace_back( + SkRect::MakeLTRB( + line_metrics[kv.first].max_right, + line_baselines_[kv.first] - line_max_ascent_[kv.first], + max_right_, + line_baselines_[kv.first] + line_max_descent_[kv.first]), + TextDirection::ltr); + } + } + + // Handle rect_height_styles. The height metrics used are all positive to + // make the signage clear here. + if (rect_height_style == RectHeightStyle::kTight) { // Ignore line max height and width and generate tight bounds. - boxes.insert(boxes.end(), kv.second.begin(), kv.second.end()); - } else { - // Set each box to the max height of each line to ensure continuity. - float min_top = DBL_MAX; - float max_bottom = 0; - for (const Paragraph::TextBox& box : kv.second) { - min_top = std::min(box.rect.fTop, min_top); - max_bottom = std::max(box.rect.fBottom, max_bottom); + boxes.insert(boxes.end(), kv.second.boxes.begin(), kv.second.boxes.end()); + } else if (rect_height_style == RectHeightStyle::kMax) { + for (const Paragraph::TextBox& box : kv.second.boxes) { + boxes.emplace_back( + SkRect::MakeLTRB( + box.rect.fLeft, + line_baselines_[kv.first] - line_max_ascent_[kv.first], + box.rect.fRight, + line_baselines_[kv.first] + line_max_descent_[kv.first]), + box.direction); + } + } else if (rect_height_style == + RectHeightStyle::kIncludeLineSpacingMiddle) { + SkScalar adjusted_bottom = + line_baselines_[kv.first] + line_max_descent_[kv.first]; + if (kv.first < line_ranges_.size() - 1) { + adjusted_bottom += (line_max_spacings_[kv.first + 1] - + line_max_ascent_[kv.first + 1]) / + 2; + } + SkScalar adjusted_top = + line_baselines_[kv.first] - line_max_ascent_[kv.first]; + if (kv.first != 0) { + adjusted_top -= + (line_max_spacings_[kv.first] - line_max_ascent_[kv.first]) / 2; } - for (const Paragraph::TextBox& box : kv.second) { - boxes.emplace_back(SkRect::MakeLTRB(box.rect.fLeft, min_top, - box.rect.fRight, max_bottom), + for (const Paragraph::TextBox& box : kv.second.boxes) { + boxes.emplace_back(SkRect::MakeLTRB(box.rect.fLeft, adjusted_top, + box.rect.fRight, adjusted_bottom), + box.direction); + } + } else if (rect_height_style == RectHeightStyle::kIncludeLineSpacingTop) { + for (const Paragraph::TextBox& box : kv.second.boxes) { + SkScalar adjusted_top = + kv.first == 0 + ? line_baselines_[kv.first] - line_max_ascent_[kv.first] + : line_baselines_[kv.first] - line_max_spacings_[kv.first]; + boxes.emplace_back( + SkRect::MakeLTRB( + box.rect.fLeft, adjusted_top, box.rect.fRight, + line_baselines_[kv.first] + line_max_descent_[kv.first]), + box.direction); + } + } else { // kIncludeLineSpacingBottom + for (const Paragraph::TextBox& box : kv.second.boxes) { + SkScalar adjusted_bottom = + line_baselines_[kv.first] + line_max_descent_[kv.first]; + if (kv.first < line_ranges_.size() - 1) { + adjusted_bottom += + -line_max_ascent_[kv.first] + line_max_spacings_[kv.first]; + } + boxes.emplace_back(SkRect::MakeLTRB(box.rect.fLeft, + line_baselines_[kv.first] - + line_max_ascent_[kv.first], + box.rect.fRight, adjusted_bottom), box.direction); } } diff --git a/third_party/txt/src/txt/paragraph.h b/third_party/txt/src/txt/paragraph.h index 637f4e4723f3a..708482aa628f1 100644 --- a/third_party/txt/src/txt/paragraph.h +++ b/third_party/txt/src/txt/paragraph.h @@ -59,19 +59,36 @@ class Paragraph { // Options for various types of bounding boxes provided by // GetRectsForRange(...). - // These options can be individually enabled, for example: - // - // (RectStyle::kTight | RectStyle::kExtendEndOfLine) - // - // provides tight bounding boxes and extends the last box per line to the end - // of the layout area. - enum RectStyle { - kNone = 0x0, // kNone cannot be combined with |. - - // Provide tight bounding boxes that fit heights per span. Otherwise, the - // heights of spans are the max of the heights of the line the span belongs - // in. - kTight = 0x1 + enum class RectHeightStyle { + // Provide tight bounding boxes that fit heights per run. + kTight, + + // The height of the boxes will be the maximum height of all runs in the + // line. All rects in the same line will be the same height. + kMax, + + // Extends the top and/or bottom edge of the bounds to fully cover any line + // spacing. The top edge of each line should be the same as the bottom edge + // of the line above. There should be no gaps in vertical coverage given any + // ParagraphStyle line_height. + // + // The top and bottom of each rect will cover half of the + // space above and half of the space below the line. + kIncludeLineSpacingMiddle, + // The line spacing will be added to the top of the rect. + kIncludeLineSpacingTop, + // The line spacing will be added to the bottom of the rect. + kIncludeLineSpacingBottom + }; + + enum class RectWidthStyle { + // Provide tight bounding boxes that fit widths to the runs of each line + // independently. + kTight, + + // Extends the width of the last rect of each line to match the position of + // the widest rect over all the lines. + kMax }; struct PositionWithAffinity { @@ -158,7 +175,8 @@ class Paragraph { // end glyph indexes, including start and excluding end. std::vector GetRectsForRange(size_t start, size_t end, - RectStyle rect_style) const; + RectHeightStyle rect_height_style, + RectWidthStyle rect_width_style) const; // Returns the index of the glyph that corresponds to the provided coordinate, // with the top left corner as the origin, and +y direction as down. @@ -240,6 +258,15 @@ class Paragraph { std::vector line_baselines_; bool did_exceed_max_lines_; + // Metrics for use in GetRectsForRange(...); + // Per-line max metrics over all runs in a given line. + std::vector line_max_spacings_; + std::vector line_max_descent_; + std::vector line_max_ascent_; + // Overall left and right extremes over all lines. + double max_right_; + double min_left_; + class BidiRun { public: BidiRun(size_t s, size_t e, TextDirection d, const TextStyle& st) diff --git a/third_party/txt/tests/paragraph_unittests.cc b/third_party/txt/tests/paragraph_unittests.cc index 43243da4e97e8..b7589f7e211fd 100644 --- a/third_party/txt/tests/paragraph_unittests.cc +++ b/third_party/txt/tests/paragraph_unittests.cc @@ -892,7 +892,7 @@ TEST_F(ParagraphTest, GetGlyphPositionAtCoordinateParagraph) { text_style.letter_spacing = 1; text_style.word_spacing = 5; text_style.color = SK_ColorBLACK; - text_style.height = 1.5; + text_style.height = 1; builder.PushStyle(text_style); builder.AddText(u16_text); @@ -930,11 +930,11 @@ TEST_F(ParagraphTest, GetGlyphPositionAtCoordinateParagraph) { 18ull); ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(20, -80).position, 1ull); ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 90).position, 18ull); - ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 180).position, 36ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 170).position, 36ull); ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(10000, 180).position, - 54ull); - ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(70, 180).position, 38ull); - ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 270).position, 54ull); + 72ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(70, 180).position, 56ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 270).position, 72ull); ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(35, 90).position, 19ull); ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(10000, 10000).position, 77ull); @@ -981,15 +981,20 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) { // Tests for GetRectsForRange() // NOTE: The base truth values may still need adjustment as the specifics // are adjusted. + Paragraph::RectHeightStyle rect_height_style = + Paragraph::RectHeightStyle::kMax; + Paragraph::RectWidthStyle rect_width_style = + Paragraph::RectWidthStyle::kTight; paint.setColor(SK_ColorRED); std::vector boxes = - paragraph->GetRectsForRange(0, 0, Paragraph::RectStyle::kNone); + paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } EXPECT_EQ(boxes.size(), 0ull); - boxes = paragraph->GetRectsForRange(0, 1, Paragraph::RectStyle::kNone); + boxes = + paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } @@ -1000,7 +1005,8 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) { EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59); paint.setColor(SK_ColorBLUE); - boxes = paragraph->GetRectsForRange(2, 8, Paragraph::RectStyle::kNone); + boxes = + paragraph->GetRectsForRange(2, 8, rect_height_style, rect_width_style); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } @@ -1011,7 +1017,8 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) { EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59); paint.setColor(SK_ColorGREEN); - boxes = paragraph->GetRectsForRange(8, 21, Paragraph::RectStyle::kNone); + boxes = + paragraph->GetRectsForRange(8, 21, rect_height_style, rect_width_style); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } @@ -1022,7 +1029,8 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) { EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59); paint.setColor(SK_ColorRED); - boxes = paragraph->GetRectsForRange(30, 100, Paragraph::RectStyle::kNone); + boxes = + paragraph->GetRectsForRange(30, 100, rect_height_style, rect_width_style); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } @@ -1040,7 +1048,8 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) { EXPECT_FLOAT_EQ(boxes[3].rect.bottom(), 295); paint.setColor(SK_ColorBLUE); - boxes = paragraph->GetRectsForRange(19, 22, Paragraph::RectStyle::kNone); + boxes = + paragraph->GetRectsForRange(19, 22, rect_height_style, rect_width_style); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } @@ -1051,7 +1060,8 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) { EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59); paint.setColor(SK_ColorRED); - boxes = paragraph->GetRectsForRange(21, 21, Paragraph::RectStyle::kNone); + boxes = + paragraph->GetRectsForRange(21, 21, rect_height_style, rect_width_style); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } @@ -1101,15 +1111,20 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeTight)) { // Tests for GetRectsForRange() // NOTE: The base truth values may still need adjustment as the specifics // are adjusted. + Paragraph::RectHeightStyle rect_height_style = + Paragraph::RectHeightStyle::kTight; + Paragraph::RectWidthStyle rect_width_style = + Paragraph::RectWidthStyle::kTight; paint.setColor(SK_ColorRED); std::vector boxes = - paragraph->GetRectsForRange(0, 0, Paragraph::RectStyle::kTight); + paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } EXPECT_EQ(boxes.size(), 0ull); - boxes = paragraph->GetRectsForRange(0, 1, Paragraph::RectStyle::kTight); + boxes = + paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } @@ -1120,7 +1135,8 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeTight)) { EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 74); paint.setColor(SK_ColorBLUE); - boxes = paragraph->GetRectsForRange(2, 8, Paragraph::RectStyle::kTight); + boxes = + paragraph->GetRectsForRange(2, 8, rect_height_style, rect_width_style); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } @@ -1130,7 +1146,8 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeTight)) { EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 74); paint.setColor(SK_ColorGREEN); - boxes = paragraph->GetRectsForRange(8, 21, Paragraph::RectStyle::kTight); + boxes = + paragraph->GetRectsForRange(8, 21, rect_height_style, rect_width_style); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } @@ -1143,10 +1160,464 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeTight)) { ASSERT_TRUE(Snapshot()); } +TEST_F(ParagraphTest, + DISABLE_ON_WINDOWS(GetRectsForRangeIncludeLineSpacingMiddle)) { + // const char* text = + // "12345, \"67890\" 12345 67890 12345 67890 12345 67890 12345 67890 + // 12345 " "67890 12345"; + const char* text = + "( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)(" + " ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)(" + " ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)"; + auto icu_text = icu::UnicodeString::fromUTF8(text); + std::u16string u16_text(icu_text.getBuffer(), + icu_text.getBuffer() + icu_text.length()); + + txt::ParagraphStyle paragraph_style; + paragraph_style.max_lines = 10; + paragraph_style.text_align = TextAlign::left; + txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); + + txt::TextStyle text_style; + text_style.font_family = "Roboto"; + text_style.font_size = 50; + text_style.letter_spacing = 0; + text_style.font_weight = FontWeight::w500; + text_style.word_spacing = 0; + text_style.color = SK_ColorBLACK; + text_style.height = 1.3; + builder.PushStyle(text_style); + + builder.AddText(u16_text); + + builder.Pop(); + + auto paragraph = builder.Build(); + paragraph->Layout(550); + + paragraph->Paint(GetCanvas(), 0, 0); + + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setAntiAlias(true); + paint.setStrokeWidth(1); + + // Tests for GetRectsForRange() + // NOTE: The base truth values may still need adjustment as the specifics + // are adjusted. + Paragraph::RectHeightStyle rect_height_style = + Paragraph::RectHeightStyle::kIncludeLineSpacingMiddle; + Paragraph::RectWidthStyle rect_width_style = Paragraph::RectWidthStyle::kMax; + paint.setColor(SK_ColorRED); + std::vector boxes = + paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 0ull); + + boxes = + paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 1ull); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), 13.744141); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 17.429688); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 82.958008); + + paint.setColor(SK_ColorBLUE); + boxes = + paragraph->GetRectsForRange(2, 8, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 1ull); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 67.429688); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), 13.744141); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 190.00781); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 82.958008); + + paint.setColor(SK_ColorGREEN); + boxes = + paragraph->GetRectsForRange(8, 21, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 1ull); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 190.00781); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), 13.744141); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 508.0625); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 82.958008); + + paint.setColor(SK_ColorRED); + boxes = + paragraph->GetRectsForRange(30, 150, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 8ull); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 190.00781); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), 82.786133); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 525.6875); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 158.95801); + + EXPECT_FLOAT_EQ(boxes[1].rect.left(), 525.6875); + EXPECT_FLOAT_EQ(boxes[1].rect.top(), 82.786133); + EXPECT_FLOAT_EQ(boxes[1].rect.right(), 570.02344); + EXPECT_FLOAT_EQ(boxes[1].rect.bottom(), 158.95801); + + EXPECT_FLOAT_EQ(boxes[2].rect.left(), 0); + EXPECT_FLOAT_EQ(boxes[2].rect.top(), 158.78613); + EXPECT_FLOAT_EQ(boxes[2].rect.right(), 531.57422); + EXPECT_FLOAT_EQ(boxes[2].rect.bottom(), 234.95801); + + EXPECT_FLOAT_EQ(boxes[3].rect.left(), 531.57422); + EXPECT_FLOAT_EQ(boxes[3].rect.top(), 158.78613); + EXPECT_FLOAT_EQ(boxes[3].rect.right(), 570.02344); + EXPECT_FLOAT_EQ(boxes[3].rect.bottom(), 234.95801); + + EXPECT_FLOAT_EQ(boxes[4].rect.left(), 0); + EXPECT_FLOAT_EQ(boxes[4].rect.top(), 234.78613); + EXPECT_FLOAT_EQ(boxes[4].rect.right(), 570.02344); + EXPECT_FLOAT_EQ(boxes[4].rect.bottom(), 310.95801); + + EXPECT_FLOAT_EQ(boxes[5].rect.left(), 0); + EXPECT_FLOAT_EQ(boxes[5].rect.top(), 310.78613); + EXPECT_FLOAT_EQ(boxes[5].rect.right(), 570.02344); + EXPECT_FLOAT_EQ(boxes[5].rect.bottom(), 386.95801); + + paint.setColor(SK_ColorBLUE); + boxes = + paragraph->GetRectsForRange(19, 22, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 1ull); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 463.72656); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), 13.744141); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 530.23047); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 82.958008); + + paint.setColor(SK_ColorRED); + boxes = + paragraph->GetRectsForRange(21, 21, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 0ull); + + ASSERT_TRUE(Snapshot()); +} + +TEST_F(ParagraphTest, + DISABLE_ON_WINDOWS(GetRectsForRangeIncludeLineSpacingTop)) { + // const char* text = + // "12345, \"67890\" 12345 67890 12345 67890 12345 67890 12345 67890 + // 12345 " "67890 12345"; + const char* text = + "( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)(" + " ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)(" + " ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)"; + auto icu_text = icu::UnicodeString::fromUTF8(text); + std::u16string u16_text(icu_text.getBuffer(), + icu_text.getBuffer() + icu_text.length()); + + txt::ParagraphStyle paragraph_style; + paragraph_style.max_lines = 10; + paragraph_style.text_align = TextAlign::left; + txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); + + txt::TextStyle text_style; + text_style.font_family = "Roboto"; + text_style.font_size = 50; + text_style.letter_spacing = 0; + text_style.font_weight = FontWeight::w500; + text_style.word_spacing = 0; + text_style.color = SK_ColorBLACK; + text_style.height = 1.3; + builder.PushStyle(text_style); + + builder.AddText(u16_text); + + builder.Pop(); + + auto paragraph = builder.Build(); + paragraph->Layout(550); + + paragraph->Paint(GetCanvas(), 0, 0); + + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setAntiAlias(true); + paint.setStrokeWidth(1); + + // Tests for GetRectsForRange() + // NOTE: The base truth values may still need adjustment as the specifics + // are adjusted. + Paragraph::RectHeightStyle rect_height_style = + Paragraph::RectHeightStyle::kIncludeLineSpacingTop; + Paragraph::RectWidthStyle rect_width_style = Paragraph::RectWidthStyle::kMax; + paint.setColor(SK_ColorRED); + std::vector boxes = + paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 0ull); + + boxes = + paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 1ull); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), 13.744141); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 17.429688); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 76); + + paint.setColor(SK_ColorBLUE); + boxes = + paragraph->GetRectsForRange(2, 8, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 1ull); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 67.429688); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), 13.744141); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 190.00781); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 76); + + paint.setColor(SK_ColorGREEN); + boxes = + paragraph->GetRectsForRange(8, 21, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 1ull); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 190.00781); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), 13.744141); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 508.0625); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 76); + + paint.setColor(SK_ColorRED); + boxes = + paragraph->GetRectsForRange(30, 150, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 8ull); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 190.00781); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), 75.828125); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 525.6875); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 152); + + EXPECT_FLOAT_EQ(boxes[1].rect.left(), 525.6875); + EXPECT_FLOAT_EQ(boxes[1].rect.top(), 75.828125); + EXPECT_FLOAT_EQ(boxes[1].rect.right(), 570.02344); + EXPECT_FLOAT_EQ(boxes[1].rect.bottom(), 152); + + EXPECT_FLOAT_EQ(boxes[2].rect.left(), 0); + EXPECT_FLOAT_EQ(boxes[2].rect.top(), 151.82812); + EXPECT_FLOAT_EQ(boxes[2].rect.right(), 531.57422); + EXPECT_FLOAT_EQ(boxes[2].rect.bottom(), 228); + + EXPECT_FLOAT_EQ(boxes[3].rect.left(), 531.57422); + EXPECT_FLOAT_EQ(boxes[3].rect.top(), 151.82812); + EXPECT_FLOAT_EQ(boxes[3].rect.right(), 570.02344); + EXPECT_FLOAT_EQ(boxes[3].rect.bottom(), 228); + + EXPECT_FLOAT_EQ(boxes[4].rect.left(), 0); + EXPECT_FLOAT_EQ(boxes[4].rect.top(), 227.82812); + EXPECT_FLOAT_EQ(boxes[4].rect.right(), 570.02344); + EXPECT_FLOAT_EQ(boxes[4].rect.bottom(), 304); + + EXPECT_FLOAT_EQ(boxes[5].rect.left(), 0); + EXPECT_FLOAT_EQ(boxes[5].rect.top(), 303.82812); + EXPECT_FLOAT_EQ(boxes[5].rect.right(), 570.02344); + EXPECT_FLOAT_EQ(boxes[5].rect.bottom(), 380); + + paint.setColor(SK_ColorBLUE); + boxes = + paragraph->GetRectsForRange(19, 22, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 1ull); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 463.72656); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), 13.744141); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 530.23047); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 76); + + paint.setColor(SK_ColorRED); + boxes = + paragraph->GetRectsForRange(21, 21, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 0ull); + + ASSERT_TRUE(Snapshot()); +} + +TEST_F(ParagraphTest, + DISABLE_ON_WINDOWS(GetRectsForRangeIncludeLineSpacingBottom)) { + // const char* text = + // "12345, \"67890\" 12345 67890 12345 67890 12345 67890 12345 67890 + // 12345 " "67890 12345"; + const char* text = + "( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)(" + " ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)(" + " ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)( ´・‿・`)"; + auto icu_text = icu::UnicodeString::fromUTF8(text); + std::u16string u16_text(icu_text.getBuffer(), + icu_text.getBuffer() + icu_text.length()); + + txt::ParagraphStyle paragraph_style; + paragraph_style.max_lines = 10; + paragraph_style.text_align = TextAlign::left; + txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); + + txt::TextStyle text_style; + text_style.font_family = "Roboto"; + text_style.font_size = 50; + text_style.letter_spacing = 0; + text_style.font_weight = FontWeight::w500; + text_style.word_spacing = 0; + text_style.color = SK_ColorBLACK; + text_style.height = 1.3; + builder.PushStyle(text_style); + + builder.AddText(u16_text); + + builder.Pop(); + + auto paragraph = builder.Build(); + paragraph->Layout(550); + + paragraph->Paint(GetCanvas(), 0, 0); + + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setAntiAlias(true); + paint.setStrokeWidth(1); + + // Tests for GetRectsForRange() + // NOTE: The base truth values may still need adjustment as the specifics + // are adjusted. + Paragraph::RectHeightStyle rect_height_style = + Paragraph::RectHeightStyle::kIncludeLineSpacingBottom; + Paragraph::RectWidthStyle rect_width_style = Paragraph::RectWidthStyle::kMax; + paint.setColor(SK_ColorRED); + std::vector boxes = + paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 0ull); + + boxes = + paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 1ull); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), 13.744141); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 17.429688); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 89.916016); + + paint.setColor(SK_ColorBLUE); + boxes = + paragraph->GetRectsForRange(2, 8, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 1ull); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 67.429688); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), 13.744141); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 190.00781); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 89.916016); + + paint.setColor(SK_ColorGREEN); + boxes = + paragraph->GetRectsForRange(8, 21, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 1ull); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 190.00781); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), 13.744141); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 508.0625); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 89.916016); + + paint.setColor(SK_ColorRED); + boxes = + paragraph->GetRectsForRange(30, 150, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 8ull); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 190.00781); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), 89.744141); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 525.6875); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 165.91602); + + EXPECT_FLOAT_EQ(boxes[1].rect.left(), 525.6875); + EXPECT_FLOAT_EQ(boxes[1].rect.top(), 89.744141); + EXPECT_FLOAT_EQ(boxes[1].rect.right(), 570.02344); + EXPECT_FLOAT_EQ(boxes[1].rect.bottom(), 165.91602); + + EXPECT_FLOAT_EQ(boxes[2].rect.left(), 0); + EXPECT_FLOAT_EQ(boxes[2].rect.top(), 165.74414); + EXPECT_FLOAT_EQ(boxes[2].rect.right(), 531.57422); + EXPECT_FLOAT_EQ(boxes[2].rect.bottom(), 241.91602); + + EXPECT_FLOAT_EQ(boxes[3].rect.left(), 531.57422); + EXPECT_FLOAT_EQ(boxes[3].rect.top(), 165.74414); + EXPECT_FLOAT_EQ(boxes[3].rect.right(), 570.02344); + EXPECT_FLOAT_EQ(boxes[3].rect.bottom(), 241.91602); + + EXPECT_FLOAT_EQ(boxes[4].rect.left(), 0); + EXPECT_FLOAT_EQ(boxes[4].rect.top(), 241.74414); + EXPECT_FLOAT_EQ(boxes[4].rect.right(), 570.02344); + EXPECT_FLOAT_EQ(boxes[4].rect.bottom(), 317.91602); + + EXPECT_FLOAT_EQ(boxes[5].rect.left(), 0); + EXPECT_FLOAT_EQ(boxes[5].rect.top(), 317.74414); + EXPECT_FLOAT_EQ(boxes[5].rect.right(), 570.02344); + EXPECT_FLOAT_EQ(boxes[5].rect.bottom(), 393.91602); + + paint.setColor(SK_ColorBLUE); + boxes = + paragraph->GetRectsForRange(19, 22, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 1ull); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 463.72656); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), 13.744141); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 530.23047); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 89.916016); + + paint.setColor(SK_ColorRED); + boxes = + paragraph->GetRectsForRange(21, 21, rect_height_style, rect_width_style); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 0ull); + + ASSERT_TRUE(Snapshot()); +} + SkRect GetCoordinatesForGlyphPosition(const txt::Paragraph& paragraph, size_t pos) { std::vector boxes = - paragraph.GetRectsForRange(pos, pos + 1, Paragraph::RectStyle::kNone); + paragraph.GetRectsForRange(pos, pos + 1, Paragraph::RectHeightStyle::kMax, + Paragraph::RectWidthStyle::kTight); return !boxes.empty() ? boxes.front().rect : SkRect::MakeEmpty(); } @@ -1718,9 +2189,12 @@ TEST_F(ParagraphTest, UnderlineShiftParagraph) { paragraph->records_[1].GetRunWidth(), paragraph2->records_[0].GetRunWidth()); - auto rects1 = paragraph->GetRectsForRange(0, 12, Paragraph::RectStyle::kNone); + auto rects1 = + paragraph->GetRectsForRange(0, 12, Paragraph::RectHeightStyle::kMax, + Paragraph::RectWidthStyle::kTight); auto rects2 = - paragraph2->GetRectsForRange(0, 12, Paragraph::RectStyle::kNone); + paragraph2->GetRectsForRange(0, 12, Paragraph::RectHeightStyle::kMax, + Paragraph::RectWidthStyle::kTight); for (size_t i = 0; i < 12; ++i) { auto r1 = GetCoordinatesForGlyphPosition(*paragraph, i);