diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 368cb3a3956..f52e181a1cc 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -1142,6 +1142,7 @@ FILE: ../../../flutter/third_party/txt/src/txt/font_skia.cc FILE: ../../../flutter/third_party/txt/src/txt/font_skia.h FILE: ../../../flutter/third_party/txt/src/txt/font_style.h FILE: ../../../flutter/third_party/txt/src/txt/font_weight.h +FILE: ../../../flutter/third_party/txt/src/txt/line_metrics.h FILE: ../../../flutter/third_party/txt/src/txt/paint_record.cc FILE: ../../../flutter/third_party/txt/src/txt/paint_record.h FILE: ../../../flutter/third_party/txt/src/txt/paragraph.h @@ -1155,6 +1156,7 @@ FILE: ../../../flutter/third_party/txt/src/txt/paragraph_txt.cc FILE: ../../../flutter/third_party/txt/src/txt/paragraph_txt.h FILE: ../../../flutter/third_party/txt/src/txt/placeholder_run.cc FILE: ../../../flutter/third_party/txt/src/txt/placeholder_run.h +FILE: ../../../flutter/third_party/txt/src/txt/run_metrics.h FILE: ../../../flutter/third_party/txt/src/txt/styled_runs.cc FILE: ../../../flutter/third_party/txt/src/txt/styled_runs.h FILE: ../../../flutter/third_party/txt/src/txt/test_font_manager.cc diff --git a/engine/src/flutter/third_party/txt/BUILD.gn b/engine/src/flutter/third_party/txt/BUILD.gn index d77d4b9c9f4..c713fbeb059 100644 --- a/engine/src/flutter/third_party/txt/BUILD.gn +++ b/engine/src/flutter/third_party/txt/BUILD.gn @@ -87,6 +87,7 @@ source_set("txt") { "src/txt/font_skia.h", "src/txt/font_style.h", "src/txt/font_weight.h", + "src/txt/line_metrics.h", "src/txt/paint_record.cc", "src/txt/paint_record.h", "src/txt/paragraph.h", @@ -101,6 +102,7 @@ source_set("txt") { "src/txt/placeholder_run.cc", "src/txt/placeholder_run.h", "src/txt/platform.h", + "src/txt/run_metrics.h", "src/txt/styled_runs.cc", "src/txt/styled_runs.h", "src/txt/test_font_manager.cc", diff --git a/engine/src/flutter/third_party/txt/src/txt/line_metrics.h b/engine/src/flutter/third_party/txt/src/txt/line_metrics.h new file mode 100644 index 00000000000..38cecd1dc71 --- /dev/null +++ b/engine/src/flutter/third_party/txt/src/txt/line_metrics.h @@ -0,0 +1,84 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIB_TXT_SRC_LINE_METRICS_H_ +#define LIB_TXT_SRC_LINE_METRICS_H_ + +#include +#include + +#include "run_metrics.h" + +namespace txt { + +class LineMetrics { + public: + // The following fields are used in the layout process itself. + + // The indexes in the text buffer the line begins and ends. + size_t start_index = 0; + size_t end_index = 0; + size_t end_excluding_whitespace = 0; + size_t end_including_newline = 0; + bool hard_break = false; + + // The following fields are tracked after or during layout to provide to + // the user as well as for computing bounding boxes. + + // The final computed ascent and descent for the line. This can be impacted by + // the strut, height, scaling, as well as outlying runs that are very tall. + // + // The top edge is `baseline - ascent` and the bottom edge is `baseline + + // descent`. Ascent and descent are provided as positive numbers. Raw numbers + // for specific runs of text can be obtained in run_metrics_map. These values + // are the cumulative metrics for the entire line. + double ascent = 0.0; + double descent = 0.0; + double unscaled_ascent = 0.0; + // Height of the line. + double height = 0.0; + // Width of the line. + double width = 0.0; + // The left edge of the line. The right edge can be obtained with `left + + // width` + double left = 0.0; + // The y position of the baseline for this line from the top of the paragraph. + double baseline = 0.0; + // Zero indexed line number. + size_t line_number = 0; + + // Mapping between text index ranges and the FontMetrics associated with + // them. The first run will be keyed under start_index. The metrics here + // are before layout and are the base values we calculate from. + std::map run_metrics; + + LineMetrics(); + + LineMetrics(size_t start, + size_t end, + size_t end_excluding_whitespace, + size_t end_including_newline, + bool hard_break) + : start_index(start), + end_index(end), + end_excluding_whitespace(end_excluding_whitespace), + end_including_newline(end_including_newline), + hard_break(hard_break) {} +}; + +} // namespace txt + +#endif // LIB_TXT_SRC_LINE_METRICS_H_ diff --git a/engine/src/flutter/third_party/txt/src/txt/paragraph.h b/engine/src/flutter/third_party/txt/src/txt/paragraph.h index a197cddd831..19710c464cf 100644 --- a/engine/src/flutter/third_party/txt/src/txt/paragraph.h +++ b/engine/src/flutter/third_party/txt/src/txt/paragraph.h @@ -1,3 +1,4 @@ + /* * Copyright 2017 Google Inc. * @@ -17,6 +18,7 @@ #ifndef LIB_TXT_SRC_PARAGRAPH_H_ #define LIB_TXT_SRC_PARAGRAPH_H_ +#include "line_metrics.h" #include "paragraph_style.h" class SkCanvas; @@ -171,6 +173,8 @@ class Paragraph { // Finds the first and last glyphs that define a word containing the glyph at // index offset. virtual Range GetWordBoundary(size_t offset) = 0; + + virtual std::vector& GetLineMetrics() = 0; }; } // namespace txt diff --git a/engine/src/flutter/third_party/txt/src/txt/paragraph_txt.cc b/engine/src/flutter/third_party/txt/src/txt/paragraph_txt.cc index 8cf2e78232b..2534aa6c7dd 100644 --- a/engine/src/flutter/third_party/txt/src/txt/paragraph_txt.cc +++ b/engine/src/flutter/third_party/txt/src/txt/paragraph_txt.cc @@ -211,6 +211,7 @@ ParagraphTxt::CodeUnitRun::CodeUnitRun(std::vector&& p, Range x, size_t line, const SkFontMetrics& metrics, + const TextStyle& st, TextDirection dir, const PlaceholderRun* placeholder) : positions(std::move(p)), @@ -218,6 +219,7 @@ ParagraphTxt::CodeUnitRun::CodeUnitRun(std::vector&& p, x_pos(x), line_number(line), font_metrics(metrics), + style(&st), direction(dir), placeholder_run(placeholder) {} @@ -250,7 +252,7 @@ void ParagraphTxt::SetInlinePlaceholders( } bool ParagraphTxt::ComputeLineBreaks() { - line_ranges_.clear(); + line_metrics_.clear(); line_widths_.clear(); max_intrinsic_width_ = 0; @@ -276,8 +278,8 @@ bool ParagraphTxt::ComputeLineBreaks() { size_t block_size = block_end - block_start; if (block_size == 0) { - line_ranges_.emplace_back(block_start, block_end, block_end, - block_end + 1, true); + line_metrics_.emplace_back(block_start, block_end, block_end, + block_end + 1, true); line_widths_.push_back(0); continue; } @@ -369,9 +371,9 @@ bool ParagraphTxt::ComputeLineBreaks() { 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_metrics_.emplace_back(line_start, line_end, + line_end_excluding_whitespace, + line_end_including_newline, hard_break); line_widths_.push_back(breaker_.getWidths()[i]); } @@ -654,6 +656,14 @@ void ParagraphTxt::Layout(double width) { needs_layout_ = false; + records_.clear(); + glyph_lines_.clear(); + code_unit_runs_.clear(); + inline_placeholder_code_unit_runs_.clear(); + max_right_ = FLT_MIN; + min_left_ = FLT_MAX; + final_line_count_ = 0; + if (!ComputeLineBreaks()) return; @@ -666,18 +676,6 @@ void ParagraphTxt::Layout(double width) { font.setSubpixel(true); font.setHinting(SkFontHinting::kSlight); - records_.clear(); - line_heights_.clear(); - line_baselines_.clear(); - glyph_lines_.clear(); - code_unit_runs_.clear(); - inline_placeholder_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; double y_offset = 0; @@ -688,12 +686,13 @@ void ParagraphTxt::Layout(double width) { ComputeStrut(&strut_, font); // Paragraph bounds tracking. - size_t line_limit = std::min(paragraph_style_.max_lines, line_ranges_.size()); - did_exceed_max_lines_ = (line_ranges_.size() > paragraph_style_.max_lines); + size_t line_limit = + std::min(paragraph_style_.max_lines, line_metrics_.size()); + did_exceed_max_lines_ = (line_metrics_.size() > paragraph_style_.max_lines); size_t placeholder_run_index = 0; for (size_t line_number = 0; line_number < line_limit; ++line_number) { - const LineRange& line_range = line_ranges_[line_number]; + LineMetrics& line_metrics = line_metrics_[line_number]; // Break the line into words if justification should be applied. std::vector> words; @@ -701,8 +700,8 @@ void ParagraphTxt::Layout(double width) { size_t word_index = 0; bool justify_line = (paragraph_style_.text_align == TextAlign::justify && - line_number != line_limit - 1 && !line_range.hard_break); - FindWords(text_, line_range.start, line_range.end, &words); + line_number != line_limit - 1 && !line_metrics.hard_break); + FindWords(text_, line_metrics.start_index, line_metrics.end_index, &words); if (justify_line) { if (words.size() > 1) { word_gap_width = @@ -716,8 +715,8 @@ void ParagraphTxt::Layout(double width) { (paragraph_style_.effective_align() == TextAlign::right || paragraph_style_.effective_align() == TextAlign::center || paragraph_style_.effective_align() == TextAlign::justify) - ? line_range.end_excluding_whitespace - : line_range.end; + ? line_metrics.end_excluding_whitespace + : line_metrics.end_index; // Find the runs comprising this line. std::vector line_runs; @@ -733,13 +732,13 @@ void ParagraphTxt::Layout(double width) { // impact on the layout. std::unique_ptr ghost_run = nullptr; if (paragraph_style_.ellipsis.empty() && - line_range.end_excluding_whitespace < line_range.end && - bidi_run.start() <= line_range.end && + line_metrics.end_excluding_whitespace < line_metrics.end_index && + bidi_run.start() <= line_metrics.end_index && bidi_run.end() > line_end_index) { ghost_run = std::make_unique( std::max(bidi_run.start(), line_end_index), - std::min(bidi_run.end(), line_range.end), bidi_run.direction(), - bidi_run.style(), true); + std::min(bidi_run.end(), line_metrics.end_index), + bidi_run.direction(), bidi_run.style(), true); } // Include the ghost run before normal run if RTL if (bidi_run.direction() == TextDirection::rtl && ghost_run != nullptr) { @@ -747,21 +746,22 @@ void ParagraphTxt::Layout(double width) { } // Emplace a normal line run. if (bidi_run.start() < line_end_index && - bidi_run.end() > line_range.start) { + bidi_run.end() > line_metrics.start_index) { // The run is a placeholder run. if (bidi_run.size() == 1 && text_[bidi_run.start()] == objReplacementChar && obj_replacement_char_indexes_.count(bidi_run.start()) != 0 && placeholder_run_index < inline_placeholders_.size()) { - line_runs.emplace_back(std::max(bidi_run.start(), line_range.start), - std::min(bidi_run.end(), line_end_index), - bidi_run.direction(), bidi_run.style(), - inline_placeholders_[placeholder_run_index]); + line_runs.emplace_back( + std::max(bidi_run.start(), line_metrics.start_index), + std::min(bidi_run.end(), line_end_index), bidi_run.direction(), + bidi_run.style(), inline_placeholders_[placeholder_run_index]); placeholder_run_index++; } else { - line_runs.emplace_back(std::max(bidi_run.start(), line_range.start), - std::min(bidi_run.end(), line_end_index), - bidi_run.direction(), bidi_run.style()); + line_runs.emplace_back( + std::max(bidi_run.start(), line_metrics.start_index), + std::min(bidi_run.end(), line_end_index), bidi_run.direction(), + bidi_run.style()); } } // Include the ghost run after normal run if LTR @@ -781,6 +781,7 @@ void ParagraphTxt::Layout(double width) { std::vector line_glyph_positions; std::vector line_code_unit_runs; std::vector line_inline_placeholder_code_unit_runs; + double run_x_offset = 0; double justify_x_offset = 0; std::vector paint_records; @@ -806,7 +807,7 @@ void ParagraphTxt::Layout(double width) { // is the last line (or lines are unlimited). const std::u16string& ellipsis = paragraph_style_.ellipsis; std::vector ellipsized_text; - if (ellipsis.length() && !isinf(width_) && !line_range.hard_break && + if (ellipsis.length() && !isinf(width_) && !line_metrics.hard_break && line_run_it == line_runs.end() - 1 && (line_number == line_limit - 1 || paragraph_style_.unlimited_lines())) { @@ -991,22 +992,30 @@ void ParagraphTxt::Layout(double width) { if (glyph_positions.empty()) continue; - SkFontMetrics metrics; - font.getMetrics(&metrics); + // Store the font metrics and TextStyle in the LineMetrics for this line + // to provide metrics upon user request. We index this RunMetrics + // instance at `run.end() - 1` to allow map::lower_bound to access the + // correct RunMetrics at any text index. + size_t run_key = run.end() - 1; + line_metrics.run_metrics.emplace(run_key, &run.style()); + SkFontMetrics* metrics = + &line_metrics.run_metrics.at(run_key).GetFontMetrics(); + font.getMetrics(metrics); + Range record_x_pos( glyph_positions.front().x_pos.start - run_x_offset, glyph_positions.back().x_pos.end - run_x_offset); if (run.is_placeholder_run()) { paint_records.emplace_back( run.style(), SkPoint::Make(run_x_offset + justify_x_offset, 0), - builder.make(), metrics, line_number, record_x_pos.start, + builder.make(), *metrics, line_number, record_x_pos.start, record_x_pos.start + run.placeholder_run()->width, run.is_ghost(), run.placeholder_run()); run_x_offset += run.placeholder_run()->width; } else { paint_records.emplace_back( run.style(), SkPoint::Make(run_x_offset + justify_x_offset, 0), - builder.make(), metrics, line_number, record_x_pos.start, + builder.make(), *metrics, line_number, record_x_pos.start, record_x_pos.end, run.is_ghost()); } justify_x_offset += justify_x_offset_delta; @@ -1030,7 +1039,9 @@ void ParagraphTxt::Layout(double width) { ? glyph_positions.back().x_pos.start + run.placeholder_run()->width : glyph_positions.back().x_pos.end), - line_number, metrics, run.direction(), run.placeholder_run()); + line_number, *metrics, run.style(), run.direction(), + run.placeholder_run()); + if (run.is_placeholder_run()) { line_inline_placeholder_code_unit_runs.push_back( line_code_unit_runs.back()); @@ -1068,11 +1079,11 @@ void ParagraphTxt::Layout(double width) { } } - size_t next_line_start = (line_number < line_ranges_.size() - 1) - ? line_ranges_[line_number + 1].start + size_t next_line_start = (line_number < line_metrics_.size() - 1) + ? line_metrics_[line_number + 1].start_index : text_.size(); glyph_lines_.emplace_back(std::move(line_glyph_positions), - next_line_start - line_range.start); + next_line_start - line_metrics.start_index); code_unit_runs_.insert(code_unit_runs_.end(), line_code_unit_runs.begin(), line_code_unit_runs.end()); inline_placeholder_code_unit_runs_.insert( @@ -1140,17 +1151,22 @@ void ParagraphTxt::Layout(double width) { ideographic_baseline_ = (max_ascent + max_descent); } - line_heights_.push_back((line_heights_.empty() ? 0 : line_heights_.back()) + - round(max_ascent + max_descent)); - line_baselines_.push_back(line_heights_.back() - max_descent); + line_metrics.height = + (line_number == 0 ? 0 : line_metrics_[line_number - 1].height) + + round(max_ascent + max_descent); + line_metrics.baseline = line_metrics.height - max_descent; + y_offset += round(max_ascent + 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_ascent); - line_max_descent_.push_back(max_descent); - line_max_ascent_.push_back(max_unscaled_ascent); + line_metrics.line_number = line_number; + line_metrics.ascent = max_ascent; + line_metrics.descent = max_descent; + line_metrics.unscaled_ascent = max_unscaled_ascent; + line_metrics.width = line_widths_[line_number]; + line_metrics.left = line_x_offset; + + final_line_count_++; for (PaintRecord& paint_record : paint_records) { paint_record.SetOffset( @@ -1221,7 +1237,8 @@ size_t ParagraphTxt::TextSize() const { } double ParagraphTxt::GetHeight() { - return line_heights_.size() ? line_heights_.back() : 0; + return final_line_count_ == 0 ? 0 + : line_metrics_[final_line_count_ - 1].height; } double ParagraphTxt::GetMaxWidth() { @@ -1539,7 +1556,7 @@ std::vector ParagraphTxt::GetRectsForRange( SkScalar min_left = FLT_MAX; }; - std::map line_metrics; + std::map line_box_metrics; // Text direction of the first line so we can extend the correct side for // RectWidthStyle::kMax. TextDirection first_line_dir = TextDirection::ltr; @@ -1557,7 +1574,7 @@ std::vector ParagraphTxt::GetRectsForRange( if (run.code_units.end <= start) continue; - double baseline = line_baselines_[run.line_number]; + double baseline = line_metrics_[run.line_number].baseline; SkScalar top = baseline + run.font_metrics.fAscent; SkScalar bottom = baseline + run.font_metrics.fDescent; @@ -1600,39 +1617,40 @@ std::vector ParagraphTxt::GetRectsForRange( // 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); + line_box_metrics[run.line_number].max_right = + std::max(line_box_metrics[run.line_number].max_right, right); + line_box_metrics[run.line_number].min_left = + std::min(line_box_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( + line_box_metrics[run.line_number].boxes.emplace_back( SkRect::MakeLTRB(left, top, right, bottom), run.direction); } // Add empty rectangles representing any newline characters within the // range. - for (size_t line_number = 0; line_number < line_ranges_.size(); + for (size_t line_number = 0; line_number < line_metrics_.size(); ++line_number) { - const LineRange& line = line_ranges_[line_number]; - if (line.start >= end) + LineMetrics& line = line_metrics_[line_number]; + if (line.start_index >= end) break; if (line.end_including_newline <= start) continue; - if (line_metrics.find(line_number) == line_metrics.end()) { - if (line.end != line.end_including_newline && line.end >= start && - line.end_including_newline <= end) { + if (line_box_metrics.find(line_number) == line_box_metrics.end()) { + if (line.end_index != line.end_including_newline && + line.end_index >= start && line.end_including_newline <= end) { SkScalar x = line_widths_[line_number]; // Move empty box to center if center aligned and is an empty line. if (x == 0 && !isinf(width_) && paragraph_style_.effective_align() == TextAlign::center) { x = width_ / 2; } - SkScalar top = (line_number > 0) ? line_heights_[line_number - 1] : 0; - SkScalar bottom = line_heights_[line_number]; - line_metrics[line_number].boxes.emplace_back( + SkScalar top = + (line_number > 0) ? line_metrics_[line_number - 1].height : 0; + SkScalar bottom = line_metrics_[line_number].height; + line_box_metrics[line_number].boxes.emplace_back( SkRect::MakeLTRB(x, top, x, bottom), TextDirection::ltr); } } @@ -1640,28 +1658,27 @@ std::vector ParagraphTxt::GetRectsForRange( // "Post-process" metrics and aggregate final rects to return. std::vector boxes; - for (const auto& kv : line_metrics) { + for (const auto& kv : line_box_metrics) { // Handle rect_width_styles. We skip the last line because not everything is // selected. + + LineMetrics& line = + line_metrics_[fmin(line_metrics_.size() - 1, fmax(0, kv.first))]; if (rect_width_style == RectWidthStyle::kMax && kv.first != max_line) { - if (line_metrics[kv.first].min_left > min_left_ && + if (line_box_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]), + line_box_metrics[kv.first].boxes.emplace_back( + SkRect::MakeLTRB(min_left_, line.baseline - line.unscaled_ascent, + line_box_metrics[kv.first].min_left, + line.baseline + line.descent), TextDirection::rtl); } - if (line_metrics[kv.first].max_right < max_right_ && + if (line_box_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]), + line_box_metrics[kv.first].boxes.emplace_back( + SkRect::MakeLTRB(line_box_metrics[kv.first].max_right, + line.baseline - line.unscaled_ascent, max_right_, + line.baseline + line.descent), TextDirection::ltr); } } @@ -1674,27 +1691,22 @@ std::vector ParagraphTxt::GetRectsForRange( } 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]), + SkRect::MakeLTRB(box.rect.fLeft, + line.baseline - line.unscaled_ascent, + box.rect.fRight, line.baseline + line.descent), 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]) / + SkScalar adjusted_bottom = line.baseline + line.descent; + if (kv.first < line_metrics_.size() - 1) { + adjusted_bottom += (line_metrics_[kv.first + 1].ascent - + line_metrics_[kv.first + 1].unscaled_ascent) / 2; } - SkScalar adjusted_top = - line_baselines_[kv.first] - line_max_ascent_[kv.first]; + SkScalar adjusted_top = line.baseline - line.unscaled_ascent; if (kv.first != 0) { - adjusted_top -= - (line_max_spacings_[kv.first] - line_max_ascent_[kv.first]) / 2; + adjusted_top -= (line.ascent - line.unscaled_ascent) / 2; } for (const Paragraph::TextBox& box : kv.second.boxes) { boxes.emplace_back(SkRect::MakeLTRB(box.rect.fLeft, adjusted_top, @@ -1703,38 +1715,33 @@ std::vector ParagraphTxt::GetRectsForRange( } } 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]; + SkScalar adjusted_top = kv.first == 0 + ? line.baseline - line.unscaled_ascent + : line.baseline - line.ascent; boxes.emplace_back( - SkRect::MakeLTRB( - box.rect.fLeft, adjusted_top, box.rect.fRight, - line_baselines_[kv.first] + line_max_descent_[kv.first]), + SkRect::MakeLTRB(box.rect.fLeft, adjusted_top, box.rect.fRight, + line.baseline + line.descent), box.direction); } } else if (rect_height_style == RectHeightStyle::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]; + SkScalar adjusted_bottom = line.baseline + line.descent; + if (kv.first < line_metrics_.size() - 1) { + adjusted_bottom += -line.unscaled_ascent + line.ascent; } - boxes.emplace_back(SkRect::MakeLTRB(box.rect.fLeft, - line_baselines_[kv.first] - - line_max_ascent_[kv.first], - box.rect.fRight, adjusted_bottom), - box.direction); + boxes.emplace_back( + SkRect::MakeLTRB(box.rect.fLeft, + line.baseline - line.unscaled_ascent, + box.rect.fRight, adjusted_bottom), + box.direction); } } else if (rect_height_style == RectHeightStyle::kStrut) { if (IsStrutValid()) { for (const Paragraph::TextBox& box : kv.second.boxes) { boxes.emplace_back( - SkRect::MakeLTRB( - box.rect.fLeft, line_baselines_[kv.first] - strut_.ascent, - box.rect.fRight, line_baselines_[kv.first] + strut_.descent), + SkRect::MakeLTRB(box.rect.fLeft, line.baseline - strut_.ascent, + box.rect.fRight, line.baseline + strut_.descent), box.direction); } } else { @@ -1750,12 +1757,12 @@ std::vector ParagraphTxt::GetRectsForRange( Paragraph::PositionWithAffinity ParagraphTxt::GetGlyphPositionAtCoordinate( double dx, double dy) { - if (line_heights_.empty()) + if (final_line_count_ <= 0) return PositionWithAffinity(0, DOWNSTREAM); size_t y_index; - for (y_index = 0; y_index < line_heights_.size() - 1; ++y_index) { - if (dy < line_heights_[y_index]) + for (y_index = 0; y_index < final_line_count_ - 1; ++y_index) { + if (dy < line_metrics_[y_index].height) break; } @@ -1845,7 +1852,7 @@ std::vector ParagraphTxt::GetRectsForPlaceholders() { // Generate initial boxes and calculate metrics. for (const CodeUnitRun& run : inline_placeholder_code_unit_runs_) { // Check to see if we are finished. - double baseline = line_baselines_[run.line_number]; + double baseline = line_metrics_[run.line_number].baseline; SkScalar top = baseline + run.font_metrics.fAscent; SkScalar bottom = baseline + run.font_metrics.fDescent; @@ -1891,7 +1898,7 @@ Paragraph::Range ParagraphTxt::GetWordBoundary(size_t offset) { } size_t ParagraphTxt::GetLineCount() { - return line_heights_.size(); + return final_line_count_; } bool ParagraphTxt::DidExceedMaxLines() { @@ -1902,4 +1909,8 @@ void ParagraphTxt::SetDirty(bool dirty) { needs_layout_ = dirty; } +std::vector& ParagraphTxt::GetLineMetrics() { + return line_metrics_; +} + } // namespace txt diff --git a/engine/src/flutter/third_party/txt/src/txt/paragraph_txt.h b/engine/src/flutter/third_party/txt/src/txt/paragraph_txt.h index c023f0d0762..319190101c5 100644 --- a/engine/src/flutter/third_party/txt/src/txt/paragraph_txt.h +++ b/engine/src/flutter/third_party/txt/src/txt/paragraph_txt.h @@ -24,11 +24,13 @@ #include "flutter/fml/compiler_specific.h" #include "flutter/fml/macros.h" #include "font_collection.h" +#include "line_metrics.h" #include "minikin/LineBreaker.h" #include "paint_record.h" #include "paragraph.h" #include "paragraph_style.h" #include "placeholder_run.h" +#include "run_metrics.h" #include "styled_runs.h" #include "third_party/googletest/googletest/include/gtest/gtest_prod.h" // nogncheck #include "third_party/skia/include/core/SkFontMetrics.h" @@ -113,6 +115,10 @@ class ParagraphTxt : public Paragraph { bool DidExceedMaxLines() override; + // Gets the full vector of LineMetrics which includes detailed data on each + // line in the final layout. + std::vector& GetLineMetrics() override; + // Sets the needs_layout_ to dirty. When Layout() is called, a new Layout will // be performed when this is set to true. Can also be used to prevent a new // Layout from being calculated by setting to false. @@ -150,6 +156,7 @@ class ParagraphTxt : public Paragraph { FRIEND_TEST(ParagraphTest, FontFallbackParagraph); FRIEND_TEST(ParagraphTest, InlinePlaceholder0xFFFCParagraph); FRIEND_TEST(ParagraphTest, FontFeaturesParagraph); + FRIEND_TEST(ParagraphTest, GetGlyphPositionAtCoordinateSegfault); // Starting data to layout. std::vector text_; @@ -172,26 +179,13 @@ class ParagraphTxt : public Paragraph { minikin::LineBreaker breaker_; mutable std::unique_ptr word_breaker_; - struct LineRange { - 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; - }; - std::vector line_ranges_; + std::vector line_metrics_; + size_t final_line_count_; std::vector line_widths_; // Stores the result of Layout(). std::vector records_; - std::vector line_heights_; - std::vector line_baselines_; bool did_exceed_max_lines_; // Strut metrics of zero will have no effect on the layout. @@ -206,11 +200,6 @@ class ParagraphTxt : public Paragraph { StrutMetrics strut_; - // 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_; @@ -293,6 +282,7 @@ class ParagraphTxt : public Paragraph { Range x_pos; size_t line_number; SkFontMetrics font_metrics; + const TextStyle* style; TextDirection direction; const PlaceholderRun* placeholder_run; @@ -301,6 +291,7 @@ class ParagraphTxt : public Paragraph { Range x, size_t line, const SkFontMetrics& metrics, + const TextStyle& st, TextDirection dir, const PlaceholderRun* placeholder); diff --git a/engine/src/flutter/third_party/txt/src/txt/run_metrics.h b/engine/src/flutter/third_party/txt/src/txt/run_metrics.h new file mode 100644 index 00000000000..a51f91230f9 --- /dev/null +++ b/engine/src/flutter/third_party/txt/src/txt/run_metrics.h @@ -0,0 +1,62 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIB_TXT_SRC_RUN_METRICS_H_ +#define LIB_TXT_SRC_RUN_METRICS_H_ + +#include "text_style.h" +#include "third_party/skia/include/core/SkFontMetrics.h" + +namespace txt { + +// Contains the font metrics and TextStyle of a unique run. +class RunMetrics { + public: + RunMetrics(const TextStyle* style) : text_style_(style) {} + + RunMetrics(const TextStyle* style, SkFontMetrics& metrics) + : text_style_(style), font_metrics_(metrics) {} + + SkFontMetrics& GetFontMetrics() { return font_metrics_; } + + const TextStyle& GetTextStyle() const { return *text_style_; } + + private: + const TextStyle* text_style_; + + // SkFontMetrics contains the following metrics: + // + // * Top distance to reserve above baseline + // * Ascent distance to reserve below baseline + // * Descent extent below baseline + // * Bottom extent below baseline + // * Leading distance to add between lines + // * AvgCharWidth average character width + // * MaxCharWidth maximum character width + // * XMin minimum x + // * XMax maximum x + // * XHeight height of lower-case 'x' + // * CapHeight height of an upper-case letter + // * UnderlineThickness underline thickness + // * UnderlinePosition underline position relative to baseline + // * StrikeoutThickness strikeout thickness + // * StrikeoutPosition strikeout position relative to baseline + SkFontMetrics font_metrics_; +}; + +} // namespace txt + +#endif // LIB_TXT_SRC_RUN_METRICS_H_ diff --git a/engine/src/flutter/third_party/txt/tests/paragraph_unittests.cc b/engine/src/flutter/third_party/txt/tests/paragraph_unittests.cc index 30e7c076a58..5496805da2b 100644 --- a/engine/src/flutter/third_party/txt/tests/paragraph_unittests.cc +++ b/engine/src/flutter/third_party/txt/tests/paragraph_unittests.cc @@ -71,6 +71,411 @@ TEST_F(ParagraphTest, SimpleParagraph) { ASSERT_TRUE(Snapshot()); } +// It is possible for the line_metrics_ vector in paragraph to have an empty +// line at the end as a result of the line breaking algorithm. This causes +// the final_line_count_ to be one less than line metrics. This tests that we +// properly handle this case and do not segfault. +TEST_F(ParagraphTest, GetGlyphPositionAtCoordinateSegfault) { + const char* text = "Hello World\nText Dialog"; + auto icu_text = icu::UnicodeString::fromUTF8(text); + std::u16string u16_text(icu_text.getBuffer(), + icu_text.getBuffer() + icu_text.length()); + + txt::ParagraphStyle paragraph_style; + txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection()); + + txt::TextStyle text_style; + // We must supply a font here, as the default is Arial, and we do not + // include Arial in our test fonts as it is proprietary. We want it to + // be Arial default though as it is one of the most common fonts on host + // platforms. On real devices/apps, Arial should be able to be resolved. + text_style.font_families = std::vector(1, "Roboto"); + text_style.color = SK_ColorBLACK; + builder.PushStyle(text_style); + builder.AddText(u16_text); + + builder.Pop(); + + auto paragraph = BuildParagraph(builder); + paragraph->Layout(GetTestCanvasWidth()); + + paragraph->Paint(GetCanvas(), 10.0, 15.0); + + ASSERT_EQ(paragraph->final_line_count_, paragraph->line_metrics_.size()); + ASSERT_EQ(paragraph->final_line_count_, 2ull); + ASSERT_EQ(paragraph->GetLineCount(), 2ull); + + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(0.2, 0.2).position, 0ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(20.2, 0.2).position, 3ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(0.2, 20.2).position, 12ull); + + // We artificially reproduce the conditions that cause segfaults in very + // specific circumstances in the wild. By adding this empty un-laid-out + // LineMetrics at the end, we force the case where final_line_count_ + // represents the true number of lines whereas line_metrics_ has one + // extra empty one. + paragraph->line_metrics_.emplace_back(23, 24, 24, 24, true); + + ASSERT_EQ(paragraph->final_line_count_, paragraph->line_metrics_.size() - 1); + ASSERT_EQ(paragraph->final_line_count_, 2ull); + ASSERT_EQ(paragraph->GetLineCount(), 2ull); + + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(0.2, 20.2).position, 12ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(0.2, 0.2).position, 0ull); + ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(20.2, 0.2).position, 3ull); + + paragraph->line_metrics_.emplace_back(24, 25, 25, 25, true); + + ASSERT_EQ(paragraph->final_line_count_, paragraph->line_metrics_.size() - 2); + ASSERT_EQ(paragraph->final_line_count_, 2ull); + ASSERT_EQ(paragraph->GetLineCount(), 2ull); + + ASSERT_TRUE(Snapshot()); +} + +TEST_F(ParagraphTest, LineMetricsParagraph1) { + const char* text = "Hello! What is going on?\nSecond line \nthirdline"; + auto icu_text = icu::UnicodeString::fromUTF8(text); + std::u16string u16_text(icu_text.getBuffer(), + icu_text.getBuffer() + icu_text.length()); + + txt::ParagraphStyle paragraph_style; + txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection()); + + txt::TextStyle text_style; + // We must supply a font here, as the default is Arial, and we do not + // include Arial in our test fonts as it is proprietary. We want it to + // be Arial default though as it is one of the most common fonts on host + // platforms. On real devices/apps, Arial should be able to be resolved. + text_style.font_families = std::vector(1, "Roboto"); + text_style.color = SK_ColorBLACK; + builder.PushStyle(text_style); + builder.AddText(u16_text); + + builder.Pop(); + + auto paragraph = BuildParagraph(builder); + paragraph->Layout(GetTestCanvasWidth()); + + paragraph->Paint(GetCanvas(), 0, 0); + + ASSERT_TRUE(Snapshot()); + + ASSERT_EQ(paragraph->GetLineMetrics().size(), 3ull); + ASSERT_EQ(paragraph->GetLineMetrics()[0].start_index, 0ull); + ASSERT_EQ(paragraph->GetLineMetrics()[0].end_index, 24ull); + ASSERT_EQ(paragraph->GetLineMetrics()[0].end_including_newline, 25ull); + ASSERT_EQ(paragraph->GetLineMetrics()[0].end_excluding_whitespace, 24ull); + ASSERT_EQ(paragraph->GetLineMetrics()[0].hard_break, true); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].ascent, 12.988281); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].descent, 3.4179688); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].width, 149.67578); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].left, 0.0); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].baseline, 12.582031); + ASSERT_EQ(paragraph->GetLineMetrics()[0].line_number, 0ull); + ASSERT_EQ(paragraph->GetLineMetrics()[0].run_metrics.size(), 1ull); + ASSERT_EQ( + paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(paragraph->GetLineMetrics()[0].start_index) + ->second.GetTextStyle() + .color, + SK_ColorBLACK); + ASSERT_EQ( + paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(paragraph->GetLineMetrics()[0].start_index) + ->second.GetTextStyle() + .font_families, + std::vector(1, "Roboto")); + ASSERT_FLOAT_EQ( + paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(paragraph->GetLineMetrics()[0].start_index) + ->second.GetFontMetrics() + .fAscent, + -12.988281); + ASSERT_FLOAT_EQ( + paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(paragraph->GetLineMetrics()[0].start_index) + ->second.GetFontMetrics() + .fDescent, + 3.4179688); + ASSERT_FLOAT_EQ( + paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(paragraph->GetLineMetrics()[0].start_index) + ->second.GetFontMetrics() + .fXHeight, + 7.3964844); + ASSERT_FLOAT_EQ( + paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(paragraph->GetLineMetrics()[0].start_index) + ->second.GetFontMetrics() + .fLeading, + 0); + ASSERT_FLOAT_EQ( + paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(paragraph->GetLineMetrics()[0].start_index) + ->second.GetFontMetrics() + .fTop, + -14.786133); + ASSERT_FLOAT_EQ( + paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(paragraph->GetLineMetrics()[0].start_index) + ->second.GetFontMetrics() + .fUnderlinePosition, + 1.0253906); + + ASSERT_EQ(paragraph->GetLineMetrics()[1].start_index, 25ull); + ASSERT_EQ(paragraph->GetLineMetrics()[1].end_index, 37ull); + ASSERT_EQ(paragraph->GetLineMetrics()[1].end_including_newline, 38ull); + ASSERT_EQ(paragraph->GetLineMetrics()[1].end_excluding_whitespace, 36ull); + ASSERT_EQ(paragraph->GetLineMetrics()[1].hard_break, true); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].ascent, 12.988281); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].descent, 3.4179688); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].width, 72.039062); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].left, 0.0); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].baseline, 28.582031); + ASSERT_EQ(paragraph->GetLineMetrics()[1].line_number, 1ull); + ASSERT_EQ(paragraph->GetLineMetrics()[1].run_metrics.size(), 1ull); + ASSERT_EQ( + paragraph->GetLineMetrics()[1] + .run_metrics.lower_bound(paragraph->GetLineMetrics()[1].start_index) + ->second.GetTextStyle() + .color, + SK_ColorBLACK); + ASSERT_EQ( + paragraph->GetLineMetrics()[1] + .run_metrics.lower_bound(paragraph->GetLineMetrics()[1].start_index) + ->second.GetTextStyle() + .font_families, + std::vector(1, "Roboto")); + ASSERT_FLOAT_EQ( + paragraph->GetLineMetrics()[1] + .run_metrics.lower_bound(paragraph->GetLineMetrics()[1].start_index) + ->second.GetFontMetrics() + .fAscent, + -12.988281); + ASSERT_FLOAT_EQ( + paragraph->GetLineMetrics()[1] + .run_metrics.lower_bound(paragraph->GetLineMetrics()[1].start_index) + ->second.GetFontMetrics() + .fDescent, + 3.4179688); + ASSERT_FLOAT_EQ( + paragraph->GetLineMetrics()[1] + .run_metrics.lower_bound(paragraph->GetLineMetrics()[1].start_index) + ->second.GetFontMetrics() + .fXHeight, + 7.3964844); + ASSERT_FLOAT_EQ( + paragraph->GetLineMetrics()[1] + .run_metrics.lower_bound(paragraph->GetLineMetrics()[1].start_index) + ->second.GetFontMetrics() + .fLeading, + 0); + ASSERT_FLOAT_EQ( + paragraph->GetLineMetrics()[1] + .run_metrics.lower_bound(paragraph->GetLineMetrics()[1].start_index) + ->second.GetFontMetrics() + .fTop, + -14.786133); + ASSERT_FLOAT_EQ( + paragraph->GetLineMetrics()[1] + .run_metrics.lower_bound(paragraph->GetLineMetrics()[1].start_index) + ->second.GetFontMetrics() + .fUnderlinePosition, + 1.0253906); +} + +TEST_F(ParagraphTest, LineMetricsParagraph2) { + const char* text = "test string alphabetic"; + auto icu_text = icu::UnicodeString::fromUTF8(text); + std::u16string alphabetic(icu_text.getBuffer(), + icu_text.getBuffer() + icu_text.length()); + + const char* text2 = "测试中文日本語한국어"; + auto icu_text2 = icu::UnicodeString::fromUTF8(text2); + std::u16string cjk(icu_text2.getBuffer(), + icu_text2.getBuffer() + icu_text2.length()); + + txt::ParagraphStyle paragraph_style; + txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection()); + + txt::TextStyle text_style; + text_style.font_families = std::vector(1, "Roboto"); + text_style.font_families.push_back("Noto Sans CJK JP"); + text_style.font_size = 27; + text_style.color = SK_ColorBLACK; + builder.PushStyle(text_style); + builder.AddText(alphabetic); + + text_style.font_size = 24; + builder.PushStyle(text_style); + builder.AddText(cjk); + + builder.Pop(); + + auto paragraph = BuildParagraph(builder); + paragraph->Layout(350); + + paragraph->Paint(GetCanvas(), 0, 0); + + ASSERT_TRUE(Snapshot()); + + ASSERT_EQ(paragraph->GetLineMetrics().size(), 2ull); + ASSERT_EQ(paragraph->GetLineMetrics()[0].start_index, 0ull); + ASSERT_EQ(paragraph->GetLineMetrics()[0].end_index, 26ull); + ASSERT_EQ(paragraph->GetLineMetrics()[0].end_including_newline, 26ull); + ASSERT_EQ(paragraph->GetLineMetrics()[0].end_excluding_whitespace, 26ull); + ASSERT_EQ(paragraph->GetLineMetrics()[0].hard_break, false); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].ascent, 27.84); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].descent, 7.6799998); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].width, 349.22266); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].left, 0.0); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].baseline, 28.32); + ASSERT_EQ(paragraph->GetLineMetrics()[0].line_number, 0ull); + ASSERT_EQ(paragraph->GetLineMetrics()[0].run_metrics.size(), 2ull); + // First run + ASSERT_EQ(paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(2) + ->second.GetTextStyle() + .font_size, + 27); + ASSERT_EQ(paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(2) + ->second.GetTextStyle() + .font_families, + text_style.font_families); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(2) + ->second.GetFontMetrics() + .fAscent, + -25.048828); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(2) + ->second.GetFontMetrics() + .fDescent, + 6.5917969); + + ASSERT_EQ(paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(21) + ->second.GetTextStyle() + .font_size, + 27); + ASSERT_EQ(paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(21) + ->second.GetTextStyle() + .font_families, + text_style.font_families); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(21) + ->second.GetFontMetrics() + .fAscent, + -25.048828); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(21) + ->second.GetFontMetrics() + .fDescent, + 6.5917969); + + // Second run + ASSERT_EQ(paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(22) + ->second.GetTextStyle() + .font_size, + 24); + ASSERT_EQ(paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(22) + ->second.GetTextStyle() + .font_families, + text_style.font_families); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(22) + ->second.GetFontMetrics() + .fAscent, + -27.84); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(22) + ->second.GetFontMetrics() + .fDescent, + 7.6799998); + + ASSERT_EQ(paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(24) + ->second.GetTextStyle() + .font_size, + 24); + ASSERT_EQ(paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(24) + ->second.GetTextStyle() + .font_families, + text_style.font_families); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(24) + ->second.GetFontMetrics() + .fAscent, + -27.84); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0] + .run_metrics.lower_bound(24) + ->second.GetFontMetrics() + .fDescent, + 7.6799998); + + ASSERT_EQ(paragraph->GetLineMetrics()[1].start_index, 26ull); + ASSERT_EQ(paragraph->GetLineMetrics()[1].end_index, 32ull); + ASSERT_EQ(paragraph->GetLineMetrics()[1].end_including_newline, 32ull); + ASSERT_EQ(paragraph->GetLineMetrics()[1].end_excluding_whitespace, 32ull); + ASSERT_EQ(paragraph->GetLineMetrics()[1].hard_break, true); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].ascent, 27.84); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].descent, 7.6799998); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].width, 138.23438); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].left, 0.0); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].baseline, 64.32); + ASSERT_EQ(paragraph->GetLineMetrics()[1].line_number, 1ull); + ASSERT_EQ(paragraph->GetLineMetrics()[1].run_metrics.size(), 1ull); + // Indexing below the line will just resolve to the first run in the line. + ASSERT_EQ(paragraph->GetLineMetrics()[1] + .run_metrics.lower_bound(3) + ->second.GetTextStyle() + .font_size, + 24); + ASSERT_EQ(paragraph->GetLineMetrics()[1] + .run_metrics.lower_bound(3) + ->second.GetTextStyle() + .font_families, + text_style.font_families); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1] + .run_metrics.lower_bound(3) + ->second.GetFontMetrics() + .fAscent, + -27.84); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1] + .run_metrics.lower_bound(3) + ->second.GetFontMetrics() + .fDescent, + 7.6799998); + + // Indexing within the line + ASSERT_EQ(paragraph->GetLineMetrics()[1] + .run_metrics.lower_bound(31) + ->second.GetTextStyle() + .font_size, + 24); + ASSERT_EQ(paragraph->GetLineMetrics()[1] + .run_metrics.lower_bound(31) + ->second.GetTextStyle() + .font_families, + text_style.font_families); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1] + .run_metrics.lower_bound(31) + ->second.GetFontMetrics() + .fAscent, + -27.84); + ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1] + .run_metrics.lower_bound(31) + ->second.GetFontMetrics() + .fDescent, + 7.6799998); +} + TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(InlinePlaceholderParagraph)) { const char* text = "012 34"; auto icu_text = icu::UnicodeString::fromUTF8(text);