diff --git a/lib/ui/text/paragraph.cc b/lib/ui/text/paragraph.cc index 94277c51be8a4..032ee1af4d9bd 100644 --- a/lib/ui/text/paragraph.cc +++ b/lib/ui/text/paragraph.cc @@ -85,7 +85,8 @@ void Paragraph::paint(Canvas* canvas, double x, double y) { } std::vector Paragraph::getRectsForRange(unsigned start, unsigned end) { - return m_paragraphImpl->getRectsForRange(start, end); + return m_paragraphImpl->getRectsForRange(start, end, + txt::Paragraph::RectStyle::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 f8e1ef09d4be2..cf71897d250e4 100644 --- a/lib/ui/text/paragraph_impl.h +++ b/lib/ui/text/paragraph_impl.h @@ -7,6 +7,7 @@ #include "flutter/lib/ui/painting/canvas.h" #include "flutter/lib/ui/text/text_box.h" +#include "flutter/third_party/txt/src/txt/paragraph.h" namespace blink { @@ -32,8 +33,10 @@ class ParagraphImpl { virtual void paint(Canvas* canvas, double x, double y) = 0; - virtual std::vector getRectsForRange(unsigned start, - unsigned end) = 0; + virtual std::vector getRectsForRange( + unsigned start, + unsigned end, + txt::Paragraph::RectStyle rect_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 ccd77287ecaa3..cc55638db2680 100644 --- a/lib/ui/text/paragraph_impl_txt.cc +++ b/lib/ui/text/paragraph_impl_txt.cc @@ -61,11 +61,13 @@ void ParagraphImplTxt::paint(Canvas* canvas, double x, double y) { m_paragraph->Paint(sk_canvas, x, y); } -std::vector ParagraphImplTxt::getRectsForRange(unsigned start, - unsigned end) { +std::vector ParagraphImplTxt::getRectsForRange( + unsigned start, + unsigned end, + txt::Paragraph::RectStyle rect_style) { std::vector result; std::vector boxes = - m_paragraph->GetRectsForRange(start, end); + m_paragraph->GetRectsForRange(start, end, rect_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 54f5dcff8127b..665d65b15cce2 100644 --- a/lib/ui/text/paragraph_impl_txt.h +++ b/lib/ui/text/paragraph_impl_txt.h @@ -8,7 +8,6 @@ #include "flutter/lib/ui/painting/canvas.h" #include "flutter/lib/ui/text/paragraph_impl.h" #include "flutter/lib/ui/text/text_box.h" -#include "flutter/third_party/txt/src/txt/paragraph.h" namespace blink { @@ -29,7 +28,10 @@ class ParagraphImplTxt : public ParagraphImpl { void layout(double width) override; void paint(Canvas* canvas, double x, double y) override; - std::vector getRectsForRange(unsigned start, unsigned end) override; + std::vector getRectsForRange( + unsigned start, + unsigned end, + txt::Paragraph::RectStyle rect_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 fc32fcc4b86eb..4084ccd1de620 100644 --- a/third_party/txt/src/txt/paragraph.cc +++ b/third_party/txt/src/txt/paragraph.cc @@ -674,11 +674,10 @@ void Paragraph::Layout(double width, bool force) { word_start_position = std::numeric_limits::quiet_NaN(); } } - } + } // for each in glyph_blob if (glyph_positions.empty()) continue; - SkPaint::FontMetrics metrics; paint.getFontMetrics(&metrics); paint_records.emplace_back(run.style(), SkPoint::Make(run_x_offset, 0), @@ -701,10 +700,10 @@ 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()); - } + } // for each in glyph_blobs run_x_offset += layout.getAdvance(); - } + } // for each in line_runs // Adjust the glyph positions based on the alignment of the line. double line_x_offset = GetLineXOffset(run_x_offset); @@ -772,7 +771,7 @@ void Paragraph::Layout(double width, bool force) { SkPoint::Make(paint_record.offset().x() + line_x_offset, y_offset)); records_.emplace_back(std::move(paint_record)); } - } + } // for each line_number if (paragraph_style_.max_lines == 1 || (paragraph_style_.unlimited_lines() && paragraph_style_.ellipsized())) { @@ -1067,8 +1066,10 @@ void Paragraph::PaintBackground(SkCanvas* canvas, canvas->drawRect(rect, record.style().background); } -std::vector Paragraph::GetRectsForRange(size_t start, - size_t end) const { +std::vector Paragraph::GetRectsForRange( + size_t start, + size_t end, + RectStyle rect_style) const { std::map> line_boxes; for (const CodeUnitRun& run : code_unit_runs_) { @@ -1123,7 +1124,23 @@ std::vector Paragraph::GetRectsForRange(size_t start, std::vector boxes; for (const auto& kv : line_boxes) { - boxes.insert(boxes.end(), kv.second.begin(), kv.second.end()); + if (rect_style & RectStyle::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); + } + for (const Paragraph::TextBox& box : kv.second) { + boxes.emplace_back(SkRect::MakeLTRB(box.rect.fLeft, min_top, + box.rect.fRight, max_bottom), + box.direction); + } + } } return boxes; } diff --git a/third_party/txt/src/txt/paragraph.h b/third_party/txt/src/txt/paragraph.h index 2cadfbb862229..152d6c42c1935 100644 --- a/third_party/txt/src/txt/paragraph.h +++ b/third_party/txt/src/txt/paragraph.h @@ -55,6 +55,25 @@ class Paragraph { enum Affinity { UPSTREAM, DOWNSTREAM }; + // TODO(garyq): Implement kIncludeLineSpacing and kExtendEndOfLine + + // 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 + }; + struct PositionWithAffinity { const size_t position; const Affinity affinity; @@ -137,7 +156,9 @@ class Paragraph { // Returns a vector of bounding boxes that enclose all text between start and // end glyph indexes, including start and excluding end. - std::vector GetRectsForRange(size_t start, size_t end) const; + std::vector GetRectsForRange(size_t start, + size_t end, + RectStyle rect_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. diff --git a/third_party/txt/tests/paragraph_unittests.cc b/third_party/txt/tests/paragraph_unittests.cc index 136b7e7886f90..88613b2ec1429 100644 --- a/third_party/txt/tests/paragraph_unittests.cc +++ b/third_party/txt/tests/paragraph_unittests.cc @@ -983,13 +983,13 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) { // are adjusted. paint.setColor(SK_ColorRED); std::vector boxes = - paragraph->GetRectsForRange(0, 0); + paragraph->GetRectsForRange(0, 0, Paragraph::RectStyle::kNone); 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); + boxes = paragraph->GetRectsForRange(0, 1, Paragraph::RectStyle::kNone); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } @@ -1000,7 +1000,7 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) { EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59); paint.setColor(SK_ColorBLUE); - boxes = paragraph->GetRectsForRange(2, 8); + boxes = paragraph->GetRectsForRange(2, 8, Paragraph::RectStyle::kNone); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } @@ -1011,7 +1011,7 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) { EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59); paint.setColor(SK_ColorGREEN); - boxes = paragraph->GetRectsForRange(8, 21); + boxes = paragraph->GetRectsForRange(8, 21, Paragraph::RectStyle::kNone); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } @@ -1022,7 +1022,7 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) { EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59); paint.setColor(SK_ColorRED); - boxes = paragraph->GetRectsForRange(30, 100); + boxes = paragraph->GetRectsForRange(30, 100, Paragraph::RectStyle::kNone); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } @@ -1040,7 +1040,7 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) { EXPECT_FLOAT_EQ(boxes[3].rect.bottom(), 295); paint.setColor(SK_ColorBLUE); - boxes = paragraph->GetRectsForRange(19, 22); + boxes = paragraph->GetRectsForRange(19, 22, Paragraph::RectStyle::kNone); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } @@ -1051,7 +1051,7 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) { EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59); paint.setColor(SK_ColorRED); - boxes = paragraph->GetRectsForRange(21, 21); + boxes = paragraph->GetRectsForRange(21, 21, Paragraph::RectStyle::kNone); for (size_t i = 0; i < boxes.size(); ++i) { GetCanvas()->drawRect(boxes[i].rect, paint); } @@ -1060,10 +1060,93 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) { ASSERT_TRUE(Snapshot()); } +TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeTight)) { + 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 = "Noto Sans CJK JP"; + 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; + 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. + paint.setColor(SK_ColorRED); + std::vector boxes = + paragraph->GetRectsForRange(0, 0, Paragraph::RectStyle::kTight); + 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); + 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(), 0); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 16.898438); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 74); + + paint.setColor(SK_ColorBLUE); + boxes = paragraph->GetRectsForRange(2, 8, Paragraph::RectStyle::kTight); + 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.top(), 0); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 264.09375); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 74); + + paint.setColor(SK_ColorGREEN); + boxes = paragraph->GetRectsForRange(8, 21, Paragraph::RectStyle::kTight); + for (size_t i = 0; i < boxes.size(); ++i) { + GetCanvas()->drawRect(boxes[i].rect, paint); + } + EXPECT_EQ(boxes.size(), 2ull); + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 264.09375); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 595.08594); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 74); + + ASSERT_TRUE(Snapshot()); +} + SkRect GetCoordinatesForGlyphPosition(const txt::Paragraph& paragraph, size_t pos) { std::vector boxes = - paragraph.GetRectsForRange(pos, pos + 1); + paragraph.GetRectsForRange(pos, pos + 1, Paragraph::RectStyle::kNone); return !boxes.empty() ? boxes.front().rect : SkRect::MakeEmpty(); } @@ -1635,8 +1718,9 @@ TEST_F(ParagraphTest, UnderlineShiftParagraph) { paragraph->records_[1].GetRunWidth(), paragraph2->records_[0].GetRunWidth()); - auto rects1 = paragraph->GetRectsForRange(0, 12); - auto rects2 = paragraph2->GetRectsForRange(0, 12); + auto rects1 = paragraph->GetRectsForRange(0, 12, Paragraph::RectStyle::kNone); + auto rects2 = + paragraph2->GetRectsForRange(0, 12, Paragraph::RectStyle::kNone); for (size_t i = 0; i < 12; ++i) { auto r1 = GetCoordinatesForGlyphPosition(*paragraph, i); diff --git a/third_party/txt/third_party/fonts/NotoSansCJK-Regular.ttc b/third_party/txt/third_party/fonts/NotoSansCJK-Regular.ttc new file mode 100755 index 0000000000000..2dd4a60625dbf Binary files /dev/null and b/third_party/txt/third_party/fonts/NotoSansCJK-Regular.ttc differ