xref: /aosp_15_r20/external/skia/modules/skparagraph/src/ParagraphImpl.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 // Copyright 2019 Google LLC.
2 #include "include/core/SkCanvas.h"
3 #include "include/core/SkFontMetrics.h"
4 #include "include/core/SkMatrix.h"
5 #include "include/core/SkPath.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 "modules/skunicode/include/SkUnicode.h"
23 #include "src/base/SkUTF.h"
24 #include "src/core/SkTextBlobPriv.h"
25 
26 #include <algorithm>
27 #include <cfloat>
28 #include <cmath>
29 #include <utility>
30 
31 using namespace skia_private;
32 
33 namespace skia {
34 namespace textlayout {
35 
36 namespace {
37 
littleRound(SkScalar a)38 SkScalar littleRound(SkScalar a) {
39     // This rounding is done to match Flutter tests. Must be removed..
40     auto val = std::fabs(a);
41     if (val < 10000) {
42         return SkScalarRoundToScalar(a * 100.0)/100.0;
43     } else if (val < 100000) {
44         return SkScalarRoundToScalar(a * 10.0)/10.0;
45     } else {
46         return SkScalarFloorToScalar(a);
47     }
48 }
49 }  // namespace
50 
operator *(const TextRange & a,const TextRange & b)51 TextRange operator*(const TextRange& a, const TextRange& b) {
52     if (a.start == b.start && a.end == b.end) return a;
53     auto begin = std::max(a.start, b.start);
54     auto end = std::min(a.end, b.end);
55     return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
56 }
57 
Paragraph(ParagraphStyle style,sk_sp<FontCollection> fonts)58 Paragraph::Paragraph(ParagraphStyle style, sk_sp<FontCollection> fonts)
59             : fFontCollection(std::move(fonts))
60             , fParagraphStyle(std::move(style))
61             , fAlphabeticBaseline(0)
62             , fIdeographicBaseline(0)
63             , fHeight(0)
64             , fWidth(0)
65             , fMaxIntrinsicWidth(0)
66             , fMinIntrinsicWidth(0)
67             , fLongestLine(0)
68             , fExceededMaxLines(0)
69 {
70     SkASSERT(fFontCollection);
71 }
72 
ParagraphImpl(const SkString & text,ParagraphStyle style,TArray<Block,true> blocks,TArray<Placeholder,true> placeholders,sk_sp<FontCollection> fonts,sk_sp<SkUnicode> unicode)73 ParagraphImpl::ParagraphImpl(const SkString& text,
74                              ParagraphStyle style,
75                              TArray<Block, true> blocks,
76                              TArray<Placeholder, true> placeholders,
77                              sk_sp<FontCollection> fonts,
78                              sk_sp<SkUnicode> unicode)
79         : Paragraph(std::move(style), std::move(fonts))
80         , fTextStyles(std::move(blocks))
81         , fPlaceholders(std::move(placeholders))
82         , fText(text)
83         , fState(kUnknown)
84         , fUnresolvedGlyphs(0)
85         , fPicture(nullptr)
86         , fStrutMetrics(false)
87         , fOldWidth(0)
88         , fOldHeight(0)
89         , fUnicode(std::move(unicode))
90         , fHasLineBreaks(false)
91         , fHasWhitespacesInside(false)
92         , fTrailingSpaces(0)
93 {
94     SkASSERT(fUnicode);
95 }
96 
ParagraphImpl(const std::u16string & utf16text,ParagraphStyle style,TArray<Block,true> blocks,TArray<Placeholder,true> placeholders,sk_sp<FontCollection> fonts,sk_sp<SkUnicode> unicode)97 ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
98                              ParagraphStyle style,
99                              TArray<Block, true> blocks,
100                              TArray<Placeholder, true> placeholders,
101                              sk_sp<FontCollection> fonts,
102                              sk_sp<SkUnicode> unicode)
103         : ParagraphImpl(SkString(),
104                         std::move(style),
105                         std::move(blocks),
106                         std::move(placeholders),
107                         std::move(fonts),
108                         std::move(unicode))
109 {
110     SkASSERT(fUnicode);
111     fText =  SkUnicode::convertUtf16ToUtf8(utf16text);
112 }
113 
114 ParagraphImpl::~ParagraphImpl() = default;
115 
unresolvedGlyphs()116 int32_t ParagraphImpl::unresolvedGlyphs() {
117     if (fState < kShaped) {
118         return -1;
119     }
120 
121     return fUnresolvedGlyphs;
122 }
123 
unresolvedCodepoints()124 std::unordered_set<SkUnichar> ParagraphImpl::unresolvedCodepoints() {
125     return fUnresolvedCodepoints;
126 }
127 
addUnresolvedCodepoints(TextRange textRange)128 void ParagraphImpl::addUnresolvedCodepoints(TextRange textRange) {
129     fUnicode->forEachCodepoint(
130         &fText[textRange.start], textRange.width(),
131         [&](SkUnichar unichar, int32_t start, int32_t end, int32_t count) {
132             fUnresolvedCodepoints.emplace(unichar);
133         }
134     );
135 }
136 
layout(SkScalar rawWidth)137 void ParagraphImpl::layout(SkScalar rawWidth) {
138     // TODO: This rounding is done to match Flutter tests. Must be removed...
139     auto floorWidth = rawWidth;
140     if (getApplyRoundingHack()) {
141         floorWidth = SkScalarFloorToScalar(floorWidth);
142     }
143 
144     if ((!SkIsFinite(rawWidth) || fLongestLine <= floorWidth) &&
145         fState >= kLineBroken &&
146          fLines.size() == 1 && fLines.front().ellipsis() == nullptr) {
147         // Most common case: one line of text (and one line is never justified, so no cluster shifts)
148         // We cannot mark it as kLineBroken because the new width can be bigger than the old width
149         fWidth = floorWidth;
150         fState = kShaped;
151     } else if (fState >= kLineBroken && fOldWidth != floorWidth) {
152         // We can use the results from SkShaper but have to do EVERYTHING ELSE again
153         fState = kShaped;
154     } else {
155         // Nothing changed case: we can reuse the data from the last layout
156     }
157 
158     if (fState < kShaped) {
159         // Check if we have the text in the cache and don't need to shape it again
160         if (!fFontCollection->getParagraphCache()->findParagraph(this)) {
161             if (fState < kIndexed) {
162                 // This only happens once at the first layout; the text is immutable
163                 // and there is no reason to repeat it
164                 if (this->computeCodeUnitProperties()) {
165                     fState = kIndexed;
166                 }
167             }
168             this->fRuns.clear();
169             this->fClusters.clear();
170             this->fClustersIndexFromCodeUnit.clear();
171             this->fClustersIndexFromCodeUnit.push_back_n(fText.size() + 1, EMPTY_INDEX);
172             if (!this->shapeTextIntoEndlessLine()) {
173                 this->resetContext();
174                 // TODO: merge the two next calls - they always come together
175                 this->resolveStrut();
176                 this->computeEmptyMetrics();
177                 this->fLines.clear();
178 
179                 // Set the important values that are not zero
180                 fWidth = floorWidth;
181                 fHeight = fEmptyMetrics.height();
182                 if (fParagraphStyle.getStrutStyle().getStrutEnabled() &&
183                     fParagraphStyle.getStrutStyle().getForceStrutHeight()) {
184                     fHeight = fStrutMetrics.height();
185                 }
186                 fAlphabeticBaseline = fEmptyMetrics.alphabeticBaseline();
187                 fIdeographicBaseline = fEmptyMetrics.ideographicBaseline();
188                 fLongestLine = FLT_MIN - FLT_MAX;  // That is what flutter has
189                 fMinIntrinsicWidth = 0;
190                 fMaxIntrinsicWidth = 0;
191                 this->fOldWidth = floorWidth;
192                 this->fOldHeight = this->fHeight;
193 
194                 return;
195             } else {
196                 // Add the paragraph to the cache
197                 fFontCollection->getParagraphCache()->updateParagraph(this);
198             }
199         }
200         fState = kShaped;
201     }
202 
203     if (fState == kShaped) {
204         this->resetContext();
205         this->resolveStrut();
206         this->computeEmptyMetrics();
207         this->fLines.clear();
208         this->breakShapedTextIntoLines(floorWidth);
209         fState = kLineBroken;
210     }
211 
212     if (fState == kLineBroken) {
213         // Build the picture lazily not until we actually have to paint (or never)
214         this->resetShifts();
215         this->formatLines(fWidth);
216         fState = kFormatted;
217     }
218 
219     this->fOldWidth = floorWidth;
220     this->fOldHeight = this->fHeight;
221 
222     if (getApplyRoundingHack()) {
223         // TODO: This rounding is done to match Flutter tests. Must be removed...
224         fMinIntrinsicWidth = littleRound(fMinIntrinsicWidth);
225         fMaxIntrinsicWidth = littleRound(fMaxIntrinsicWidth);
226     }
227 
228     // TODO: This is strictly Flutter thing. Must be factored out into some flutter code
229     if (fParagraphStyle.getMaxLines() == 1 ||
230         (fParagraphStyle.unlimited_lines() && fParagraphStyle.ellipsized())) {
231         fMinIntrinsicWidth = fMaxIntrinsicWidth;
232     }
233 
234     // TODO: Since min and max are calculated differently it's possible to get a rounding error
235     //  that would make min > max. Sort it out later, make it the same for now
236     if (fMaxIntrinsicWidth < fMinIntrinsicWidth) {
237         fMaxIntrinsicWidth = fMinIntrinsicWidth;
238     }
239 
240     //SkDebugf("layout('%s', %f): %f %f\n", fText.c_str(), rawWidth, fMinIntrinsicWidth, fMaxIntrinsicWidth);
241 }
242 
paint(SkCanvas * canvas,SkScalar x,SkScalar y)243 void ParagraphImpl::paint(SkCanvas* canvas, SkScalar x, SkScalar y) {
244     CanvasParagraphPainter painter(canvas);
245     paint(&painter, x, y);
246 }
247 
paint(ParagraphPainter * painter,SkScalar x,SkScalar y)248 void ParagraphImpl::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
249     for (auto& line : fLines) {
250         line.paint(painter, x, y);
251     }
252 }
253 
resetContext()254 void ParagraphImpl::resetContext() {
255     fAlphabeticBaseline = 0;
256     fHeight = 0;
257     fWidth = 0;
258     fIdeographicBaseline = 0;
259     fMaxIntrinsicWidth = 0;
260     fMinIntrinsicWidth = 0;
261     fLongestLine = 0;
262     fMaxWidthWithTrailingSpaces = 0;
263     fExceededMaxLines = false;
264 }
265 
266 // shapeTextIntoEndlessLine is the thing that calls this method
computeCodeUnitProperties()267 bool ParagraphImpl::computeCodeUnitProperties() {
268 
269     if (nullptr == fUnicode) {
270         return false;
271     }
272 
273     // Get bidi regions
274     auto textDirection = fParagraphStyle.getTextDirection() == TextDirection::kLtr
275                               ? SkUnicode::TextDirection::kLTR
276                               : SkUnicode::TextDirection::kRTL;
277     if (!fUnicode->getBidiRegions(fText.c_str(), fText.size(), textDirection, &fBidiRegions)) {
278         return false;
279     }
280 
281     // Collect all spaces and some extra information
282     // (and also substitute \t with a space while we are at it)
283     if (!fUnicode->computeCodeUnitFlags(&fText[0],
284                                         fText.size(),
285                                         this->paragraphStyle().getReplaceTabCharacters(),
286                                         &fCodeUnitProperties)) {
287         return false;
288     }
289 
290     // Get some information about trailing spaces / hard line breaks
291     fTrailingSpaces = fText.size();
292     TextIndex firstWhitespace = EMPTY_INDEX;
293     for (int i = 0; i < fCodeUnitProperties.size(); ++i) {
294         auto flags = fCodeUnitProperties[i];
295         if (SkUnicode::hasPartOfWhiteSpaceBreakFlag(flags)) {
296             if (fTrailingSpaces  == fText.size()) {
297                 fTrailingSpaces = i;
298             }
299             if (firstWhitespace == EMPTY_INDEX) {
300                 firstWhitespace = i;
301             }
302         } else {
303             fTrailingSpaces = fText.size();
304         }
305         if (SkUnicode::hasHardLineBreakFlag(flags)) {
306             fHasLineBreaks = true;
307         }
308     }
309 
310     if (firstWhitespace < fTrailingSpaces) {
311         fHasWhitespacesInside = true;
312     }
313 
314     return true;
315 }
316 
is_ascii_7bit_space(int c)317 static bool is_ascii_7bit_space(int c) {
318     SkASSERT(c >= 0 && c <= 127);
319 
320     // Extracted from https://en.wikipedia.org/wiki/Whitespace_character
321     //
322     enum WS {
323         kHT    = 9,
324         kLF    = 10,
325         kVT    = 11,
326         kFF    = 12,
327         kCR    = 13,
328         kSP    = 32,    // too big to use as shift
329     };
330 #define M(shift)    (1 << (shift))
331     constexpr uint32_t kSpaceMask = M(kHT) | M(kLF) | M(kVT) | M(kFF) | M(kCR);
332     // we check for Space (32) explicitly, since it is too large to shift
333     return (c == kSP) || (c <= 31 && (kSpaceMask & M(c)));
334 #undef M
335 }
336 
Cluster(ParagraphImpl * owner,RunIndex runIndex,size_t start,size_t end,SkSpan<const char> text,SkScalar width,SkScalar height)337 Cluster::Cluster(ParagraphImpl* owner,
338                  RunIndex runIndex,
339                  size_t start,
340                  size_t end,
341                  SkSpan<const char> text,
342                  SkScalar width,
343                  SkScalar height)
344         : fOwner(owner)
345         , fRunIndex(runIndex)
346         , fTextRange(text.begin() - fOwner->text().begin(), text.end() - fOwner->text().begin())
347         , fGraphemeRange(EMPTY_RANGE)
348         , fStart(start)
349         , fEnd(end)
350         , fWidth(width)
351         , fHeight(height)
352         , fHalfLetterSpacing(0.0)
353         , fIsIdeographic(false) {
354     size_t whiteSpacesBreakLen = 0;
355     size_t intraWordBreakLen = 0;
356 
357     const char* ch = text.begin();
358     if (text.end() - ch == 1 && *(const unsigned char*)ch <= 0x7F) {
359         // I am not even sure it's worth it if we do not save a unicode call
360         if (is_ascii_7bit_space(*ch)) {
361             ++whiteSpacesBreakLen;
362         }
363     } else {
364         for (auto i = fTextRange.start; i < fTextRange.end; ++i) {
365             if (fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kPartOfWhiteSpaceBreak)) {
366                 ++whiteSpacesBreakLen;
367             }
368             if (fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kPartOfIntraWordBreak)) {
369                 ++intraWordBreakLen;
370             }
371             if (fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kIdeographic)) {
372                 fIsIdeographic = true;
373             }
374         }
375     }
376 
377     fIsWhiteSpaceBreak = whiteSpacesBreakLen == fTextRange.width();
378     fIsIntraWordBreak = intraWordBreakLen == fTextRange.width();
379     fIsHardBreak = fOwner->codeUnitHasProperty(fTextRange.end,
380                                                SkUnicode::CodeUnitFlags::kHardLineBreakBefore);
381 }
382 
calculateWidth(size_t start,size_t end,bool clip) const383 SkScalar Run::calculateWidth(size_t start, size_t end, bool clip) const {
384     SkASSERT(start <= end);
385     // clip |= end == size();  // Clip at the end of the run?
386     auto correction = 0.0f;
387     if (end > start && !fJustificationShifts.empty()) {
388         // This is not a typo: we are using Point as a pair of SkScalars
389         correction = fJustificationShifts[end - 1].fX -
390                      fJustificationShifts[start].fY;
391     }
392     return posX(end) - posX(start) + correction;
393 }
394 
395 // In some cases we apply spacing to glyphs first and then build the cluster table, in some we do
396 // the opposite - just to optimize the most common case.
applySpacingAndBuildClusterTable()397 void ParagraphImpl::applySpacingAndBuildClusterTable() {
398 
399     // Check all text styles to see what we have to do (if anything)
400     size_t letterSpacingStyles = 0;
401     bool hasWordSpacing = false;
402     for (auto& block : fTextStyles) {
403         if (block.fRange.width() > 0) {
404             if (!SkScalarNearlyZero(block.fStyle.getLetterSpacing())) {
405                 ++letterSpacingStyles;
406             }
407             if (!SkScalarNearlyZero(block.fStyle.getWordSpacing())) {
408                 hasWordSpacing = true;
409             }
410         }
411     }
412 
413     if (letterSpacingStyles == 0 && !hasWordSpacing) {
414         // We don't have to do anything about spacing (most common case)
415         this->buildClusterTable();
416         return;
417     }
418 
419     if (letterSpacingStyles == 1 && !hasWordSpacing && fTextStyles.size() == 1 &&
420         fTextStyles[0].fRange.width() == fText.size() && fRuns.size() == 1) {
421         // We have to letter space the entire paragraph (second most common case)
422         auto& run = fRuns[0];
423         auto& style = fTextStyles[0].fStyle;
424         run.addSpacesEvenly(style.getLetterSpacing());
425         this->buildClusterTable();
426         // This is something Flutter requires
427         for (auto& cluster : fClusters) {
428             cluster.setHalfLetterSpacing(style.getLetterSpacing()/2);
429         }
430         return;
431     }
432 
433     // The complex case: many text styles with spacing (possibly not adjusted to glyphs)
434     this->buildClusterTable();
435 
436     // Walk through all the clusters in the direction of shaped text
437     // (we have to walk through the styles in the same order, too)
438     // Not breaking the iteration on every run!
439     SkScalar shift = 0;
440     bool soFarWhitespacesOnly = true;
441     bool wordSpacingPending = false;
442     Cluster* lastSpaceCluster = nullptr;
443     for (auto& run : fRuns) {
444 
445         // Skip placeholder runs
446         if (run.isPlaceholder()) {
447             continue;
448         }
449 
450         run.iterateThroughClusters([this, &run, &shift, &soFarWhitespacesOnly, &wordSpacingPending, &lastSpaceCluster](Cluster* cluster) {
451             // Shift the cluster (shift collected from the previous clusters)
452             run.shift(cluster, shift);
453 
454             // Synchronize styles (one cluster can be covered by few styles)
455             Block* currentStyle = fTextStyles.begin();
456             while (!cluster->startsIn(currentStyle->fRange)) {
457                 currentStyle++;
458                 SkASSERT(currentStyle != fTextStyles.end());
459             }
460 
461             SkASSERT(!currentStyle->fStyle.isPlaceholder());
462 
463             // Process word spacing
464             if (currentStyle->fStyle.getWordSpacing() != 0) {
465                 if (cluster->isWhitespaceBreak() && cluster->isSoftBreak()) {
466                     if (!soFarWhitespacesOnly) {
467                         lastSpaceCluster = cluster;
468                         wordSpacingPending = true;
469                     }
470                 } else if (wordSpacingPending) {
471                     SkScalar spacing = currentStyle->fStyle.getWordSpacing();
472                     if (cluster->fRunIndex != lastSpaceCluster->fRunIndex) {
473                         // If the last space cluster belongs to the previous run
474                         // we have to extend that cluster and that run
475                         lastSpaceCluster->run().addSpacesAtTheEnd(spacing, lastSpaceCluster);
476                         lastSpaceCluster->run().extend(lastSpaceCluster, spacing);
477                     } else {
478                         run.addSpacesAtTheEnd(spacing, lastSpaceCluster);
479                     }
480 
481                     run.shift(cluster, spacing);
482                     shift += spacing;
483                     wordSpacingPending = false;
484                 }
485             }
486             // Process letter spacing
487             if (currentStyle->fStyle.getLetterSpacing() != 0) {
488                 shift += run.addSpacesEvenly(currentStyle->fStyle.getLetterSpacing(), cluster);
489             }
490 
491             if (soFarWhitespacesOnly && !cluster->isWhitespaceBreak()) {
492                 soFarWhitespacesOnly = false;
493             }
494         });
495     }
496 }
497 
498 // Clusters in the order of the input text
buildClusterTable()499 void ParagraphImpl::buildClusterTable() {
500     // It's possible that one grapheme includes few runs; we cannot handle it
501     // so we break graphemes by the runs instead
502     // It's not the ideal solution and has to be revisited later
503     int cluster_count = 1;
504     for (auto& run : fRuns) {
505         cluster_count += run.isPlaceholder() ? 1 : run.size();
506         fCodeUnitProperties[run.fTextRange.start] |= SkUnicode::CodeUnitFlags::kGraphemeStart;
507         fCodeUnitProperties[run.fTextRange.start] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
508     }
509     if (!fRuns.empty()) {
510         fCodeUnitProperties[fRuns.back().textRange().end] |= SkUnicode::CodeUnitFlags::kGraphemeStart;
511         fCodeUnitProperties[fRuns.back().textRange().end] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
512     }
513     fClusters.reserve_exact(fClusters.size() + cluster_count);
514 
515     // Walk through all the run in the direction of input text
516     for (auto& run : fRuns) {
517         auto runIndex = run.index();
518         auto runStart = fClusters.size();
519         if (run.isPlaceholder()) {
520             // Add info to cluster indexes table (text -> cluster)
521             for (auto i = run.textRange().start; i < run.textRange().end; ++i) {
522               fClustersIndexFromCodeUnit[i] = fClusters.size();
523             }
524             // There are no glyphs but we want to have one cluster
525             fClusters.emplace_back(this, runIndex, 0ul, 1ul, this->text(run.textRange()), run.advance().fX, run.advance().fY);
526             fCodeUnitProperties[run.textRange().start] |= SkUnicode::CodeUnitFlags::kSoftLineBreakBefore;
527             fCodeUnitProperties[run.textRange().end] |= SkUnicode::CodeUnitFlags::kSoftLineBreakBefore;
528         } else {
529             // Walk through the glyph in the direction of input text
530             run.iterateThroughClustersInTextOrder([runIndex, this](size_t glyphStart,
531                                                                    size_t glyphEnd,
532                                                                    size_t charStart,
533                                                                    size_t charEnd,
534                                                                    SkScalar width,
535                                                                    SkScalar height) {
536                 SkASSERT(charEnd >= charStart);
537                 // Add info to cluster indexes table (text -> cluster)
538                 for (auto i = charStart; i < charEnd; ++i) {
539                   fClustersIndexFromCodeUnit[i] = fClusters.size();
540                 }
541                 SkSpan<const char> text(fText.c_str() + charStart, charEnd - charStart);
542                 fClusters.emplace_back(this, runIndex, glyphStart, glyphEnd, text, width, height);
543                 fCodeUnitProperties[charStart] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
544             });
545         }
546         fCodeUnitProperties[run.textRange().start] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
547 
548         run.setClusterRange(runStart, fClusters.size());
549         fMaxIntrinsicWidth += run.advance().fX;
550     }
551     fClustersIndexFromCodeUnit[fText.size()] = fClusters.size();
552     fClusters.emplace_back(this, EMPTY_RUN, 0, 0, this->text({fText.size(), fText.size()}), 0, 0);
553 }
554 
shapeTextIntoEndlessLine()555 bool ParagraphImpl::shapeTextIntoEndlessLine() {
556 
557     if (fText.size() == 0) {
558         return false;
559     }
560 
561     fUnresolvedCodepoints.clear();
562     fFontSwitches.clear();
563 
564     OneLineShaper oneLineShaper(this);
565     auto result = oneLineShaper.shape();
566     fUnresolvedGlyphs = oneLineShaper.unresolvedGlyphs();
567 
568     this->applySpacingAndBuildClusterTable();
569 
570     return result;
571 }
572 
breakShapedTextIntoLines(SkScalar maxWidth)573 void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
574 
575     if (!fHasLineBreaks &&
576         !fHasWhitespacesInside &&
577         fPlaceholders.size() == 1 &&
578         fRuns.size() == 1 && fRuns[0].fAdvance.fX <= maxWidth) {
579         // This is a short version of a line breaking when we know that:
580         // 1. We have only one line of text
581         // 2. It's shaped into a single run
582         // 3. There are no placeholders
583         // 4. There are no linebreaks (which will format text into multiple lines)
584         // 5. There are no whitespaces so the minIntrinsicWidth=maxIntrinsicWidth
585         // (To think about that, the last condition is not quite right;
586         // we should calculate minIntrinsicWidth by soft line breaks.
587         // However, it's how it's done in Flutter now)
588         auto& run = this->fRuns[0];
589         auto advance = run.advance();
590         auto textRange = TextRange(0, this->text().size());
591         auto textExcludingSpaces = TextRange(0, fTrailingSpaces);
592         InternalLineMetrics metrics(this->strutForceHeight());
593         metrics.add(&run);
594         auto disableFirstAscent = this->paragraphStyle().getTextHeightBehavior() &
595                                   TextHeightBehavior::kDisableFirstAscent;
596         auto disableLastDescent = this->paragraphStyle().getTextHeightBehavior() &
597                                   TextHeightBehavior::kDisableLastDescent;
598         if (disableFirstAscent) {
599             metrics.fAscent = metrics.fRawAscent;
600         }
601         if (disableLastDescent) {
602             metrics.fDescent = metrics.fRawDescent;
603         }
604         if (this->strutEnabled()) {
605             this->strutMetrics().updateLineMetrics(metrics);
606         }
607         ClusterIndex trailingSpaces = fClusters.size();
608         do {
609             --trailingSpaces;
610             auto& cluster = fClusters[trailingSpaces];
611             if (!cluster.isWhitespaceBreak()) {
612                 ++trailingSpaces;
613                 break;
614             }
615             advance.fX -= cluster.width();
616         } while (trailingSpaces != 0);
617 
618         advance.fY = metrics.height();
619         auto clusterRange = ClusterRange(0, trailingSpaces);
620         auto clusterRangeWithGhosts = ClusterRange(0, this->clusters().size() - 1);
621         this->addLine(SkPoint::Make(0, 0), advance,
622                       textExcludingSpaces, textRange, textRange,
623                       clusterRange, clusterRangeWithGhosts, run.advance().x(),
624                       metrics);
625 
626         fLongestLine = nearlyZero(advance.fX) ? run.advance().fX : advance.fX;
627         fHeight = advance.fY;
628         fWidth = maxWidth;
629         fMaxIntrinsicWidth = run.advance().fX;
630         fMinIntrinsicWidth = advance.fX;
631         fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline();
632         fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline();
633         fExceededMaxLines = false;
634         return;
635     }
636 
637     TextWrapper textWrapper;
638     textWrapper.breakTextIntoLines(
639             this,
640             maxWidth,
641             [&](TextRange textExcludingSpaces,
642                 TextRange text,
643                 TextRange textWithNewlines,
644                 ClusterRange clusters,
645                 ClusterRange clustersWithGhosts,
646                 SkScalar widthWithSpaces,
647                 size_t startPos,
648                 size_t endPos,
649                 SkVector offset,
650                 SkVector advance,
651                 InternalLineMetrics metrics,
652                 bool addEllipsis) {
653                 // TODO: Take in account clipped edges
654                 auto& line = this->addLine(offset, advance, textExcludingSpaces, text, textWithNewlines, clusters, clustersWithGhosts, widthWithSpaces, metrics);
655                 if (addEllipsis) {
656                     line.createEllipsis(maxWidth, this->getEllipsis(), true);
657                 }
658                 fLongestLine = std::max(fLongestLine, nearlyZero(line.width()) ? widthWithSpaces : line.width());
659             });
660 
661     fHeight = textWrapper.height();
662     fWidth = maxWidth;
663     fMaxIntrinsicWidth = textWrapper.maxIntrinsicWidth();
664     fMinIntrinsicWidth = textWrapper.minIntrinsicWidth();
665     fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline();
666     fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline();
667     fExceededMaxLines = textWrapper.exceededMaxLines();
668 }
669 
formatLines(SkScalar maxWidth)670 void ParagraphImpl::formatLines(SkScalar maxWidth) {
671     auto effectiveAlign = fParagraphStyle.effective_align();
672     const bool isLeftAligned = effectiveAlign == TextAlign::kLeft
673         || (effectiveAlign == TextAlign::kJustify && fParagraphStyle.getTextDirection() == TextDirection::kLtr);
674 
675     if (!SkIsFinite(maxWidth) && !isLeftAligned) {
676         // Special case: clean all text in case of maxWidth == INF & align != left
677         // We had to go through shaping though because we need all the measurement numbers
678         fLines.clear();
679         return;
680     }
681 
682     for (auto& line : fLines) {
683         line.format(effectiveAlign, maxWidth);
684     }
685 }
686 
resolveStrut()687 void ParagraphImpl::resolveStrut() {
688     auto strutStyle = this->paragraphStyle().getStrutStyle();
689     if (!strutStyle.getStrutEnabled() || strutStyle.getFontSize() < 0) {
690         return;
691     }
692 
693     std::vector<sk_sp<SkTypeface>> typefaces = fFontCollection->findTypefaces(strutStyle.getFontFamilies(), strutStyle.getFontStyle(), std::nullopt);
694     if (typefaces.empty()) {
695         SkDEBUGF("Could not resolve strut font\n");
696         return;
697     }
698 
699     SkFont font(typefaces.front(), strutStyle.getFontSize());
700     SkFontMetrics metrics;
701     font.getMetrics(&metrics);
702     const SkScalar strutLeading = strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize();
703 
704     if (strutStyle.getHeightOverride()) {
705         SkScalar strutAscent = 0.0f;
706         SkScalar strutDescent = 0.0f;
707         // The half leading flag doesn't take effect unless there's height override.
708         if (strutStyle.getHalfLeading()) {
709             const auto occupiedHeight = metrics.fDescent - metrics.fAscent;
710             auto flexibleHeight = strutStyle.getHeight() * strutStyle.getFontSize() - occupiedHeight;
711             // Distribute the flexible height evenly over and under.
712             flexibleHeight /= 2;
713             strutAscent = metrics.fAscent - flexibleHeight;
714             strutDescent = metrics.fDescent + flexibleHeight;
715         } else {
716             const SkScalar strutMetricsHeight = metrics.fDescent - metrics.fAscent + metrics.fLeading;
717             const auto strutHeightMultiplier = strutMetricsHeight == 0
718               ? strutStyle.getHeight()
719               : strutStyle.getHeight() * strutStyle.getFontSize() / strutMetricsHeight;
720             strutAscent = metrics.fAscent * strutHeightMultiplier;
721             strutDescent = metrics.fDescent * strutHeightMultiplier;
722         }
723         fStrutMetrics = InternalLineMetrics(
724             strutAscent,
725             strutDescent,
726             strutLeading,
727             metrics.fAscent, metrics.fDescent, metrics.fLeading);
728     } else {
729         fStrutMetrics = InternalLineMetrics(
730                 metrics.fAscent,
731                 metrics.fDescent,
732                 strutLeading);
733     }
734     fStrutMetrics.setForceStrut(this->paragraphStyle().getStrutStyle().getForceStrutHeight());
735 }
736 
findAllBlocks(TextRange textRange)737 BlockRange ParagraphImpl::findAllBlocks(TextRange textRange) {
738     BlockIndex begin = EMPTY_BLOCK;
739     BlockIndex end = EMPTY_BLOCK;
740     for (int index = 0; index < fTextStyles.size(); ++index) {
741         auto& block = fTextStyles[index];
742         if (block.fRange.end <= textRange.start) {
743             continue;
744         }
745         if (block.fRange.start >= textRange.end) {
746             break;
747         }
748         if (begin == EMPTY_BLOCK) {
749             begin = index;
750         }
751         end = index;
752     }
753 
754     if (begin == EMPTY_INDEX || end == EMPTY_INDEX) {
755         // It's possible if some text is not covered with any text style
756         // Not in Flutter but in direct use of SkParagraph
757         return EMPTY_RANGE;
758     }
759 
760     return { begin, end + 1 };
761 }
762 
addLine(SkVector offset,SkVector advance,TextRange textExcludingSpaces,TextRange text,TextRange textIncludingNewLines,ClusterRange clusters,ClusterRange clustersWithGhosts,SkScalar widthWithSpaces,InternalLineMetrics sizes)763 TextLine& ParagraphImpl::addLine(SkVector offset,
764                                  SkVector advance,
765                                  TextRange textExcludingSpaces,
766                                  TextRange text,
767                                  TextRange textIncludingNewLines,
768                                  ClusterRange clusters,
769                                  ClusterRange clustersWithGhosts,
770                                  SkScalar widthWithSpaces,
771                                  InternalLineMetrics sizes) {
772     // Define a list of styles that covers the line
773     auto blocks = findAllBlocks(textExcludingSpaces);
774     return fLines.emplace_back(this, offset, advance, blocks,
775                                textExcludingSpaces, text, textIncludingNewLines,
776                                clusters, clustersWithGhosts, widthWithSpaces, sizes);
777 }
778 
779 // Returns a vector of bounding boxes that enclose all text between
780 // start and end glyph indexes, including start and excluding end
getRectsForRange(unsigned start,unsigned end,RectHeightStyle rectHeightStyle,RectWidthStyle rectWidthStyle)781 std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
782                                                      unsigned end,
783                                                      RectHeightStyle rectHeightStyle,
784                                                      RectWidthStyle rectWidthStyle) {
785     std::vector<TextBox> results;
786     if (fText.isEmpty()) {
787         if (start == 0 && end > 0) {
788             // On account of implied "\n" that is always at the end of the text
789             //SkDebugf("getRectsForRange(%d, %d): %f\n", start, end, fHeight);
790             results.emplace_back(SkRect::MakeXYWH(0, 0, 0, fHeight), fParagraphStyle.getTextDirection());
791         }
792         return results;
793     }
794 
795     this->ensureUTF16Mapping();
796 
797     if (start >= end || start > SkToSizeT(fUTF8IndexForUTF16Index.size()) || end == 0) {
798         return results;
799     }
800 
801     // Adjust the text to grapheme edges
802     // Apparently, text editor CAN move inside graphemes but CANNOT select a part of it.
803     // I don't know why - the solution I have here returns an empty box for every query that
804     // does not contain an end of a grapheme.
805     // Once a cursor is inside a complex grapheme I can press backspace and cause trouble.
806     // To avoid any problems, I will not allow any selection of a part of a grapheme.
807     // One flutter test fails because of it but the editing experience is correct
808     // (although you have to press the cursor many times before it moves to the next grapheme).
809     TextRange text(fText.size(), fText.size());
810     // TODO: This is probably a temp change that makes SkParagraph work as TxtLib
811     //  (so we can compare the results). We now include in the selection box only the graphemes
812     //  that belongs to the given [start:end) range entirely (not the ones that intersect with it)
813     if (start < SkToSizeT(fUTF8IndexForUTF16Index.size())) {
814         auto utf8 = fUTF8IndexForUTF16Index[start];
815         // If start points to a trailing surrogate, skip it
816         if (start > 0 && fUTF8IndexForUTF16Index[start - 1] == utf8) {
817             utf8 = fUTF8IndexForUTF16Index[start + 1];
818         }
819         text.start = this->findNextGraphemeBoundary(utf8);
820     }
821     if (end < SkToSizeT(fUTF8IndexForUTF16Index.size())) {
822         auto utf8 = this->findPreviousGraphemeBoundary(fUTF8IndexForUTF16Index[end]);
823         text.end = utf8;
824     }
825     //SkDebugf("getRectsForRange(%d,%d) -> (%d:%d)\n", start, end, text.start, text.end);
826     for (auto& line : fLines) {
827         auto lineText = line.textWithNewlines();
828         auto intersect = lineText * text;
829         if (intersect.empty() && lineText.start != text.start) {
830             continue;
831         }
832 
833         line.getRectsForRange(intersect, rectHeightStyle, rectWidthStyle, results);
834     }
835 /*
836     SkDebugf("getRectsForRange(%d, %d)\n", start, end);
837     for (auto& r : results) {
838         r.rect.fLeft = littleRound(r.rect.fLeft);
839         r.rect.fRight = littleRound(r.rect.fRight);
840         r.rect.fTop = littleRound(r.rect.fTop);
841         r.rect.fBottom = littleRound(r.rect.fBottom);
842         SkDebugf("[%f:%f * %f:%f]\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom);
843     }
844 */
845     return results;
846 }
847 
getRectsForPlaceholders()848 std::vector<TextBox> ParagraphImpl::getRectsForPlaceholders() {
849   std::vector<TextBox> boxes;
850   if (fText.isEmpty()) {
851        return boxes;
852   }
853   if (fPlaceholders.size() == 1) {
854        // We always have one fake placeholder
855        return boxes;
856   }
857   for (auto& line : fLines) {
858       line.getRectsForPlaceholders(boxes);
859   }
860   /*
861   SkDebugf("getRectsForPlaceholders('%s'): %d\n", fText.c_str(), boxes.size());
862   for (auto& r : boxes) {
863       r.rect.fLeft = littleRound(r.rect.fLeft);
864       r.rect.fRight = littleRound(r.rect.fRight);
865       r.rect.fTop = littleRound(r.rect.fTop);
866       r.rect.fBottom = littleRound(r.rect.fBottom);
867       SkDebugf("[%f:%f * %f:%f] %s\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom,
868                (r.direction == TextDirection::kLtr ? "left" : "right"));
869   }
870   */
871   return boxes;
872 }
873 
874 // TODO: Optimize (save cluster <-> codepoint connection)
getGlyphPositionAtCoordinate(SkScalar dx,SkScalar dy)875 PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) {
876 
877     if (fText.isEmpty()) {
878         return {0, Affinity::kDownstream};
879     }
880 
881     this->ensureUTF16Mapping();
882 
883     for (auto& line : fLines) {
884         // Let's figure out if we can stop looking
885         auto offsetY = line.offset().fY;
886         if (dy >= offsetY + line.height() && &line != &fLines.back()) {
887             // This line is not good enough
888             continue;
889         }
890 
891         // This is so far the the line vertically closest to our coordinates
892         // (or the first one, or the only one - all the same)
893 
894         auto result = line.getGlyphPositionAtCoordinate(dx);
895         //SkDebugf("getGlyphPositionAtCoordinate(%f, %f): %d %s\n", dx, dy, result.position,
896         //   result.affinity == Affinity::kUpstream ? "up" : "down");
897         return result;
898     }
899 
900     return {0, Affinity::kDownstream};
901 }
902 
903 // Finds the first and last glyphs that define a word containing
904 // the glyph at index offset.
905 // By "glyph" they mean a character index - indicated by Minikin's code
getWordBoundary(unsigned offset)906 SkRange<size_t> ParagraphImpl::getWordBoundary(unsigned offset) {
907 
908     if (fWords.empty()) {
909         if (!fUnicode->getWords(fText.c_str(), fText.size(), nullptr, &fWords)) {
910             return {0, 0 };
911         }
912     }
913 
914     int32_t start = 0;
915     int32_t end = 0;
916     for (size_t i = 0; i < fWords.size(); ++i) {
917         auto word = fWords[i];
918         if (word <= offset) {
919             start = word;
920             end = word;
921         } else if (word > offset) {
922             end = word;
923             break;
924         }
925     }
926 
927     //SkDebugf("getWordBoundary(%d): %d - %d\n", offset, start, end);
928     return { SkToU32(start), SkToU32(end) };
929 }
930 
getLineMetrics(std::vector<LineMetrics> & metrics)931 void ParagraphImpl::getLineMetrics(std::vector<LineMetrics>& metrics) {
932     metrics.clear();
933     for (auto& line : fLines) {
934         metrics.emplace_back(line.getMetrics());
935     }
936 }
937 
text(TextRange textRange)938 SkSpan<const char> ParagraphImpl::text(TextRange textRange) {
939     SkASSERT(textRange.start <= fText.size() && textRange.end <= fText.size());
940     auto start = fText.c_str() + textRange.start;
941     return SkSpan<const char>(start, textRange.width());
942 }
943 
clusters(ClusterRange clusterRange)944 SkSpan<Cluster> ParagraphImpl::clusters(ClusterRange clusterRange) {
945     SkASSERT(clusterRange.start < SkToSizeT(fClusters.size()) &&
946              clusterRange.end <= SkToSizeT(fClusters.size()));
947     return SkSpan<Cluster>(&fClusters[clusterRange.start], clusterRange.width());
948 }
949 
cluster(ClusterIndex clusterIndex)950 Cluster& ParagraphImpl::cluster(ClusterIndex clusterIndex) {
951     SkASSERT(clusterIndex < SkToSizeT(fClusters.size()));
952     return fClusters[clusterIndex];
953 }
954 
runByCluster(ClusterIndex clusterIndex)955 Run& ParagraphImpl::runByCluster(ClusterIndex clusterIndex) {
956     auto start = cluster(clusterIndex);
957     return this->run(start.fRunIndex);
958 }
959 
blocks(BlockRange blockRange)960 SkSpan<Block> ParagraphImpl::blocks(BlockRange blockRange) {
961     SkASSERT(blockRange.start < SkToSizeT(fTextStyles.size()) &&
962              blockRange.end <= SkToSizeT(fTextStyles.size()));
963     return SkSpan<Block>(&fTextStyles[blockRange.start], blockRange.width());
964 }
965 
block(BlockIndex blockIndex)966 Block& ParagraphImpl::block(BlockIndex blockIndex) {
967     SkASSERT(blockIndex < SkToSizeT(fTextStyles.size()));
968     return fTextStyles[blockIndex];
969 }
970 
setState(InternalState state)971 void ParagraphImpl::setState(InternalState state) {
972     if (fState <= state) {
973         fState = state;
974         return;
975     }
976 
977     fState = state;
978     switch (fState) {
979         case kUnknown:
980             SkASSERT(false);
981             /*
982             // The text is immutable and so are all the text indexing properties
983             // taken from SkUnicode
984             fCodeUnitProperties.reset();
985             fWords.clear();
986             fBidiRegions.clear();
987             fUTF8IndexForUTF16Index.reset();
988             fUTF16IndexForUTF8Index.reset();
989             */
990             [[fallthrough]];
991 
992         case kIndexed:
993             fRuns.clear();
994             fClusters.clear();
995             [[fallthrough]];
996 
997         case kShaped:
998             fLines.clear();
999             [[fallthrough]];
1000 
1001         case kLineBroken:
1002             fPicture = nullptr;
1003             [[fallthrough]];
1004 
1005         default:
1006             break;
1007     }
1008 }
1009 
computeEmptyMetrics()1010 void ParagraphImpl::computeEmptyMetrics() {
1011 
1012     // The empty metrics is used to define the height of the empty lines
1013     // Unfortunately, Flutter has 2 different cases for that:
1014     // 1. An empty line inside the text
1015     // 2. An empty paragraph
1016     // In the first case SkParagraph takes the metrics from the default paragraph style
1017     // In the second case it should take it from the current text style
1018     bool emptyParagraph = fRuns.empty();
1019     TextStyle textStyle = paragraphStyle().getTextStyle();
1020     if (emptyParagraph && !fTextStyles.empty()) {
1021         textStyle = fTextStyles.back().fStyle;
1022     }
1023 
1024     auto typefaces = fontCollection()->findTypefaces(
1025       textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
1026     auto typeface = typefaces.empty() ? nullptr : typefaces.front();
1027 
1028     SkFont font(typeface, textStyle.getFontSize());
1029     fEmptyMetrics = InternalLineMetrics(font, paragraphStyle().getStrutStyle().getForceStrutHeight());
1030 
1031     if (!paragraphStyle().getStrutStyle().getForceStrutHeight() &&
1032         textStyle.getHeightOverride()) {
1033         const auto intrinsicHeight = fEmptyMetrics.height();
1034         const auto strutHeight = textStyle.getHeight() * textStyle.getFontSize();
1035         if (paragraphStyle().getStrutStyle().getHalfLeading()) {
1036             fEmptyMetrics.update(
1037                 fEmptyMetrics.ascent(),
1038                 fEmptyMetrics.descent(),
1039                 fEmptyMetrics.leading() + strutHeight - intrinsicHeight);
1040         } else {
1041             const auto multiplier = strutHeight / intrinsicHeight;
1042             fEmptyMetrics.update(
1043                 fEmptyMetrics.ascent() * multiplier,
1044                 fEmptyMetrics.descent() * multiplier,
1045                 fEmptyMetrics.leading() * multiplier);
1046         }
1047     }
1048 
1049     if (emptyParagraph) {
1050         // For an empty text we apply both TextHeightBehaviour flags
1051         // In case of non-empty paragraph TextHeightBehaviour flags will be applied at the appropriate place
1052         // We have to do it here because we skip wrapping for an empty text
1053         auto disableFirstAscent = (paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent) == TextHeightBehavior::kDisableFirstAscent;
1054         auto disableLastDescent = (paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent) == TextHeightBehavior::kDisableLastDescent;
1055         fEmptyMetrics.update(
1056             disableFirstAscent ? fEmptyMetrics.rawAscent() : fEmptyMetrics.ascent(),
1057             disableLastDescent ? fEmptyMetrics.rawDescent() : fEmptyMetrics.descent(),
1058             fEmptyMetrics.leading());
1059     }
1060 
1061     if (fParagraphStyle.getStrutStyle().getStrutEnabled()) {
1062         fStrutMetrics.updateLineMetrics(fEmptyMetrics);
1063     }
1064 }
1065 
getEllipsis() const1066 SkString ParagraphImpl::getEllipsis() const {
1067 
1068     auto ellipsis8 = fParagraphStyle.getEllipsis();
1069     auto ellipsis16 = fParagraphStyle.getEllipsisUtf16();
1070     if (!ellipsis8.isEmpty()) {
1071         return ellipsis8;
1072     } else {
1073         return SkUnicode::convertUtf16ToUtf8(fParagraphStyle.getEllipsisUtf16());
1074     }
1075 }
1076 
updateFontSize(size_t from,size_t to,SkScalar fontSize)1077 void ParagraphImpl::updateFontSize(size_t from, size_t to, SkScalar fontSize) {
1078 
1079   SkASSERT(from == 0 && to == fText.size());
1080   auto defaultStyle = fParagraphStyle.getTextStyle();
1081   defaultStyle.setFontSize(fontSize);
1082   fParagraphStyle.setTextStyle(defaultStyle);
1083 
1084   for (auto& textStyle : fTextStyles) {
1085     textStyle.fStyle.setFontSize(fontSize);
1086   }
1087 
1088   fState = std::min(fState, kIndexed);
1089   fOldWidth = 0;
1090   fOldHeight = 0;
1091 }
1092 
updateTextAlign(TextAlign textAlign)1093 void ParagraphImpl::updateTextAlign(TextAlign textAlign) {
1094     fParagraphStyle.setTextAlign(textAlign);
1095 
1096     if (fState >= kLineBroken) {
1097         fState = kLineBroken;
1098     }
1099 }
1100 
updateForegroundPaint(size_t from,size_t to,SkPaint paint)1101 void ParagraphImpl::updateForegroundPaint(size_t from, size_t to, SkPaint paint) {
1102     SkASSERT(from == 0 && to == fText.size());
1103     auto defaultStyle = fParagraphStyle.getTextStyle();
1104     defaultStyle.setForegroundColor(paint);
1105     fParagraphStyle.setTextStyle(defaultStyle);
1106 
1107     for (auto& textStyle : fTextStyles) {
1108         textStyle.fStyle.setForegroundColor(paint);
1109     }
1110 }
1111 
updateBackgroundPaint(size_t from,size_t to,SkPaint paint)1112 void ParagraphImpl::updateBackgroundPaint(size_t from, size_t to, SkPaint paint) {
1113     SkASSERT(from == 0 && to == fText.size());
1114     auto defaultStyle = fParagraphStyle.getTextStyle();
1115     defaultStyle.setBackgroundColor(paint);
1116     fParagraphStyle.setTextStyle(defaultStyle);
1117 
1118     for (auto& textStyle : fTextStyles) {
1119         textStyle.fStyle.setBackgroundColor(paint);
1120     }
1121 }
1122 
countSurroundingGraphemes(TextRange textRange) const1123 TArray<TextIndex> ParagraphImpl::countSurroundingGraphemes(TextRange textRange) const {
1124     textRange = textRange.intersection({0, fText.size()});
1125     TArray<TextIndex> graphemes;
1126     if ((fCodeUnitProperties[textRange.start] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
1127         // Count the previous partial grapheme
1128         graphemes.emplace_back(textRange.start);
1129     }
1130     for (auto index = textRange.start; index < textRange.end; ++index) {
1131         if ((fCodeUnitProperties[index] & SkUnicode::CodeUnitFlags::kGraphemeStart) != 0) {
1132             graphemes.emplace_back(index);
1133         }
1134     }
1135     return graphemes;
1136 }
1137 
findPreviousGraphemeBoundary(TextIndex utf8) const1138 TextIndex ParagraphImpl::findPreviousGraphemeBoundary(TextIndex utf8) const {
1139     while (utf8 > 0 &&
1140           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
1141         --utf8;
1142     }
1143     return utf8;
1144 }
1145 
findNextGraphemeBoundary(TextIndex utf8) const1146 TextIndex ParagraphImpl::findNextGraphemeBoundary(TextIndex utf8) const {
1147     while (utf8 < fText.size() &&
1148           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
1149         ++utf8;
1150     }
1151     return utf8;
1152 }
1153 
findNextGlyphClusterBoundary(TextIndex utf8) const1154 TextIndex ParagraphImpl::findNextGlyphClusterBoundary(TextIndex utf8) const {
1155     while (utf8 < fText.size() &&
1156           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGlyphClusterStart) == 0) {
1157         ++utf8;
1158     }
1159     return utf8;
1160 }
1161 
findPreviousGlyphClusterBoundary(TextIndex utf8) const1162 TextIndex ParagraphImpl::findPreviousGlyphClusterBoundary(TextIndex utf8) const {
1163     while (utf8 > 0 &&
1164           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGlyphClusterStart) == 0) {
1165         --utf8;
1166     }
1167     return utf8;
1168 }
1169 
ensureUTF16Mapping()1170 void ParagraphImpl::ensureUTF16Mapping() {
1171     fillUTF16MappingOnce([&] {
1172         SkUnicode::extractUtfConversionMapping(
1173                 this->text(),
1174                 [&](size_t index) { fUTF8IndexForUTF16Index.emplace_back(index); },
1175                 [&](size_t index) { fUTF16IndexForUTF8Index.emplace_back(index); });
1176     });
1177 }
1178 
visit(const Visitor & visitor)1179 void ParagraphImpl::visit(const Visitor& visitor) {
1180     int lineNumber = 0;
1181     for (auto& line : fLines) {
1182         line.ensureTextBlobCachePopulated();
1183         for (auto& rec : line.fTextBlobCache) {
1184             if (rec.fBlob == nullptr) {
1185                 continue;
1186             }
1187             SkTextBlob::Iter iter(*rec.fBlob);
1188             SkTextBlob::Iter::ExperimentalRun run;
1189 
1190             STArray<128, uint32_t> clusterStorage;
1191             const Run* R = rec.fVisitor_Run;
1192             const uint32_t* clusterPtr = &R->fClusterIndexes[0];
1193 
1194             if (R->fClusterStart > 0) {
1195                 int count = R->fClusterIndexes.size();
1196                 clusterStorage.reset(count);
1197                 for (int i = 0; i < count; ++i) {
1198                     clusterStorage[i] = R->fClusterStart + R->fClusterIndexes[i];
1199                 }
1200                 clusterPtr = &clusterStorage[0];
1201             }
1202             clusterPtr += rec.fVisitor_Pos;
1203 
1204             while (iter.experimentalNext(&run)) {
1205                 const Paragraph::VisitorInfo info = {
1206                     run.font,
1207                     rec.fOffset,
1208                     rec.fClipRect.fRight,
1209                     run.count,
1210                     run.glyphs,
1211                     run.positions,
1212                     clusterPtr,
1213                     0,  // flags
1214                 };
1215                 visitor(lineNumber, &info);
1216                 clusterPtr += run.count;
1217             }
1218         }
1219         visitor(lineNumber, nullptr);   // signal end of line
1220         lineNumber += 1;
1221     }
1222 }
1223 
getLineNumberAt(TextIndex codeUnitIndex) const1224 int ParagraphImpl::getLineNumberAt(TextIndex codeUnitIndex) const {
1225     if (codeUnitIndex >= fText.size()) {
1226         return -1;
1227     }
1228     size_t startLine = 0;
1229     size_t endLine = fLines.size() - 1;
1230     if (fLines.empty() || fLines[endLine].textWithNewlines().end <= codeUnitIndex) {
1231         return -1;
1232     }
1233 
1234     while (endLine > startLine) {
1235         // startLine + 1 <= endLine, so we have startLine <= midLine <= endLine - 1.
1236         const size_t midLine = (endLine + startLine) / 2;
1237         const TextRange midLineRange = fLines[midLine].textWithNewlines();
1238         if (codeUnitIndex < midLineRange.start) {
1239             endLine = midLine - 1;
1240         } else if (midLineRange.end <= codeUnitIndex) {
1241             startLine = midLine + 1;
1242         } else {
1243             return midLine;
1244         }
1245     }
1246     SkASSERT(startLine == endLine);
1247     return startLine;
1248 }
1249 
getLineNumberAtUTF16Offset(size_t codeUnitIndex)1250 int ParagraphImpl::getLineNumberAtUTF16Offset(size_t codeUnitIndex) {
1251     this->ensureUTF16Mapping();
1252     if (codeUnitIndex >= SkToSizeT(fUTF8IndexForUTF16Index.size())) {
1253         return -1;
1254     }
1255     const TextIndex utf8 = fUTF8IndexForUTF16Index[codeUnitIndex];
1256     return getLineNumberAt(utf8);
1257 }
1258 
getLineMetricsAt(int lineNumber,LineMetrics * lineMetrics) const1259 bool ParagraphImpl::getLineMetricsAt(int lineNumber, LineMetrics* lineMetrics) const {
1260     if (lineNumber < 0 || lineNumber >= fLines.size()) {
1261         return false;
1262     }
1263     auto& line = fLines[lineNumber];
1264     if (lineMetrics) {
1265         *lineMetrics = line.getMetrics();
1266     }
1267     return true;
1268 }
1269 
getActualTextRange(int lineNumber,bool includeSpaces) const1270 TextRange ParagraphImpl::getActualTextRange(int lineNumber, bool includeSpaces) const {
1271     if (lineNumber < 0 || lineNumber >= fLines.size()) {
1272         return EMPTY_TEXT;
1273     }
1274     auto& line = fLines[lineNumber];
1275     return includeSpaces ? line.text() : line.trimmedText();
1276 }
1277 
getGlyphClusterAt(TextIndex codeUnitIndex,GlyphClusterInfo * glyphInfo)1278 bool ParagraphImpl::getGlyphClusterAt(TextIndex codeUnitIndex, GlyphClusterInfo* glyphInfo) {
1279     const int lineNumber = getLineNumberAt(codeUnitIndex);
1280     if (lineNumber == -1) {
1281         return false;
1282     }
1283     auto& line = fLines[lineNumber];
1284     for (auto c = line.clustersWithSpaces().start; c < line.clustersWithSpaces().end; ++c) {
1285         auto& cluster = fClusters[c];
1286         if (cluster.contains(codeUnitIndex)) {
1287             std::vector<TextBox> boxes;
1288             line.getRectsForRange(cluster.textRange(),
1289                                     RectHeightStyle::kTight,
1290                                     RectWidthStyle::kTight,
1291                                     boxes);
1292             if (!boxes.empty()) {
1293                 if (glyphInfo) {
1294                     *glyphInfo = {boxes[0].rect, cluster.textRange(), boxes[0].direction};
1295                 }
1296                 return true;
1297             }
1298         }
1299     }
1300     return false;
1301 }
1302 
getClosestGlyphClusterAt(SkScalar dx,SkScalar dy,GlyphClusterInfo * glyphInfo)1303 bool ParagraphImpl::getClosestGlyphClusterAt(SkScalar dx,
1304                                              SkScalar dy,
1305                                              GlyphClusterInfo* glyphInfo) {
1306     const PositionWithAffinity res = this->getGlyphPositionAtCoordinate(dx, dy);
1307     SkASSERT(res.position != 0 || res.affinity != Affinity::kUpstream);
1308     const size_t utf16Offset = res.position + (res.affinity == Affinity::kDownstream ? 0 : -1);
1309     this->ensureUTF16Mapping();
1310     SkASSERT(utf16Offset < SkToSizeT(fUTF8IndexForUTF16Index.size()));
1311     return this->getGlyphClusterAt(fUTF8IndexForUTF16Index[utf16Offset], glyphInfo);
1312 }
1313 
getGlyphInfoAtUTF16Offset(size_t codeUnitIndex,GlyphInfo * glyphInfo)1314 bool ParagraphImpl::getGlyphInfoAtUTF16Offset(size_t codeUnitIndex, GlyphInfo* glyphInfo) {
1315     this->ensureUTF16Mapping();
1316     if (codeUnitIndex >= SkToSizeT(fUTF8IndexForUTF16Index.size())) {
1317         return false;
1318     }
1319     const TextIndex utf8 = fUTF8IndexForUTF16Index[codeUnitIndex];
1320     const int lineNumber = getLineNumberAt(utf8);
1321     if (lineNumber == -1) {
1322         return false;
1323     }
1324     if (glyphInfo == nullptr) {
1325         return true;
1326     }
1327     const TextLine& line = fLines[lineNumber];
1328     const TextIndex startIndex = findPreviousGraphemeBoundary(utf8);
1329     const TextIndex endIndex = findNextGraphemeBoundary(utf8 + 1);
1330     const ClusterIndex glyphClusterIndex = clusterIndex(utf8);
1331     const Cluster& glyphCluster = cluster(glyphClusterIndex);
1332 
1333     // `startIndex` and `endIndex` must be on the same line.
1334     std::vector<TextBox> boxes;
1335     line.getRectsForRange({startIndex, endIndex}, RectHeightStyle::kTight, RectWidthStyle::kTight, boxes);
1336     // TODO: currently placeholders with height=0 and width=0 are ignored so boxes
1337     // can be empty. These placeholders should still be reported for their
1338     // offset information.
1339     if (glyphInfo && !boxes.empty()) {
1340         *glyphInfo = {
1341             boxes[0].rect,
1342             { fUTF16IndexForUTF8Index[startIndex], fUTF16IndexForUTF8Index[endIndex] },
1343             boxes[0].direction,
1344             glyphCluster.run().isEllipsis(),
1345         };
1346     }
1347     return true;
1348 }
1349 
getClosestUTF16GlyphInfoAt(SkScalar dx,SkScalar dy,GlyphInfo * glyphInfo)1350 bool ParagraphImpl::getClosestUTF16GlyphInfoAt(SkScalar dx, SkScalar dy, GlyphInfo* glyphInfo) {
1351     const PositionWithAffinity res = this->getGlyphPositionAtCoordinate(dx, dy);
1352     SkASSERT(res.position != 0 || res.affinity != Affinity::kUpstream);
1353     const size_t utf16Offset = res.position + (res.affinity == Affinity::kDownstream ? 0 : -1);
1354     return getGlyphInfoAtUTF16Offset(utf16Offset, glyphInfo);
1355 }
1356 
getFontAt(TextIndex codeUnitIndex) const1357 SkFont ParagraphImpl::getFontAt(TextIndex codeUnitIndex) const {
1358     for (auto& run : fRuns) {
1359         const auto textRange = run.textRange();
1360         if (textRange.start <= codeUnitIndex && codeUnitIndex < textRange.end) {
1361             return run.font();
1362         }
1363     }
1364     return SkFont();
1365 }
1366 
getFontAtUTF16Offset(size_t codeUnitIndex)1367 SkFont ParagraphImpl::getFontAtUTF16Offset(size_t codeUnitIndex) {
1368     ensureUTF16Mapping();
1369     if (codeUnitIndex >= SkToSizeT(fUTF8IndexForUTF16Index.size())) {
1370         return SkFont();
1371     }
1372     const TextIndex utf8 = fUTF8IndexForUTF16Index[codeUnitIndex];
1373     for (auto& run : fRuns) {
1374         const auto textRange = run.textRange();
1375         if (textRange.start <= utf8 && utf8 < textRange.end) {
1376             return run.font();
1377         }
1378     }
1379     return SkFont();
1380 }
1381 
getFonts() const1382 std::vector<Paragraph::FontInfo> ParagraphImpl::getFonts() const {
1383     std::vector<FontInfo> results;
1384     for (auto& run : fRuns) {
1385         results.emplace_back(run.font(), run.textRange());
1386     }
1387     return results;
1388 }
1389 
extendedVisit(const ExtendedVisitor & visitor)1390 void ParagraphImpl::extendedVisit(const ExtendedVisitor& visitor) {
1391     int lineNumber = 0;
1392     for (auto& line : fLines) {
1393         line.iterateThroughVisualRuns(
1394             false,
1395             [&](const Run* run,
1396                 SkScalar runOffsetInLine,
1397                 TextRange textRange,
1398                 SkScalar* runWidthInLine) {
1399                 *runWidthInLine = line.iterateThroughSingleRunByStyles(
1400                 TextLine::TextAdjustment::GlyphCluster,
1401                 run,
1402                 runOffsetInLine,
1403                 textRange,
1404                 StyleType::kNone,
1405                 [&](TextRange textRange,
1406                     const TextStyle& style,
1407                     const TextLine::ClipContext& context) {
1408                     SkScalar correctedBaseline = SkScalarFloorToScalar(
1409                         line.baseline() + style.getBaselineShift() + 0.5);
1410                     SkPoint offset =
1411                         SkPoint::Make(line.offset().fX + context.fTextShift,
1412                                       line.offset().fY + correctedBaseline);
1413                     SkRect rect = context.clip.makeOffset(line.offset());
1414                     AutoSTArray<16, SkRect> glyphBounds;
1415                     glyphBounds.reset(SkToInt(run->size()));
1416                     run->font().getBounds(run->glyphs().data(),
1417                                           SkToInt(run->size()),
1418                                           glyphBounds.data(),
1419                                           nullptr);
1420                     STArray<128, uint32_t> clusterStorage;
1421                     const uint32_t* clusterPtr = run->clusterIndexes().data();
1422                     if (run->fClusterStart > 0) {
1423                         clusterStorage.reset(context.size);
1424                         for (size_t i = 0; i < context.size; ++i) {
1425                           clusterStorage[i] =
1426                               run->fClusterStart + run->fClusterIndexes[i];
1427                         }
1428                         clusterPtr = &clusterStorage[0];
1429                     }
1430                     const Paragraph::ExtendedVisitorInfo info = {
1431                         run->font(),
1432                         offset,
1433                         SkSize::Make(rect.width(), rect.height()),
1434                         SkToS16(context.size),
1435                         &run->glyphs()[context.pos],
1436                         &run->fPositions[context.pos],
1437                         &glyphBounds[context.pos],
1438                         clusterPtr,
1439                         0,  // flags
1440                     };
1441                     visitor(lineNumber, &info);
1442                 });
1443             return true;
1444             });
1445         visitor(lineNumber, nullptr);   // signal end of line
1446         lineNumber += 1;
1447     }
1448 }
1449 
getPath(int lineNumber,SkPath * dest)1450 int ParagraphImpl::getPath(int lineNumber, SkPath* dest) {
1451     int notConverted = 0;
1452     auto& line = fLines[lineNumber];
1453     line.iterateThroughVisualRuns(
1454               false,
1455               [&](const Run* run,
1456                   SkScalar runOffsetInLine,
1457                   TextRange textRange,
1458                   SkScalar* runWidthInLine) {
1459           *runWidthInLine = line.iterateThroughSingleRunByStyles(
1460           TextLine::TextAdjustment::GlyphCluster,
1461           run,
1462           runOffsetInLine,
1463           textRange,
1464           StyleType::kNone,
1465           [&](TextRange textRange,
1466               const TextStyle& style,
1467               const TextLine::ClipContext& context) {
1468               const SkFont& font = run->font();
1469               SkScalar correctedBaseline = SkScalarFloorToScalar(
1470                 line.baseline() + style.getBaselineShift() + 0.5);
1471               SkPoint offset =
1472                   SkPoint::Make(line.offset().fX + context.fTextShift,
1473                                 line.offset().fY + correctedBaseline);
1474               SkRect rect = context.clip.makeOffset(offset);
1475               struct Rec {
1476                   SkPath* fPath;
1477                   SkPoint fOffset;
1478                   const SkPoint* fPos;
1479                   int fNotConverted;
1480               } rec =
1481                   {dest, SkPoint::Make(rect.left(), rect.top()),
1482                    &run->positions()[context.pos], 0};
1483               font.getPaths(&run->glyphs()[context.pos], context.size,
1484                     [](const SkPath* path, const SkMatrix& mx, void* ctx) {
1485                         Rec* rec = reinterpret_cast<Rec*>(ctx);
1486                         if (path) {
1487                             SkMatrix total = mx;
1488                             total.postTranslate(rec->fPos->fX + rec->fOffset.fX,
1489                                                 rec->fPos->fY + rec->fOffset.fY);
1490                             rec->fPath->addPath(*path, total);
1491                         } else {
1492                             rec->fNotConverted++;
1493                         }
1494                         rec->fPos += 1; // move to the next glyph's position
1495                     }, &rec);
1496               notConverted += rec.fNotConverted;
1497           });
1498         return true;
1499     });
1500 
1501     return notConverted;
1502 }
1503 
GetPath(SkTextBlob * textBlob)1504 SkPath Paragraph::GetPath(SkTextBlob* textBlob) {
1505     SkPath path;
1506     SkTextBlobRunIterator iter(textBlob);
1507     while (!iter.done()) {
1508         SkFont font = iter.font();
1509         struct Rec { SkPath* fDst; SkPoint fOffset; const SkPoint* fPos; } rec =
1510             {&path, {textBlob->bounds().left(), textBlob->bounds().top()},
1511              iter.points()};
1512         font.getPaths(iter.glyphs(), iter.glyphCount(),
1513             [](const SkPath* src, const SkMatrix& mx, void* ctx) {
1514                 Rec* rec = (Rec*)ctx;
1515                 if (src) {
1516                     SkMatrix tmp(mx);
1517                     tmp.postTranslate(rec->fPos->fX - rec->fOffset.fX,
1518                                       rec->fPos->fY - rec->fOffset.fY);
1519                     rec->fDst->addPath(*src, tmp);
1520                 }
1521                 rec->fPos += 1;
1522             },
1523             &rec);
1524         iter.next();
1525     }
1526     return path;
1527 }
1528 
containsEmoji(SkTextBlob * textBlob)1529 bool ParagraphImpl::containsEmoji(SkTextBlob* textBlob) {
1530     bool result = false;
1531     SkTextBlobRunIterator iter(textBlob);
1532     while (!iter.done() && !result) {
1533         // Walk through all the text by codepoints
1534         this->getUnicode()->forEachCodepoint(iter.text(), iter.textSize(),
1535            [&](SkUnichar unichar, int32_t start, int32_t end, int32_t count) {
1536                 if (this->getUnicode()->isEmoji(unichar)) {
1537                     result = true;
1538                 }
1539             });
1540         iter.next();
1541     }
1542     return result;
1543 }
1544 
containsColorFontOrBitmap(SkTextBlob * textBlob)1545 bool ParagraphImpl::containsColorFontOrBitmap(SkTextBlob* textBlob) {
1546     SkTextBlobRunIterator iter(textBlob);
1547     bool flag = false;
1548     while (!iter.done() && !flag) {
1549         iter.font().getPaths(
1550             (const SkGlyphID*) iter.glyphs(),
1551             iter.glyphCount(),
1552             [](const SkPath* path, const SkMatrix& mx, void* ctx) {
1553                 if (path == nullptr) {
1554                     bool* flag1 = (bool*)ctx;
1555                     *flag1 = true;
1556                 }
1557             }, &flag);
1558         iter.next();
1559     }
1560     return flag;
1561 }
1562 
1563 }  // namespace textlayout
1564 }  // namespace skia
1565