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: |
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 | |
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. |
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 | |
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 | |
129 | int GetWeight(const TextStyle& style) { |
130 | return GetWeight(style.font_weight); |
131 | } |
132 | |
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 | |
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 | |
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 | |
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 | |
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 | |
202 | void ParagraphTxt::GlyphPosition::Shift(double delta) { |
203 | x_pos.Shift(delta); |
204 | } |
205 | |
206 | ParagraphTxt::GlyphLine::GlyphLine(std::vector<GlyphPosition>&& p, size_t tcu) |
207 | : positions(std::move(p)), total_code_units(tcu) {} |
208 | |
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 | 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 | |
226 | void ParagraphTxt::CodeUnitRun::Shift(double delta) { |
227 | x_pos.Shift(delta); |
228 | for (GlyphPosition& position : positions) |
229 | position.Shift(delta); |
230 | } |
231 | |
232 | ParagraphTxt::ParagraphTxt() { |
233 | breaker_.setLocale(icu::Locale(), nullptr); |
234 | } |
235 | |
236 | ParagraphTxt::~ParagraphTxt() = default; |
237 | |
238 | void 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 | |
246 | void 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 | |
254 | bool 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 | |
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 | |
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 | |
515 | bool ParagraphTxt::IsStrutValid() const { |
516 | // Font size must be positive. |
517 | return (paragraph_style_.strut_enabled && |
518 | paragraph_style_.strut_font_size >= 0); |
519 | } |
520 | |
521 | void 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 | |
586 | void 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 |
660 | void 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 | |
1179 | void 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 | |
1274 | double 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 | |
1293 | const ParagraphStyle& ParagraphTxt::GetParagraphStyle() const { |
1294 | return paragraph_style_; |
1295 | } |
1296 | |
1297 | double ParagraphTxt::GetAlphabeticBaseline() { |
1298 | FML_DCHECK(!needs_layout_) << "only valid after layout" ; |
1299 | // Currently -fAscent |
1300 | return alphabetic_baseline_; |
1301 | } |
1302 | |
1303 | double 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 | |
1309 | double ParagraphTxt::GetMaxIntrinsicWidth() { |
1310 | FML_DCHECK(!needs_layout_) << "only valid after layout" ; |
1311 | return max_intrinsic_width_; |
1312 | } |
1313 | |
1314 | double ParagraphTxt::GetMinIntrinsicWidth() { |
1315 | FML_DCHECK(!needs_layout_) << "only valid after layout" ; |
1316 | return min_intrinsic_width_; |
1317 | } |
1318 | |
1319 | size_t ParagraphTxt::TextSize() const { |
1320 | FML_DCHECK(!needs_layout_) << "only valid after layout" ; |
1321 | return text_.size(); |
1322 | } |
1323 | |
1324 | double 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 | |
1330 | double ParagraphTxt::GetMaxWidth() { |
1331 | FML_DCHECK(!needs_layout_) << "only valid after layout" ; |
1332 | return width_; |
1333 | } |
1334 | |
1335 | double ParagraphTxt::GetLongestLine() { |
1336 | FML_DCHECK(!needs_layout_) << "only valid after layout" ; |
1337 | return longest_line_; |
1338 | } |
1339 | |
1340 | void ParagraphTxt::SetParagraphStyle(const ParagraphStyle& style) { |
1341 | needs_layout_ = true; |
1342 | paragraph_style_ = style; |
1343 | } |
1344 | |
1345 | void ParagraphTxt::SetFontCollection( |
1346 | std::shared_ptr<FontCollection> font_collection) { |
1347 | font_collection_ = std::move(font_collection); |
1348 | } |
1349 | |
1350 | std::shared_ptr<minikin::FontCollection> |
1351 | ParagraphTxt::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 | |
1367 | sk_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. |
1380 | void 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 | |
1404 | void 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 | |
1556 | void 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 | |
1598 | void 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 | |
1611 | void 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 | |
1632 | std::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 | |
1852 | Paragraph::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. |
1935 | std::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 | |
1974 | Paragraph::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 | |
1998 | size_t ParagraphTxt::GetLineCount() { |
1999 | FML_DCHECK(!needs_layout_) << "only valid after layout" ; |
2000 | return final_line_count_; |
2001 | } |
2002 | |
2003 | bool ParagraphTxt::DidExceedMaxLines() { |
2004 | FML_DCHECK(!needs_layout_) << "only valid after layout" ; |
2005 | return did_exceed_max_lines_; |
2006 | } |
2007 | |
2008 | void ParagraphTxt::SetDirty(bool dirty) { |
2009 | needs_layout_ = dirty; |
2010 | } |
2011 | |
2012 | std::vector<LineMetrics>& ParagraphTxt::GetLineMetrics() { |
2013 | FML_DCHECK(!needs_layout_) << "only valid after layout" ; |
2014 | return line_metrics_; |
2015 | } |
2016 | |
2017 | } // namespace txt |
2018 | |