From f6359e4111d37652409e2392fdb3cb72f1119c2a Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Mon, 7 May 2018 17:16:03 -0700 Subject: [PATCH] libtxt: exclude trailing whitespace from right-justified lines (#5190) If a line is right justified, then remove any trailing whitespace from the text range given to Minikin. Right justification shifts the line's glyphs by the layout advance computed by Minikin, and this advance should exclude whitespace so that the last visible character will be flush with the right margin. See https://github.com/flutter/flutter/issues/16333 --- third_party/txt/src/minikin/LineBreaker.cpp | 2 +- third_party/txt/src/minikin/LineBreaker.h | 2 + third_party/txt/src/txt/paragraph.cc | 48 ++++++++++++--------- third_party/txt/src/txt/paragraph.h | 11 +++-- third_party/txt/src/txt/paragraph_style.cc | 12 ++++++ third_party/txt/src/txt/paragraph_style.h | 3 ++ 6 files changed, 53 insertions(+), 25 deletions(-) diff --git a/third_party/txt/src/minikin/LineBreaker.cpp b/third_party/txt/src/minikin/LineBreaker.cpp index 0245e579b..adff2a1e7 100644 --- a/third_party/txt/src/minikin/LineBreaker.cpp +++ b/third_party/txt/src/minikin/LineBreaker.cpp @@ -107,7 +107,7 @@ void LineBreaker::setIndents(const std::vector& indents) { // end of line. It is the Unicode set: // [[:General_Category=Space_Separator:]-[:Line_Break=Glue:]], plus '\n'. Note: // all such characters are in the BMP, so it's ok to use code units for this. -static bool isLineEndSpace(uint16_t c) { +bool isLineEndSpace(uint16_t c) { return c == '\n' || c == ' ' || c == 0x1680 || (0x2000 <= c && c <= 0x200A && c != 0x2007) || c == 0x205F || c == 0x3000; diff --git a/third_party/txt/src/minikin/LineBreaker.h b/third_party/txt/src/minikin/LineBreaker.h index c4ba9b265..4789850bb 100644 --- a/third_party/txt/src/minikin/LineBreaker.h +++ b/third_party/txt/src/minikin/LineBreaker.h @@ -49,6 +49,8 @@ enum HyphenationFrequency { kHyphenationFrequency_Full = 2 }; +bool isLineEndSpace(uint16_t c); + // TODO: want to generalize to be able to handle array of line widths class LineWidths { public: diff --git a/third_party/txt/src/txt/paragraph.cc b/third_party/txt/src/txt/paragraph.cc index 378ea6c82..f75c4a0ae 100644 --- a/third_party/txt/src/txt/paragraph.cc +++ b/third_party/txt/src/txt/paragraph.cc @@ -258,7 +258,8 @@ bool Paragraph::ComputeLineBreaks() { size_t block_size = block_end - block_start; if (block_size == 0) { - line_ranges_.emplace_back(block_start, block_end, block_end + 1, true); + line_ranges_.emplace_back(block_start, block_end, block_end, + block_end + 1, true); line_widths_.push_back(0); continue; } @@ -311,7 +312,14 @@ bool Paragraph::ComputeLineBreaks() { bool hard_break = i == breaks_count - 1; size_t line_end_including_newline = (hard_break && line_end < text_.size()) ? line_end + 1 : line_end; + size_t line_end_excluding_whitespace = line_end; + while ( + line_end_excluding_whitespace > 0 && + minikin::isLineEndSpace(text_[line_end_excluding_whitespace - 1])) { + line_end_excluding_whitespace--; + } line_ranges_.emplace_back(line_start, line_end, + line_end_excluding_whitespace, line_end_including_newline, hard_break); line_widths_.push_back(breaker_.getWidths()[i]); } @@ -462,13 +470,20 @@ void Paragraph::Layout(double width, bool force) { } } + // Exclude trailing whitespace from right-justified lines so the last + // visible character in the line will be flush with the right margin. + size_t line_end_index = + (paragraph_style_.effective_align() == TextAlign::right) + ? line_range.end_excluding_whitespace + : line_range.end; + // Find the runs comprising this line. std::vector line_runs; for (const BidiRun& bidi_run : bidi_runs) { - if (bidi_run.start() < line_range.end && + if (bidi_run.start() < line_end_index && bidi_run.end() > line_range.start) { line_runs.emplace_back(std::max(bidi_run.start(), line_range.start), - std::min(bidi_run.end(), line_range.end), + std::min(bidi_run.end(), line_end_index), bidi_run.direction(), bidi_run.style()); } } @@ -687,7 +702,7 @@ void Paragraph::Layout(double width, bool force) { } // Adjust the glyph positions based on the alignment of the line. - double line_x_offset = GetLineXOffset(line_number, run_x_offset); + double line_x_offset = GetLineXOffset(run_x_offset); if (line_x_offset) { for (CodeUnitRun& code_unit_run : line_code_unit_runs) { for (GlyphPosition& position : code_unit_run.positions) { @@ -780,25 +795,16 @@ void Paragraph::Layout(double width, bool force) { }); } -double Paragraph::GetLineXOffset(size_t line_number, - double line_total_advance) { - if (line_number >= line_widths_.size() || isinf(width_)) +double Paragraph::GetLineXOffset(double line_total_advance) { + if (isinf(width_)) return 0; - TextAlign align = paragraph_style_.text_align; - TextDirection direction = paragraph_style_.text_direction; - - if (align == TextAlign::right || - (align == TextAlign::start && direction == TextDirection::rtl) || - (align == TextAlign::end && direction == TextDirection::ltr)) { - // If this line has a soft break, then use the line width calculated by the - // line breaker because that width excludes the soft break's whitespace. - double text_width = line_ranges_[line_number].hard_break - ? line_total_advance - : line_widths_[line_number]; - return width_ - text_width; - } else if (paragraph_style_.text_align == TextAlign::center) { - return (width_ - line_widths_[line_number]) / 2; + TextAlign align = paragraph_style_.effective_align(); + + if (align == TextAlign::right) { + return width_ - line_total_advance; + } else if (align == TextAlign::center) { + return (width_ - line_total_advance) / 2; } else { return 0; } diff --git a/third_party/txt/src/txt/paragraph.h b/third_party/txt/src/txt/paragraph.h index f70d5828d..454cdc9ac 100644 --- a/third_party/txt/src/txt/paragraph.h +++ b/third_party/txt/src/txt/paragraph.h @@ -187,9 +187,14 @@ class Paragraph { mutable std::unique_ptr word_breaker_; struct LineRange { - LineRange(size_t s, size_t e, size_t ewn, bool h) - : start(s), end(e), end_including_newline(ewn), hard_break(h) {} + LineRange(size_t s, size_t e, size_t eew, size_t ein, bool h) + : start(s), + end(e), + end_excluding_whitespace(eew), + end_including_newline(ein), + hard_break(h) {} size_t start, end; + size_t end_excluding_whitespace; size_t end_including_newline; bool hard_break; }; @@ -300,7 +305,7 @@ class Paragraph { // Calculate the starting X offset of a line based on the line's width and // alignment. - double GetLineXOffset(size_t line_number, double line_total_advance); + double GetLineXOffset(double line_total_advance); // Creates and draws the decorations onto the canvas. void PaintDecorations(SkCanvas* canvas, const PaintRecord& record); diff --git a/third_party/txt/src/txt/paragraph_style.cc b/third_party/txt/src/txt/paragraph_style.cc index 5c0040978..fbb89cbfc 100644 --- a/third_party/txt/src/txt/paragraph_style.cc +++ b/third_party/txt/src/txt/paragraph_style.cc @@ -37,4 +37,16 @@ bool ParagraphStyle::ellipsized() const { return !ellipsis.empty(); } +TextAlign ParagraphStyle::effective_align() const { + if (text_align == TextAlign::start) { + return (text_direction == TextDirection::ltr) ? TextAlign::left + : TextAlign::right; + } else if (text_align == TextAlign::end) { + return (text_direction == TextDirection::ltr) ? TextAlign::right + : TextAlign::left; + } else { + return text_align; + } +} + } // namespace txt diff --git a/third_party/txt/src/txt/paragraph_style.h b/third_party/txt/src/txt/paragraph_style.h index 13d32106c..3b04269d8 100644 --- a/third_party/txt/src/txt/paragraph_style.h +++ b/third_party/txt/src/txt/paragraph_style.h @@ -66,6 +66,9 @@ class ParagraphStyle { bool unlimited_lines() const; bool ellipsized() const; + + // Return a text alignment value that is not dependent on the text direction. + TextAlign effective_align() const; }; } // namespace txt -- GitLab