Coverage Report

Created: 2024-09-14 07:19

/src/skia/modules/skparagraph/src/TextLine.cpp
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2019 Google LLC.
2
3
#include "modules/skparagraph/src/TextLine.h"
4
5
#include "include/core/SkBlurTypes.h"
6
#include "include/core/SkFont.h"
7
#include "include/core/SkFontMetrics.h"
8
#include "include/core/SkMaskFilter.h"
9
#include "include/core/SkPaint.h"
10
#include "include/core/SkSpan.h"
11
#include "include/core/SkString.h"
12
#include "include/core/SkTextBlob.h"
13
#include "include/core/SkTypes.h"
14
#include "include/private/base/SkTemplates.h"
15
#include "include/private/base/SkTo.h"
16
#include "modules/skparagraph/include/DartTypes.h"
17
#include "modules/skparagraph/include/Metrics.h"
18
#include "modules/skparagraph/include/ParagraphPainter.h"
19
#include "modules/skparagraph/include/ParagraphStyle.h"
20
#include "modules/skparagraph/include/TextShadow.h"
21
#include "modules/skparagraph/include/TextStyle.h"
22
#include "modules/skparagraph/src/Decorations.h"
23
#include "modules/skparagraph/src/ParagraphImpl.h"
24
#include "modules/skparagraph/src/ParagraphPainterImpl.h"
25
#include "modules/skshaper/include/SkShaper.h"
26
#include "modules/skshaper/include/SkShaper_harfbuzz.h"
27
#include "modules/skshaper/include/SkShaper_skunicode.h"
28
29
#include <algorithm>
30
#include <iterator>
31
#include <limits>
32
#include <map>
33
#include <memory>
34
#include <tuple>
35
#include <type_traits>
36
#include <utility>
37
38
using namespace skia_private;
39
40
namespace skia {
41
namespace textlayout {
42
43
namespace {
44
45
// TODO: deal with all the intersection functionality
46
0
TextRange intersected(const TextRange& a, const TextRange& b) {
47
0
    if (a.start == b.start && a.end == b.end) return a;
48
0
    auto begin = std::max(a.start, b.start);
49
0
    auto end = std::min(a.end, b.end);
50
0
    return end >= begin ? TextRange(begin, end) : EMPTY_TEXT;
51
0
}
52
53
0
SkScalar littleRound(SkScalar a) {
54
    // This rounding is done to match Flutter tests. Must be removed..
55
0
  return SkScalarRoundToScalar(a * 100.0)/100.0;
56
0
}
57
58
0
TextRange operator*(const TextRange& a, const TextRange& b) {
59
0
    if (a.start == b.start && a.end == b.end) return a;
60
0
    auto begin = std::max(a.start, b.start);
61
0
    auto end = std::min(a.end, b.end);
62
0
    return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
63
0
}
64
65
0
int compareRound(SkScalar a, SkScalar b, bool applyRoundingHack) {
66
    // There is a rounding error that gets bigger when maxWidth gets bigger
67
    // VERY long zalgo text (> 100000) on a VERY long line (> 10000)
68
    // Canvas scaling affects it
69
    // Letter spacing affects it
70
    // It has to be relative to be useful
71
0
    auto base = std::max(SkScalarAbs(a), SkScalarAbs(b));
72
0
    auto diff = SkScalarAbs(a - b);
73
0
    if (nearlyZero(base) || diff / base < 0.001f) {
74
0
        return 0;
75
0
    }
76
77
0
    auto ra = a;
78
0
    auto rb = b;
79
80
0
    if (applyRoundingHack) {
81
0
        ra = littleRound(a);
82
0
        rb = littleRound(b);
83
0
    }
84
0
    if (ra < rb) {
85
0
        return -1;
86
0
    } else {
87
0
        return 1;
88
0
    }
89
0
}
90
91
}  // namespace
92
93
TextLine::TextLine(ParagraphImpl* owner,
94
                   SkVector offset,
95
                   SkVector advance,
96
                   BlockRange blocks,
97
                   TextRange textExcludingSpaces,
98
                   TextRange text,
99
                   TextRange textIncludingNewlines,
100
                   ClusterRange clusters,
101
                   ClusterRange clustersWithGhosts,
102
                   SkScalar widthWithSpaces,
103
                   InternalLineMetrics sizes)
104
        : fOwner(owner)
105
        , fBlockRange(blocks)
106
        , fTextExcludingSpaces(textExcludingSpaces)
107
        , fText(text)
108
        , fTextIncludingNewlines(textIncludingNewlines)
109
        , fClusterRange(clusters)
110
        , fGhostClusterRange(clustersWithGhosts)
111
        , fRunsInVisualOrder()
112
        , fAdvance(advance)
113
        , fOffset(offset)
114
        , fShift(0.0)
115
        , fWidthWithSpaces(widthWithSpaces)
116
        , fEllipsis(nullptr)
117
        , fSizes(sizes)
118
        , fHasBackground(false)
119
        , fHasShadows(false)
120
        , fHasDecorations(false)
121
        , fAscentStyle(LineMetricStyle::CSS)
122
        , fDescentStyle(LineMetricStyle::CSS)
123
0
        , fTextBlobCachePopulated(false) {
124
    // Reorder visual runs
125
0
    auto& start = owner->cluster(fGhostClusterRange.start);
126
0
    auto& end = owner->cluster(fGhostClusterRange.end - 1);
127
0
    size_t numRuns = end.runIndex() - start.runIndex() + 1;
128
129
0
    for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
130
0
        auto b = fOwner->styles().begin() + index;
131
0
        if (b->fStyle.hasBackground()) {
132
0
            fHasBackground = true;
133
0
        }
134
0
        if (b->fStyle.getDecorationType() != TextDecoration::kNoDecoration) {
135
0
            fHasDecorations = true;
136
0
        }
137
0
        if (b->fStyle.getShadowNumber() > 0) {
138
0
            fHasShadows = true;
139
0
        }
140
0
    }
141
142
    // Get the logical order
143
144
    // This is just chosen to catch the common/fast cases. Feel free to tweak.
145
0
    constexpr int kPreallocCount = 4;
146
0
    AutoSTArray<kPreallocCount, SkUnicode::BidiLevel> runLevels(numRuns);
147
0
    std::vector<RunIndex> placeholdersInOriginalOrder;
148
0
    size_t runLevelsIndex = 0;
149
    // Placeholders must be laid out using the original order in which they were added
150
    // in the input. The API does not provide a way to indicate that a placeholder
151
    // position was moved due to bidi reordering.
152
0
    for (auto runIndex = start.runIndex(); runIndex <= end.runIndex(); ++runIndex) {
153
0
        auto& run = fOwner->run(runIndex);
154
0
        runLevels[runLevelsIndex++] = run.fBidiLevel;
155
0
        fMaxRunMetrics.add(
156
0
            InternalLineMetrics(run.correctAscent(), run.correctDescent(), run.fFontMetrics.fLeading));
157
0
        if (run.isPlaceholder()) {
158
0
            placeholdersInOriginalOrder.push_back(runIndex);
159
0
        }
160
0
    }
161
0
    SkASSERT(runLevelsIndex == numRuns);
162
163
0
    AutoSTArray<kPreallocCount, int32_t> logicalOrder(numRuns);
164
165
    // TODO: hide all these logic in SkUnicode?
166
0
    fOwner->getUnicode()->reorderVisual(runLevels.data(), numRuns, logicalOrder.data());
167
0
    auto firstRunIndex = start.runIndex();
168
0
    auto placeholderIter = placeholdersInOriginalOrder.begin();
169
0
    for (auto index : logicalOrder) {
170
0
        auto runIndex = firstRunIndex + index;
171
0
        if (fOwner->run(runIndex).isPlaceholder()) {
172
0
            fRunsInVisualOrder.push_back(*placeholderIter++);
173
0
        } else {
174
0
            fRunsInVisualOrder.push_back(runIndex);
175
0
        }
176
0
    }
177
178
    // TODO: This is the fix for flutter. Must be removed...
179
0
    for (auto cluster = &start; cluster <= &end; ++cluster) {
180
0
        if (!cluster->run().isPlaceholder()) {
181
0
            fShift += cluster->getHalfLetterSpacing();
182
0
            break;
183
0
        }
184
0
    }
185
0
}
186
187
0
void TextLine::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
188
0
    if (fHasBackground) {
189
0
        this->iterateThroughVisualRuns(false,
190
0
            [painter, x, y, this]
191
0
            (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
192
0
                *runWidthInLine = this->iterateThroughSingleRunByStyles(
193
0
                TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kBackground,
194
0
                [painter, x, y, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
195
0
                    this->paintBackground(painter, x, y, textRange, style, context);
196
0
                });
197
0
            return true;
198
0
            });
199
0
    }
200
201
0
    if (fHasShadows) {
202
0
        this->iterateThroughVisualRuns(false,
203
0
            [painter, x, y, this]
204
0
            (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
205
0
            *runWidthInLine = this->iterateThroughSingleRunByStyles(
206
0
                TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kShadow,
207
0
                [painter, x, y, this]
208
0
                (TextRange textRange, const TextStyle& style, const ClipContext& context) {
209
0
                    this->paintShadow(painter, x, y, textRange, style, context);
210
0
                });
211
0
            return true;
212
0
            });
213
0
    }
214
215
0
    this->ensureTextBlobCachePopulated();
216
217
0
    for (auto& record : fTextBlobCache) {
218
0
        record.paint(painter, x, y);
219
0
    }
220
221
0
    if (fHasDecorations) {
222
0
        this->iterateThroughVisualRuns(false,
223
0
            [painter, x, y, this]
224
0
            (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
225
0
                *runWidthInLine = this->iterateThroughSingleRunByStyles(
226
0
                TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kDecorations,
227
0
                [painter, x, y, this]
228
0
                (TextRange textRange, const TextStyle& style, const ClipContext& context) {
229
0
                    this->paintDecorations(painter, x, y, textRange, style, context);
230
0
                });
231
0
                return true;
232
0
        });
233
0
    }
234
0
}
235
236
0
void TextLine::ensureTextBlobCachePopulated() {
237
0
    if (fTextBlobCachePopulated) {
238
0
        return;
239
0
    }
240
0
    if (fBlockRange.width() == 1 &&
241
0
        fRunsInVisualOrder.size() == 1 &&
242
0
        fEllipsis == nullptr &&
243
0
        fOwner->run(fRunsInVisualOrder[0]).placeholderStyle() == nullptr) {
244
0
        if (fClusterRange.width() == 0) {
245
0
            return;
246
0
        }
247
        // Most common and most simple case
248
0
        const auto& style = fOwner->block(fBlockRange.start).fStyle;
249
0
        const auto& run = fOwner->run(fRunsInVisualOrder[0]);
250
0
        auto clip = SkRect::MakeXYWH(0.0f, this->sizes().runTop(&run, this->fAscentStyle),
251
0
                                     fAdvance.fX,
252
0
                                     run.calculateHeight(this->fAscentStyle, this->fDescentStyle));
253
254
0
        auto& start = fOwner->cluster(fClusterRange.start);
255
0
        auto& end = fOwner->cluster(fClusterRange.end - 1);
256
0
        SkASSERT(start.runIndex() == end.runIndex());
257
0
        GlyphRange glyphs;
258
0
        if (run.leftToRight()) {
259
0
            glyphs = GlyphRange(start.startPos(),
260
0
                                end.isHardBreak() ? end.startPos() : end.endPos());
261
0
        } else {
262
0
            glyphs = GlyphRange(end.startPos(),
263
0
                                start.isHardBreak() ? start.startPos() : start.endPos());
264
0
        }
265
0
        ClipContext context = {/*run=*/&run,
266
0
                               /*pos=*/glyphs.start,
267
0
                               /*size=*/glyphs.width(),
268
0
                               /*fTextShift=*/-run.positionX(glyphs.start), // starting position
269
0
                               /*clip=*/clip,                               // entire line
270
0
                               /*fExcludedTrailingSpaces=*/0.0f,            // no need for that
271
0
                               /*clippingNeeded=*/false};                   // no need for that
272
0
        this->buildTextBlob(fTextExcludingSpaces, style, context);
273
0
    } else {
274
0
        this->iterateThroughVisualRuns(false,
275
0
           [this](const Run* run,
276
0
                  SkScalar runOffsetInLine,
277
0
                  TextRange textRange,
278
0
                  SkScalar* runWidthInLine) {
279
0
               if (run->placeholderStyle() != nullptr) {
280
0
                   *runWidthInLine = run->advance().fX;
281
0
                   return true;
282
0
               }
283
0
               *runWidthInLine = this->iterateThroughSingleRunByStyles(
284
0
                   TextAdjustment::GlyphCluster,
285
0
                   run,
286
0
                   runOffsetInLine,
287
0
                   textRange,
288
0
                   StyleType::kForeground,
289
0
                   [this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
290
0
                       this->buildTextBlob(textRange, style, context);
291
0
                   });
292
0
               return true;
293
0
           });
294
0
    }
295
0
    fTextBlobCachePopulated = true;
296
0
}
297
298
0
void TextLine::format(TextAlign align, SkScalar maxWidth) {
299
0
    SkScalar delta = maxWidth - this->width();
300
0
    if (delta <= 0) {
301
0
        return;
302
0
    }
303
304
    // We do nothing for left align
305
0
    if (align == TextAlign::kJustify) {
306
0
        if (!this->endsWithHardLineBreak()) {
307
0
            this->justify(maxWidth);
308
0
        } else if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
309
            // Justify -> Right align
310
0
            fShift = delta;
311
0
        }
312
0
    } else if (align == TextAlign::kRight) {
313
0
        fShift = delta;
314
0
    } else if (align == TextAlign::kCenter) {
315
0
        fShift = delta / 2;
316
0
    }
317
0
}
318
319
0
void TextLine::scanStyles(StyleType styleType, const RunStyleVisitor& visitor) {
320
0
    if (this->empty()) {
321
0
        return;
322
0
    }
323
324
0
    this->iterateThroughVisualRuns(
325
0
            false,
326
0
            [this, visitor, styleType](
327
0
                    const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width) {
328
0
                *width = this->iterateThroughSingleRunByStyles(
329
0
                        TextAdjustment::GlyphCluster,
330
0
                        run,
331
0
                        runOffset,
332
0
                        textRange,
333
0
                        styleType,
334
0
                        [visitor](TextRange textRange,
335
0
                                  const TextStyle& style,
336
0
                                  const ClipContext& context) {
337
0
                            visitor(textRange, style, context);
338
0
                        });
339
0
                return true;
340
0
            });
341
0
}
342
343
0
SkRect TextLine::extendHeight(const ClipContext& context) const {
344
0
    SkRect result = context.clip;
345
0
    result.fBottom += std::max(this->fMaxRunMetrics.height() - this->height(), 0.0f);
346
0
    return result;
347
0
}
348
349
0
void TextLine::buildTextBlob(TextRange textRange, const TextStyle& style, const ClipContext& context) {
350
0
    if (context.run->placeholderStyle() != nullptr) {
351
0
        return;
352
0
    }
353
354
0
    fTextBlobCache.emplace_back();
355
0
    TextBlobRecord& record = fTextBlobCache.back();
356
357
0
    if (style.hasForeground()) {
358
0
        record.fPaint = style.getForegroundPaintOrID();
359
0
    } else {
360
0
        std::get<SkPaint>(record.fPaint).setColor(style.getColor());
361
0
    }
362
0
    record.fVisitor_Run = context.run;
363
0
    record.fVisitor_Pos = context.pos;
364
365
    // TODO: This is the change for flutter, must be removed later
366
0
    SkTextBlobBuilder builder;
367
0
    context.run->copyTo(builder, SkToU32(context.pos), context.size);
368
0
    record.fClippingNeeded = context.clippingNeeded;
369
0
    if (context.clippingNeeded) {
370
0
        record.fClipRect = extendHeight(context).makeOffset(this->offset());
371
0
    } else {
372
0
        record.fClipRect = context.clip.makeOffset(this->offset());
373
0
    }
374
375
0
    SkASSERT(nearlyEqual(context.run->baselineShift(), style.getBaselineShift()));
376
0
    SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() +  0.5);
377
0
    record.fBlob = builder.make();
378
0
    if (record.fBlob != nullptr) {
379
0
        record.fBounds.joinPossiblyEmptyRect(record.fBlob->bounds());
380
0
    }
381
382
0
    record.fOffset = SkPoint::Make(this->offset().fX + context.fTextShift,
383
0
                                   this->offset().fY + correctedBaseline);
384
0
}
385
386
0
void TextLine::TextBlobRecord::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
387
0
    if (fClippingNeeded) {
388
0
        painter->save();
389
0
        painter->clipRect(fClipRect.makeOffset(x, y));
390
0
    }
391
0
    painter->drawTextBlob(fBlob, x + fOffset.x(), y + fOffset.y(), fPaint);
392
0
    if (fClippingNeeded) {
393
0
        painter->restore();
394
0
    }
395
0
}
396
397
void TextLine::paintBackground(ParagraphPainter* painter,
398
                               SkScalar x,
399
                               SkScalar y,
400
                               TextRange textRange,
401
                               const TextStyle& style,
402
0
                               const ClipContext& context) const {
403
0
    if (style.hasBackground()) {
404
0
        painter->drawRect(context.clip.makeOffset(this->offset() + SkPoint::Make(x, y)),
405
0
                          style.getBackgroundPaintOrID());
406
0
    }
407
0
}
408
409
void TextLine::paintShadow(ParagraphPainter* painter,
410
                           SkScalar x,
411
                           SkScalar y,
412
                           TextRange textRange,
413
                           const TextStyle& style,
414
0
                           const ClipContext& context) const {
415
0
    SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() + 0.5);
416
417
0
    for (TextShadow shadow : style.getShadows()) {
418
0
        if (!shadow.hasShadow()) continue;
419
420
0
        SkTextBlobBuilder builder;
421
0
        context.run->copyTo(builder, context.pos, context.size);
422
423
0
        if (context.clippingNeeded) {
424
0
            painter->save();
425
0
            SkRect clip = extendHeight(context);
426
0
            clip.offset(x, y);
427
0
            clip.offset(this->offset());
428
0
            painter->clipRect(clip);
429
0
        }
430
0
        auto blob = builder.make();
431
0
        painter->drawTextShadow(blob,
432
0
            x + this->offset().fX + shadow.fOffset.x() + context.fTextShift,
433
0
            y + this->offset().fY + shadow.fOffset.y() + correctedBaseline,
434
0
            shadow.fColor,
435
0
            SkDoubleToScalar(shadow.fBlurSigma));
436
0
        if (context.clippingNeeded) {
437
0
            painter->restore();
438
0
        }
439
0
    }
440
0
}
441
442
0
void TextLine::paintDecorations(ParagraphPainter* painter, SkScalar x, SkScalar y, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
443
0
    ParagraphPainterAutoRestore ppar(painter);
444
0
    painter->translate(x + this->offset().fX, y + this->offset().fY + style.getBaselineShift());
445
0
    Decorations decorations;
446
0
    SkScalar correctedBaseline = SkScalarFloorToScalar(-this->sizes().rawAscent() + style.getBaselineShift() + 0.5);
447
0
    decorations.paint(painter, style, context, correctedBaseline);
448
0
}
449
450
0
void TextLine::justify(SkScalar maxWidth) {
451
0
    int whitespacePatches = 0;
452
0
    SkScalar textLen = 0;
453
0
    SkScalar whitespaceLen = 0;
454
0
    bool whitespacePatch = false;
455
    // Take leading whitespaces width but do not increment a whitespace patch number
456
0
    bool leadingWhitespaces = false;
457
0
    this->iterateThroughClustersInGlyphsOrder(false, false,
458
0
        [&](const Cluster* cluster, ClusterIndex index, bool ghost) {
459
0
            if (cluster->isWhitespaceBreak()) {
460
0
                if (index == 0) {
461
0
                    leadingWhitespaces = true;
462
0
                } else if (!whitespacePatch && !leadingWhitespaces) {
463
                    // We only count patches BETWEEN words, not before
464
0
                    ++whitespacePatches;
465
0
                }
466
0
                whitespacePatch = !leadingWhitespaces;
467
0
                whitespaceLen += cluster->width();
468
0
            } else if (cluster->isIdeographic()) {
469
                // Whitespace break before and after
470
0
                if (!whitespacePatch && index != 0) {
471
                    // We only count patches BETWEEN words, not before
472
0
                    ++whitespacePatches; // before
473
0
                }
474
0
                whitespacePatch = true;
475
0
                leadingWhitespaces = false;
476
0
                ++whitespacePatches;    // after
477
0
            } else {
478
0
                whitespacePatch = false;
479
0
                leadingWhitespaces = false;
480
0
            }
481
0
            textLen += cluster->width();
482
0
            return true;
483
0
        });
484
485
0
    if (whitespacePatch) {
486
        // We only count patches BETWEEN words, not after
487
0
        --whitespacePatches;
488
0
    }
489
0
    if (whitespacePatches == 0) {
490
0
        if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
491
            // Justify -> Right align
492
0
            fShift = maxWidth - textLen;
493
0
        }
494
0
        return;
495
0
    }
496
497
0
    SkScalar step = (maxWidth - textLen + whitespaceLen) / whitespacePatches;
498
0
    SkScalar shift = 0.0f;
499
0
    SkScalar prevShift = 0.0f;
500
501
    // Deal with the ghost spaces
502
0
    auto ghostShift = maxWidth - this->fAdvance.fX;
503
    // Spread the extra whitespaces
504
0
    whitespacePatch = false;
505
    // Do not break on leading whitespaces
506
0
    leadingWhitespaces = false;
507
0
    this->iterateThroughClustersInGlyphsOrder(false, true, [&](const Cluster* cluster, ClusterIndex index, bool ghost) {
508
509
0
        if (ghost) {
510
0
            if (cluster->run().leftToRight()) {
511
0
                this->shiftCluster(cluster, ghostShift, ghostShift);
512
0
            }
513
0
            return true;
514
0
        }
515
516
0
        if (cluster->isWhitespaceBreak()) {
517
0
            if (index == 0) {
518
0
                leadingWhitespaces = true;
519
0
            } else if (!whitespacePatch && !leadingWhitespaces) {
520
0
                shift += step;
521
0
                whitespacePatch = true;
522
0
                --whitespacePatches;
523
0
            }
524
0
            shift -= cluster->width();
525
0
        } else if (cluster->isIdeographic()) {
526
0
            if (!whitespacePatch && index != 0) {
527
0
                shift += step;
528
0
               --whitespacePatches;
529
0
            }
530
0
            whitespacePatch = false;
531
0
            leadingWhitespaces = false;
532
0
        } else {
533
0
            whitespacePatch = false;
534
0
            leadingWhitespaces = false;
535
0
        }
536
0
        this->shiftCluster(cluster, shift, prevShift);
537
0
        prevShift = shift;
538
        // We skip ideographic whitespaces
539
0
        if (!cluster->isWhitespaceBreak() && cluster->isIdeographic()) {
540
0
            shift += step;
541
0
            whitespacePatch = true;
542
0
            --whitespacePatches;
543
0
        }
544
0
        return true;
545
0
    });
546
547
0
    if (whitespacePatch && whitespacePatches < 0) {
548
0
        whitespacePatches++;
549
0
        shift -= step;
550
0
    }
551
552
0
    SkAssertResult(nearlyEqual(shift, maxWidth - textLen));
553
0
    SkASSERT(whitespacePatches == 0);
554
555
0
    this->fWidthWithSpaces += ghostShift;
556
0
    this->fAdvance.fX = maxWidth;
557
0
}
558
559
0
void TextLine::shiftCluster(const Cluster* cluster, SkScalar shift, SkScalar prevShift) {
560
561
0
    auto& run = cluster->run();
562
0
    auto start = cluster->startPos();
563
0
    auto end = cluster->endPos();
564
565
0
    if (end == run.size()) {
566
        // Set the same shift for the fake last glyph (to avoid all extra checks)
567
0
        ++end;
568
0
    }
569
570
0
    if (run.fJustificationShifts.empty()) {
571
        // Do not fill this array until needed
572
0
        run.fJustificationShifts.push_back_n(run.size() + 1, { 0, 0 });
573
0
    }
574
575
0
    for (size_t pos = start; pos < end; ++pos) {
576
0
        run.fJustificationShifts[pos] = { shift, prevShift };
577
0
    }
578
0
}
579
580
0
void TextLine::createEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool) {
581
    // Replace some clusters with the ellipsis
582
    // Go through the clusters in the reverse logical order
583
    // taking off cluster by cluster until the ellipsis fits
584
0
    SkScalar width = fAdvance.fX;
585
0
    RunIndex lastRun = EMPTY_RUN;
586
0
    std::unique_ptr<Run> ellipsisRun;
587
0
    for (auto clusterIndex = fGhostClusterRange.end; clusterIndex > fGhostClusterRange.start; --clusterIndex) {
588
0
        auto& cluster = fOwner->cluster(clusterIndex - 1);
589
        // Shape the ellipsis if the run has changed
590
0
        if (lastRun != cluster.runIndex()) {
591
0
            ellipsisRun = this->shapeEllipsis(ellipsis, &cluster);
592
0
            if (ellipsisRun->advance().fX > maxWidth) {
593
                // Ellipsis is bigger than the entire line; no way we can add it at all
594
                // BUT! We can keep scanning in case the next run will give us better results
595
0
                lastRun = EMPTY_RUN;
596
0
                continue;
597
0
            } else {
598
                // We may need to continue
599
0
                lastRun = cluster.runIndex();
600
0
            }
601
0
        }
602
        // See if it fits
603
0
        if (width + ellipsisRun->advance().fX > maxWidth) {
604
0
            width -= cluster.width();
605
            // Continue if the ellipsis does not fit
606
0
            continue;
607
0
        }
608
        // We found enough room for the ellipsis
609
0
        fAdvance.fX = width;
610
0
        fEllipsis = std::move(ellipsisRun);
611
0
        fEllipsis->setOwner(fOwner);
612
613
        // Let's update the line
614
0
        fClusterRange.end = clusterIndex;
615
0
        fGhostClusterRange.end = fClusterRange.end;
616
0
        fEllipsis->fClusterStart = cluster.textRange().start;
617
0
        fText.end = cluster.textRange().end;
618
0
        fTextIncludingNewlines.end = cluster.textRange().end;
619
0
        fTextExcludingSpaces.end = cluster.textRange().end;
620
0
        break;
621
0
    }
622
623
0
    if (!fEllipsis) {
624
        // Weird situation: ellipsis does not fit; no ellipsis then
625
0
        fClusterRange.end = fClusterRange.start;
626
0
        fGhostClusterRange.end = fClusterRange.start;
627
0
        fText.end = fText.start;
628
0
        fTextIncludingNewlines.end = fTextIncludingNewlines.start;
629
0
        fTextExcludingSpaces.end = fTextExcludingSpaces.start;
630
0
        fAdvance.fX = 0;
631
0
    }
632
0
}
633
634
0
std::unique_ptr<Run> TextLine::shapeEllipsis(const SkString& ellipsis, const Cluster* cluster) {
635
636
0
    class ShapeHandler final : public SkShaper::RunHandler {
637
0
    public:
638
0
        ShapeHandler(SkScalar lineHeight, bool useHalfLeading, SkScalar baselineShift, const SkString& ellipsis)
639
0
            : fRun(nullptr), fLineHeight(lineHeight), fUseHalfLeading(useHalfLeading), fBaselineShift(baselineShift), fEllipsis(ellipsis) {}
640
0
        std::unique_ptr<Run> run() & { return std::move(fRun); }
641
642
0
    private:
643
0
        void beginLine() override {}
644
645
0
        void runInfo(const RunInfo&) override {}
646
647
0
        void commitRunInfo() override {}
648
649
0
        Buffer runBuffer(const RunInfo& info) override {
650
0
            SkASSERT(!fRun);
651
0
            fRun = std::make_unique<Run>(nullptr, info, 0, fLineHeight, fUseHalfLeading, fBaselineShift, 0, 0);
652
0
            return fRun->newRunBuffer();
653
0
        }
654
655
0
        void commitRunBuffer(const RunInfo& info) override {
656
0
            fRun->fAdvance.fX = info.fAdvance.fX;
657
0
            fRun->fAdvance.fY = fRun->advance().fY;
658
0
            fRun->fPlaceholderIndex = std::numeric_limits<size_t>::max();
659
0
            fRun->fEllipsis = true;
660
0
        }
661
662
0
        void commitLine() override {}
663
664
0
        std::unique_ptr<Run> fRun;
665
0
        SkScalar fLineHeight;
666
0
        bool fUseHalfLeading;
667
0
        SkScalar fBaselineShift;
668
0
        SkString fEllipsis;
669
0
    };
670
671
0
    const Run& run = cluster->run();
672
0
    TextStyle textStyle = fOwner->paragraphStyle().getTextStyle();
673
0
    for (auto i = fBlockRange.start; i < fBlockRange.end; ++i) {
674
0
        auto& block = fOwner->block(i);
675
0
        if (run.leftToRight() && cluster->textRange().end <= block.fRange.end) {
676
0
            textStyle = block.fStyle;
677
0
            break;
678
0
        } else if (!run.leftToRight() && cluster->textRange().start <= block.fRange.end) {
679
0
            textStyle = block.fStyle;
680
0
            break;
681
0
        }
682
0
    }
683
684
0
    auto shaped = [&](sk_sp<SkTypeface> typeface, sk_sp<SkFontMgr> fallback) -> std::unique_ptr<Run> {
685
0
        ShapeHandler handler(run.heightMultiplier(), run.useHalfLeading(), run.baselineShift(), ellipsis);
686
0
        SkFont font(std::move(typeface), textStyle.getFontSize());
687
0
        font.setEdging(SkFont::Edging::kAntiAlias);
688
0
        font.setHinting(SkFontHinting::kSlight);
689
0
        font.setSubpixel(true);
690
691
0
        std::unique_ptr<SkShaper> shaper = SkShapers::HB::ShapeDontWrapOrReorder(
692
0
                fOwner->getUnicode(), fallback ? fallback : SkFontMgr::RefEmpty());
693
694
0
        const SkBidiIterator::Level defaultLevel = SkBidiIterator::kLTR;
695
0
        const char* utf8 = ellipsis.c_str();
696
0
        size_t utf8Bytes = ellipsis.size();
697
698
0
        std::unique_ptr<SkShaper::BiDiRunIterator> bidi = SkShapers::unicode::BidiRunIterator(
699
0
                fOwner->getUnicode(), utf8, utf8Bytes, defaultLevel);
700
0
        SkASSERT(bidi);
701
702
0
        std::unique_ptr<SkShaper::LanguageRunIterator> language =
703
0
                SkShaper::MakeStdLanguageRunIterator(utf8, utf8Bytes);
704
0
        SkASSERT(language);
705
706
0
        std::unique_ptr<SkShaper::ScriptRunIterator> script =
707
0
                SkShapers::HB::ScriptRunIterator(utf8, utf8Bytes);
708
0
        SkASSERT(script);
709
710
0
        std::unique_ptr<SkShaper::FontRunIterator> fontRuns = SkShaper::MakeFontMgrRunIterator(
711
0
                utf8, utf8Bytes, font, fallback ? fallback : SkFontMgr::RefEmpty());
712
0
        SkASSERT(fontRuns);
713
714
0
        shaper->shape(utf8,
715
0
                      utf8Bytes,
716
0
                      *fontRuns,
717
0
                      *bidi,
718
0
                      *script,
719
0
                      *language,
720
0
                      nullptr,
721
0
                      0,
722
0
                      std::numeric_limits<SkScalar>::max(),
723
0
                      &handler);
724
0
        auto ellipsisRun = handler.run();
725
0
        ellipsisRun->fTextRange = TextRange(0, ellipsis.size());
726
0
        ellipsisRun->fOwner = fOwner;
727
0
        return ellipsisRun;
728
0
    };
729
730
    // Check the current font
731
0
    auto ellipsisRun = shaped(run.fFont.refTypeface(), nullptr);
732
0
    if (ellipsisRun->isResolved()) {
733
0
        return ellipsisRun;
734
0
    }
735
736
    // Check all allowed fonts
737
0
    std::vector<sk_sp<SkTypeface>> typefaces = fOwner->fontCollection()->findTypefaces(
738
0
            textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
739
0
    for (const auto& typeface : typefaces) {
740
0
        ellipsisRun = shaped(typeface, nullptr);
741
0
        if (ellipsisRun->isResolved()) {
742
0
            return ellipsisRun;
743
0
        }
744
0
    }
745
746
    // Try the fallback
747
0
    if (fOwner->fontCollection()->fontFallbackEnabled()) {
748
0
        const char* ch = ellipsis.c_str();
749
0
      SkUnichar unicode = SkUTF::NextUTF8WithReplacement(&ch,
750
0
                                                         ellipsis.c_str()
751
0
                                                             + ellipsis.size());
752
        // We do not expect emojis in ellipsis so if they appeat there
753
        // they will not be resolved with the pretiest color emoji font
754
0
        auto typeface = fOwner->fontCollection()->defaultFallback(
755
0
                                            unicode,
756
0
                                            textStyle.getFontStyle(),
757
0
                                            textStyle.getLocale());
758
0
        if (typeface) {
759
0
            ellipsisRun = shaped(typeface, fOwner->fontCollection()->getFallbackManager());
760
0
            if (ellipsisRun->isResolved()) {
761
0
                return ellipsisRun;
762
0
            }
763
0
        }
764
0
    }
765
0
    return ellipsisRun;
766
0
}
767
768
TextLine::ClipContext TextLine::measureTextInsideOneRun(TextRange textRange,
769
                                                        const Run* run,
770
                                                        SkScalar runOffsetInLine,
771
                                                        SkScalar textOffsetInRunInLine,
772
                                                        bool includeGhostSpaces,
773
0
                                                        TextAdjustment textAdjustment) const {
774
0
    ClipContext result = { run, 0, run->size(), 0, SkRect::MakeEmpty(), 0, false };
775
776
0
    if (run->fEllipsis) {
777
        // Both ellipsis and placeholders can only be measured as one glyph
778
0
        result.fTextShift = runOffsetInLine;
779
0
        result.clip = SkRect::MakeXYWH(runOffsetInLine,
780
0
                                       sizes().runTop(run, this->fAscentStyle),
781
0
                                       run->advance().fX,
782
0
                                       run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
783
0
        return result;
784
0
    } else if (run->isPlaceholder()) {
785
0
        result.fTextShift = runOffsetInLine;
786
0
        if (SkIsFinite(run->fFontMetrics.fAscent)) {
787
0
          result.clip = SkRect::MakeXYWH(runOffsetInLine,
788
0
                                         sizes().runTop(run, this->fAscentStyle),
789
0
                                         run->advance().fX,
790
0
                                         run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
791
0
        } else {
792
0
            result.clip = SkRect::MakeXYWH(runOffsetInLine, run->fFontMetrics.fAscent, run->advance().fX, 0);
793
0
        }
794
0
        return result;
795
0
    } else if (textRange.empty()) {
796
0
        return result;
797
0
    }
798
799
0
    TextRange originalTextRange(textRange); // We need it for proportional measurement
800
    // Find [start:end] clusters for the text
801
0
    while (true) {
802
        // Update textRange by cluster edges (shift start up to the edge of the cluster)
803
        // TODO: remove this limitation?
804
0
        TextRange updatedTextRange;
805
0
        bool found;
806
0
        std::tie(found, updatedTextRange.start, updatedTextRange.end) =
807
0
                                        run->findLimitingGlyphClusters(textRange);
808
0
        if (!found) {
809
0
            return result;
810
0
        }
811
812
0
        if ((textAdjustment & TextAdjustment::Grapheme) == 0) {
813
0
            textRange = updatedTextRange;
814
0
            break;
815
0
        }
816
817
        // Update text range by grapheme edges (shift start up to the edge of the grapheme)
818
0
        std::tie(found, updatedTextRange.start, updatedTextRange.end) =
819
0
                                    run->findLimitingGraphemes(updatedTextRange);
820
0
        if (updatedTextRange == textRange) {
821
0
            break;
822
0
        }
823
824
        // Some clusters are inside graphemes and we need to adjust them
825
        //SkDebugf("Correct range: [%d:%d) -> [%d:%d)\n", textRange.start, textRange.end, startIndex, endIndex);
826
0
        textRange = updatedTextRange;
827
828
        // Move the start until it's on the grapheme edge (and glypheme, too)
829
0
    }
830
0
    Cluster* start = &fOwner->cluster(fOwner->clusterIndex(textRange.start));
831
0
    Cluster* end = &fOwner->cluster(fOwner->clusterIndex(textRange.end - (textRange.width() == 0 ? 0 : 1)));
832
833
0
    if (!run->leftToRight()) {
834
0
        std::swap(start, end);
835
0
    }
836
0
    result.pos = start->startPos();
837
0
    result.size = (end->isHardBreak() ? end->startPos() : end->endPos()) - start->startPos();
838
0
    auto textStartInRun = run->positionX(start->startPos());
839
0
    auto textStartInLine = runOffsetInLine + textOffsetInRunInLine;
840
0
    if (!run->leftToRight()) {
841
0
        std::swap(start, end);
842
0
    }
843
/*
844
    if (!run->fJustificationShifts.empty()) {
845
        SkDebugf("Justification for [%d:%d)\n", textRange.start, textRange.end);
846
        for (auto i = result.pos; i < result.pos + result.size; ++i) {
847
            auto j = run->fJustificationShifts[i];
848
            SkDebugf("[%d] = %f %f\n", i, j.fX, j.fY);
849
        }
850
    }
851
*/
852
    // Calculate the clipping rectangle for the text with cluster edges
853
    // There are 2 cases:
854
    // EOL (when we expect the last cluster clipped without any spaces)
855
    // Anything else (when we want the cluster width contain all the spaces -
856
    // coming from letter spacing or word spacing or justification)
857
0
    result.clip =
858
0
            SkRect::MakeXYWH(0,
859
0
                             sizes().runTop(run, this->fAscentStyle),
860
0
                             run->calculateWidth(result.pos, result.pos + result.size, false),
861
0
                             run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
862
863
    // Correct the width in case the text edges don't match clusters
864
    // TODO: This is where we get smart about selecting a part of a cluster
865
    //  by shaping each grapheme separately and then use the result sizes
866
    //  to calculate the proportions
867
0
    auto leftCorrection = start->sizeToChar(originalTextRange.start);
868
0
    auto rightCorrection = end->sizeFromChar(originalTextRange.end - 1);
869
    /*
870
    SkDebugf("[%d: %d) => [%d: %d), @%d, %d: [%f:%f) + [%f:%f) = ", // جَآَهُ
871
             originalTextRange.start, originalTextRange.end, textRange.start, textRange.end,
872
             result.pos, result.size,
873
             result.clip.fLeft, result.clip.fRight, leftCorrection, rightCorrection);
874
     */
875
0
    result.clippingNeeded = leftCorrection != 0 || rightCorrection != 0;
876
0
    if (run->leftToRight()) {
877
0
        result.clip.fLeft += leftCorrection;
878
0
        result.clip.fRight -= rightCorrection;
879
0
        textStartInLine -= leftCorrection;
880
0
    } else {
881
0
        result.clip.fRight -= leftCorrection;
882
0
        result.clip.fLeft += rightCorrection;
883
0
        textStartInLine -= rightCorrection;
884
0
    }
885
886
0
    result.clip.offset(textStartInLine, 0);
887
    //SkDebugf("@%f[%f:%f)\n", textStartInLine, result.clip.fLeft, result.clip.fRight);
888
889
0
    if (compareRound(result.clip.fRight, fAdvance.fX, fOwner->getApplyRoundingHack()) > 0 && !includeGhostSpaces) {
890
        // There are few cases when we need it.
891
        // The most important one: we measure the text with spaces at the end (or at the beginning in RTL)
892
        // and we should ignore these spaces
893
0
        if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kLtr) {
894
            // We only use this member for LTR
895
0
            result.fExcludedTrailingSpaces = std::max(result.clip.fRight - fAdvance.fX, 0.0f);
896
0
            result.clippingNeeded = true;
897
0
            result.clip.fRight = fAdvance.fX;
898
0
        }
899
0
    }
900
901
0
    if (result.clip.width() < 0) {
902
        // Weird situation when glyph offsets move the glyph to the left
903
        // (happens with zalgo texts, for instance)
904
0
        result.clip.fRight = result.clip.fLeft;
905
0
    }
906
907
    // The text must be aligned with the lineOffset
908
0
    result.fTextShift = textStartInLine - textStartInRun;
909
910
0
    return result;
911
0
}
912
913
void TextLine::iterateThroughClustersInGlyphsOrder(bool reversed,
914
                                                   bool includeGhosts,
915
0
                                                   const ClustersVisitor& visitor) const {
916
    // Walk through the clusters in the logical order (or reverse)
917
0
    SkSpan<const size_t> runs(fRunsInVisualOrder.data(), fRunsInVisualOrder.size());
918
0
    bool ignore = false;
919
0
    ClusterIndex index = 0;
920
0
    directional_for_each(runs, !reversed, [&](decltype(runs[0]) r) {
921
0
        if (ignore) return;
922
0
        auto run = this->fOwner->run(r);
923
0
        auto trimmedRange = fClusterRange.intersection(run.clusterRange());
924
0
        auto trailedRange = fGhostClusterRange.intersection(run.clusterRange());
925
0
        SkASSERT(trimmedRange.start == trailedRange.start);
926
927
0
        auto trailed = fOwner->clusters(trailedRange);
928
0
        auto trimmed = fOwner->clusters(trimmedRange);
929
0
        directional_for_each(trailed, reversed != run.leftToRight(), [&](Cluster& cluster) {
930
0
            if (ignore) return;
931
0
            bool ghost =  &cluster >= trimmed.end();
932
0
            if (!includeGhosts && ghost) {
933
0
                return;
934
0
            }
935
0
            if (!visitor(&cluster, index++, ghost)) {
936
937
0
                ignore = true;
938
0
                return;
939
0
            }
940
0
        });
941
0
    });
942
0
}
943
944
SkScalar TextLine::iterateThroughSingleRunByStyles(TextAdjustment textAdjustment,
945
                                                   const Run* run,
946
                                                   SkScalar runOffset,
947
                                                   TextRange textRange,
948
                                                   StyleType styleType,
949
0
                                                   const RunStyleVisitor& visitor) const {
950
0
    auto correctContext = [&](TextRange textRange, SkScalar textOffsetInRun) -> ClipContext {
951
0
        auto result = this->measureTextInsideOneRun(
952
0
                textRange, run, runOffset, textOffsetInRun, false, textAdjustment);
953
0
        if (styleType == StyleType::kDecorations) {
954
            // Decorations are drawn based on the real font metrics (regardless of styles and strut)
955
0
            result.clip.fTop = this->sizes().runTop(run, LineMetricStyle::CSS);
956
0
            result.clip.fBottom = result.clip.fTop +
957
0
                                  run->calculateHeight(LineMetricStyle::CSS, LineMetricStyle::CSS);
958
0
        }
959
0
        return result;
960
0
    };
961
962
0
    if (run->fEllipsis) {
963
        // Extra efforts to get the ellipsis text style
964
0
        ClipContext clipContext = correctContext(run->textRange(), 0.0f);
965
0
        TextRange testRange(run->fClusterStart, run->fClusterStart + run->textRange().width());
966
0
        for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
967
0
           auto block = fOwner->styles().begin() + index;
968
0
           auto intersect = intersected(block->fRange, testRange);
969
0
           if (intersect.width() > 0) {
970
0
               visitor(testRange, block->fStyle, clipContext);
971
0
               return run->advance().fX;
972
0
           }
973
0
        }
974
0
        SkASSERT(false);
975
0
    }
976
977
0
    if (styleType == StyleType::kNone) {
978
0
        ClipContext clipContext = correctContext(textRange, 0.0f);
979
        // The placehoder can have height=0 or (exclusively) width=0 and still be a thing
980
0
        if (clipContext.clip.height() > 0.0f || clipContext.clip.width() > 0.0f) {
981
0
            visitor(textRange, TextStyle(), clipContext);
982
0
            return clipContext.clip.width();
983
0
        } else {
984
0
            return 0;
985
0
        }
986
0
    }
987
988
0
    TextIndex start = EMPTY_INDEX;
989
0
    size_t size = 0;
990
0
    const TextStyle* prevStyle = nullptr;
991
0
    SkScalar textOffsetInRun = 0;
992
993
0
    const BlockIndex blockRangeSize = fBlockRange.end - fBlockRange.start;
994
0
    for (BlockIndex index = 0; index <= blockRangeSize; ++index) {
995
996
0
        TextRange intersect;
997
0
        TextStyle* style = nullptr;
998
0
        if (index < blockRangeSize) {
999
0
            auto block = fOwner->styles().begin() +
1000
0
                 (run->leftToRight() ? fBlockRange.start + index : fBlockRange.end - index - 1);
1001
1002
            // Get the text
1003
0
            intersect = intersected(block->fRange, textRange);
1004
0
            if (intersect.width() == 0) {
1005
0
                if (start == EMPTY_INDEX) {
1006
                    // This style is not applicable to the text yet
1007
0
                    continue;
1008
0
                } else {
1009
                    // We have found all the good styles already
1010
                    // but we need to process the last one of them
1011
0
                    intersect = TextRange(start, start + size);
1012
0
                    index = fBlockRange.end;
1013
0
                }
1014
0
            } else {
1015
                // Get the style
1016
0
                style = &block->fStyle;
1017
0
                if (start != EMPTY_INDEX && style->matchOneAttribute(styleType, *prevStyle)) {
1018
0
                    size += intersect.width();
1019
                    // RTL text intervals move backward
1020
0
                    start = std::min(intersect.start, start);
1021
0
                    continue;
1022
0
                } else if (start == EMPTY_INDEX ) {
1023
                    // First time only
1024
0
                    prevStyle = style;
1025
0
                    size = intersect.width();
1026
0
                    start = intersect.start;
1027
0
                    continue;
1028
0
                }
1029
0
            }
1030
0
        } else if (prevStyle != nullptr) {
1031
            // This is the last style
1032
0
        } else {
1033
0
            break;
1034
0
        }
1035
1036
        // We have the style and the text
1037
0
        auto runStyleTextRange = TextRange(start, start + size);
1038
0
        ClipContext clipContext = correctContext(runStyleTextRange, textOffsetInRun);
1039
0
        textOffsetInRun += clipContext.clip.width();
1040
0
        if (clipContext.clip.height() == 0) {
1041
0
            continue;
1042
0
        }
1043
0
        visitor(runStyleTextRange, *prevStyle, clipContext);
1044
1045
        // Start all over again
1046
0
        prevStyle = style;
1047
0
        start = intersect.start;
1048
0
        size = intersect.width();
1049
0
    }
1050
0
    return textOffsetInRun;
1051
0
}
1052
1053
0
void TextLine::iterateThroughVisualRuns(bool includingGhostSpaces, const RunVisitor& visitor) const {
1054
1055
    // Walk through all the runs that intersect with the line in visual order
1056
0
    SkScalar width = 0;
1057
0
    SkScalar runOffset = 0;
1058
0
    SkScalar totalWidth = 0;
1059
0
    auto textRange = includingGhostSpaces ? this->textWithNewlines() : this->trimmedText();
1060
1061
0
    if (this->ellipsis() != nullptr && fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
1062
0
        runOffset = this->ellipsis()->offset().fX;
1063
0
        if (visitor(ellipsis(), runOffset, ellipsis()->textRange(), &width)) {
1064
0
        }
1065
0
    }
1066
1067
0
    for (auto& runIndex : fRunsInVisualOrder) {
1068
1069
0
        const auto run = &this->fOwner->run(runIndex);
1070
0
        auto lineIntersection = intersected(run->textRange(), textRange);
1071
0
        if (lineIntersection.width() == 0 && this->width() != 0) {
1072
            // TODO: deal with empty runs in a better way
1073
0
            continue;
1074
0
        }
1075
0
        if (!run->leftToRight() && runOffset == 0 && includingGhostSpaces) {
1076
            // runOffset does not take in account a possibility
1077
            // that RTL run could start before the line (trailing spaces)
1078
            // so we need to do runOffset -= "trailing whitespaces length"
1079
0
            TextRange whitespaces = intersected(
1080
0
                    TextRange(fTextExcludingSpaces.end, fTextIncludingNewlines.end), run->fTextRange);
1081
0
            if (whitespaces.width() > 0) {
1082
0
                auto whitespacesLen = measureTextInsideOneRun(whitespaces, run, runOffset, 0, true, TextAdjustment::GlyphCluster).clip.width();
1083
0
                runOffset -= whitespacesLen;
1084
0
            }
1085
0
        }
1086
0
        runOffset += width;
1087
0
        totalWidth += width;
1088
0
        if (!visitor(run, runOffset, lineIntersection, &width)) {
1089
0
            return;
1090
0
        }
1091
0
    }
1092
1093
0
    runOffset += width;
1094
0
    totalWidth += width;
1095
1096
0
    if (this->ellipsis() != nullptr && fOwner->paragraphStyle().getTextDirection() == TextDirection::kLtr) {
1097
0
        if (visitor(ellipsis(), runOffset, ellipsis()->textRange(), &width)) {
1098
0
            totalWidth += width;
1099
0
        }
1100
0
    }
1101
1102
0
    if (!includingGhostSpaces && compareRound(totalWidth, this->width(), fOwner->getApplyRoundingHack()) != 0) {
1103
    // This is a very important assert!
1104
    // It asserts that 2 different ways of calculation come with the same results
1105
0
        SkDEBUGFAILF("ASSERT: %f != %f\n", totalWidth, this->width());
1106
0
    }
1107
0
}
1108
1109
0
SkVector TextLine::offset() const {
1110
0
    return fOffset + SkVector::Make(fShift, 0);
1111
0
}
1112
1113
0
LineMetrics TextLine::getMetrics() const {
1114
0
    LineMetrics result;
1115
0
    SkASSERT(fOwner);
1116
1117
    // Fill out the metrics
1118
0
    fOwner->ensureUTF16Mapping();
1119
0
    result.fStartIndex = fOwner->getUTF16Index(fTextExcludingSpaces.start);
1120
0
    result.fEndExcludingWhitespaces = fOwner->getUTF16Index(fTextExcludingSpaces.end);
1121
0
    result.fEndIndex = fOwner->getUTF16Index(fText.end);
1122
0
    result.fEndIncludingNewline = fOwner->getUTF16Index(fTextIncludingNewlines.end);
1123
0
    result.fHardBreak = endsWithHardLineBreak();
1124
0
    result.fAscent = - fMaxRunMetrics.ascent();
1125
0
    result.fDescent = fMaxRunMetrics.descent();
1126
0
    result.fUnscaledAscent = - fMaxRunMetrics.ascent(); // TODO: implement
1127
0
    result.fHeight = fAdvance.fY;
1128
0
    result.fWidth = fAdvance.fX;
1129
0
    if (fOwner->getApplyRoundingHack()) {
1130
0
        result.fHeight = littleRound(result.fHeight);
1131
0
        result.fWidth = littleRound(result.fWidth);
1132
0
    }
1133
0
    result.fLeft = this->offset().fX;
1134
    // This is Flutter definition of a baseline
1135
0
    result.fBaseline = this->offset().fY + this->height() - this->sizes().descent();
1136
0
    result.fLineNumber = this - fOwner->lines().begin();
1137
1138
    // Fill out the style parts
1139
0
    this->iterateThroughVisualRuns(false,
1140
0
        [this, &result]
1141
0
        (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1142
0
        if (run->placeholderStyle() != nullptr) {
1143
0
            *runWidthInLine = run->advance().fX;
1144
0
            return true;
1145
0
        }
1146
0
        *runWidthInLine = this->iterateThroughSingleRunByStyles(
1147
0
        TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kForeground,
1148
0
        [&result, &run](TextRange textRange, const TextStyle& style, const ClipContext& context) {
1149
0
            SkFontMetrics fontMetrics;
1150
0
            run->fFont.getMetrics(&fontMetrics);
1151
0
            StyleMetrics styleMetrics(&style, fontMetrics);
1152
0
            result.fLineMetrics.emplace(textRange.start, styleMetrics);
1153
0
        });
1154
0
        return true;
1155
0
    });
1156
1157
0
    return result;
1158
0
}
1159
1160
0
bool TextLine::isFirstLine() const {
1161
0
    return this == &fOwner->lines().front();
1162
0
}
1163
1164
0
bool TextLine::isLastLine() const {
1165
0
    return this == &fOwner->lines().back();
1166
0
}
1167
1168
0
bool TextLine::endsWithHardLineBreak() const {
1169
    // TODO: For some reason Flutter imagines a hard line break at the end of the last line.
1170
    //  To be removed...
1171
0
    return (fGhostClusterRange.width() > 0 && fOwner->cluster(fGhostClusterRange.end - 1).isHardBreak()) ||
1172
0
           fEllipsis != nullptr ||
1173
0
           fGhostClusterRange.end == fOwner->clusters().size() - 1;
1174
0
}
1175
1176
void TextLine::getRectsForRange(TextRange textRange0,
1177
                                RectHeightStyle rectHeightStyle,
1178
                                RectWidthStyle rectWidthStyle,
1179
                                std::vector<TextBox>& boxes) const
1180
0
{
1181
0
    const Run* lastRun = nullptr;
1182
0
    auto startBox = boxes.size();
1183
0
    this->iterateThroughVisualRuns(true,
1184
0
        [textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox, this]
1185
0
        (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1186
0
        *runWidthInLine = this->iterateThroughSingleRunByStyles(
1187
0
        TextAdjustment::GraphemeGluster, run, runOffsetInLine, textRange, StyleType::kNone,
1188
0
        [run, runOffsetInLine, textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox, this]
1189
0
        (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& lineContext) {
1190
1191
0
            auto intersect = textRange * textRange0;
1192
0
            if (intersect.empty()) {
1193
0
                return true;
1194
0
            }
1195
1196
0
            auto paragraphStyle = fOwner->paragraphStyle();
1197
1198
            // Found a run that intersects with the text
1199
0
            auto context = this->measureTextInsideOneRun(
1200
0
                    intersect, run, runOffsetInLine, 0, true, TextAdjustment::GraphemeGluster);
1201
0
            SkRect clip = context.clip;
1202
0
            clip.offset(lineContext.fTextShift - context.fTextShift, 0);
1203
1204
0
            switch (rectHeightStyle) {
1205
0
                case RectHeightStyle::kMax:
1206
                    // TODO: Change it once flutter rolls into google3
1207
                    //  (probably will break things if changed before)
1208
0
                    clip.fBottom = this->height();
1209
0
                    clip.fTop = this->sizes().delta();
1210
0
                    break;
1211
0
                case RectHeightStyle::kIncludeLineSpacingTop: {
1212
0
                    clip.fBottom = this->height();
1213
0
                    clip.fTop = this->sizes().delta();
1214
0
                    auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1215
0
                    if (isFirstLine()) {
1216
0
                        clip.fTop += verticalShift;
1217
0
                    }
1218
0
                    break;
1219
0
                }
1220
0
                case RectHeightStyle::kIncludeLineSpacingMiddle: {
1221
0
                    clip.fBottom = this->height();
1222
0
                    clip.fTop = this->sizes().delta();
1223
0
                    auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1224
0
                    clip.offset(0, verticalShift / 2.0);
1225
0
                    if (isFirstLine()) {
1226
0
                        clip.fTop += verticalShift / 2.0;
1227
0
                    }
1228
0
                    if (isLastLine()) {
1229
0
                        clip.fBottom -= verticalShift / 2.0;
1230
0
                    }
1231
0
                    break;
1232
0
                 }
1233
0
                case RectHeightStyle::kIncludeLineSpacingBottom: {
1234
0
                    clip.fBottom = this->height();
1235
0
                    clip.fTop = this->sizes().delta();
1236
0
                    auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1237
0
                    clip.offset(0, verticalShift);
1238
0
                    if (isLastLine()) {
1239
0
                        clip.fBottom -= verticalShift;
1240
0
                    }
1241
0
                    break;
1242
0
                }
1243
0
                case RectHeightStyle::kStrut: {
1244
0
                    const auto& strutStyle = paragraphStyle.getStrutStyle();
1245
0
                    if (strutStyle.getStrutEnabled()
1246
0
                        && strutStyle.getFontSize() > 0) {
1247
0
                        auto strutMetrics = fOwner->strutMetrics();
1248
0
                        auto top = this->baseline();
1249
0
                        clip.fTop = top + strutMetrics.ascent();
1250
0
                        clip.fBottom = top + strutMetrics.descent();
1251
0
                    }
1252
0
                }
1253
0
                break;
1254
0
                case RectHeightStyle::kTight: {
1255
0
                    if (run->fHeightMultiplier <= 0) {
1256
0
                        break;
1257
0
                    }
1258
0
                    const auto effectiveBaseline = this->baseline() + this->sizes().delta();
1259
0
                    clip.fTop = effectiveBaseline + run->ascent();
1260
0
                    clip.fBottom = effectiveBaseline + run->descent();
1261
0
                }
1262
0
                break;
1263
0
                default:
1264
0
                    SkASSERT(false);
1265
0
                break;
1266
0
            }
1267
1268
            // Separate trailing spaces and move them in the default order of the paragraph
1269
            // in case the run order and the paragraph order don't match
1270
0
            SkRect trailingSpaces = SkRect::MakeEmpty();
1271
0
            if (this->trimmedText().end <this->textWithNewlines().end && // Line has trailing space
1272
0
                this->textWithNewlines().end == intersect.end &&         // Range is at the end of the line
1273
0
                this->trimmedText().end > intersect.start)               // Range has more than just spaces
1274
0
            {
1275
0
                auto delta = this->spacesWidth();
1276
0
                trailingSpaces = SkRect::MakeXYWH(0, 0, 0, 0);
1277
                // There are trailing spaces in this run
1278
0
                if (paragraphStyle.getTextAlign() == TextAlign::kJustify && isLastLine())
1279
0
                {
1280
                    // TODO: this is just a patch. Make it right later (when it's clear what and how)
1281
0
                    trailingSpaces = clip;
1282
0
                    if(run->leftToRight()) {
1283
0
                        trailingSpaces.fLeft = this->width();
1284
0
                        clip.fRight = this->width();
1285
0
                    } else {
1286
0
                        trailingSpaces.fRight = 0;
1287
0
                        clip.fLeft = 0;
1288
0
                    }
1289
0
                } else if (paragraphStyle.getTextDirection() == TextDirection::kRtl &&
1290
0
                    !run->leftToRight())
1291
0
                {
1292
                    // Split
1293
0
                    trailingSpaces = clip;
1294
0
                    trailingSpaces.fLeft = - delta;
1295
0
                    trailingSpaces.fRight = 0;
1296
0
                    clip.fLeft += delta;
1297
0
                } else if (paragraphStyle.getTextDirection() == TextDirection::kLtr &&
1298
0
                    run->leftToRight())
1299
0
                {
1300
                    // Split
1301
0
                    trailingSpaces = clip;
1302
0
                    trailingSpaces.fLeft = this->width();
1303
0
                    trailingSpaces.fRight = trailingSpaces.fLeft + delta;
1304
0
                    clip.fRight -= delta;
1305
0
                }
1306
0
            }
1307
1308
0
            clip.offset(this->offset());
1309
0
            if (trailingSpaces.width() > 0) {
1310
0
                trailingSpaces.offset(this->offset());
1311
0
            }
1312
1313
            // Check if we can merge two boxes instead of adding a new one
1314
0
            auto merge = [&lastRun, &context, &boxes](SkRect clip) {
1315
0
                bool mergedBoxes = false;
1316
0
                if (!boxes.empty() &&
1317
0
                    lastRun != nullptr &&
1318
0
                    context.run->leftToRight() == lastRun->leftToRight() &&
1319
0
                    lastRun->placeholderStyle() == nullptr &&
1320
0
                    context.run->placeholderStyle() == nullptr &&
1321
0
                    nearlyEqual(lastRun->heightMultiplier(),
1322
0
                                context.run->heightMultiplier()) &&
1323
0
                    lastRun->font() == context.run->font())
1324
0
                {
1325
0
                    auto& lastBox = boxes.back();
1326
0
                    if (nearlyEqual(lastBox.rect.fTop, clip.fTop) &&
1327
0
                        nearlyEqual(lastBox.rect.fBottom, clip.fBottom) &&
1328
0
                            (nearlyEqual(lastBox.rect.fLeft, clip.fRight) ||
1329
0
                             nearlyEqual(lastBox.rect.fRight, clip.fLeft)))
1330
0
                    {
1331
0
                        lastBox.rect.fLeft = std::min(lastBox.rect.fLeft, clip.fLeft);
1332
0
                        lastBox.rect.fRight = std::max(lastBox.rect.fRight, clip.fRight);
1333
0
                        mergedBoxes = true;
1334
0
                    }
1335
0
                }
1336
0
                lastRun = context.run;
1337
0
                return mergedBoxes;
1338
0
            };
1339
1340
0
            if (!merge(clip)) {
1341
0
                boxes.emplace_back(clip, context.run->getTextDirection());
1342
0
            }
1343
0
            if (!nearlyZero(trailingSpaces.width()) && !merge(trailingSpaces)) {
1344
0
                boxes.emplace_back(trailingSpaces, paragraphStyle.getTextDirection());
1345
0
            }
1346
1347
0
            if (rectWidthStyle == RectWidthStyle::kMax && !isLastLine()) {
1348
                // Align the very left/right box horizontally
1349
0
                auto lineStart = this->offset().fX;
1350
0
                auto lineEnd = this->offset().fX + this->width();
1351
0
                auto left = boxes[startBox];
1352
0
                auto right = boxes.back();
1353
0
                if (left.rect.fLeft > lineStart && left.direction == TextDirection::kRtl) {
1354
0
                    left.rect.fRight = left.rect.fLeft;
1355
0
                    left.rect.fLeft = 0;
1356
0
                    boxes.insert(boxes.begin() + startBox + 1, left);
1357
0
                }
1358
0
                if (right.direction == TextDirection::kLtr &&
1359
0
                    right.rect.fRight >= lineEnd &&
1360
0
                    right.rect.fRight < fOwner->widthWithTrailingSpaces()) {
1361
0
                    right.rect.fLeft = right.rect.fRight;
1362
0
                    right.rect.fRight = fOwner->widthWithTrailingSpaces();
1363
0
                    boxes.emplace_back(right);
1364
0
                }
1365
0
            }
1366
1367
0
            return true;
1368
0
        });
1369
0
        return true;
1370
0
    });
1371
0
    if (fOwner->getApplyRoundingHack()) {
1372
0
        for (auto& r : boxes) {
1373
0
            r.rect.fLeft = littleRound(r.rect.fLeft);
1374
0
            r.rect.fRight = littleRound(r.rect.fRight);
1375
0
            r.rect.fTop = littleRound(r.rect.fTop);
1376
0
            r.rect.fBottom = littleRound(r.rect.fBottom);
1377
0
        }
1378
0
    }
1379
0
}
1380
1381
0
PositionWithAffinity TextLine::getGlyphPositionAtCoordinate(SkScalar dx) {
1382
1383
0
    if (SkScalarNearlyZero(this->width()) && SkScalarNearlyZero(this->spacesWidth())) {
1384
        // TODO: this is one of the flutter changes that have to go away eventually
1385
        //  Empty line is a special case in txtlib (but only when there are no spaces, too)
1386
0
        auto utf16Index = fOwner->getUTF16Index(this->fTextExcludingSpaces.end);
1387
0
        return { SkToS32(utf16Index) , kDownstream };
1388
0
    }
1389
1390
0
    PositionWithAffinity result(0, Affinity::kDownstream);
1391
0
    this->iterateThroughVisualRuns(true,
1392
0
        [this, dx, &result]
1393
0
        (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1394
0
            bool keepLooking = true;
1395
0
            *runWidthInLine = this->iterateThroughSingleRunByStyles(
1396
0
            TextAdjustment::GraphemeGluster, run, runOffsetInLine, textRange, StyleType::kNone,
1397
0
            [this, run, dx, &result, &keepLooking]
1398
0
            (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context0) {
1399
1400
0
                SkScalar offsetX = this->offset().fX;
1401
0
                ClipContext context = context0;
1402
1403
                // Correct the clip size because libtxt counts trailing spaces
1404
0
                if (run->leftToRight()) {
1405
0
                    context.clip.fRight += context.fExcludedTrailingSpaces; // extending clip to the right
1406
0
                } else {
1407
                    // Clip starts from 0; we cannot extend it to the left from that
1408
0
                }
1409
                // However, we need to offset the clip
1410
0
                context.clip.offset(offsetX, 0.0f);
1411
1412
                // This patch will help us to avoid a floating point error
1413
0
                if (SkScalarNearlyEqual(context.clip.fRight, dx, 0.01f)) {
1414
0
                    context.clip.fRight = dx;
1415
0
                }
1416
1417
0
                if (dx <= context.clip.fLeft) {
1418
                    // All the other runs are placed right of this one
1419
0
                    auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos));
1420
0
                    if (run->leftToRight()) {
1421
0
                        result = { SkToS32(utf16Index), kDownstream};
1422
0
                        keepLooking = false;
1423
0
                    } else {
1424
0
                        result = { SkToS32(utf16Index + 1), kUpstream};
1425
                        // If we haven't reached the end of the run we need to keep looking
1426
0
                        keepLooking = context.pos != 0;
1427
0
                    }
1428
                    // For RTL we go another way
1429
0
                    return !run->leftToRight();
1430
0
                }
1431
1432
0
                if (dx >= context.clip.fRight) {
1433
                    // We have to keep looking ; just in case keep the last one as the closest
1434
0
                    auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos + context.size));
1435
0
                    if (run->leftToRight()) {
1436
0
                        result = {SkToS32(utf16Index), kUpstream};
1437
0
                    } else {
1438
0
                        result = {SkToS32(utf16Index), kDownstream};
1439
0
                    }
1440
                    // For RTL we go another way
1441
0
                    return run->leftToRight();
1442
0
                }
1443
1444
                // So we found the run that contains our coordinates
1445
                // Find the glyph position in the run that is the closest left of our point
1446
                // TODO: binary search
1447
0
                size_t found = context.pos;
1448
0
                for (size_t index = context.pos; index < context.pos + context.size; ++index) {
1449
                    // TODO: this rounding is done to match Flutter tests. Must be removed..
1450
0
                    auto end = context.run->positionX(index) + context.fTextShift + offsetX;
1451
0
                    if (fOwner->getApplyRoundingHack()) {
1452
0
                        end = littleRound(end);
1453
0
                    }
1454
0
                    if (end > dx) {
1455
0
                        break;
1456
0
                    } else if (end == dx && !context.run->leftToRight()) {
1457
                        // When we move RTL variable end points to the beginning of the code point which is included
1458
0
                        found = index;
1459
0
                        break;
1460
0
                    }
1461
0
                    found = index;
1462
0
                }
1463
1464
0
                SkScalar glyphemePosLeft = context.run->positionX(found) + context.fTextShift + offsetX;
1465
0
                SkScalar glyphemesWidth = context.run->positionX(found + 1) - context.run->positionX(found);
1466
1467
                // Find the grapheme range that contains the point
1468
0
                auto clusterIndex8 = context.run->globalClusterIndex(found);
1469
0
                auto clusterEnd8 = context.run->globalClusterIndex(found + 1);
1470
0
                auto graphemes = fOwner->countSurroundingGraphemes({clusterIndex8, clusterEnd8});
1471
1472
0
                SkScalar center = glyphemePosLeft + glyphemesWidth / 2;
1473
0
                if (graphemes.size() > 1) {
1474
                    // Calculate the position proportionally based on grapheme count
1475
0
                    SkScalar averageGraphemeWidth = glyphemesWidth / graphemes.size();
1476
0
                    SkScalar delta = dx - glyphemePosLeft;
1477
0
                    int graphemeIndex = SkScalarNearlyZero(averageGraphemeWidth)
1478
0
                                         ? 0
1479
0
                                         : SkScalarFloorToInt(delta / averageGraphemeWidth);
1480
0
                    auto graphemeCenter = glyphemePosLeft + graphemeIndex * averageGraphemeWidth +
1481
0
                                          averageGraphemeWidth / 2;
1482
0
                    auto graphemeUtf8Index = graphemes[graphemeIndex];
1483
0
                    if ((dx < graphemeCenter) == context.run->leftToRight()) {
1484
0
                        size_t utf16Index = fOwner->getUTF16Index(graphemeUtf8Index);
1485
0
                        result = { SkToS32(utf16Index), kDownstream };
1486
0
                    } else {
1487
0
                        size_t utf16Index = fOwner->getUTF16Index(graphemeUtf8Index + 1);
1488
0
                        result = { SkToS32(utf16Index), kUpstream };
1489
0
                    }
1490
                    // Keep UTF16 index as is
1491
0
                } else if ((dx < center) == context.run->leftToRight()) {
1492
0
                    size_t utf16Index = fOwner->getUTF16Index(clusterIndex8);
1493
0
                    result = { SkToS32(utf16Index), kDownstream };
1494
0
                } else {
1495
0
                    size_t utf16Index = context.run->leftToRight()
1496
0
                                                ? fOwner->getUTF16Index(clusterEnd8)
1497
0
                                                : fOwner->getUTF16Index(clusterIndex8) + 1;
1498
0
                    result = { SkToS32(utf16Index), kUpstream };
1499
0
                }
1500
1501
0
                return keepLooking = false;
1502
1503
0
            });
1504
0
            return keepLooking;
1505
0
        }
1506
0
    );
1507
0
    return result;
1508
0
}
1509
1510
0
void TextLine::getRectsForPlaceholders(std::vector<TextBox>& boxes) {
1511
0
    this->iterateThroughVisualRuns(
1512
0
        true,
1513
0
        [&boxes, this](const Run* run, SkScalar runOffset, TextRange textRange,
1514
0
                        SkScalar* width) {
1515
0
                auto context = this->measureTextInsideOneRun(
1516
0
                        textRange, run, runOffset, 0, true, TextAdjustment::GraphemeGluster);
1517
0
                *width = context.clip.width();
1518
1519
0
            if (textRange.width() == 0) {
1520
0
                return true;
1521
0
            }
1522
0
            if (!run->isPlaceholder()) {
1523
0
                return true;
1524
0
            }
1525
1526
0
            SkRect clip = context.clip;
1527
0
            clip.offset(this->offset());
1528
1529
0
            if (fOwner->getApplyRoundingHack()) {
1530
0
                clip.fLeft = littleRound(clip.fLeft);
1531
0
                clip.fRight = littleRound(clip.fRight);
1532
0
                clip.fTop = littleRound(clip.fTop);
1533
0
                clip.fBottom = littleRound(clip.fBottom);
1534
0
            }
1535
0
            boxes.emplace_back(clip, run->getTextDirection());
1536
0
            return true;
1537
0
        });
1538
0
}
1539
}  // namespace textlayout
1540
}  // namespace skia