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