Coverage Report

Created: 2024-05-20 07:14

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