diff --git a/third_party/txt/src/txt/paragraph.cc b/third_party/txt/src/txt/paragraph.cc index 33712f77161efdd10c4c6ab10cadf8e7b583c56f..aa497308911daa85ff71f0d3027ac23ae14b47c9 100644 --- a/third_party/txt/src/txt/paragraph.cc +++ b/third_party/txt/src/txt/paragraph.cc @@ -402,6 +402,33 @@ bool Paragraph::ComputeBidiRuns(std::vector* result) { if (!U_SUCCESS(status)) return false; + // Detect if final trailing run is a single ambiguous whitespace. + // We want to bundle the final ambiguous whitespace with the preceding + // run in order to maintain logical typing behavior when mixing RTL and LTR + // text. We do not want this to be a true ghost run since the contrasting + // directionality causes the trailing space to not render at the visual end of + // the paragraph. + // + // This only applies to the final whitespace at the end as other whitespace is + // no longer ambiguous when surrounded by additional text. + bool has_trailing_whitespace = false; + int32_t bidi_run_start, bidi_run_length; + if (bidi_run_count > 1) { + ubidi_getVisualRun(bidi.get(), bidi_run_count - 1, &bidi_run_start, + &bidi_run_length); + if (!U_SUCCESS(status)) + return false; + if (bidi_run_length == 1) { + UChar32 last_char; + U16_GET(text_.data(), 0, bidi_run_start + bidi_run_length - 1, + static_cast(text_.size()), last_char); + if (u_hasBinaryProperty(last_char, UCHAR_WHITE_SPACE)) { + has_trailing_whitespace = true; + bidi_run_count--; + } + } + } + // Build a map of styled runs indexed by start position. std::map styled_run_map; for (size_t i = 0; i < runs_.size(); ++i) { @@ -411,7 +438,6 @@ bool Paragraph::ComputeBidiRuns(std::vector* result) { for (int32_t bidi_run_index = 0; bidi_run_index < bidi_run_count; ++bidi_run_index) { - int32_t bidi_run_start, bidi_run_length; UBiDiDirection direction = ubidi_getVisualRun( bidi.get(), bidi_run_index, &bidi_run_start, &bidi_run_length); if (!U_SUCCESS(status)) @@ -438,6 +464,11 @@ bool Paragraph::ComputeBidiRuns(std::vector* result) { if (bidi_run_length == 0) continue; + // Attach the final trailing whitespace as part of this run. + if (has_trailing_whitespace && bidi_run_index == bidi_run_count - 1) { + bidi_run_length++; + } + size_t bidi_run_end = bidi_run_start + bidi_run_length; TextDirection text_direction = direction == UBIDI_RTL ? TextDirection::rtl : TextDirection::ltr; diff --git a/third_party/txt/tests/paragraph_unittests.cc b/third_party/txt/tests/paragraph_unittests.cc index 48033d8200001be33ba8afbc5bf3230f16f81c72..40a36d06fd7fe07a0ef17f0813e4a02e56fce71f 100644 --- a/third_party/txt/tests/paragraph_unittests.cc +++ b/third_party/txt/tests/paragraph_unittests.cc @@ -2100,6 +2100,137 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(ArabicRectsParagraph)) { ASSERT_TRUE(Snapshot()); } +// Trailing space at the end of the arabic rtl run should be at the left end of +// the arabic run. +TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(ArabicRectsLTRLeftAlignParagraph)) { + const char* text = "Helloبمباركة التقليدية قام عن. تصفح يد "; + 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 = 14; + paragraph_style.text_align = TextAlign::left; + paragraph_style.text_direction = TextDirection::ltr; + txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); + + txt::TextStyle text_style; + text_style.font_families = std::vector(1, "Noto Naskh Arabic"); + text_style.font_size = 26; + text_style.letter_spacing = 1; + text_style.word_spacing = 5; + text_style.color = SK_ColorBLACK; + text_style.height = 1; + text_style.decoration = TextDecoration::kUnderline; + text_style.decoration_color = SK_ColorBLACK; + builder.PushStyle(text_style); + + builder.AddText(u16_text); + + builder.Pop(); + + auto paragraph = builder.Build(); + paragraph->Layout(GetTestCanvasWidth() - 100); + + paragraph->Paint(GetCanvas(), 0, 0); + + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setAntiAlias(true); + paint.setStrokeWidth(1); + + // Tests for GetRectsForRange() + 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(36, 40, 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(), 89.40625); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), -0.26855469); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 121.87891); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 44); + + ASSERT_EQ(paragraph_style.text_align, + paragraph->GetParagraphStyle().text_align); + + ASSERT_TRUE(Snapshot()); +} + +// Trailing space at the end of the arabic rtl run should be at the left end of +// the arabic run and be a ghost space. +TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(ArabicRectsLTRRightAlignParagraph)) { + const char* text = "Helloبمباركة التقليدية قام عن. تصفح يد "; + 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 = 14; + paragraph_style.text_align = TextAlign::right; + paragraph_style.text_direction = TextDirection::ltr; + txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection()); + + txt::TextStyle text_style; + text_style.font_families = std::vector(1, "Noto Naskh Arabic"); + text_style.font_size = 26; + text_style.letter_spacing = 1; + text_style.word_spacing = 5; + text_style.color = SK_ColorBLACK; + text_style.height = 1; + text_style.decoration = TextDecoration::kUnderline; + text_style.decoration_color = SK_ColorBLACK; + builder.PushStyle(text_style); + + builder.AddText(u16_text); + + builder.Pop(); + + auto paragraph = builder.Build(); + paragraph->Layout(GetTestCanvasWidth() - 100); + + paragraph->Paint(GetCanvas(), 0, 0); + + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setAntiAlias(true); + paint.setStrokeWidth(1); + + // Tests for GetRectsForRange() + 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(36, 40, 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(), 2ull); + + EXPECT_FLOAT_EQ(boxes[0].rect.left(), 556.54688); + EXPECT_FLOAT_EQ(boxes[0].rect.top(), -0.26855469); + EXPECT_FLOAT_EQ(boxes[0].rect.right(), 577.78125); + EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 44); + + EXPECT_FLOAT_EQ(boxes[1].rect.left(), 545.30859); + EXPECT_FLOAT_EQ(boxes[1].rect.top(), -0.26855469); + EXPECT_FLOAT_EQ(boxes[1].rect.right(), 557.04688); + EXPECT_FLOAT_EQ(boxes[1].rect.bottom(), 44); + + ASSERT_EQ(paragraph_style.text_align, + paragraph->GetParagraphStyle().text_align); + + ASSERT_TRUE(Snapshot()); +} + TEST_F(ParagraphTest, GetGlyphPositionAtCoordinateParagraph) { const char* text = "12345 67890 12345 67890 12345 67890 12345 67890 12345 67890 12345 "