• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 Google LLC.
2 
3 #include "include/core/SkCanvas.h"
4 #include "include/core/SkFontMetrics.h"
5 #include "include/core/SkMatrix.h"
6 #include "include/core/SkPictureRecorder.h"
7 #include "include/core/SkSpan.h"
8 #include "include/core/SkTypeface.h"
9 #include "include/private/base/SkTFitsIn.h"
10 #include "include/private/base/SkTo.h"
11 #include "modules/skparagraph/include/Metrics.h"
12 #include "modules/skparagraph/include/Paragraph.h"
13 #include "modules/skparagraph/include/ParagraphPainter.h"
14 #include "modules/skparagraph/include/ParagraphStyle.h"
15 #include "modules/skparagraph/include/TextStyle.h"
16 #include "modules/skparagraph/src/OneLineShaper.h"
17 #include "modules/skparagraph/src/ParagraphImpl.h"
18 #include "modules/skparagraph/src/ParagraphPainterImpl.h"
19 #include "modules/skparagraph/src/Run.h"
20 #include "modules/skparagraph/src/TextLine.h"
21 #include "modules/skparagraph/src/TextWrapper.h"
22 #include "src/base/SkUTF.h"
23 #include <math.h>
24 #include <algorithm>
25 #include <utility>
26 
27 
28 namespace skia {
29 namespace textlayout {
30 
31 namespace {
32 
littleRound(SkScalar a)33 SkScalar littleRound(SkScalar a) {
34     // This rounding is done to match Flutter tests. Must be removed..
35     auto val = std::fabs(a);
36     if (val < 10000) {
37         return SkScalarRoundToScalar(a * 100.0)/100.0;
38     } else if (val < 100000) {
39         return SkScalarRoundToScalar(a * 10.0)/10.0;
40     } else {
41         return SkScalarFloorToScalar(a);
42     }
43 }
44 }  // namespace
45 
operator *(const TextRange & a,const TextRange & b)46 TextRange operator*(const TextRange& a, const TextRange& b) {
47     if (a.start == b.start && a.end == b.end) return a;
48     auto begin = std::max(a.start, b.start);
49     auto end = std::min(a.end, b.end);
50     return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
51 }
52 
Paragraph(ParagraphStyle style,sk_sp<FontCollection> fonts)53 Paragraph::Paragraph(ParagraphStyle style, sk_sp<FontCollection> fonts)
54             : fFontCollection(std::move(fonts))
55             , fParagraphStyle(std::move(style))
56             , fAlphabeticBaseline(0)
57             , fIdeographicBaseline(0)
58             , fHeight(0)
59             , fWidth(0)
60             , fMaxIntrinsicWidth(0)
61             , fMinIntrinsicWidth(0)
62             , fLongestLine(0)
63             , fExceededMaxLines(0)
64 { }
65 
ParagraphImpl(const SkString & text,ParagraphStyle style,SkTArray<Block,true> blocks,SkTArray<Placeholder,true> placeholders,sk_sp<FontCollection> fonts,std::shared_ptr<SkUnicode> unicode)66 ParagraphImpl::ParagraphImpl(const SkString& text,
67                              ParagraphStyle style,
68                              SkTArray<Block, true> blocks,
69                              SkTArray<Placeholder, true> placeholders,
70                              sk_sp<FontCollection> fonts,
71                              std::shared_ptr<SkUnicode> unicode)
72         : Paragraph(std::move(style), std::move(fonts))
73         , fTextStyles(std::move(blocks))
74         , fPlaceholders(std::move(placeholders))
75         , fText(text)
76         , fState(kUnknown)
77         , fUnresolvedGlyphs(0)
78         , fPicture(nullptr)
79         , fStrutMetrics(false)
80         , fOldWidth(0)
81         , fOldHeight(0)
82         , fUnicode(std::move(unicode))
83         , fHasLineBreaks(false)
84         , fHasWhitespacesInside(false)
85         , fTrailingSpaces(0)
86 {
87     SkASSERT(fUnicode);
88 }
89 
ParagraphImpl(const std::u16string & utf16text,ParagraphStyle style,SkTArray<Block,true> blocks,SkTArray<Placeholder,true> placeholders,sk_sp<FontCollection> fonts,std::shared_ptr<SkUnicode> unicode)90 ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
91                              ParagraphStyle style,
92                              SkTArray<Block, true> blocks,
93                              SkTArray<Placeholder, true> placeholders,
94                              sk_sp<FontCollection> fonts,
95                              std::shared_ptr<SkUnicode> unicode)
96         : ParagraphImpl(SkString(),
97                         std::move(style),
98                         std::move(blocks),
99                         std::move(placeholders),
100                         std::move(fonts),
101                         std::move(unicode))
102 {
103     SkASSERT(fUnicode);
104     fText =  SkUnicode::convertUtf16ToUtf8(utf16text);
105 }
106 
107 ParagraphImpl::~ParagraphImpl() = default;
108 
unresolvedGlyphs()109 int32_t ParagraphImpl::unresolvedGlyphs() {
110     if (fState < kShaped) {
111         return -1;
112     }
113 
114     return fUnresolvedGlyphs;
115 }
116 
layout(SkScalar rawWidth)117 void ParagraphImpl::layout(SkScalar rawWidth) {
118 
119     // TODO: This rounding is done to match Flutter tests. Must be removed...
120     auto floorWidth = SkScalarFloorToScalar(rawWidth);
121 
122     if ((!SkScalarIsFinite(rawWidth) || fLongestLine <= floorWidth) &&
123         fState >= kLineBroken &&
124          fLines.size() == 1 && fLines.front().ellipsis() == nullptr) {
125         // Most common case: one line of text (and one line is never justified, so no cluster shifts)
126         // We cannot mark it as kLineBroken because the new width can be bigger than the old width
127         fWidth = floorWidth;
128         fState = kShaped;
129     } else if (fState >= kLineBroken && fOldWidth != floorWidth) {
130         // We can use the results from SkShaper but have to do EVERYTHING ELSE again
131         fState = kShaped;
132     } else {
133         // Nothing changed case: we can reuse the data from the last layout
134     }
135 
136     if (fState < kShaped) {
137         // Check if we have the text in the cache and don't need to shape it again
138         if (!fFontCollection->getParagraphCache()->findParagraph(this)) {
139             if (fState < kIndexed) {
140                 // This only happens once at the first layout; the text is immutable
141                 // and there is no reason to repeat it
142                 if (this->computeCodeUnitProperties()) {
143                     fState = kIndexed;
144                 }
145             }
146             this->fRuns.clear();
147             this->fClusters.clear();
148             this->fClustersIndexFromCodeUnit.clear();
149             this->fClustersIndexFromCodeUnit.push_back_n(fText.size() + 1, EMPTY_INDEX);
150             if (!this->shapeTextIntoEndlessLine()) {
151                 this->resetContext();
152                 // TODO: merge the two next calls - they always come together
153                 this->resolveStrut();
154                 this->computeEmptyMetrics();
155                 this->fLines.clear();
156 
157                 // Set the important values that are not zero
158                 fWidth = floorWidth;
159                 fHeight = fEmptyMetrics.height();
160                 if (fParagraphStyle.getStrutStyle().getStrutEnabled() &&
161                     fParagraphStyle.getStrutStyle().getForceStrutHeight()) {
162                     fHeight = fStrutMetrics.height();
163                 }
164                 fAlphabeticBaseline = fEmptyMetrics.alphabeticBaseline();
165                 fIdeographicBaseline = fEmptyMetrics.ideographicBaseline();
166                 fLongestLine = FLT_MIN - FLT_MAX;  // That is what flutter has
167                 fMinIntrinsicWidth = 0;
168                 fMaxIntrinsicWidth = 0;
169                 this->fOldWidth = floorWidth;
170                 this->fOldHeight = this->fHeight;
171 
172                 return;
173             } else {
174                 // Add the paragraph to the cache
175                 fFontCollection->getParagraphCache()->updateParagraph(this);
176             }
177         }
178         fState = kShaped;
179     }
180 
181     if (fState == kShaped) {
182         this->resetContext();
183         this->resolveStrut();
184         this->computeEmptyMetrics();
185         this->fLines.clear();
186         this->breakShapedTextIntoLines(floorWidth);
187         fState = kLineBroken;
188     }
189 
190     if (fState == kLineBroken) {
191         // Build the picture lazily not until we actually have to paint (or never)
192         this->resetShifts();
193         this->formatLines(fWidth);
194         fState = kFormatted;
195     }
196 
197     this->fOldWidth = floorWidth;
198     this->fOldHeight = this->fHeight;
199 
200     // TODO: This rounding is done to match Flutter tests. Must be removed...
201     fMinIntrinsicWidth = littleRound(fMinIntrinsicWidth);
202     fMaxIntrinsicWidth = littleRound(fMaxIntrinsicWidth);
203 
204     // TODO: This is strictly Flutter thing. Must be factored out into some flutter code
205     if (fParagraphStyle.getMaxLines() == 1 ||
206         (fParagraphStyle.unlimited_lines() && fParagraphStyle.ellipsized())) {
207         fMinIntrinsicWidth = fMaxIntrinsicWidth;
208     }
209 
210     // TODO: Since min and max are calculated differently it's possible to get a rounding error
211     //  that would make min > max. Sort it out later, make it the same for now
212     if (fMaxIntrinsicWidth < fMinIntrinsicWidth) {
213         fMaxIntrinsicWidth = fMinIntrinsicWidth;
214     }
215 
216     //SkDebugf("layout('%s', %f): %f %f\n", fText.c_str(), rawWidth, fMinIntrinsicWidth, fMaxIntrinsicWidth);
217 }
218 
paint(SkCanvas * canvas,SkScalar x,SkScalar y)219 void ParagraphImpl::paint(SkCanvas* canvas, SkScalar x, SkScalar y) {
220     CanvasParagraphPainter painter(canvas);
221     paint(&painter, x, y);
222 }
223 
paint(ParagraphPainter * painter,SkScalar x,SkScalar y)224 void ParagraphImpl::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
225     for (auto& line : fLines) {
226         line.paint(painter, x, y);
227     }
228 }
229 
resetContext()230 void ParagraphImpl::resetContext() {
231     fAlphabeticBaseline = 0;
232     fHeight = 0;
233     fWidth = 0;
234     fIdeographicBaseline = 0;
235     fMaxIntrinsicWidth = 0;
236     fMinIntrinsicWidth = 0;
237     fLongestLine = 0;
238     fMaxWidthWithTrailingSpaces = 0;
239     fExceededMaxLines = false;
240 }
241 
242 // shapeTextIntoEndlessLine is the thing that calls this method
computeCodeUnitProperties()243 bool ParagraphImpl::computeCodeUnitProperties() {
244 
245     if (nullptr == fUnicode) {
246         return false;
247     }
248 
249     // Get bidi regions
250     auto textDirection = fParagraphStyle.getTextDirection() == TextDirection::kLtr
251                               ? SkUnicode::TextDirection::kLTR
252                               : SkUnicode::TextDirection::kRTL;
253     if (!fUnicode->getBidiRegions(fText.c_str(), fText.size(), textDirection, &fBidiRegions)) {
254         return false;
255     }
256 
257     // Collect all spaces and some extra information
258     // (and also substitute \t with a space while we are at it)
259     if (!fUnicode->computeCodeUnitFlags(&fText[0],
260                                         fText.size(),
261                                         this->paragraphStyle().getReplaceTabCharacters(),
262                                         &fCodeUnitProperties)) {
263         return false;
264     }
265 
266     // Get some information about trailing spaces / hard line breaks
267     fTrailingSpaces = fText.size();
268     TextIndex firstWhitespace = EMPTY_INDEX;
269     for (int i = 0; i < fCodeUnitProperties.size(); ++i) {
270         auto flags = fCodeUnitProperties[i];
271         if (SkUnicode::isPartOfWhiteSpaceBreak(flags)) {
272             if (fTrailingSpaces  == fText.size()) {
273                 fTrailingSpaces = i;
274             }
275             if (firstWhitespace == EMPTY_INDEX) {
276                 firstWhitespace = i;
277             }
278         } else {
279             fTrailingSpaces = fText.size();
280         }
281         if (SkUnicode::isHardLineBreak(flags)) {
282             fHasLineBreaks = true;
283         }
284     }
285 
286     if (firstWhitespace < fTrailingSpaces) {
287         fHasWhitespacesInside = true;
288     }
289 
290     return true;
291 }
292 
is_ascii_7bit_space(int c)293 static bool is_ascii_7bit_space(int c) {
294     SkASSERT(c >= 0 && c <= 127);
295 
296     // Extracted from https://en.wikipedia.org/wiki/Whitespace_character
297     //
298     enum WS {
299         kHT    = 9,
300         kLF    = 10,
301         kVT    = 11,
302         kFF    = 12,
303         kCR    = 13,
304         kSP    = 32,    // too big to use as shift
305     };
306 #define M(shift)    (1 << (shift))
307     constexpr uint32_t kSpaceMask = M(kHT) | M(kLF) | M(kVT) | M(kFF) | M(kCR);
308     // we check for Space (32) explicitly, since it is too large to shift
309     return (c == kSP) || (c <= 31 && (kSpaceMask & M(c)));
310 #undef M
311 }
312 
Cluster(ParagraphImpl * owner,RunIndex runIndex,size_t start,size_t end,SkSpan<const char> text,SkScalar width,SkScalar height)313 Cluster::Cluster(ParagraphImpl* owner,
314                  RunIndex runIndex,
315                  size_t start,
316                  size_t end,
317                  SkSpan<const char> text,
318                  SkScalar width,
319                  SkScalar height)
320         : fOwner(owner)
321         , fRunIndex(runIndex)
322         , fTextRange(text.begin() - fOwner->text().begin(), text.end() - fOwner->text().begin())
323         , fGraphemeRange(EMPTY_RANGE)
324         , fStart(start)
325         , fEnd(end)
326         , fWidth(width)
327         , fHeight(height)
328         , fHalfLetterSpacing(0.0) {
329     size_t whiteSpacesBreakLen = 0;
330     size_t intraWordBreakLen = 0;
331 
332     const char* ch = text.begin();
333     if (text.end() - ch == 1 && *(unsigned char*)ch <= 0x7F) {
334         // I am not even sure it's worth it if we do not save a unicode call
335         if (is_ascii_7bit_space(*ch)) {
336             ++whiteSpacesBreakLen;
337         }
338     } else {
339         for (auto i = fTextRange.start; i < fTextRange.end; ++i) {
340             if (fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kPartOfWhiteSpaceBreak)) {
341                 ++whiteSpacesBreakLen;
342             }
343             if (fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kPartOfIntraWordBreak)) {
344                 ++intraWordBreakLen;
345             }
346         }
347     }
348 
349     fIsWhiteSpaceBreak = whiteSpacesBreakLen == fTextRange.width();
350     fIsIntraWordBreak = intraWordBreakLen == fTextRange.width();
351     fIsHardBreak = fOwner->codeUnitHasProperty(fTextRange.end,
352                                                SkUnicode::CodeUnitFlags::kHardLineBreakBefore);
353 }
354 
calculateWidth(size_t start,size_t end,bool clip) const355 SkScalar Run::calculateWidth(size_t start, size_t end, bool clip) const {
356     SkASSERT(start <= end);
357     // clip |= end == size();  // Clip at the end of the run?
358     auto correction = 0.0f;
359     if (end > start && !fJustificationShifts.empty()) {
360         // This is not a typo: we are using Point as a pair of SkScalars
361         correction = fJustificationShifts[end - 1].fX -
362                      fJustificationShifts[start].fY;
363     }
364     return posX(end) - posX(start) + correction;
365 }
366 
367 // In some cases we apply spacing to glyphs first and then build the cluster table, in some we do
368 // the opposite - just to optimize the most common case.
applySpacingAndBuildClusterTable()369 void ParagraphImpl::applySpacingAndBuildClusterTable() {
370 
371     // Check all text styles to see what we have to do (if anything)
372     size_t letterSpacingStyles = 0;
373     bool hasWordSpacing = false;
374     for (auto& block : fTextStyles) {
375         if (block.fRange.width() > 0) {
376             if (!SkScalarNearlyZero(block.fStyle.getLetterSpacing())) {
377                 ++letterSpacingStyles;
378             }
379             if (!SkScalarNearlyZero(block.fStyle.getWordSpacing())) {
380                 hasWordSpacing = true;
381             }
382         }
383     }
384 
385     if (letterSpacingStyles == 0 && !hasWordSpacing) {
386         // We don't have to do anything about spacing (most common case)
387         this->buildClusterTable();
388         return;
389     }
390 
391     if (letterSpacingStyles == 1 && !hasWordSpacing && fTextStyles.size() == 1 &&
392         fTextStyles[0].fRange.width() == fText.size() && fRuns.size() == 1) {
393         // We have to letter space the entire paragraph (second most common case)
394         auto& run = fRuns[0];
395         auto& style = fTextStyles[0].fStyle;
396         run.addSpacesEvenly(style.getLetterSpacing());
397         this->buildClusterTable();
398         // This is something Flutter requires
399         for (auto& cluster : fClusters) {
400             cluster.setHalfLetterSpacing(style.getLetterSpacing()/2);
401         }
402         return;
403     }
404 
405     // The complex case: many text styles with spacing (possibly not adjusted to glyphs)
406     this->buildClusterTable();
407 
408     // Walk through all the clusters in the direction of shaped text
409     // (we have to walk through the styles in the same order, too)
410     SkScalar shift = 0;
411     for (auto& run : fRuns) {
412 
413         // Skip placeholder runs
414         if (run.isPlaceholder()) {
415             continue;
416         }
417         bool soFarWhitespacesOnly = true;
418         bool wordSpacingPending = false;
419         Cluster* lastSpaceCluster = nullptr;
420         run.iterateThroughClusters([this, &run, &shift, &soFarWhitespacesOnly, &wordSpacingPending, &lastSpaceCluster](Cluster* cluster) {
421             // Shift the cluster (shift collected from the previous clusters)
422             run.shift(cluster, shift);
423 
424             // Synchronize styles (one cluster can be covered by few styles)
425             Block* currentStyle = fTextStyles.begin();
426             while (!cluster->startsIn(currentStyle->fRange)) {
427                 currentStyle++;
428                 SkASSERT(currentStyle != fTextStyles.end());
429             }
430 
431             SkASSERT(!currentStyle->fStyle.isPlaceholder());
432 
433             // Process word spacing
434             if (currentStyle->fStyle.getWordSpacing() != 0) {
435                 if (cluster->isWhitespaceBreak() && cluster->isSoftBreak()) {
436                     if (!soFarWhitespacesOnly) {
437                         lastSpaceCluster = cluster;
438                         wordSpacingPending = true;
439                     }
440                 } else if (wordSpacingPending) {
441                     SkScalar spacing = currentStyle->fStyle.getWordSpacing();
442                     run.addSpacesAtTheEnd(spacing, lastSpaceCluster);
443                     run.shift(cluster, spacing);
444                     shift += spacing;
445                     wordSpacingPending = false;
446                 }
447             }
448             // Process letter spacing
449             if (currentStyle->fStyle.getLetterSpacing() != 0) {
450                 shift += run.addSpacesEvenly(currentStyle->fStyle.getLetterSpacing(), cluster);
451             }
452 
453             if (soFarWhitespacesOnly && !cluster->isWhitespaceBreak()) {
454                 soFarWhitespacesOnly = false;
455             }
456         });
457     }
458 }
459 
460 // Clusters in the order of the input text
buildClusterTable()461 void ParagraphImpl::buildClusterTable() {
462     // It's possible that one grapheme includes few runs; we cannot handle it
463     // so we break graphemes by the runs instead
464     // It's not the ideal solution and has to be revisited later
465     int cluster_count = 1;
466     for (auto& run : fRuns) {
467         cluster_count += run.isPlaceholder() ? 1 : run.size();
468         fCodeUnitProperties[run.fTextRange.start] |= SkUnicode::CodeUnitFlags::kGraphemeStart;
469         fCodeUnitProperties[run.fTextRange.start] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
470     }
471     if (!fRuns.empty()) {
472         fCodeUnitProperties[fRuns.back().textRange().end] |= SkUnicode::CodeUnitFlags::kGraphemeStart;
473         fCodeUnitProperties[fRuns.back().textRange().end] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
474     }
475     fClusters.reserve_back(cluster_count);
476 
477     // Walk through all the run in the direction of input text
478     for (auto& run : fRuns) {
479         auto runIndex = run.index();
480         auto runStart = fClusters.size();
481         if (run.isPlaceholder()) {
482             // Add info to cluster indexes table (text -> cluster)
483             for (auto i = run.textRange().start; i < run.textRange().end; ++i) {
484               fClustersIndexFromCodeUnit[i] = fClusters.size();
485             }
486             // There are no glyphs but we want to have one cluster
487             fClusters.emplace_back(this, runIndex, 0ul, 1ul, this->text(run.textRange()), run.advance().fX, run.advance().fY);
488             fCodeUnitProperties[run.textRange().start] |= SkUnicode::CodeUnitFlags::kSoftLineBreakBefore;
489             fCodeUnitProperties[run.textRange().end] |= SkUnicode::CodeUnitFlags::kSoftLineBreakBefore;
490         } else {
491             // Walk through the glyph in the direction of input text
492             run.iterateThroughClustersInTextOrder([runIndex, this](size_t glyphStart,
493                                                                    size_t glyphEnd,
494                                                                    size_t charStart,
495                                                                    size_t charEnd,
496                                                                    SkScalar width,
497                                                                    SkScalar height) {
498                 SkASSERT(charEnd >= charStart);
499                 // Add info to cluster indexes table (text -> cluster)
500                 for (auto i = charStart; i < charEnd; ++i) {
501                   fClustersIndexFromCodeUnit[i] = fClusters.size();
502                 }
503                 SkSpan<const char> text(fText.c_str() + charStart, charEnd - charStart);
504                 fClusters.emplace_back(this, runIndex, glyphStart, glyphEnd, text, width, height);
505                 fCodeUnitProperties[charStart] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
506             });
507         }
508         fCodeUnitProperties[run.textRange().start] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
509 
510         run.setClusterRange(runStart, fClusters.size());
511         fMaxIntrinsicWidth += run.advance().fX;
512     }
513     fClustersIndexFromCodeUnit[fText.size()] = fClusters.size();
514     fClusters.emplace_back(this, EMPTY_RUN, 0, 0, this->text({fText.size(), fText.size()}), 0, 0);
515 }
516 
shapeTextIntoEndlessLine()517 bool ParagraphImpl::shapeTextIntoEndlessLine() {
518 
519     if (fText.size() == 0) {
520         return false;
521     }
522 
523     fFontSwitches.clear();
524 
525     OneLineShaper oneLineShaper(this);
526     auto result = oneLineShaper.shape();
527     fUnresolvedGlyphs = oneLineShaper.unresolvedGlyphs();
528 
529     this->applySpacingAndBuildClusterTable();
530 
531     return result;
532 }
533 
breakShapedTextIntoLines(SkScalar maxWidth)534 void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
535 
536     if (!fHasLineBreaks &&
537         !fHasWhitespacesInside &&
538         fPlaceholders.size() == 1 &&
539         fRuns.size() == 1 && fRuns[0].fAdvance.fX <= maxWidth) {
540         // This is a short version of a line breaking when we know that:
541         // 1. We have only one line of text
542         // 2. It's shaped into a single run
543         // 3. There are no placeholders
544         // 4. There are no linebreaks (which will format text into multiple lines)
545         // 5. There are no whitespaces so the minIntrinsicWidth=maxIntrinsicWidth
546         // (To think about that, the last condition is not quite right;
547         // we should calculate minIntrinsicWidth by soft line breaks.
548         // However, it's how it's done in Flutter now)
549         auto& run = this->fRuns[0];
550         auto advance = run.advance();
551         auto textRange = TextRange(0, this->text().size());
552         auto textExcludingSpaces = TextRange(0, fTrailingSpaces);
553         InternalLineMetrics metrics(this->strutForceHeight());
554         metrics.add(&run);
555         auto disableFirstAscent = this->paragraphStyle().getTextHeightBehavior() &
556                                   TextHeightBehavior::kDisableFirstAscent;
557         auto disableLastDescent = this->paragraphStyle().getTextHeightBehavior() &
558                                   TextHeightBehavior::kDisableLastDescent;
559         if (disableFirstAscent) {
560             metrics.fAscent = metrics.fRawAscent;
561         }
562         if (disableLastDescent) {
563             metrics.fDescent = metrics.fRawDescent;
564         }
565         if (this->strutEnabled()) {
566             this->strutMetrics().updateLineMetrics(metrics);
567         }
568         ClusterIndex trailingSpaces = fClusters.size();
569         do {
570             --trailingSpaces;
571             auto& cluster = fClusters[trailingSpaces];
572             if (!cluster.isWhitespaceBreak()) {
573                 ++trailingSpaces;
574                 break;
575             }
576             advance.fX -= cluster.width();
577         } while (trailingSpaces != 0);
578 
579         advance.fY = metrics.height();
580         auto clusterRange = ClusterRange(0, trailingSpaces);
581         auto clusterRangeWithGhosts = ClusterRange(0, this->clusters().size() - 1);
582         this->addLine(SkPoint::Make(0, 0), advance,
583                       textExcludingSpaces, textRange, textRange,
584                       clusterRange, clusterRangeWithGhosts, run.advance().x(),
585                       metrics);
586 
587         fLongestLine = nearlyZero(advance.fX) ? run.advance().fX : advance.fX;
588         fHeight = advance.fY;
589         fWidth = maxWidth;
590         fMaxIntrinsicWidth = run.advance().fX;
591         fMinIntrinsicWidth = advance.fX;
592         fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline();
593         fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline();
594         fExceededMaxLines = false;
595         return;
596     }
597 
598     TextWrapper textWrapper;
599     textWrapper.breakTextIntoLines(
600             this,
601             maxWidth,
602             [&](TextRange textExcludingSpaces,
603                 TextRange text,
604                 TextRange textWithNewlines,
605                 ClusterRange clusters,
606                 ClusterRange clustersWithGhosts,
607                 SkScalar widthWithSpaces,
608                 size_t startPos,
609                 size_t endPos,
610                 SkVector offset,
611                 SkVector advance,
612                 InternalLineMetrics metrics,
613                 bool addEllipsis) {
614                 // TODO: Take in account clipped edges
615                 auto& line = this->addLine(offset, advance, textExcludingSpaces, text, textWithNewlines, clusters, clustersWithGhosts, widthWithSpaces, metrics);
616                 if (addEllipsis) {
617                     line.createEllipsis(maxWidth, this->getEllipsis(), true);
618                 }
619                 fLongestLine = std::max(fLongestLine, nearlyZero(advance.fX) ? widthWithSpaces : advance.fX);
620             });
621 
622     fHeight = textWrapper.height();
623     fWidth = maxWidth;
624     fMaxIntrinsicWidth = textWrapper.maxIntrinsicWidth();
625     fMinIntrinsicWidth = textWrapper.minIntrinsicWidth();
626     fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline();
627     fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline();
628     fExceededMaxLines = textWrapper.exceededMaxLines();
629 }
630 
formatLines(SkScalar maxWidth)631 void ParagraphImpl::formatLines(SkScalar maxWidth) {
632     auto effectiveAlign = fParagraphStyle.effective_align();
633     const bool isLeftAligned = effectiveAlign == TextAlign::kLeft
634         || (effectiveAlign == TextAlign::kJustify && fParagraphStyle.getTextDirection() == TextDirection::kLtr);
635 
636     if (!SkScalarIsFinite(maxWidth) && !isLeftAligned) {
637         // Special case: clean all text in case of maxWidth == INF & align != left
638         // We had to go through shaping though because we need all the measurement numbers
639         fLines.clear();
640         return;
641     }
642 
643     for (auto& line : fLines) {
644         line.format(effectiveAlign, maxWidth);
645     }
646 }
647 
resolveStrut()648 void ParagraphImpl::resolveStrut() {
649     auto strutStyle = this->paragraphStyle().getStrutStyle();
650     if (!strutStyle.getStrutEnabled() || strutStyle.getFontSize() < 0) {
651         return;
652     }
653 
654     std::vector<sk_sp<SkTypeface>> typefaces = fFontCollection->findTypefaces(strutStyle.getFontFamilies(), strutStyle.getFontStyle(), std::nullopt);
655     if (typefaces.empty()) {
656         SkDEBUGF("Could not resolve strut font\n");
657         return;
658     }
659 
660     SkFont font(typefaces.front(), strutStyle.getFontSize());
661     SkFontMetrics metrics;
662     font.getMetrics(&metrics);
663 
664     if (strutStyle.getHeightOverride()) {
665         auto strutHeight = metrics.fDescent - metrics.fAscent;
666         auto strutMultiplier = strutStyle.getHeight() * strutStyle.getFontSize();
667         fStrutMetrics = InternalLineMetrics(
668             (metrics.fAscent / strutHeight) * strutMultiplier,
669             (metrics.fDescent / strutHeight) * strutMultiplier,
670                 strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize(),
671             metrics.fAscent, metrics.fDescent, metrics.fLeading);
672     } else {
673         fStrutMetrics = InternalLineMetrics(
674                 metrics.fAscent,
675                 metrics.fDescent,
676                 strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize());
677     }
678     fStrutMetrics.setForceStrut(this->paragraphStyle().getStrutStyle().getForceStrutHeight());
679 }
680 
findAllBlocks(TextRange textRange)681 BlockRange ParagraphImpl::findAllBlocks(TextRange textRange) {
682     BlockIndex begin = EMPTY_BLOCK;
683     BlockIndex end = EMPTY_BLOCK;
684     for (int index = 0; index < fTextStyles.size(); ++index) {
685         auto& block = fTextStyles[index];
686         if (block.fRange.end <= textRange.start) {
687             continue;
688         }
689         if (block.fRange.start >= textRange.end) {
690             break;
691         }
692         if (begin == EMPTY_BLOCK) {
693             begin = index;
694         }
695         end = index;
696     }
697 
698     if (begin == EMPTY_INDEX || end == EMPTY_INDEX) {
699         // It's possible if some text is not covered with any text style
700         // Not in Flutter but in direct use of SkParagraph
701         return EMPTY_RANGE;
702     }
703 
704     return { begin, end + 1 };
705 }
706 
addLine(SkVector offset,SkVector advance,TextRange textExcludingSpaces,TextRange text,TextRange textIncludingNewLines,ClusterRange clusters,ClusterRange clustersWithGhosts,SkScalar widthWithSpaces,InternalLineMetrics sizes)707 TextLine& ParagraphImpl::addLine(SkVector offset,
708                                  SkVector advance,
709                                  TextRange textExcludingSpaces,
710                                  TextRange text,
711                                  TextRange textIncludingNewLines,
712                                  ClusterRange clusters,
713                                  ClusterRange clustersWithGhosts,
714                                  SkScalar widthWithSpaces,
715                                  InternalLineMetrics sizes) {
716     // Define a list of styles that covers the line
717     auto blocks = findAllBlocks(textExcludingSpaces);
718     return fLines.emplace_back(this, offset, advance, blocks,
719                                textExcludingSpaces, text, textIncludingNewLines,
720                                clusters, clustersWithGhosts, widthWithSpaces, sizes);
721 }
722 
723 // Returns a vector of bounding boxes that enclose all text between
724 // start and end glyph indexes, including start and excluding end
getRectsForRange(unsigned start,unsigned end,RectHeightStyle rectHeightStyle,RectWidthStyle rectWidthStyle)725 std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
726                                                      unsigned end,
727                                                      RectHeightStyle rectHeightStyle,
728                                                      RectWidthStyle rectWidthStyle) {
729     std::vector<TextBox> results;
730     if (fText.isEmpty()) {
731         if (start == 0 && end > 0) {
732             // On account of implied "\n" that is always at the end of the text
733             //SkDebugf("getRectsForRange(%d, %d): %f\n", start, end, fHeight);
734             results.emplace_back(SkRect::MakeXYWH(0, 0, 0, fHeight), fParagraphStyle.getTextDirection());
735         }
736         return results;
737     }
738 
739     ensureUTF16Mapping();
740 
741     if (start >= end || start > SkToSizeT(fUTF8IndexForUTF16Index.size()) || end == 0) {
742         return results;
743     }
744 
745     // Adjust the text to grapheme edges
746     // Apparently, text editor CAN move inside graphemes but CANNOT select a part of it.
747     // I don't know why - the solution I have here returns an empty box for every query that
748     // does not contain an end of a grapheme.
749     // Once a cursor is inside a complex grapheme I can press backspace and cause trouble.
750     // To avoid any problems, I will not allow any selection of a part of a grapheme.
751     // One flutter test fails because of it but the editing experience is correct
752     // (although you have to press the cursor many times before it moves to the next grapheme).
753     TextRange text(fText.size(), fText.size());
754     // TODO: This is probably a temp change that makes SkParagraph work as TxtLib
755     //  (so we can compare the results). We now include in the selection box only the graphemes
756     //  that belongs to the given [start:end) range entirely (not the ones that intersect with it)
757     if (start < SkToSizeT(fUTF8IndexForUTF16Index.size())) {
758         auto utf8 = fUTF8IndexForUTF16Index[start];
759         // If start points to a trailing surrogate, skip it
760         if (start > 0 && fUTF8IndexForUTF16Index[start - 1] == utf8) {
761             utf8 = fUTF8IndexForUTF16Index[start + 1];
762         }
763         text.start = this->findNextGraphemeBoundary(utf8);
764     }
765     if (end < SkToSizeT(fUTF8IndexForUTF16Index.size())) {
766         auto utf8 = this->findPreviousGraphemeBoundary(fUTF8IndexForUTF16Index[end]);
767         text.end = utf8;
768     }
769     //SkDebugf("getRectsForRange(%d,%d) -> (%d:%d)\n", start, end, text.start, text.end);
770     for (auto& line : fLines) {
771         auto lineText = line.textWithNewlines();
772         auto intersect = lineText * text;
773         if (intersect.empty() && lineText.start != text.start) {
774             continue;
775         }
776 
777         line.getRectsForRange(intersect, rectHeightStyle, rectWidthStyle, results);
778     }
779 /*
780     SkDebugf("getRectsForRange(%d, %d)\n", start, end);
781     for (auto& r : results) {
782         r.rect.fLeft = littleRound(r.rect.fLeft);
783         r.rect.fRight = littleRound(r.rect.fRight);
784         r.rect.fTop = littleRound(r.rect.fTop);
785         r.rect.fBottom = littleRound(r.rect.fBottom);
786         SkDebugf("[%f:%f * %f:%f]\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom);
787     }
788 */
789     return results;
790 }
791 
getRectsForPlaceholders()792 std::vector<TextBox> ParagraphImpl::getRectsForPlaceholders() {
793   std::vector<TextBox> boxes;
794   if (fText.isEmpty()) {
795        return boxes;
796   }
797   if (fPlaceholders.size() == 1) {
798        // We always have one fake placeholder
799        return boxes;
800   }
801   for (auto& line : fLines) {
802       line.getRectsForPlaceholders(boxes);
803   }
804   /*
805   SkDebugf("getRectsForPlaceholders('%s'): %d\n", fText.c_str(), boxes.size());
806   for (auto& r : boxes) {
807       r.rect.fLeft = littleRound(r.rect.fLeft);
808       r.rect.fRight = littleRound(r.rect.fRight);
809       r.rect.fTop = littleRound(r.rect.fTop);
810       r.rect.fBottom = littleRound(r.rect.fBottom);
811       SkDebugf("[%f:%f * %f:%f] %s\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom,
812                (r.direction == TextDirection::kLtr ? "left" : "right"));
813   }
814   */
815   return boxes;
816 }
817 
818 // TODO: Optimize (save cluster <-> codepoint connection)
getGlyphPositionAtCoordinate(SkScalar dx,SkScalar dy)819 PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) {
820 
821     if (fText.isEmpty()) {
822         return {0, Affinity::kDownstream};
823     }
824 
825     ensureUTF16Mapping();
826 
827     for (auto& line : fLines) {
828         // Let's figure out if we can stop looking
829         auto offsetY = line.offset().fY;
830         if (dy >= offsetY + line.height() && &line != &fLines.back()) {
831             // This line is not good enough
832             continue;
833         }
834 
835         // This is so far the the line vertically closest to our coordinates
836         // (or the first one, or the only one - all the same)
837 
838         auto result = line.getGlyphPositionAtCoordinate(dx);
839         //SkDebugf("getGlyphPositionAtCoordinate(%f, %f): %d %s\n", dx, dy, result.position,
840         //   result.affinity == Affinity::kUpstream ? "up" : "down");
841         return result;
842     }
843 
844     return {0, Affinity::kDownstream};
845 }
846 
847 // Finds the first and last glyphs that define a word containing
848 // the glyph at index offset.
849 // By "glyph" they mean a character index - indicated by Minikin's code
getWordBoundary(unsigned offset)850 SkRange<size_t> ParagraphImpl::getWordBoundary(unsigned offset) {
851 
852     if (fWords.empty()) {
853         if (!fUnicode->getWords(fText.c_str(), fText.size(), nullptr, &fWords)) {
854             return {0, 0 };
855         }
856     }
857 
858     int32_t start = 0;
859     int32_t end = 0;
860     for (size_t i = 0; i < fWords.size(); ++i) {
861         auto word = fWords[i];
862         if (word <= offset) {
863             start = word;
864             end = word;
865         } else if (word > offset) {
866             end = word;
867             break;
868         }
869     }
870 
871     //SkDebugf("getWordBoundary(%d): %d - %d\n", offset, start, end);
872     return { SkToU32(start), SkToU32(end) };
873 }
874 
getLineMetrics(std::vector<LineMetrics> & metrics)875 void ParagraphImpl::getLineMetrics(std::vector<LineMetrics>& metrics) {
876     metrics.clear();
877     for (auto& line : fLines) {
878         metrics.emplace_back(line.getMetrics());
879     }
880 }
881 
text(TextRange textRange)882 SkSpan<const char> ParagraphImpl::text(TextRange textRange) {
883     SkASSERT(textRange.start <= fText.size() && textRange.end <= fText.size());
884     auto start = fText.c_str() + textRange.start;
885     return SkSpan<const char>(start, textRange.width());
886 }
887 
clusters(ClusterRange clusterRange)888 SkSpan<Cluster> ParagraphImpl::clusters(ClusterRange clusterRange) {
889     SkASSERT(clusterRange.start < SkToSizeT(fClusters.size()) &&
890              clusterRange.end <= SkToSizeT(fClusters.size()));
891     return SkSpan<Cluster>(&fClusters[clusterRange.start], clusterRange.width());
892 }
893 
cluster(ClusterIndex clusterIndex)894 Cluster& ParagraphImpl::cluster(ClusterIndex clusterIndex) {
895     SkASSERT(clusterIndex < SkToSizeT(fClusters.size()));
896     return fClusters[clusterIndex];
897 }
898 
runByCluster(ClusterIndex clusterIndex)899 Run& ParagraphImpl::runByCluster(ClusterIndex clusterIndex) {
900     auto start = cluster(clusterIndex);
901     return this->run(start.fRunIndex);
902 }
903 
blocks(BlockRange blockRange)904 SkSpan<Block> ParagraphImpl::blocks(BlockRange blockRange) {
905     SkASSERT(blockRange.start < SkToSizeT(fTextStyles.size()) &&
906              blockRange.end <= SkToSizeT(fTextStyles.size()));
907     return SkSpan<Block>(&fTextStyles[blockRange.start], blockRange.width());
908 }
909 
block(BlockIndex blockIndex)910 Block& ParagraphImpl::block(BlockIndex blockIndex) {
911     SkASSERT(blockIndex < SkToSizeT(fTextStyles.size()));
912     return fTextStyles[blockIndex];
913 }
914 
setState(InternalState state)915 void ParagraphImpl::setState(InternalState state) {
916     if (fState <= state) {
917         fState = state;
918         return;
919     }
920 
921     fState = state;
922     switch (fState) {
923         case kUnknown:
924             SkASSERT(false);
925             /*
926             // The text is immutable and so are all the text indexing properties
927             // taken from SkUnicode
928             fCodeUnitProperties.reset();
929             fWords.clear();
930             fBidiRegions.clear();
931             fUTF8IndexForUTF16Index.reset();
932             fUTF16IndexForUTF8Index.reset();
933             */
934             [[fallthrough]];
935 
936         case kIndexed:
937             fRuns.clear();
938             fClusters.clear();
939             [[fallthrough]];
940 
941         case kShaped:
942             fLines.clear();
943             [[fallthrough]];
944 
945         case kLineBroken:
946             fPicture = nullptr;
947             [[fallthrough]];
948 
949         default:
950             break;
951     }
952 }
953 
computeEmptyMetrics()954 void ParagraphImpl::computeEmptyMetrics() {
955 
956     // The empty metrics is used to define the height of the empty lines
957     // Unfortunately, Flutter has 2 different cases for that:
958     // 1. An empty line inside the text
959     // 2. An empty paragraph
960     // In the first case SkParagraph takes the metrics from the default paragraph style
961     // In the second case it should take it from the current text style
962     bool emptyParagraph = fRuns.empty();
963     TextStyle textStyle = paragraphStyle().getTextStyle();
964     if (emptyParagraph && !fTextStyles.empty()) {
965         textStyle = fTextStyles.back().fStyle;
966     }
967 
968     auto typefaces = fontCollection()->findTypefaces(
969       textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
970     auto typeface = typefaces.empty() ? nullptr : typefaces.front();
971 
972     SkFont font(typeface, textStyle.getFontSize());
973     fEmptyMetrics = InternalLineMetrics(font, paragraphStyle().getStrutStyle().getForceStrutHeight());
974 
975     if (!paragraphStyle().getStrutStyle().getForceStrutHeight() &&
976         textStyle.getHeightOverride()) {
977         const auto intrinsicHeight = fEmptyMetrics.height();
978         const auto strutHeight = textStyle.getHeight() * textStyle.getFontSize();
979         if (paragraphStyle().getStrutStyle().getHalfLeading()) {
980             fEmptyMetrics.update(
981                 fEmptyMetrics.ascent(),
982                 fEmptyMetrics.descent(),
983                 fEmptyMetrics.leading() + strutHeight - intrinsicHeight);
984         } else {
985             const auto multiplier = strutHeight / intrinsicHeight;
986             fEmptyMetrics.update(
987                 fEmptyMetrics.ascent() * multiplier,
988                 fEmptyMetrics.descent() * multiplier,
989                 fEmptyMetrics.leading() * multiplier);
990         }
991     }
992 
993     if (emptyParagraph) {
994         // For an empty text we apply both TextHeightBehaviour flags
995         // In case of non-empty paragraph TextHeightBehaviour flags will be applied at the appropriate place
996         // We have to do it here because we skip wrapping for an empty text
997         auto disableFirstAscent = (paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent) == TextHeightBehavior::kDisableFirstAscent;
998         auto disableLastDescent = (paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent) == TextHeightBehavior::kDisableLastDescent;
999         fEmptyMetrics.update(
1000             disableFirstAscent ? fEmptyMetrics.rawAscent() : fEmptyMetrics.ascent(),
1001             disableLastDescent ? fEmptyMetrics.rawDescent() : fEmptyMetrics.descent(),
1002             fEmptyMetrics.leading());
1003     }
1004 
1005     if (fParagraphStyle.getStrutStyle().getStrutEnabled()) {
1006         fStrutMetrics.updateLineMetrics(fEmptyMetrics);
1007     }
1008 }
1009 
getEllipsis() const1010 SkString ParagraphImpl::getEllipsis() const {
1011 
1012     auto ellipsis8 = fParagraphStyle.getEllipsis();
1013     auto ellipsis16 = fParagraphStyle.getEllipsisUtf16();
1014     if (!ellipsis8.isEmpty()) {
1015         return ellipsis8;
1016     } else {
1017         return SkUnicode::convertUtf16ToUtf8(fParagraphStyle.getEllipsisUtf16());
1018     }
1019 }
1020 
updateFontSize(size_t from,size_t to,SkScalar fontSize)1021 void ParagraphImpl::updateFontSize(size_t from, size_t to, SkScalar fontSize) {
1022 
1023   SkASSERT(from == 0 && to == fText.size());
1024   auto defaultStyle = fParagraphStyle.getTextStyle();
1025   defaultStyle.setFontSize(fontSize);
1026   fParagraphStyle.setTextStyle(defaultStyle);
1027 
1028   for (auto& textStyle : fTextStyles) {
1029     textStyle.fStyle.setFontSize(fontSize);
1030   }
1031 
1032   fState = std::min(fState, kIndexed);
1033   fOldWidth = 0;
1034   fOldHeight = 0;
1035 }
1036 
updateTextAlign(TextAlign textAlign)1037 void ParagraphImpl::updateTextAlign(TextAlign textAlign) {
1038     fParagraphStyle.setTextAlign(textAlign);
1039 
1040     if (fState >= kLineBroken) {
1041         fState = kLineBroken;
1042     }
1043 }
1044 
updateForegroundPaint(size_t from,size_t to,SkPaint paint)1045 void ParagraphImpl::updateForegroundPaint(size_t from, size_t to, SkPaint paint) {
1046     SkASSERT(from == 0 && to == fText.size());
1047     auto defaultStyle = fParagraphStyle.getTextStyle();
1048     defaultStyle.setForegroundColor(paint);
1049     fParagraphStyle.setTextStyle(defaultStyle);
1050 
1051     for (auto& textStyle : fTextStyles) {
1052         textStyle.fStyle.setForegroundColor(paint);
1053     }
1054 }
1055 
updateBackgroundPaint(size_t from,size_t to,SkPaint paint)1056 void ParagraphImpl::updateBackgroundPaint(size_t from, size_t to, SkPaint paint) {
1057     SkASSERT(from == 0 && to == fText.size());
1058     auto defaultStyle = fParagraphStyle.getTextStyle();
1059     defaultStyle.setBackgroundColor(paint);
1060     fParagraphStyle.setTextStyle(defaultStyle);
1061 
1062     for (auto& textStyle : fTextStyles) {
1063         textStyle.fStyle.setBackgroundColor(paint);
1064     }
1065 }
1066 
findPreviousGraphemeBoundary(TextIndex utf8)1067 TextIndex ParagraphImpl::findPreviousGraphemeBoundary(TextIndex utf8) {
1068     while (utf8 > 0 &&
1069           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
1070         --utf8;
1071     }
1072     return utf8;
1073 }
1074 
findNextGraphemeBoundary(TextIndex utf8)1075 TextIndex ParagraphImpl::findNextGraphemeBoundary(TextIndex utf8) {
1076     while (utf8 < fText.size() &&
1077           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
1078         ++utf8;
1079     }
1080     return utf8;
1081 }
1082 
findNextGlyphClusterBoundary(TextIndex utf8)1083 TextIndex ParagraphImpl::findNextGlyphClusterBoundary(TextIndex utf8) {
1084     while (utf8 < fText.size() &&
1085           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGlyphClusterStart) == 0) {
1086         ++utf8;
1087     }
1088     return utf8;
1089 }
1090 
findPreviousGlyphClusterBoundary(TextIndex utf8)1091 TextIndex ParagraphImpl::findPreviousGlyphClusterBoundary(TextIndex utf8) {
1092     while (utf8 > 0 &&
1093           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGlyphClusterStart) == 0) {
1094         --utf8;
1095     }
1096     return utf8;
1097 }
1098 
ensureUTF16Mapping()1099 void ParagraphImpl::ensureUTF16Mapping() {
1100     fillUTF16MappingOnce([&] {
1101         fUnicode->extractUtfConversionMapping(
1102                 this->text(),
1103                 [&](size_t index) { fUTF8IndexForUTF16Index.emplace_back(index); },
1104                 [&](size_t index) { fUTF16IndexForUTF8Index.emplace_back(index); });
1105     });
1106 }
1107 
visit(const Visitor & visitor)1108 void ParagraphImpl::visit(const Visitor& visitor) {
1109     int lineNumber = 0;
1110     for (auto& line : fLines) {
1111         line.ensureTextBlobCachePopulated();
1112         for (auto& rec : line.fTextBlobCache) {
1113             SkTextBlob::Iter iter(*rec.fBlob);
1114             SkTextBlob::Iter::ExperimentalRun run;
1115 
1116             SkSTArray<128, uint32_t> clusterStorage;
1117             const Run* R = rec.fVisitor_Run;
1118             const uint32_t* clusterPtr = &R->fClusterIndexes[0];
1119 
1120             if (R->fClusterStart > 0) {
1121                 int count = R->fClusterIndexes.size();
1122                 clusterStorage.reset(count);
1123                 for (int i = 0; i < count; ++i) {
1124                     clusterStorage[i] = R->fClusterStart + R->fClusterIndexes[i];
1125                 }
1126                 clusterPtr = &clusterStorage[0];
1127             }
1128             clusterPtr += rec.fVisitor_Pos;
1129 
1130             while (iter.experimentalNext(&run)) {
1131                 const Paragraph::VisitorInfo info = {
1132                     run.font,
1133                     rec.fOffset,
1134                     rec.fClipRect.fRight,
1135                     run.count,
1136                     run.glyphs,
1137                     run.positions,
1138                     clusterPtr,
1139                     0,  // flags
1140                 };
1141                 visitor(lineNumber, &info);
1142                 clusterPtr += run.count;
1143             }
1144         }
1145         visitor(lineNumber, nullptr);   // signal end of line
1146         lineNumber += 1;
1147     }
1148 }
1149 
1150 }  // namespace textlayout
1151 }  // namespace skia
1152