Flutter Engine
paragraph_txt.cc
Go to the documentation of this file.
1 /*
2  * Copyright 2017 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "paragraph_txt.h"
18 
19 #include <hb.h>
20 #include <minikin/Layout.h>
21 
22 #include <algorithm>
23 #include <cstring>
24 #include <limits>
25 #include <map>
26 #include <numeric>
27 #include <utility>
28 #include <vector>
29 
30 #include "flutter/fml/logging.h"
31 #include "font_collection.h"
32 #include "font_skia.h"
34 #include "minikin/GraphemeBreak.h"
35 #include "minikin/HbFontCache.h"
36 #include "minikin/LayoutUtils.h"
37 #include "minikin/LineBreaker.h"
38 #include "minikin/MinikinFont.h"
39 #include "third_party/skia/include/core/SkCanvas.h"
40 #include "third_party/skia/include/core/SkFont.h"
41 #include "third_party/skia/include/core/SkFontMetrics.h"
42 #include "third_party/skia/include/core/SkMaskFilter.h"
43 #include "third_party/skia/include/core/SkPaint.h"
44 #include "third_party/skia/include/core/SkTextBlob.h"
45 #include "third_party/skia/include/core/SkTypeface.h"
46 #include "third_party/skia/include/effects/SkDashPathEffect.h"
47 #include "third_party/skia/include/effects/SkDiscretePathEffect.h"
48 #include "unicode/ubidi.h"
49 #include "unicode/utf16.h"
50 
51 namespace txt {
52 namespace {
53 
54 class GlyphTypeface {
55  public:
56  GlyphTypeface(sk_sp<SkTypeface> typeface, minikin::FontFakery fakery)
57  : typeface_(std::move(typeface)),
58  fake_bold_(fakery.isFakeBold()),
59  fake_italic_(fakery.isFakeItalic()) {}
60 
61  bool operator==(GlyphTypeface& other) {
62  return other.typeface_.get() == typeface_.get() &&
63  other.fake_bold_ == fake_bold_ && other.fake_italic_ == fake_italic_;
64  }
65 
66  bool operator!=(GlyphTypeface& other) { return !(*this == other); }
67 
68  void apply(SkFont& font) {
69  font.setTypeface(typeface_);
70  font.setEmbolden(fake_bold_);
71  font.setSkewX(fake_italic_ ? -SK_Scalar1 / 4 : 0);
72  }
73 
74  private:
75  sk_sp<SkTypeface> typeface_;
76  bool fake_bold_;
77  bool fake_italic_;
78 };
79 
80 GlyphTypeface GetGlyphTypeface(const minikin::Layout& layout, size_t index) {
81  const FontSkia* font = static_cast<const FontSkia*>(layout.getFont(index));
82  return GlyphTypeface(font->GetSkTypeface(), layout.getFakery(index));
83 }
84 
85 // Return ranges of text that have the same typeface in the layout.
86 std::vector<Paragraph::Range<size_t>> GetLayoutTypefaceRuns(
87  const minikin::Layout& layout) {
88  std::vector<Paragraph::Range<size_t>> result;
89  if (layout.nGlyphs() == 0)
90  return result;
91  size_t run_start = 0;
92  GlyphTypeface run_typeface = GetGlyphTypeface(layout, run_start);
93  for (size_t i = 1; i < layout.nGlyphs(); ++i) {
94  GlyphTypeface typeface = GetGlyphTypeface(layout, i);
95  if (typeface != run_typeface) {
96  result.emplace_back(run_start, i);
97  run_start = i;
98  run_typeface = typeface;
99  }
100  }
101  result.emplace_back(run_start, layout.nGlyphs());
102  return result;
103 }
104 
105 int GetWeight(const FontWeight weight) {
106  switch (weight) {
107  case FontWeight::w100:
108  return 1;
109  case FontWeight::w200:
110  return 2;
111  case FontWeight::w300:
112  return 3;
113  case FontWeight::w400: // Normal.
114  return 4;
115  case FontWeight::w500:
116  return 5;
117  case FontWeight::w600:
118  return 6;
119  case FontWeight::w700: // Bold.
120  return 7;
121  case FontWeight::w800:
122  return 8;
123  case FontWeight::w900:
124  return 9;
125  default:
126  return -1;
127  }
128 }
129 
130 int GetWeight(const TextStyle& style) {
131  return GetWeight(style.font_weight);
132 }
133 
134 bool GetItalic(const TextStyle& style) {
135  switch (style.font_style) {
136  case FontStyle::italic:
137  return true;
138  case FontStyle::normal:
139  default:
140  return false;
141  }
142 }
143 
144 minikin::FontStyle GetMinikinFontStyle(const TextStyle& style) {
145  uint32_t language_list_id =
146  style.locale.empty()
149  return minikin::FontStyle(language_list_id, 0, GetWeight(style),
150  GetItalic(style));
151 }
152 
153 void GetFontAndMinikinPaint(const TextStyle& style,
154  minikin::FontStyle* font,
155  minikin::MinikinPaint* paint) {
156  *font = GetMinikinFontStyle(style);
157  paint->size = style.font_size;
158  // Divide by font size so letter spacing is pixels, not proportional to font
159  // size.
160  paint->letterSpacing = style.letter_spacing / style.font_size;
161  paint->wordSpacing = style.word_spacing;
162  paint->scaleX = 1.0f;
163  // Prevent spacing rounding in Minikin. This causes jitter when switching
164  // between same text content with different runs composing it, however, it
165  // also produces more accurate layouts.
167  paint->fontFeatureSettings = style.font_features.GetFeatureSettings();
168 }
169 
170 void FindWords(const std::vector<uint16_t>& text,
171  size_t start,
172  size_t end,
173  std::vector<Paragraph::Range<size_t>>* words) {
174  bool in_word = false;
175  size_t word_start;
176  for (size_t i = start; i < end; ++i) {
177  bool is_space = minikin::isWordSpace(text[i]);
178  if (!in_word && !is_space) {
179  word_start = i;
180  in_word = true;
181  } else if (in_word && is_space) {
182  words->emplace_back(word_start, i);
183  in_word = false;
184  }
185  }
186  if (in_word)
187  words->emplace_back(word_start, end);
188 }
189 
190 } // namespace
191 
192 static const float kDoubleDecorationSpacing = 3.0f;
193 
194 ParagraphTxt::GlyphPosition::GlyphPosition(double x_start,
195  double x_advance,
196  size_t code_unit_index,
197  size_t code_unit_width)
198  : code_units(code_unit_index, code_unit_index + code_unit_width),
199  x_pos(x_start, x_start + x_advance) {}
200 
201 void ParagraphTxt::GlyphPosition::Shift(double delta) {
202  x_pos.Shift(delta);
203 }
204 
205 ParagraphTxt::GlyphLine::GlyphLine(std::vector<GlyphPosition>&& p, size_t tcu)
206  : positions(std::move(p)), total_code_units(tcu) {}
207 
208 ParagraphTxt::CodeUnitRun::CodeUnitRun(std::vector<GlyphPosition>&& p,
209  Range<size_t> cu,
210  Range<double> x,
211  size_t line,
212  const SkFontMetrics& metrics,
213  const TextStyle& st,
214  TextDirection dir,
215  const PlaceholderRun* placeholder)
216  : positions(std::move(p)),
217  code_units(cu),
218  x_pos(x),
219  line_number(line),
220  font_metrics(metrics),
221  style(&st),
222  direction(dir),
223  placeholder_run(placeholder) {}
224 
225 void ParagraphTxt::CodeUnitRun::Shift(double delta) {
226  x_pos.Shift(delta);
227  for (GlyphPosition& position : positions)
228  position.Shift(delta);
229 }
230 
232  breaker_.setLocale(icu::Locale(), nullptr);
233 }
234 
235 ParagraphTxt::~ParagraphTxt() = default;
236 
237 void ParagraphTxt::SetText(std::vector<uint16_t> text, StyledRuns runs) {
238  SetDirty(true);
239  if (text.size() == 0)
240  return;
241  text_ = std::move(text);
242  runs_ = std::move(runs);
243 }
244 
245 void ParagraphTxt::SetInlinePlaceholders(
246  std::vector<PlaceholderRun> inline_placeholders,
247  std::unordered_set<size_t> obj_replacement_char_indexes) {
248  needs_layout_ = true;
249  inline_placeholders_ = std::move(inline_placeholders);
250  obj_replacement_char_indexes_ = std::move(obj_replacement_char_indexes);
251 }
252 
253 bool ParagraphTxt::ComputeLineBreaks() {
254  line_metrics_.clear();
255  line_widths_.clear();
256  max_intrinsic_width_ = 0;
257 
258  std::vector<size_t> newline_positions;
259  // Discover and add all hard breaks.
260  for (size_t i = 0; i < text_.size(); ++i) {
261  ULineBreak ulb = static_cast<ULineBreak>(
262  u_getIntPropertyValue(text_[i], UCHAR_LINE_BREAK));
263  if (ulb == U_LB_LINE_FEED || ulb == U_LB_MANDATORY_BREAK)
264  newline_positions.push_back(i);
265  }
266  // Break at the end of the paragraph.
267  newline_positions.push_back(text_.size());
268 
269  // Calculate and add any breaks due to a line being too long.
270  size_t run_index = 0;
271  size_t inline_placeholder_index = 0;
272  for (size_t newline_index = 0; newline_index < newline_positions.size();
273  ++newline_index) {
274  size_t block_start =
275  (newline_index > 0) ? newline_positions[newline_index - 1] + 1 : 0;
276  size_t block_end = newline_positions[newline_index];
277  size_t block_size = block_end - block_start;
278 
279  if (block_size == 0) {
280  line_metrics_.emplace_back(block_start, block_end, block_end,
281  block_end + 1, true);
282  line_widths_.push_back(0);
283  continue;
284  }
285 
286  // Setup breaker. We wait to set the line width in order to account for the
287  // widths of the inline placeholders, which are calcualted in the loop over
288  // the runs.
289  breaker_.setLineWidths(0.0f, 0, width_);
290  breaker_.setJustified(paragraph_style_.text_align == TextAlign::justify);
291  breaker_.setStrategy(paragraph_style_.break_strategy);
292  breaker_.resize(block_size);
293  memcpy(breaker_.buffer(), text_.data() + block_start,
294  block_size * sizeof(text_[0]));
295  breaker_.setText();
296 
297  // Add the runs that include this line to the LineBreaker.
298  double block_total_width = 0;
299  while (run_index < runs_.size()) {
300  StyledRuns::Run run = runs_.GetRun(run_index);
301  if (run.start >= block_end)
302  break;
303  if (run.end < block_start) {
304  run_index++;
305  continue;
306  }
307 
308  minikin::FontStyle font;
309  minikin::MinikinPaint paint;
310  GetFontAndMinikinPaint(run.style, &font, &paint);
311  std::shared_ptr<minikin::FontCollection> collection =
312  GetMinikinFontCollectionForStyle(run.style);
313  if (collection == nullptr) {
314  FML_LOG(INFO) << "Could not find font collection for families \""
315  << (run.style.font_families.empty()
316  ? ""
317  : run.style.font_families[0])
318  << "\".";
319  return false;
320  }
321  size_t run_start = std::max(run.start, block_start) - block_start;
322  size_t run_end = std::min(run.end, block_end) - block_start;
323  bool isRtl = (paragraph_style_.text_direction == TextDirection::rtl);
324 
325  // Check if the run is an object replacement character-only run. We should
326  // leave space for inline placeholder and break around it if appropriate.
327  if (run.end - run.start == 1 &&
328  obj_replacement_char_indexes_.count(run.start) != 0 &&
329  text_[run.start] == objReplacementChar &&
330  inline_placeholder_index < inline_placeholders_.size()) {
331  // Is a inline placeholder run.
332  PlaceholderRun placeholder_run =
333  inline_placeholders_[inline_placeholder_index];
334  block_total_width += placeholder_run.width;
335 
336  // Inject custom width into minikin breaker. (Uses LibTxt-minikin
337  // patch).
338  breaker_.setCustomCharWidth(run_start, placeholder_run.width);
339 
340  // Called with nullptr as paint in order to use the custom widths passed
341  // above.
342  breaker_.addStyleRun(nullptr, collection, font, run_start, run_end,
343  isRtl);
344  inline_placeholder_index++;
345  } else {
346  // Is a regular text run.
347  double run_width = breaker_.addStyleRun(&paint, collection, font,
348  run_start, run_end, isRtl);
349  block_total_width += run_width;
350  }
351 
352  if (run.end > block_end)
353  break;
354  run_index++;
355  }
356  max_intrinsic_width_ = std::max(max_intrinsic_width_, block_total_width);
357 
358  size_t breaks_count = breaker_.computeBreaks();
359  const int* breaks = breaker_.getBreaks();
360  for (size_t i = 0; i < breaks_count; ++i) {
361  size_t break_start = (i > 0) ? breaks[i - 1] : 0;
362  size_t line_start = break_start + block_start;
363  size_t line_end = breaks[i] + block_start;
364  bool hard_break = i == breaks_count - 1;
365  size_t line_end_including_newline =
366  (hard_break && line_end < text_.size()) ? line_end + 1 : line_end;
367  size_t line_end_excluding_whitespace = line_end;
368  while (
369  line_end_excluding_whitespace > line_start &&
370  minikin::isLineEndSpace(text_[line_end_excluding_whitespace - 1])) {
371  line_end_excluding_whitespace--;
372  }
373  line_metrics_.emplace_back(line_start, line_end,
374  line_end_excluding_whitespace,
375  line_end_including_newline, hard_break);
376  line_widths_.push_back(breaker_.getWidths()[i]);
377  }
378 
379  breaker_.finish();
380  }
381 
382  return true;
383 }
384 
385 bool ParagraphTxt::ComputeBidiRuns(std::vector<BidiRun>* result) {
386  if (text_.empty())
387  return true;
388 
389  auto ubidi_closer = [](UBiDi* b) { ubidi_close(b); };
390  std::unique_ptr<UBiDi, decltype(ubidi_closer)> bidi(ubidi_open(),
391  ubidi_closer);
392  if (!bidi)
393  return false;
394 
395  UBiDiLevel paraLevel = (paragraph_style_.text_direction == TextDirection::rtl)
396  ? UBIDI_RTL
397  : UBIDI_LTR;
398  UErrorCode status = U_ZERO_ERROR;
399  ubidi_setPara(bidi.get(), reinterpret_cast<const UChar*>(text_.data()),
400  text_.size(), paraLevel, nullptr, &status);
401  if (!U_SUCCESS(status))
402  return false;
403 
404  int32_t bidi_run_count = ubidi_countRuns(bidi.get(), &status);
405  if (!U_SUCCESS(status))
406  return false;
407 
408  // Detect if final trailing run is a single ambiguous whitespace.
409  // We want to bundle the final ambiguous whitespace with the preceding
410  // run in order to maintain logical typing behavior when mixing RTL and LTR
411  // text. We do not want this to be a true ghost run since the contrasting
412  // directionality causes the trailing space to not render at the visual end of
413  // the paragraph.
414  //
415  // This only applies to the final whitespace at the end as other whitespace is
416  // no longer ambiguous when surrounded by additional text.
417 
418  // TODO(garyq): Handle this in the text editor caret code instead at layout
419  // level.
420  bool has_trailing_whitespace = false;
421  int32_t bidi_run_start, bidi_run_length;
422  if (bidi_run_count > 1) {
423  ubidi_getVisualRun(bidi.get(), bidi_run_count - 1, &bidi_run_start,
424  &bidi_run_length);
425  if (!U_SUCCESS(status))
426  return false;
427  if (bidi_run_length == 1) {
428  UChar32 last_char;
429  U16_GET(text_.data(), 0, bidi_run_start + bidi_run_length - 1,
430  static_cast<int>(text_.size()), last_char);
431  if (u_hasBinaryProperty(last_char, UCHAR_WHITE_SPACE)) {
432  // Check if the trailing whitespace occurs before the previous run or
433  // not. If so, this trailing whitespace was a leading whitespace.
434  int32_t second_last_bidi_run_start, second_last_bidi_run_length;
435  ubidi_getVisualRun(bidi.get(), bidi_run_count - 2,
436  &second_last_bidi_run_start,
437  &second_last_bidi_run_length);
438  if (bidi_run_start ==
439  second_last_bidi_run_start + second_last_bidi_run_length) {
440  has_trailing_whitespace = true;
441  bidi_run_count--;
442  }
443  }
444  }
445  }
446 
447  // Build a map of styled runs indexed by start position.
448  std::map<size_t, StyledRuns::Run> styled_run_map;
449  for (size_t i = 0; i < runs_.size(); ++i) {
450  StyledRuns::Run run = runs_.GetRun(i);
451  styled_run_map.emplace(std::make_pair(run.start, run));
452  }
453 
454  for (int32_t bidi_run_index = 0; bidi_run_index < bidi_run_count;
455  ++bidi_run_index) {
456  UBiDiDirection direction = ubidi_getVisualRun(
457  bidi.get(), bidi_run_index, &bidi_run_start, &bidi_run_length);
458  if (!U_SUCCESS(status))
459  return false;
460 
461  // Exclude the leading bidi control character if present.
462  UChar32 first_char;
463  U16_GET(text_.data(), 0, bidi_run_start, static_cast<int>(text_.size()),
464  first_char);
465  if (u_hasBinaryProperty(first_char, UCHAR_BIDI_CONTROL)) {
466  bidi_run_start++;
467  bidi_run_length--;
468  }
469  if (bidi_run_length == 0)
470  continue;
471 
472  // Exclude the trailing bidi control character if present.
473  UChar32 last_char;
474  U16_GET(text_.data(), 0, bidi_run_start + bidi_run_length - 1,
475  static_cast<int>(text_.size()), last_char);
476  if (u_hasBinaryProperty(last_char, UCHAR_BIDI_CONTROL)) {
477  bidi_run_length--;
478  }
479  if (bidi_run_length == 0)
480  continue;
481 
482  // Attach the final trailing whitespace as part of this run.
483  if (has_trailing_whitespace && bidi_run_index == bidi_run_count - 1) {
484  bidi_run_length++;
485  }
486 
487  size_t bidi_run_end = bidi_run_start + bidi_run_length;
488  TextDirection text_direction =
489  direction == UBIDI_RTL ? TextDirection::rtl : TextDirection::ltr;
490 
491  // Break this bidi run into chunks based on text style.
492  std::vector<BidiRun> chunks;
493  size_t chunk_start = bidi_run_start;
494  while (chunk_start < bidi_run_end) {
495  auto styled_run_iter = styled_run_map.upper_bound(chunk_start);
496  styled_run_iter--;
497  const StyledRuns::Run& styled_run = styled_run_iter->second;
498  size_t chunk_end = std::min(bidi_run_end, styled_run.end);
499  chunks.emplace_back(chunk_start, chunk_end, text_direction,
500  styled_run.style);
501  chunk_start = chunk_end;
502  }
503 
504  if (text_direction == TextDirection::ltr) {
505  result->insert(result->end(), chunks.begin(), chunks.end());
506  } else {
507  result->insert(result->end(), chunks.rbegin(), chunks.rend());
508  }
509  }
510 
511  return true;
512 }
513 
514 bool ParagraphTxt::IsStrutValid() const {
515  // Font size must be positive.
516  return (paragraph_style_.strut_enabled &&
517  paragraph_style_.strut_font_size >= 0);
518 }
519 
520 void ParagraphTxt::ComputeStrut(StrutMetrics* strut, SkFont& font) {
521  strut->ascent = 0;
522  strut->descent = 0;
523  strut->leading = 0;
524  strut->half_leading = 0;
525  strut->line_height = 0;
526  strut->force_strut = false;
527 
528  if (!IsStrutValid())
529  return;
530 
531  // force_strut makes all lines have exactly the strut metrics, and ignores all
532  // actual metrics. We only force the strut if the strut is non-zero and valid.
533  strut->force_strut = paragraph_style_.force_strut_height;
534  minikin::FontStyle minikin_font_style(
535  0, GetWeight(paragraph_style_.strut_font_weight),
536  paragraph_style_.strut_font_style == FontStyle::italic);
537 
538  std::shared_ptr<minikin::FontCollection> collection =
539  font_collection_->GetMinikinFontCollectionForFamilies(
540  paragraph_style_.strut_font_families, "");
541  if (!collection) {
542  return;
543  }
544  minikin::FakedFont faked_font = collection->baseFontFaked(minikin_font_style);
545 
546  if (faked_font.font != nullptr) {
547  SkString str;
548  static_cast<FontSkia*>(faked_font.font)
549  ->GetSkTypeface()
550  ->getFamilyName(&str);
551  font.setTypeface(static_cast<FontSkia*>(faked_font.font)->GetSkTypeface());
552  font.setSize(paragraph_style_.strut_font_size);
553  SkFontMetrics strut_metrics;
554  font.getMetrics(&strut_metrics);
555 
556  if (paragraph_style_.strut_has_height_override) {
557  double metrics_height = -strut_metrics.fAscent + strut_metrics.fDescent;
558  strut->ascent = (-strut_metrics.fAscent / metrics_height) *
559  paragraph_style_.strut_height *
560  paragraph_style_.strut_font_size;
561  strut->descent = (strut_metrics.fDescent / metrics_height) *
562  paragraph_style_.strut_height *
563  paragraph_style_.strut_font_size;
564  strut->leading =
565  // Zero leading if there is no user specified strut leading.
566  paragraph_style_.strut_leading < 0
567  ? 0
568  : (paragraph_style_.strut_leading *
569  paragraph_style_.strut_font_size);
570  } else {
571  strut->ascent = -strut_metrics.fAscent;
572  strut->descent = strut_metrics.fDescent;
573  strut->leading =
574  // Use font's leading if there is no user specified strut leading.
575  paragraph_style_.strut_leading < 0
576  ? strut_metrics.fLeading
577  : (paragraph_style_.strut_leading *
578  paragraph_style_.strut_font_size);
579  }
580  strut->half_leading = strut->leading / 2;
581  strut->line_height = strut->ascent + strut->descent + strut->leading;
582  }
583 }
584 
585 void ParagraphTxt::ComputePlaceholder(PlaceholderRun* placeholder_run,
586  double& ascent,
587  double& descent) {
588  if (placeholder_run != nullptr) {
589  // Calculate how much to shift the ascent and descent to account
590  // for the baseline choice.
591  //
592  // TODO(garyq): implement for various baselines. Currently only
593  // supports for alphabetic and ideographic
594  double baseline_adjustment = 0;
595  switch (placeholder_run->baseline) {
597  baseline_adjustment = 0;
598  break;
599  }
601  baseline_adjustment = -descent / 2;
602  break;
603  }
604  }
605  // Convert the ascent and descent from the font's to the placeholder
606  // rect's.
607  switch (placeholder_run->alignment) {
609  ascent = baseline_adjustment + placeholder_run->baseline_offset;
610  descent = -baseline_adjustment + placeholder_run->height -
611  placeholder_run->baseline_offset;
612  break;
613  }
615  ascent = baseline_adjustment + placeholder_run->height;
616  descent = -baseline_adjustment;
617  break;
618  }
620  descent = baseline_adjustment + placeholder_run->height;
621  ascent = -baseline_adjustment;
622  break;
623  }
625  descent = placeholder_run->height - ascent;
626  break;
627  }
629  ascent = placeholder_run->height - descent;
630  break;
631  }
633  double mid = (ascent - descent) / 2;
634  ascent = mid + placeholder_run->height / 2;
635  descent = -mid + placeholder_run->height / 2;
636  break;
637  }
638  }
639  placeholder_run->baseline_offset = ascent;
640  }
641 }
642 
643 // Implementation outline:
644 //
645 // -For each line:
646 // -Compute Bidi runs, convert into line_runs (keeps in-line-range runs, adds
647 // special runs)
648 // -For each line_run (runs in the line):
649 // -Calculate ellipsis
650 // -Obtain font
651 // -layout.doLayout(...), genereates glyph blobs
652 // -For each glyph blob:
653 // -Convert glyph blobs into pixel metrics/advances
654 // -Store as paint records (for painting) and code unit runs (for metrics
655 // and boxes).
656 // -Apply letter spacing, alignment, justification, etc
657 // -Calculate line vertical layout (ascent, descent, etc)
658 // -Store per-line metrics
660  double rounded_width = floor(width);
661  // Do not allow calling layout multiple times without changing anything.
662  if (!needs_layout_ && rounded_width == width_) {
663  return;
664  }
665 
666  width_ = rounded_width;
667 
668  needs_layout_ = false;
669 
670  records_.clear();
671  glyph_lines_.clear();
672  code_unit_runs_.clear();
673  inline_placeholder_code_unit_runs_.clear();
674  max_right_ = FLT_MIN;
675  min_left_ = FLT_MAX;
676  final_line_count_ = 0;
677 
678  if (!ComputeLineBreaks())
679  return;
680 
681  std::vector<BidiRun> bidi_runs;
682  if (!ComputeBidiRuns(&bidi_runs))
683  return;
684 
685  SkFont font;
686  font.setEdging(SkFont::Edging::kAntiAlias);
687  font.setSubpixel(true);
688  font.setHinting(SkFontHinting::kSlight);
689 
690  minikin::Layout layout;
691  SkTextBlobBuilder builder;
692  double y_offset = 0;
693  double prev_max_descent = 0;
694  double max_word_width = 0;
695 
696  // Compute strut minimums according to paragraph_style_.
697  ComputeStrut(&strut_, font);
698 
699  // Paragraph bounds tracking.
700  size_t line_limit =
701  std::min(paragraph_style_.max_lines, line_metrics_.size());
702  did_exceed_max_lines_ = (line_metrics_.size() > paragraph_style_.max_lines);
703 
704  size_t placeholder_run_index = 0;
705  for (size_t line_number = 0; line_number < line_limit; ++line_number) {
706  LineMetrics& line_metrics = line_metrics_[line_number];
707 
708  // Break the line into words if justification should be applied.
709  std::vector<Range<size_t>> words;
710  double word_gap_width = 0;
711  size_t word_index = 0;
712  bool justify_line =
713  (paragraph_style_.text_align == TextAlign::justify &&
714  line_number != line_limit - 1 && !line_metrics.hard_break);
715  FindWords(text_, line_metrics.start_index, line_metrics.end_index, &words);
716  if (justify_line) {
717  if (words.size() > 1) {
718  word_gap_width =
719  (width_ - line_widths_[line_number]) / (words.size() - 1);
720  }
721  }
722 
723  // Exclude trailing whitespace from justified lines so the last visible
724  // character in the line will be flush with the right margin.
725  size_t line_end_index =
726  (paragraph_style_.effective_align() == TextAlign::right ||
727  paragraph_style_.effective_align() == TextAlign::center ||
728  paragraph_style_.effective_align() == TextAlign::justify)
729  ? line_metrics.end_excluding_whitespace
730  : line_metrics.end_index;
731 
732  // Find the runs comprising this line.
733  std::vector<BidiRun> line_runs;
734  for (const BidiRun& bidi_run : bidi_runs) {
735  // A "ghost" run is a run that does not impact the layout, breaking,
736  // alignment, width, etc but is still "visible" through getRectsForRange.
737  // For example, trailing whitespace on centered text can be scrolled
738  // through with the caret but will not wrap the line.
739  //
740  // Here, we add an additional run for the whitespace, but dont
741  // let it impact metrics. After layout of the whitespace run, we do not
742  // add its width into the x-offset adjustment, effectively nullifying its
743  // impact on the layout.
744  std::unique_ptr<BidiRun> ghost_run = nullptr;
745  if (paragraph_style_.ellipsis.empty() &&
746  line_metrics.end_excluding_whitespace < line_metrics.end_index &&
747  bidi_run.start() <= line_metrics.end_index &&
748  bidi_run.end() > line_end_index) {
749  ghost_run = std::make_unique<BidiRun>(
750  std::max(bidi_run.start(), line_end_index),
751  std::min(bidi_run.end(), line_metrics.end_index),
752  bidi_run.direction(), bidi_run.style(), true);
753  }
754  // Include the ghost run before normal run if RTL
755  if (bidi_run.direction() == TextDirection::rtl && ghost_run != nullptr) {
756  line_runs.push_back(*ghost_run);
757  }
758  // Emplace a normal line run.
759  if (bidi_run.start() < line_end_index &&
760  bidi_run.end() > line_metrics.start_index) {
761  // The run is a placeholder run.
762  if (bidi_run.size() == 1 &&
763  text_[bidi_run.start()] == objReplacementChar &&
764  obj_replacement_char_indexes_.count(bidi_run.start()) != 0 &&
765  placeholder_run_index < inline_placeholders_.size()) {
766  line_runs.emplace_back(
767  std::max(bidi_run.start(), line_metrics.start_index),
768  std::min(bidi_run.end(), line_end_index), bidi_run.direction(),
769  bidi_run.style(), inline_placeholders_[placeholder_run_index]);
770  placeholder_run_index++;
771  } else {
772  line_runs.emplace_back(
773  std::max(bidi_run.start(), line_metrics.start_index),
774  std::min(bidi_run.end(), line_end_index), bidi_run.direction(),
775  bidi_run.style());
776  }
777  }
778  // Include the ghost run after normal run if LTR
779  if (bidi_run.direction() == TextDirection::ltr && ghost_run != nullptr) {
780  line_runs.push_back(*ghost_run);
781  }
782  }
783  bool line_runs_all_rtl =
784  line_runs.size() &&
785  std::accumulate(
786  line_runs.begin(), line_runs.end(), true,
787  [](const bool a, const BidiRun& b) { return a && b.is_rtl(); });
788  if (line_runs_all_rtl) {
789  std::reverse(words.begin(), words.end());
790  }
791 
792  std::vector<GlyphPosition> line_glyph_positions;
793  std::vector<CodeUnitRun> line_code_unit_runs;
794  std::vector<CodeUnitRun> line_inline_placeholder_code_unit_runs;
795 
796  double run_x_offset = 0;
797  double justify_x_offset = 0;
798  std::vector<PaintRecord> paint_records;
799 
800  for (auto line_run_it = line_runs.begin(); line_run_it != line_runs.end();
801  ++line_run_it) {
802  const BidiRun& run = *line_run_it;
803  minikin::FontStyle minikin_font;
804  minikin::MinikinPaint minikin_paint;
805  GetFontAndMinikinPaint(run.style(), &minikin_font, &minikin_paint);
806  font.setSize(run.style().font_size);
807 
808  std::shared_ptr<minikin::FontCollection> minikin_font_collection =
809  GetMinikinFontCollectionForStyle(run.style());
810 
811  // Lay out this run.
812  uint16_t* text_ptr = text_.data();
813  size_t text_start = run.start();
814  size_t text_count = run.end() - run.start();
815  size_t text_size = text_.size();
816 
817  // Apply ellipsizing if the run was not completely laid out and this
818  // is the last line (or lines are unlimited).
819  const std::u16string& ellipsis = paragraph_style_.ellipsis;
820  std::vector<uint16_t> ellipsized_text;
821  if (ellipsis.length() && !isinf(width_) && !line_metrics.hard_break &&
822  line_run_it == line_runs.end() - 1 &&
823  (line_number == line_limit - 1 ||
824  paragraph_style_.unlimited_lines())) {
825  float ellipsis_width = layout.measureText(
826  reinterpret_cast<const uint16_t*>(ellipsis.data()), 0,
827  ellipsis.length(), ellipsis.length(), run.is_rtl(), minikin_font,
828  minikin_paint, minikin_font_collection, nullptr);
829 
830  std::vector<float> text_advances(text_count);
831  float text_width =
832  layout.measureText(text_ptr, text_start, text_count, text_.size(),
833  run.is_rtl(), minikin_font, minikin_paint,
834  minikin_font_collection, text_advances.data());
835 
836  // Truncate characters from the text until the ellipsis fits.
837  size_t truncate_count = 0;
838  while (truncate_count < text_count &&
839  run_x_offset + text_width + ellipsis_width > width_) {
840  text_width -= text_advances[text_count - truncate_count - 1];
841  truncate_count++;
842  }
843 
844  ellipsized_text.reserve(text_count - truncate_count +
845  ellipsis.length());
846  ellipsized_text.insert(ellipsized_text.begin(),
847  text_.begin() + run.start(),
848  text_.begin() + run.end() - truncate_count);
849  ellipsized_text.insert(ellipsized_text.end(), ellipsis.begin(),
850  ellipsis.end());
851  text_ptr = ellipsized_text.data();
852  text_start = 0;
853  text_count = ellipsized_text.size();
854  text_size = text_count;
855 
856  // If there is no line limit, then skip all lines after the ellipsized
857  // line.
858  if (paragraph_style_.unlimited_lines()) {
859  line_limit = line_number + 1;
860  did_exceed_max_lines_ = true;
861  }
862  }
863 
864  layout.doLayout(text_ptr, text_start, text_count, text_size, run.is_rtl(),
865  minikin_font, minikin_paint, minikin_font_collection);
866 
867  if (layout.nGlyphs() == 0)
868  continue;
869 
870  // When laying out RTL ghost runs, shift the run_x_offset here by the
871  // advance so that the ghost run is positioned to the left of the first
872  // real run of text in the line. However, since we do not want it to
873  // impact the layout of real text, this advance is subsequently added
874  // back into the run_x_offset after the ghost run positions have been
875  // calcuated and before the next real run of text is laid out, ensuring
876  // later runs are laid out in the same position as if there were no ghost
877  // run.
878  if (run.is_ghost() && run.is_rtl())
879  run_x_offset -= layout.getAdvance();
880 
881  std::vector<float> layout_advances(text_count);
882  layout.getAdvances(layout_advances.data());
883 
884  // Break the layout into blobs that share the same SkPaint parameters.
885  std::vector<Range<size_t>> glyph_blobs = GetLayoutTypefaceRuns(layout);
886 
887  double word_start_position = std::numeric_limits<double>::quiet_NaN();
888 
889  // Build a Skia text blob from each group of glyphs.
890  for (const Range<size_t>& glyph_blob : glyph_blobs) {
891  std::vector<GlyphPosition> glyph_positions;
892 
893  GetGlyphTypeface(layout, glyph_blob.start).apply(font);
894  const SkTextBlobBuilder::RunBuffer& blob_buffer =
895  builder.allocRunPos(font, glyph_blob.end - glyph_blob.start);
896 
897  double justify_x_offset_delta = 0;
898  for (size_t glyph_index = glyph_blob.start;
899  glyph_index < glyph_blob.end;) {
900  size_t cluster_start_glyph_index = glyph_index;
901  uint32_t cluster = layout.getGlyphCluster(cluster_start_glyph_index);
902  double glyph_x_offset;
903  // Add all the glyphs in this cluster to the text blob.
904  do {
905  size_t blob_index = glyph_index - glyph_blob.start;
906  blob_buffer.glyphs[blob_index] = layout.getGlyphId(glyph_index);
907 
908  size_t pos_index = blob_index * 2;
909  blob_buffer.pos[pos_index] =
910  layout.getX(glyph_index) + justify_x_offset_delta;
911  blob_buffer.pos[pos_index + 1] = layout.getY(glyph_index);
912 
913  if (glyph_index == cluster_start_glyph_index)
914  glyph_x_offset = blob_buffer.pos[pos_index];
915 
916  glyph_index++;
917  } while (glyph_index < glyph_blob.end &&
918  layout.getGlyphCluster(glyph_index) == cluster);
919 
920  Range<int32_t> glyph_code_units(cluster, 0);
921  std::vector<size_t> grapheme_code_unit_counts;
922  if (run.is_rtl()) {
923  if (cluster_start_glyph_index > 0) {
924  glyph_code_units.end =
925  layout.getGlyphCluster(cluster_start_glyph_index - 1);
926  } else {
927  glyph_code_units.end = text_count;
928  }
929  grapheme_code_unit_counts.push_back(glyph_code_units.width());
930  } else {
931  if (glyph_index < layout.nGlyphs()) {
932  glyph_code_units.end = layout.getGlyphCluster(glyph_index);
933  } else {
934  glyph_code_units.end = text_count;
935  }
936 
937  // The glyph may be a ligature. Determine how many graphemes are
938  // joined into this glyph and how many input code units map to
939  // each grapheme.
940  size_t code_unit_count = 1;
941  for (int32_t offset = glyph_code_units.start + 1;
942  offset < glyph_code_units.end; ++offset) {
944  layout_advances.data(), text_ptr, text_start, text_count,
945  text_start + offset)) {
946  grapheme_code_unit_counts.push_back(code_unit_count);
947  code_unit_count = 1;
948  } else {
949  code_unit_count++;
950  }
951  }
952  grapheme_code_unit_counts.push_back(code_unit_count);
953  }
954  float glyph_advance = layout.getCharAdvance(glyph_code_units.start);
955  float grapheme_advance =
956  glyph_advance / grapheme_code_unit_counts.size();
957 
958  glyph_positions.emplace_back(run_x_offset + glyph_x_offset,
959  grapheme_advance,
960  run.start() + glyph_code_units.start,
961  grapheme_code_unit_counts[0]);
962 
963  // Compute positions for the additional graphemes in the ligature.
964  for (size_t i = 1; i < grapheme_code_unit_counts.size(); ++i) {
965  glyph_positions.emplace_back(
966  glyph_positions.back().x_pos.end, grapheme_advance,
967  glyph_positions.back().code_units.start +
968  grapheme_code_unit_counts[i - 1],
969  grapheme_code_unit_counts[i]);
970  }
971 
972  bool at_word_start = false;
973  bool at_word_end = false;
974  if (word_index < words.size()) {
975  at_word_start =
976  words[word_index].start == run.start() + glyph_code_units.start;
977  at_word_end =
978  words[word_index].end == run.start() + glyph_code_units.end;
979  if (line_runs_all_rtl) {
980  std::swap(at_word_start, at_word_end);
981  }
982  }
983 
984  if (at_word_start) {
985  word_start_position = run_x_offset + glyph_x_offset;
986  }
987 
988  if (at_word_end) {
989  if (justify_line) {
990  justify_x_offset_delta += word_gap_width;
991  }
992  word_index++;
993 
994  if (!isnan(word_start_position)) {
995  double word_width =
996  glyph_positions.back().x_pos.end - word_start_position;
997  max_word_width = std::max(word_width, max_word_width);
998  word_start_position = std::numeric_limits<double>::quiet_NaN();
999  }
1000  }
1001  } // for each in glyph_blob
1002 
1003  if (glyph_positions.empty())
1004  continue;
1005 
1006  // Store the font metrics and TextStyle in the LineMetrics for this line
1007  // to provide metrics upon user request. We index this RunMetrics
1008  // instance at `run.end() - 1` to allow map::lower_bound to access the
1009  // correct RunMetrics at any text index.
1010  size_t run_key = run.end() - 1;
1011  line_metrics.run_metrics.emplace(run_key, &run.style());
1012  SkFontMetrics* metrics =
1013  &line_metrics.run_metrics.at(run_key).font_metrics;
1014  font.getMetrics(metrics);
1015 
1016  Range<double> record_x_pos(
1017  glyph_positions.front().x_pos.start - run_x_offset,
1018  glyph_positions.back().x_pos.end - run_x_offset);
1019  if (run.is_placeholder_run()) {
1020  paint_records.emplace_back(
1021  run.style(), SkPoint::Make(run_x_offset + justify_x_offset, 0),
1022  builder.make(), *metrics, line_number, record_x_pos.start,
1023  record_x_pos.start + run.placeholder_run()->width, run.is_ghost(),
1024  run.placeholder_run());
1025  run_x_offset += run.placeholder_run()->width;
1026  } else {
1027  paint_records.emplace_back(
1028  run.style(), SkPoint::Make(run_x_offset + justify_x_offset, 0),
1029  builder.make(), *metrics, line_number, record_x_pos.start,
1030  record_x_pos.end, run.is_ghost());
1031  }
1032  justify_x_offset += justify_x_offset_delta;
1033 
1034  line_glyph_positions.insert(line_glyph_positions.end(),
1035  glyph_positions.begin(),
1036  glyph_positions.end());
1037 
1038  // Add a record of glyph positions sorted by code unit index.
1039  std::vector<GlyphPosition> code_unit_positions(glyph_positions);
1040  std::sort(code_unit_positions.begin(), code_unit_positions.end(),
1041  [](const GlyphPosition& a, const GlyphPosition& b) {
1042  return a.code_units.start < b.code_units.start;
1043  });
1044 
1045  double blob_x_pos_start = glyph_positions.front().x_pos.start;
1046  double blob_x_pos_end = run.is_placeholder_run()
1047  ? glyph_positions.back().x_pos.start +
1048  run.placeholder_run()->width
1049  : glyph_positions.back().x_pos.end;
1050  line_code_unit_runs.emplace_back(
1051  std::move(code_unit_positions),
1052  Range<size_t>(run.start(), run.end()),
1053  Range<double>(blob_x_pos_start, blob_x_pos_end), line_number,
1054  *metrics, run.style(), run.direction(), run.placeholder_run());
1055 
1056  if (run.is_placeholder_run()) {
1057  line_inline_placeholder_code_unit_runs.push_back(
1058  line_code_unit_runs.back());
1059  }
1060 
1061  if (!run.is_ghost()) {
1062  min_left_ = std::min(min_left_, blob_x_pos_start);
1063  max_right_ = std::max(max_right_, blob_x_pos_end);
1064  }
1065  } // for each in glyph_blobs
1066 
1067  // Do not increase x offset for LTR trailing ghost runs as it should not
1068  // impact the layout of visible glyphs. RTL tailing ghost runs have the
1069  // advance subtracted, so we do add the advance here to reset the
1070  // run_x_offset. We do keep the record though so GetRectsForRange() can
1071  // find metrics for trailing spaces.
1072  if ((!run.is_ghost() || run.is_rtl()) && !run.is_placeholder_run()) {
1073  run_x_offset += layout.getAdvance();
1074  }
1075  } // for each in line_runs
1076 
1077  // Adjust the glyph positions based on the alignment of the line.
1078  double line_x_offset = GetLineXOffset(run_x_offset, justify_line);
1079  if (line_x_offset) {
1080  for (CodeUnitRun& code_unit_run : line_code_unit_runs) {
1081  code_unit_run.Shift(line_x_offset);
1082  }
1083  for (CodeUnitRun& code_unit_run :
1084  line_inline_placeholder_code_unit_runs) {
1085  code_unit_run.Shift(line_x_offset);
1086  }
1087  for (GlyphPosition& position : line_glyph_positions) {
1088  position.Shift(line_x_offset);
1089  }
1090  }
1091 
1092  size_t next_line_start = (line_number < line_metrics_.size() - 1)
1093  ? line_metrics_[line_number + 1].start_index
1094  : text_.size();
1095  glyph_lines_.emplace_back(std::move(line_glyph_positions),
1096  next_line_start - line_metrics.start_index);
1097  code_unit_runs_.insert(code_unit_runs_.end(), line_code_unit_runs.begin(),
1098  line_code_unit_runs.end());
1099  inline_placeholder_code_unit_runs_.insert(
1100  inline_placeholder_code_unit_runs_.end(),
1101  line_inline_placeholder_code_unit_runs.begin(),
1102  line_inline_placeholder_code_unit_runs.end());
1103 
1104  // Calculate the amount to advance in the y direction. This is done by
1105  // computing the maximum ascent and descent with respect to the strut.
1106  double max_ascent = strut_.ascent + strut_.half_leading;
1107  double max_descent = strut_.descent + strut_.half_leading;
1108  double max_unscaled_ascent = 0;
1109  for (const PaintRecord& paint_record : paint_records) {
1110  UpdateLineMetrics(paint_record.metrics(), paint_record.style(),
1111  max_ascent, max_descent, max_unscaled_ascent,
1112  paint_record.GetPlaceholderRun(), line_number,
1113  line_limit);
1114  }
1115 
1116  // If no fonts were actually rendered, then compute a baseline based on the
1117  // font of the paragraph style.
1118  if (paint_records.empty()) {
1119  SkFontMetrics metrics;
1120  TextStyle style(paragraph_style_.GetTextStyle());
1121  font.setTypeface(GetDefaultSkiaTypeface(style));
1122  font.setSize(style.font_size);
1123  font.getMetrics(&metrics);
1124  UpdateLineMetrics(metrics, style, max_ascent, max_descent,
1125  max_unscaled_ascent, nullptr, line_number, line_limit);
1126  }
1127 
1128  // Calculate the baselines. This is only done on the first line.
1129  if (line_number == 0) {
1130  alphabetic_baseline_ = max_ascent;
1131  // TODO(garyq): Ideographic baseline is currently bottom of EM
1132  // box, which is not correct. This should be obtained from metrics.
1133  // Skia currently does not support various baselines.
1134  ideographic_baseline_ = (max_ascent + max_descent);
1135  }
1136 
1137  line_metrics.height =
1138  (line_number == 0 ? 0 : line_metrics_[line_number - 1].height) +
1139  round(max_ascent + max_descent);
1140  line_metrics.baseline = line_metrics.height - max_descent;
1141 
1142  y_offset += round(max_ascent + prev_max_descent);
1143  prev_max_descent = max_descent;
1144 
1145  line_metrics.line_number = line_number;
1146  line_metrics.ascent = max_ascent;
1147  line_metrics.descent = max_descent;
1148  line_metrics.unscaled_ascent = max_unscaled_ascent;
1149  line_metrics.width = line_widths_[line_number];
1150  line_metrics.left = line_x_offset;
1151 
1152  final_line_count_++;
1153 
1154  for (PaintRecord& paint_record : paint_records) {
1155  paint_record.SetOffset(
1156  SkPoint::Make(paint_record.offset().x() + line_x_offset, y_offset));
1157  records_.emplace_back(std::move(paint_record));
1158  }
1159  } // for each line_number
1160 
1161  if (paragraph_style_.max_lines == 1 ||
1162  (paragraph_style_.unlimited_lines() && paragraph_style_.ellipsized())) {
1163  min_intrinsic_width_ = max_intrinsic_width_;
1164  } else {
1165  min_intrinsic_width_ = std::min(max_word_width, max_intrinsic_width_);
1166  }
1167 
1168  std::sort(code_unit_runs_.begin(), code_unit_runs_.end(),
1169  [](const CodeUnitRun& a, const CodeUnitRun& b) {
1170  return a.code_units.start < b.code_units.start;
1171  });
1172 
1173  longest_line_ = max_right_ - min_left_;
1174 }
1175 
1176 void ParagraphTxt::UpdateLineMetrics(const SkFontMetrics& metrics,
1177  const TextStyle& style,
1178  double& max_ascent,
1179  double& max_descent,
1180  double& max_unscaled_ascent,
1181  PlaceholderRun* placeholder_run,
1182  size_t line_number,
1183  size_t line_limit) {
1184  if (!strut_.force_strut) {
1185  double ascent;
1186  double descent;
1187  if (style.has_height_override) {
1188  // Scale the ascent and descent such that the sum of ascent and
1189  // descent is `fontsize * style.height * style.font_size`.
1190  //
1191  // The raw metrics do not add up to fontSize. The state of font
1192  // metrics is a mess:
1193  //
1194  // Each font has 4 sets of vertical metrics:
1195  //
1196  // * hhea: hheaAscender, hheaDescender, hheaLineGap.
1197  // Used by Apple.
1198  // * OS/2 typo: typoAscender, typoDescender, typoLineGap.
1199  // Used sometimes by Windows for layout.
1200  // * OS/2 win: winAscent, winDescent.
1201  // Also used by Windows, generally will be cut if extends past
1202  // these metrics.
1203  // * EM Square: ascent, descent
1204  // Not actively used, but this defines the 'scale' of the
1205  // units used.
1206  //
1207  // `Use Typo Metrics` is a boolean that, when enabled, prefers
1208  // typo metrics over win metrics. Default is off. Enabled by most
1209  // modern fonts.
1210  //
1211  // In addition to these different sets of metrics, there are also
1212  // multiple strategies for using these metrics:
1213  //
1214  // * Adobe: Set hhea values to typo equivalents.
1215  // * Microsoft: Set hhea values to win equivalents.
1216  // * Web: Use hhea values for text, regardless of `Use Typo Metrics`
1217  // The hheaLineGap is distributed half across the top and half
1218  // across the bottom of the line.
1219  // Exceptions:
1220  // Windows: All browsers respect `Use Typo Metrics`
1221  // Firefox respects `Use Typo Metrics`.
1222  //
1223  // This pertains to this code in that it is ambiguous which set of
1224  // metrics we are actually using via SkFontMetrics. This in turn
1225  // means that if we use the raw metrics, we will see differences
1226  // between platforms as well as unpredictable line heights.
1227  //
1228  // A more thorough explanation is available at
1229  // https://glyphsapp.com/tutorials/vertical-metrics
1230  //
1231  // Doing this ascent/descent normalization to the EM Square allows
1232  // a sane, consistent, and reasonable line height to be specified,
1233  // though it breaks with what is done by any of the platforms above.
1234  double metrics_height = -metrics.fAscent + metrics.fDescent;
1235  ascent =
1236  (-metrics.fAscent / metrics_height) * style.height * style.font_size;
1237  descent =
1238  (metrics.fDescent / metrics_height) * style.height * style.font_size;
1239  } else {
1240  // Use the font-provided ascent, descent, and leading directly.
1241  ascent = (-metrics.fAscent + metrics.fLeading / 2);
1242  descent = (metrics.fDescent + metrics.fLeading / 2);
1243  }
1244 
1245  // Account for text_height_behavior in paragraph_style_.
1246  //
1247  // Disable first line ascent modifications.
1248  if (line_number == 0 && paragraph_style_.text_height_behavior &
1250  ascent = -metrics.fAscent;
1251  }
1252  // Disable last line descent modifications.
1253  if (line_number == line_limit - 1 &&
1254  paragraph_style_.text_height_behavior &
1256  descent = metrics.fDescent;
1257  }
1258 
1259  ComputePlaceholder(placeholder_run, ascent, descent);
1260 
1261  max_ascent = std::max(ascent, max_ascent);
1262  max_descent = std::max(descent, max_descent);
1263  }
1264 
1265  max_unscaled_ascent =
1266  std::max(placeholder_run == nullptr ? -metrics.fAscent
1267  : placeholder_run->baseline_offset,
1268  max_unscaled_ascent);
1269 };
1270 
1271 double ParagraphTxt::GetLineXOffset(double line_total_advance,
1272  bool justify_line) {
1273  if (isinf(width_))
1274  return 0;
1275 
1276  TextAlign align = paragraph_style_.effective_align();
1277 
1278  if (align == TextAlign::right ||
1279  (align == TextAlign::justify &&
1280  paragraph_style_.text_direction == TextDirection::rtl &&
1281  !justify_line)) {
1282  return width_ - line_total_advance;
1283  } else if (align == TextAlign::center) {
1284  return (width_ - line_total_advance) / 2;
1285  } else {
1286  return 0;
1287  }
1288 }
1289 
1291  return paragraph_style_;
1292 }
1293 
1295  FML_DCHECK(!needs_layout_) << "only valid after layout";
1296  // Currently -fAscent
1297  return alphabetic_baseline_;
1298 }
1299 
1301  FML_DCHECK(!needs_layout_) << "only valid after layout";
1302  // TODO(garyq): Currently -fAscent + fUnderlinePosition. Verify this.
1303  return ideographic_baseline_;
1304 }
1305 
1307  FML_DCHECK(!needs_layout_) << "only valid after layout";
1308  return max_intrinsic_width_;
1309 }
1310 
1312  FML_DCHECK(!needs_layout_) << "only valid after layout";
1313  return min_intrinsic_width_;
1314 }
1315 
1316 size_t ParagraphTxt::TextSize() const {
1317  FML_DCHECK(!needs_layout_) << "only valid after layout";
1318  return text_.size();
1319 }
1320 
1322  FML_DCHECK(!needs_layout_) << "only valid after layout";
1323  return final_line_count_ == 0 ? 0
1324  : line_metrics_[final_line_count_ - 1].height;
1325 }
1326 
1328  FML_DCHECK(!needs_layout_) << "only valid after layout";
1329  return width_;
1330 }
1331 
1333  FML_DCHECK(!needs_layout_) << "only valid after layout";
1334  return longest_line_;
1335 }
1336 
1337 void ParagraphTxt::SetParagraphStyle(const ParagraphStyle& style) {
1338  needs_layout_ = true;
1339  paragraph_style_ = style;
1340 }
1341 
1342 void ParagraphTxt::SetFontCollection(
1343  std::shared_ptr<FontCollection> font_collection) {
1344  font_collection_ = std::move(font_collection);
1345 }
1346 
1347 std::shared_ptr<minikin::FontCollection>
1348 ParagraphTxt::GetMinikinFontCollectionForStyle(const TextStyle& style) {
1349  std::string locale;
1350  if (!style.locale.empty()) {
1351  uint32_t language_list_id =
1353  const minikin::FontLanguages& langs =
1354  minikin::FontLanguageListCache::getById(language_list_id);
1355  if (langs.size()) {
1356  locale = langs[0].getString();
1357  }
1358  }
1359 
1360  return font_collection_->GetMinikinFontCollectionForFamilies(
1361  style.font_families, locale);
1362 }
1363 
1364 sk_sp<SkTypeface> ParagraphTxt::GetDefaultSkiaTypeface(const TextStyle& style) {
1365  std::shared_ptr<minikin::FontCollection> collection =
1366  GetMinikinFontCollectionForStyle(style);
1367  if (!collection) {
1368  return nullptr;
1369  }
1370  minikin::FakedFont faked_font =
1371  collection->baseFontFaked(GetMinikinFontStyle(style));
1372  return static_cast<FontSkia*>(faked_font.font)->GetSkTypeface();
1373 }
1374 
1375 // The x,y coordinates will be the very top left corner of the rendered
1376 // paragraph.
1377 void ParagraphTxt::Paint(SkCanvas* canvas, double x, double y) {
1378  SkPoint base_offset = SkPoint::Make(x, y);
1379  SkPaint paint;
1380  // Paint the background first before painting any text to prevent
1381  // potential overlap.
1382  for (const PaintRecord& record : records_) {
1383  PaintBackground(canvas, record, base_offset);
1384  }
1385  for (const PaintRecord& record : records_) {
1386  if (record.style().has_foreground) {
1387  paint = record.style().foreground;
1388  } else {
1389  paint.reset();
1390  paint.setColor(record.style().color);
1391  }
1392  SkPoint offset = base_offset + record.offset();
1393  if (record.GetPlaceholderRun() == nullptr) {
1394  PaintShadow(canvas, record, offset);
1395  canvas->drawTextBlob(record.text(), offset.x(), offset.y(), paint);
1396  }
1397  PaintDecorations(canvas, record, base_offset);
1398  }
1399 }
1400 
1401 void ParagraphTxt::PaintDecorations(SkCanvas* canvas,
1402  const PaintRecord& record,
1403  SkPoint base_offset) {
1404  if (record.style().decoration == TextDecoration::kNone)
1405  return;
1406 
1407  if (record.isGhost())
1408  return;
1409 
1410  const SkFontMetrics& metrics = record.metrics();
1411  SkPaint paint;
1412  paint.setStyle(SkPaint::kStroke_Style);
1413  if (record.style().decoration_color == SK_ColorTRANSPARENT) {
1414  paint.setColor(record.style().color);
1415  } else {
1416  paint.setColor(record.style().decoration_color);
1417  }
1418  paint.setAntiAlias(true);
1419 
1420  // This is set to 2 for the double line style
1421  int decoration_count = 1;
1422 
1423  // Filled when drawing wavy decorations.
1424  SkPath path;
1425 
1426  double width = record.GetRunWidth();
1427 
1428  SkScalar underline_thickness;
1429  if ((metrics.fFlags &
1430  SkFontMetrics::FontMetricsFlags::kUnderlineThicknessIsValid_Flag) &&
1431  metrics.fUnderlineThickness > 0) {
1432  underline_thickness = metrics.fUnderlineThickness;
1433  } else {
1434  // Backup value if the fUnderlineThickness metric is not available:
1435  // Divide by 14pt as it is the default size.
1436  underline_thickness = record.style().font_size / 14.0f;
1437  }
1438  paint.setStrokeWidth(underline_thickness *
1440 
1441  SkPoint record_offset = base_offset + record.offset();
1442  SkScalar x = record_offset.x() + record.x_start();
1443  SkScalar y = record_offset.y();
1444 
1445  // Setup the decorations.
1446  switch (record.style().decoration_style) {
1448  break;
1449  }
1451  decoration_count = 2;
1452  break;
1453  }
1454  // Note: the intervals are scaled by the thickness of the line, so it is
1455  // possible to change spacing by changing the decoration_thickness
1456  // property of TextStyle.
1458  // Divide by 14pt as it is the default size.
1459  const float scale = record.style().font_size / 14.0f;
1460  const SkScalar intervals[] = {1.0f * scale, 1.5f * scale, 1.0f * scale,
1461  1.5f * scale};
1462  size_t count = sizeof(intervals) / sizeof(intervals[0]);
1463  paint.setPathEffect(SkPathEffect::MakeCompose(
1464  SkDashPathEffect::Make(intervals, count, 0.0f),
1465  SkDiscretePathEffect::Make(0, 0)));
1466  break;
1467  }
1468  // Note: the intervals are scaled by the thickness of the line, so it is
1469  // possible to change spacing by changing the decoration_thickness
1470  // property of TextStyle.
1472  // Divide by 14pt as it is the default size.
1473  const float scale = record.style().font_size / 14.0f;
1474  const SkScalar intervals[] = {4.0f * scale, 2.0f * scale, 4.0f * scale,
1475  2.0f * scale};
1476  size_t count = sizeof(intervals) / sizeof(intervals[0]);
1477  paint.setPathEffect(SkPathEffect::MakeCompose(
1478  SkDashPathEffect::Make(intervals, count, 0.0f),
1479  SkDiscretePathEffect::Make(0, 0)));
1480  break;
1481  }
1483  ComputeWavyDecoration(
1484  path, x, y, width,
1485  underline_thickness * record.style().decoration_thickness_multiplier);
1486  break;
1487  }
1488  }
1489 
1490  // Draw the decorations.
1491  // Use a for loop for "kDouble" decoration style
1492  for (int i = 0; i < decoration_count; i++) {
1493  double y_offset = i * underline_thickness * kDoubleDecorationSpacing;
1494  double y_offset_original = y_offset;
1495  // Underline
1496  if (record.style().decoration & TextDecoration::kUnderline) {
1497  y_offset +=
1498  (metrics.fFlags &
1499  SkFontMetrics::FontMetricsFlags::kUnderlinePositionIsValid_Flag)
1500  ? metrics.fUnderlinePosition
1501  : underline_thickness;
1503  canvas->drawLine(x, y + y_offset, x + width, y + y_offset, paint);
1504  } else {
1505  SkPath offsetPath = path;
1506  offsetPath.offset(0, y_offset);
1507  canvas->drawPath(offsetPath, paint);
1508  }
1509  y_offset = y_offset_original;
1510  }
1511  // Overline
1512  if (record.style().decoration & TextDecoration::kOverline) {
1513  // We subtract fAscent here because for double overlines, we want the
1514  // second line to be above, not below the first.
1515  y_offset -= metrics.fAscent;
1517  canvas->drawLine(x, y - y_offset, x + width, y - y_offset, paint);
1518  } else {
1519  SkPath offsetPath = path;
1520  offsetPath.offset(0, -y_offset);
1521  canvas->drawPath(offsetPath, paint);
1522  }
1523  y_offset = y_offset_original;
1524  }
1525  // Strikethrough
1527  if (metrics.fFlags &
1528  SkFontMetrics::FontMetricsFlags::kStrikeoutThicknessIsValid_Flag)
1529  paint.setStrokeWidth(metrics.fStrikeoutThickness *
1531  // Make sure the double line is "centered" vertically.
1532  y_offset += (decoration_count - 1.0) * underline_thickness *
1533  kDoubleDecorationSpacing / -2.0;
1534  y_offset +=
1535  (metrics.fFlags &
1536  SkFontMetrics::FontMetricsFlags::kStrikeoutPositionIsValid_Flag)
1537  ? metrics.fStrikeoutPosition
1538  // Backup value if the strikeoutposition metric is not
1539  // available:
1540  : metrics.fXHeight / -2.0;
1542  canvas->drawLine(x, y + y_offset, x + width, y + y_offset, paint);
1543  } else {
1544  SkPath offsetPath = path;
1545  offsetPath.offset(0, y_offset);
1546  canvas->drawPath(offsetPath, paint);
1547  }
1548  y_offset = y_offset_original;
1549  }
1550  }
1551 }
1552 
1553 void ParagraphTxt::ComputeWavyDecoration(SkPath& path,
1554  double x,
1555  double y,
1556  double width,
1557  double thickness) {
1558  int wave_count = 0;
1559  double x_start = 0;
1560  // One full wavelength is 4 * thickness.
1561  double quarter = thickness;
1562  path.moveTo(x, y);
1563  double remaining = width;
1564  while (x_start + (quarter * 2) < width) {
1565  path.rQuadTo(quarter, wave_count % 2 == 0 ? -quarter : quarter, quarter * 2,
1566  0);
1567  x_start += quarter * 2;
1568  remaining = width - x_start;
1569  ++wave_count;
1570  }
1571  // Manually add a final partial quad for the remaining width that do
1572  // not fit nicely into a half-wavelength.
1573  // The following math is based off of quadratic bezier equations:
1574  //
1575  // * Let P(x) be the equation for the curve.
1576  // * Let P0 = start, P1 = control point, P2 = end
1577  // * P(x) = -2x^2 - 2x
1578  // * P0 = (0, 0)
1579  // * P1 = 2P(0.5) - 0.5 * P0 - 0.5 * P2
1580  // * P2 = P(remaining / (wavelength / 2))
1581  //
1582  // Simplified implementation coursesy of @jim-flar at
1583  // https://github.com/flutter/engine/pull/9468#discussion_r297872739
1584  // Unsimplified original version at
1585  // https://github.com/flutter/engine/pull/9468#discussion_r297879129
1586 
1587  double x1 = remaining / 2;
1588  double y1 = remaining / 2 * (wave_count % 2 == 0 ? -1 : 1);
1589  double x2 = remaining;
1590  double y2 = (remaining - remaining * remaining / (quarter * 2)) *
1591  (wave_count % 2 == 0 ? -1 : 1);
1592  path.rQuadTo(x1, y1, x2, y2);
1593 }
1594 
1595 void ParagraphTxt::PaintBackground(SkCanvas* canvas,
1596  const PaintRecord& record,
1597  SkPoint base_offset) {
1598  if (!record.style().has_background)
1599  return;
1600 
1601  const SkFontMetrics& metrics = record.metrics();
1602  SkRect rect(SkRect::MakeLTRB(record.x_start(), metrics.fAscent,
1603  record.x_end(), metrics.fDescent));
1604  rect.offset(base_offset + record.offset());
1605  canvas->drawRect(rect, record.style().background);
1606 }
1607 
1608 void ParagraphTxt::PaintShadow(SkCanvas* canvas,
1609  const PaintRecord& record,
1610  SkPoint offset) {
1611  if (record.style().text_shadows.size() == 0)
1612  return;
1613  for (TextShadow text_shadow : record.style().text_shadows) {
1614  if (!text_shadow.hasShadow()) {
1615  continue;
1616  }
1617 
1618  SkPaint paint;
1619  paint.setColor(text_shadow.color);
1620  if (text_shadow.blur_radius != 0.0) {
1621  paint.setMaskFilter(SkMaskFilter::MakeBlur(
1622  kNormal_SkBlurStyle, text_shadow.blur_radius, false));
1623  }
1624  canvas->drawTextBlob(record.text(), offset.x() + text_shadow.offset.x(),
1625  offset.y() + text_shadow.offset.y(), paint);
1626  }
1627 }
1628 
1629 std::vector<Paragraph::TextBox> ParagraphTxt::GetRectsForRange(
1630  size_t start,
1631  size_t end,
1632  RectHeightStyle rect_height_style,
1633  RectWidthStyle rect_width_style) {
1634  FML_DCHECK(!needs_layout_) << "only valid after layout";
1635  // Struct that holds calculated metrics for each line.
1636  struct LineBoxMetrics {
1637  std::vector<Paragraph::TextBox> boxes;
1638  // Per-line metrics for max and min coordinates for left and right boxes.
1639  // These metrics cannot be calculated in layout generically because of
1640  // selections that do not cover the whole line.
1641  SkScalar max_right = FLT_MIN;
1642  SkScalar min_left = FLT_MAX;
1643  };
1644 
1645  std::map<size_t, LineBoxMetrics> line_box_metrics;
1646  // Text direction of the first line so we can extend the correct side for
1647  // RectWidthStyle::kMax.
1648  TextDirection first_line_dir = TextDirection::ltr;
1649  std::map<size_t, size_t> newline_x_positions;
1650 
1651  // Lines that are actually in the requested range.
1652  size_t max_line = 0;
1653  size_t min_line = INT_MAX;
1654  size_t glyph_length = 0;
1655 
1656  // Generate initial boxes and calculate metrics.
1657  for (const CodeUnitRun& run : code_unit_runs_) {
1658  // Check to see if we are finished.
1659  if (run.code_units.start >= end)
1660  break;
1661 
1662  // Update new line x position with the ending of last bidi run on the line
1663  newline_x_positions[run.line_number] =
1664  run.direction == TextDirection::ltr ? run.x_pos.end : run.x_pos.start;
1665 
1666  if (run.code_units.end <= start)
1667  continue;
1668 
1669  double baseline = line_metrics_[run.line_number].baseline;
1670  SkScalar top = baseline + run.font_metrics.fAscent;
1671  SkScalar bottom = baseline + run.font_metrics.fDescent;
1672 
1673  if (run.placeholder_run !=
1674  nullptr) { // Use inline placeholder size as height.
1675  top = baseline - run.placeholder_run->baseline_offset;
1676  bottom = baseline + run.placeholder_run->height -
1677  run.placeholder_run->baseline_offset;
1678  }
1679 
1680  max_line = std::max(run.line_number, max_line);
1681  min_line = std::min(run.line_number, min_line);
1682 
1683  // Calculate left and right.
1684  SkScalar left, right;
1685  if (run.code_units.start >= start && run.code_units.end <= end) {
1686  left = run.x_pos.start;
1687  right = run.x_pos.end;
1688  } else {
1689  left = SK_ScalarMax;
1690  right = SK_ScalarMin;
1691  for (const GlyphPosition& gp : run.positions) {
1692  if (gp.code_units.start >= start && gp.code_units.end <= end) {
1693  left = std::min(left, static_cast<SkScalar>(gp.x_pos.start));
1694  right = std::max(right, static_cast<SkScalar>(gp.x_pos.end));
1695  } else if (gp.code_units.end == end) {
1696  // Calculate left and right when we are at
1697  // the last position of a combining character.
1698  glyph_length = (gp.code_units.end - gp.code_units.start) - 1;
1699  if (gp.code_units.start ==
1700  std::max<size_t>(0, (start - glyph_length))) {
1701  left = std::min(left, static_cast<SkScalar>(gp.x_pos.start));
1702  right = std::max(right, static_cast<SkScalar>(gp.x_pos.end));
1703  }
1704  }
1705  }
1706  if (left == SK_ScalarMax || right == SK_ScalarMin)
1707  continue;
1708  }
1709  // Keep track of the min and max horizontal coordinates over all lines. Not
1710  // needed for kTight.
1711  if (rect_width_style == RectWidthStyle::kMax) {
1712  line_box_metrics[run.line_number].max_right =
1713  std::max(line_box_metrics[run.line_number].max_right, right);
1714  line_box_metrics[run.line_number].min_left =
1715  std::min(line_box_metrics[run.line_number].min_left, left);
1716  if (min_line == run.line_number) {
1717  first_line_dir = run.direction;
1718  }
1719  }
1720  line_box_metrics[run.line_number].boxes.emplace_back(
1721  SkRect::MakeLTRB(left, top, right, bottom), run.direction);
1722  }
1723 
1724  // Add empty rectangles representing any newline characters within the
1725  // range.
1726  for (size_t line_number = 0; line_number < line_metrics_.size();
1727  ++line_number) {
1728  LineMetrics& line = line_metrics_[line_number];
1729  if (line.start_index >= end)
1730  break;
1731  if (line.end_including_newline <= start)
1732  continue;
1733  if (line_box_metrics.find(line_number) == line_box_metrics.end()) {
1734  if (line.end_index != line.end_including_newline &&
1735  line.end_index >= start && line.end_including_newline <= end) {
1736  SkScalar x;
1737  auto it = newline_x_positions.find(line_number);
1738  if (it != newline_x_positions.end()) {
1739  x = it->second;
1740  } else {
1741  x = GetLineXOffset(0, false);
1742  }
1743  SkScalar top =
1744  (line_number > 0) ? line_metrics_[line_number - 1].height : 0;
1745  SkScalar bottom = line_metrics_[line_number].height;
1746  line_box_metrics[line_number].boxes.emplace_back(
1747  SkRect::MakeLTRB(x, top, x, bottom), TextDirection::ltr);
1748  }
1749  }
1750  }
1751 
1752  // "Post-process" metrics and aggregate final rects to return.
1753  std::vector<Paragraph::TextBox> boxes;
1754  for (const auto& kv : line_box_metrics) {
1755  // Handle rect_width_styles. We skip the last line because not everything is
1756  // selected.
1757 
1758  LineMetrics& line =
1759  line_metrics_[fmin(line_metrics_.size() - 1, fmax(0, kv.first))];
1760  if (rect_width_style == RectWidthStyle::kMax && kv.first != max_line) {
1761  if (line_box_metrics[kv.first].min_left > min_left_ &&
1762  (kv.first != min_line || first_line_dir == TextDirection::rtl)) {
1763  line_box_metrics[kv.first].boxes.emplace_back(
1764  SkRect::MakeLTRB(min_left_, line.baseline - line.unscaled_ascent,
1765  line_box_metrics[kv.first].min_left,
1766  line.baseline + line.descent),
1768  }
1769  if (line_box_metrics[kv.first].max_right < max_right_ &&
1770  (kv.first != min_line || first_line_dir == TextDirection::ltr)) {
1771  line_box_metrics[kv.first].boxes.emplace_back(
1772  SkRect::MakeLTRB(line_box_metrics[kv.first].max_right,
1773  line.baseline - line.unscaled_ascent, max_right_,
1774  line.baseline + line.descent),
1776  }
1777  }
1778 
1779  // Handle rect_height_styles. The height metrics used are all positive to
1780  // make the signage clear here.
1781  if (rect_height_style == RectHeightStyle::kTight) {
1782  // Ignore line max height and width and generate tight bounds.
1783  boxes.insert(boxes.end(), kv.second.boxes.begin(), kv.second.boxes.end());
1784  } else if (rect_height_style == RectHeightStyle::kMax) {
1785  for (const Paragraph::TextBox& box : kv.second.boxes) {
1786  boxes.emplace_back(
1787  SkRect::MakeLTRB(box.rect.fLeft, line.baseline - line.ascent,
1788  box.rect.fRight, line.baseline + line.descent),
1789  box.direction);
1790  }
1791  } else if (rect_height_style ==
1793  SkScalar adjusted_bottom = line.baseline + line.descent;
1794  if (kv.first < line_metrics_.size() - 1) {
1795  adjusted_bottom += (line_metrics_[kv.first + 1].ascent -
1796  line_metrics_[kv.first + 1].unscaled_ascent) /
1797  2;
1798  }
1799  SkScalar adjusted_top = line.baseline - line.unscaled_ascent;
1800  if (kv.first != 0) {
1801  adjusted_top -= (line.ascent - line.unscaled_ascent) / 2;
1802  }
1803  for (const Paragraph::TextBox& box : kv.second.boxes) {
1804  boxes.emplace_back(SkRect::MakeLTRB(box.rect.fLeft, adjusted_top,
1805  box.rect.fRight, adjusted_bottom),
1806  box.direction);
1807  }
1808  } else if (rect_height_style == RectHeightStyle::kIncludeLineSpacingTop) {
1809  for (const Paragraph::TextBox& box : kv.second.boxes) {
1810  SkScalar adjusted_top = kv.first == 0
1811  ? line.baseline - line.unscaled_ascent
1812  : line.baseline - line.ascent;
1813  boxes.emplace_back(
1814  SkRect::MakeLTRB(box.rect.fLeft, adjusted_top, box.rect.fRight,
1815  line.baseline + line.descent),
1816  box.direction);
1817  }
1818  } else if (rect_height_style ==
1820  for (const Paragraph::TextBox& box : kv.second.boxes) {
1821  SkScalar adjusted_bottom = line.baseline + line.descent;
1822  if (kv.first < line_metrics_.size() - 1) {
1823  adjusted_bottom += -line.unscaled_ascent + line.ascent;
1824  }
1825  boxes.emplace_back(
1826  SkRect::MakeLTRB(box.rect.fLeft,
1827  line.baseline - line.unscaled_ascent,
1828  box.rect.fRight, adjusted_bottom),
1829  box.direction);
1830  }
1831  } else if (rect_height_style == RectHeightStyle::kStrut) {
1832  if (IsStrutValid()) {
1833  for (const Paragraph::TextBox& box : kv.second.boxes) {
1834  boxes.emplace_back(
1835  SkRect::MakeLTRB(box.rect.fLeft, line.baseline - strut_.ascent,
1836  box.rect.fRight, line.baseline + strut_.descent),
1837  box.direction);
1838  }
1839  } else {
1840  // Fall back to tight bounds if the strut is invalid.
1841  boxes.insert(boxes.end(), kv.second.boxes.begin(),
1842  kv.second.boxes.end());
1843  }
1844  }
1845  }
1846  return boxes;
1847 }
1848 
1850  double dx,
1851  double dy) {
1852  FML_DCHECK(!needs_layout_) << "only valid after layout";
1853  if (final_line_count_ <= 0)
1854  return PositionWithAffinity(0, DOWNSTREAM);
1855 
1856  size_t y_index;
1857  for (y_index = 0; y_index < final_line_count_ - 1; ++y_index) {
1858  if (dy < line_metrics_[y_index].height)
1859  break;
1860  }
1861 
1862  const std::vector<GlyphPosition>& line_glyph_position =
1863  glyph_lines_[y_index].positions;
1864  if (line_glyph_position.empty()) {
1865  int line_start_index =
1866  std::accumulate(glyph_lines_.begin(), glyph_lines_.begin() + y_index, 0,
1867  [](const int a, const GlyphLine& b) {
1868  return a + static_cast<int>(b.total_code_units);
1869  });
1870  return PositionWithAffinity(line_start_index, DOWNSTREAM);
1871  }
1872 
1873  size_t x_index;
1874  const GlyphPosition* gp = nullptr;
1875  for (x_index = 0; x_index < line_glyph_position.size(); ++x_index) {
1876  double glyph_end = (x_index < line_glyph_position.size() - 1)
1877  ? line_glyph_position[x_index + 1].x_pos.start
1878  : line_glyph_position[x_index].x_pos.end;
1879  if (dx < glyph_end) {
1880  gp = &line_glyph_position[x_index];
1881  break;
1882  }
1883  }
1884 
1885  if (gp == nullptr) {
1886  const GlyphPosition& last_glyph = line_glyph_position.back();
1887  return PositionWithAffinity(last_glyph.code_units.end, UPSTREAM);
1888  }
1889 
1890  // Find the direction of the run that contains this glyph.
1891  TextDirection direction = TextDirection::ltr;
1892  for (const CodeUnitRun& run : code_unit_runs_) {
1893  if (gp->code_units.start >= run.code_units.start &&
1894  gp->code_units.end <= run.code_units.end) {
1895  direction = run.direction;
1896  break;
1897  }
1898  }
1899 
1900  double glyph_center = (gp->x_pos.start + gp->x_pos.end) / 2;
1901  if ((direction == TextDirection::ltr && dx < glyph_center) ||
1902  (direction == TextDirection::rtl && dx >= glyph_center)) {
1903  return PositionWithAffinity(gp->code_units.start, DOWNSTREAM);
1904  } else {
1905  return PositionWithAffinity(gp->code_units.end, UPSTREAM);
1906  }
1907 }
1908 
1909 // We don't cache this because since this returns all boxes, it is usually
1910 // unnecessary to call this multiple times in succession.
1911 std::vector<Paragraph::TextBox> ParagraphTxt::GetRectsForPlaceholders() {
1912  FML_DCHECK(!needs_layout_) << "only valid after layout";
1913  // Struct that holds calculated metrics for each line.
1914  struct LineBoxMetrics {
1915  std::vector<Paragraph::TextBox> boxes;
1916  // Per-line metrics for max and min coordinates for left and right boxes.
1917  // These metrics cannot be calculated in layout generically because of
1918  // selections that do not cover the whole line.
1919  SkScalar max_right = FLT_MIN;
1920  SkScalar min_left = FLT_MAX;
1921  };
1922 
1923  std::vector<Paragraph::TextBox> boxes;
1924 
1925  // Generate initial boxes and calculate metrics.
1926  for (const CodeUnitRun& run : inline_placeholder_code_unit_runs_) {
1927  // Check to see if we are finished.
1928  double baseline = line_metrics_[run.line_number].baseline;
1929  SkScalar top = baseline + run.font_metrics.fAscent;
1930  SkScalar bottom = baseline + run.font_metrics.fDescent;
1931 
1932  if (run.placeholder_run !=
1933  nullptr) { // Use inline placeholder size as height.
1934  top = baseline - run.placeholder_run->baseline_offset;
1935  bottom = baseline + run.placeholder_run->height -
1936  run.placeholder_run->baseline_offset;
1937  }
1938 
1939  // Calculate left and right.
1940  SkScalar left, right;
1941  left = run.x_pos.start;
1942  right = run.x_pos.end;
1943 
1944  boxes.emplace_back(SkRect::MakeLTRB(left, top, right, bottom),
1945  run.direction);
1946  }
1947  return boxes;
1948 }
1949 
1951  FML_DCHECK(!needs_layout_) << "only valid after layout";
1952  if (text_.size() == 0)
1953  return Range<size_t>(0, 0);
1954 
1955  if (!word_breaker_) {
1956  UErrorCode status = U_ZERO_ERROR;
1957  word_breaker_.reset(
1958  icu::BreakIterator::createWordInstance(icu::Locale(), status));
1959  if (!U_SUCCESS(status))
1960  return Range<size_t>(0, 0);
1961  }
1962 
1963  word_breaker_->setText(icu::UnicodeString(false, text_.data(), text_.size()));
1964 
1965  int32_t prev_boundary = word_breaker_->preceding(offset + 1);
1966  int32_t next_boundary = word_breaker_->next();
1967  if (prev_boundary == icu::BreakIterator::DONE)
1968  prev_boundary = offset;
1969  if (next_boundary == icu::BreakIterator::DONE)
1970  next_boundary = offset;
1971  return Range<size_t>(prev_boundary, next_boundary);
1972 }
1973 
1975  FML_DCHECK(!needs_layout_) << "only valid after layout";
1976  return final_line_count_;
1977 }
1978 
1980  FML_DCHECK(!needs_layout_) << "only valid after layout";
1981  return did_exceed_max_lines_;
1982 }
1983 
1984 void ParagraphTxt::SetDirty(bool dirty) {
1985  needs_layout_ = dirty;
1986 }
1987 
1988 std::vector<LineMetrics>& ParagraphTxt::GetLineMetrics() {
1989  FML_DCHECK(!needs_layout_) << "only valid after layout";
1990  return line_metrics_;
1991 }
1992 
1993 } // namespace txt
double GetAlphabeticBaseline() override
bool has_background
Definition: text_style.h:56
FontStyle
Definition: font_style.h:22
DEF_SWITCHES_START snapshot asset path
Definition: switches.h:32
const int * getBreaks() const
Definition: LineBreaker.h:159
std::vector< Paragraph::TextBox > GetRectsForPlaceholders() override
const TextStyle & style
Definition: styled_runs.h:34
std::vector< std::string > strut_font_families
std::vector< TextShadow > text_shadows
Definition: text_style.h:62
size_t TextSize() const
#define FML_DCHECK(condition)
Definition: logging.h:86
static bool isGraphemeBreak(const float *advances, const uint16_t *buf, size_t start, size_t count, size_t offset)
double GetMaxWidth() override
bool isGhost() const
Definition: paint_record.h:87
bool has_height_override
Definition: text_style.h:54
void setCustomCharWidth(size_t offset, float width)
TextStyle GetTextStyle() const
double height
Definition: text_style.h:53
double decoration_thickness_multiplier
Definition: text_style.h:43
Definition: ref_ptr.h:252
FontWeight strut_font_weight
FontWeight
Definition: font_weight.h:22
virtual ~ParagraphTxt()
std::string fontFeatureSettings
Definition: MinikinFont.h:60
void resize(size_t size)
Definition: LineBreaker.h:102
float getAdvance() const
bool ellipsized() const
std::string locale
Definition: text_style.h:55
double GetMinIntrinsicWidth() override
uint16_t * buffer()
Definition: LineBreaker.h:109
const MinikinFont * getFont(int i) const
double font_size
Definition: text_style.h:50
#define FML_LOG(severity)
Definition: logging.h:65
std::u16string ellipsis
bool operator!=(C p1, const scoped_nsprotocol< C > &p2)
size_t size() const
Definition: FontLanguage.h:140
TextAlign effective_align() const
size_t end_excluding_whitespace
Definition: line_metrics.h:34
float getCharAdvance(size_t i) const
Definition: Layout.h:113
void setLineWidths(float firstWidth, int firstWidthLineCount, float restWidth)
void setStrategy(BreakStrategy strategy)
Definition: LineBreaker.h:124
size_t nGlyphs() const
virtual void Layout(double width) override
void swap(scoped_nsprotocol< C > &p1, scoped_nsprotocol< C > &p2)
double GetLongestLine() override
std::vector< TextBox > GetRectsForRange(size_t start, size_t end, RectHeightStyle rect_height_style, RectWidthStyle rect_width_style) override
double GetHeight() override
double x_end() const
Definition: paint_record.h:82
FontStyle strut_font_style
Run GetRun(size_t index) const
Definition: styled_runs.cc:71
FontFakery getFakery(int i) const
double GetRunWidth() const
Definition: paint_record.h:83
unsigned int getGlyphId(int i) const
float getY(int i) const
size_t size() const
Definition: styled_runs.h:59
bool isWordSpace(uint16_t code_unit)
SkColor decoration_color
Definition: text_style.h:40
TextDirection direction
Definition: paragraph.h:81
float addStyleRun(MinikinPaint *paint, const std::shared_ptr< FontCollection > &typeface, FontStyle style, size_t start, size_t end, bool isRtl)
TextDecorationStyle decoration_style
Definition: text_style.h:41
std::vector< std::string > font_families
Definition: text_style.h:49
const ParagraphStyle & GetParagraphStyle() const
const SkFontMetrics & metrics() const
Definition: paint_record.h:75
void setJustified(bool justified)
Definition: LineBreaker.h:126
int32_t height
bool operator==(const MockTexture::PaintCall &a, const MockTexture::PaintCall &b)
Definition: mock_texture.cc:21
SkColor color
Definition: text_style.h:36
const int objReplacementChar
Definition: paragraph_txt.h:48
std::map< size_t, RunMetrics > run_metrics
Definition: line_metrics.h:68
int32_t width
float getX(int i) const
void setLocale(const icu::Locale &locale, Hyphenator *hyphenator)
void doLayout(const uint16_t *buf, size_t start, size_t count, size_t bufSize, bool isRtl, const FontStyle &style, const MinikinPaint &paint, const std::shared_ptr< FontCollection > &collection)
bool DidExceedMaxLines() override
double GetIdeographicBaseline() override
double GetMaxIntrinsicWidth() override
PlaceholderAlignment alignment
std::vector< LineMetrics > & GetLineMetrics() override
Match the baseline of the placeholder with the baseline.
double x_start() const
Definition: paint_record.h:81
const float * getWidths() const
Definition: LineBreaker.h:161
virtual void Paint(SkCanvas *canvas, double x, double y) override
if(event->type==GDK_BUTTON_PRESS)
Definition: fl_view.cc:79
static uint32_t registerLanguageList(const std::string &languages)
SkPoint offset() const
Definition: paint_record.h:69
uint32_t getGlyphCluster(int i) const
SkPaint background
Definition: text_style.h:57
bool unlimited_lines() const
PositionWithAffinity GetGlyphPositionAtCoordinate(double dx, double dy) override
static const FontLanguages & getById(uint32_t id)
TextDirection text_direction
minikin::BreakStrategy break_strategy
static float measureText(const uint16_t *buf, size_t start, size_t count, size_t bufSize, bool isRtl, const FontStyle &style, const MinikinPaint &paint, const std::shared_ptr< FontCollection > &collection, float *advances)
Range< size_t > GetWordBoundary(size_t offset) override
void SetDirty(bool dirty=true)
MinikinFont * font
Definition: FontFamily.h:108
void getAdvances(float *advances)
size_t end_including_newline
Definition: line_metrics.h:35
TextBaseline baseline
double unscaled_ascent
Definition: line_metrics.h:50
SkTextBlob * text() const
Definition: paint_record.h:73
const TextStyle & style() const
Definition: paint_record.h:77
static const float kDoubleDecorationSpacing
bool isLineEndSpace(uint16_t c)