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