diff --git a/engine/src/flutter/include/minikin/LineBreaker.h b/engine/src/flutter/include/minikin/LineBreaker.h index c2f546db76..1a6b1888db 100644 --- a/engine/src/flutter/include/minikin/LineBreaker.h +++ b/engine/src/flutter/include/minikin/LineBreaker.h @@ -166,7 +166,7 @@ class LineBreaker { // is having some kind of callback (or virtual class, or maybe even template), which could // easily be instantiated with Minikin's Layout. Future work for when needed. float addStyleRun(MinikinPaint* paint, const std::shared_ptr& typeface, - FontStyle style, size_t start, size_t end, bool isRtl); + FontStyle style, size_t start, size_t end, bool isRtl, double letterSpacing = 0); void addReplacement(size_t start, size_t end, float width); @@ -230,6 +230,7 @@ class LineBreaker { icu::Locale mLocale; std::vectormTextBuf; std::vectormCharWidths; + std::vectormCharSpacing; Hyphenator* mHyphenator; std::vector mHyphBuf; diff --git a/engine/src/flutter/libs/minikin/LineBreaker.cpp b/engine/src/flutter/libs/minikin/LineBreaker.cpp index e75c7bf523..97f4798253 100644 --- a/engine/src/flutter/libs/minikin/LineBreaker.cpp +++ b/engine/src/flutter/libs/minikin/LineBreaker.cpp @@ -113,7 +113,7 @@ static bool isLineEndSpace(uint16_t c) { // This method finds the candidate word breaks (using the ICU break iterator) and sends them // to addCandidate. float LineBreaker::addStyleRun(MinikinPaint* paint, const std::shared_ptr& typeface, - FontStyle style, size_t start, size_t end, bool isRtl) { + FontStyle style, size_t start, size_t end, bool isRtl, double letterSpacing) { float width = 0.0f; int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR; @@ -138,6 +138,10 @@ float LineBreaker::addStyleRun(MinikinPaint* paint, const std::shared_ptrGetMinikinFontCollectionForFamily( run.style.font_family), - font, run.start, run.end, false); + font, run.start, run.end, false, + run.style.letter_spacing); } } @@ -171,6 +172,8 @@ void Paragraph::Layout(double width, bool force) { lines_ = 0; line_widths_ = std::vector(); line_heights_ = std::vector(); + + // Set padding elements to have a minimum point. line_heights_.push_back(0); glyph_position_x_ = std::vector>(); glyph_position_x_.push_back(std::vector()); @@ -283,6 +286,7 @@ void Paragraph::Layout(double width, bool force) { // Check if we should remove trailing whitespace of blobs. size_t trailing_length = 0; while ( + paragraph_style_.text_align == TextAlign::justify && minikin::isWordSpace( text_[character_index + blob_length - trailing_length - 1]) && layout_end == next_break) { @@ -613,7 +617,43 @@ void Paragraph::PaintWavyDecoration(SkCanvas* canvas, std::vector Paragraph::GetRectsForRange(size_t start, size_t end) const { - return std::vector(); + std::vector rects; + end = fmin(end, text_.size() - 1); + while (start <= end) { + SkIPoint word_bounds = GetWordBoundary(start); + word_bounds.fY = fmin(end + 1, word_bounds.fY); + word_bounds.fX = fmax(start, word_bounds.fX); + start = word_bounds.fY; + SkRect left_limits = GetCoordinatesForGlyphPosition(word_bounds.fX + 1); + SkRect right_limits = GetCoordinatesForGlyphPosition(word_bounds.fY); + if (left_limits.top() < right_limits.top()) { + rects.push_back(SkRect::MakeLTRB( + 0, right_limits.top(), right_limits.right(), right_limits.bottom())); + } else { + rects.push_back(SkRect::MakeLTRB(left_limits.left(), left_limits.top(), + right_limits.right(), + right_limits.bottom())); + } + } + return rects; +} + +SkRect Paragraph::GetCoordinatesForGlyphPosition(size_t pos) const { + size_t remainder = fmin(pos, text_.size()); + size_t line = 1; + for (line = 1; line < line_heights_.size() - 1; ++line) { + if (remainder > glyph_position_x_[line].size() - 2) { + remainder -= glyph_position_x_[line].size() - 2; + } else { + break; + } + } + return SkRect::MakeLTRB(glyph_position_x_[line][remainder], + line_heights_[line - 1], + remainder < glyph_position_x_[line].size() - 2 + ? glyph_position_x_[line][remainder + 1] + : line_widths_[line - 1], + line_heights_[line]); } size_t Paragraph::GetGlyphPositionAtCoordinate(double dx, double dy) const { @@ -643,7 +683,8 @@ size_t Paragraph::GetGlyphPositionAtCoordinate(double dx, double dy) const { } SkIPoint Paragraph::GetWordBoundary(size_t offset) const { - return SkIPoint::Make(0, 0); + // TODO(garyq): Implement. + return SkIPoint::Make(0, offset + 1); } int Paragraph::GetLineCount() const { diff --git a/engine/src/flutter/src/paragraph.h b/engine/src/flutter/src/paragraph.h index a23b9e6771..a59398cc56 100644 --- a/engine/src/flutter/src/paragraph.h +++ b/engine/src/flutter/src/paragraph.h @@ -61,6 +61,8 @@ class Paragraph { size_t GetGlyphPositionAtCoordinate(double dx, double dy) const; + SkRect GetCoordinatesForGlyphPosition(size_t pos) const; + SkIPoint GetWordBoundary(size_t offset) const; int GetLineCount() const; diff --git a/engine/src/flutter/tests/txt/paragraph_unittests.cc b/engine/src/flutter/tests/txt/paragraph_unittests.cc index 9158740594..4d19aa453c 100644 --- a/engine/src/flutter/tests/txt/paragraph_unittests.cc +++ b/engine/src/flutter/tests/txt/paragraph_unittests.cc @@ -303,6 +303,9 @@ TEST_F(RenderTest, LeftAlignParagraph) { paragraph->Layout(GetTestCanvasWidth() - 100); paragraph->Paint(GetCanvas(), 0, 0); + + ASSERT_TRUE(Snapshot()); + ASSERT_EQ(paragraph->text_.size(), std::string{text}.length()); for (size_t i = 0; i < u16_text.length(); i++) { ASSERT_EQ(paragraph->text_[i], u16_text[i]); @@ -343,11 +346,9 @@ TEST_F(RenderTest, LeftAlignParagraph) { // Tests for GetGlyphPositionAtCoordinate() ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(0, 0), 0ull); ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 1), 0ull); - ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 30), 74ull); - ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 60), 142ull); - ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(2000, 30), 141ull); - - ASSERT_TRUE(Snapshot()); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 30), 68ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 60), 134ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(2000, 30), 133ull); } TEST_F(RenderTest, RightAlignParagraph) { @@ -856,7 +857,7 @@ TEST_F(RenderTest, DISABLED_ArabicParagraph) { TEST_F(RenderTest, GetGlyphPositionAtCoordinateParagraph) { const char* text = "12345 67890 12345 67890 12345 67890 12345 67890 12345 67890 12345 " - "67890"; + "67890 12345"; auto icu_text = icu::UnicodeString::fromUTF8(text); std::u16string u16_text(icu_text.getBuffer(), icu_text.getBuffer() + icu_text.length()); @@ -880,27 +881,32 @@ TEST_F(RenderTest, GetGlyphPositionAtCoordinateParagraph) { builder.Pop(); auto paragraph = builder.Build(); - paragraph->Layout(GetTestCanvasWidth() - 500); + paragraph->Layout(550); paragraph->Paint(GetCanvas(), 0, 0); + ASSERT_TRUE(Snapshot()); + // Tests for GetGlyphPositionAtCoordinate() // NOTE: resulting values can be a few off from their respective positions in // the original text because the final trailing whitespaces are sometimes not // drawn and therefore are not active glyphs. + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(-10000, -10000), 0ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(-1, -1), 0ull); ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(0, 0), 0ull); ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(3, 3), 0ull); ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(35, 1), 1ull); - ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(100000, 20), 16ull); - ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(100000, 80), 33ull); - ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 80), 17ull); - ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 160), 34ull); - ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(10000, 160), 50ull); - ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(70, 160), 36ull); - ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 270), 51ull); - ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(35, 80), 18ull); - ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(10000, 10000), 68ull); - ASSERT_TRUE(Snapshot()); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(100000, 20), 17ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(100000, 80), 35ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(-100000, 80), 18ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(20, -80), 0ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 80), 18ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 160), 36ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(10000, 160), 53ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(70, 160), 38ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 270), 54ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(35, 80), 19ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(10000, 10000), 77ull); } } // namespace txt