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