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