Flutter Engine
The Flutter Engine
TextLine.cpp
Go to the documentation of this file.
1// Copyright 2019 Google LLC.
2
4
10#include "include/core/SkSpan.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
38using namespace skia_private;
39
40namespace skia {
41namespace textlayout {
42
43namespace {
44
45// TODO: deal with all the intersection functionality
46TextRange intersected(const TextRange& a, const TextRange& b) {
47 if (a.start == b.start && a.end == b.end) return a;
48 auto begin = std::max(a.start, b.start);
49 auto end = std::min(a.end, b.end);
50 return end >= begin ? TextRange(begin, end) : EMPTY_TEXT;
51}
52
53SkScalar littleRound(SkScalar a) {
54 // This rounding is done to match Flutter tests. Must be removed..
55 return SkScalarRoundToScalar(a * 100.0)/100.0;
56}
57
58TextRange operator*(const TextRange& a, const TextRange& b) {
59 if (a.start == b.start && a.end == b.end) return a;
60 auto begin = std::max(a.start, b.start);
61 auto end = std::min(a.end, b.end);
62 return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
63}
64
65int 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
72 auto diff = SkScalarAbs(a - b);
73 if (nearlyZero(base) || diff / base < 0.001f) {
74 return 0;
75 }
76
77 auto ra = a;
78 auto rb = b;
79
80 if (applyRoundingHack) {
81 ra = littleRound(a);
82 rb = littleRound(b);
83 }
84 if (ra < rb) {
85 return -1;
86 } else {
87 return 1;
88 }
89}
90
91} // namespace
92
95 SkVector advance,
96 BlockRange blocks,
97 TextRange textExcludingSpaces,
99 TextRange textIncludingNewlines,
100 ClusterRange clusters,
101 ClusterRange clustersWithGhosts,
102 SkScalar widthWithSpaces,
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 , fTextBlobCachePopulated(false) {
124 // Reorder visual runs
125 auto& start = owner->cluster(fGhostClusterRange.start);
126 auto& end = owner->cluster(fGhostClusterRange.end - 1);
127 size_t numRuns = end.runIndex() - start.runIndex() + 1;
128
129 for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
130 auto b = fOwner->styles().begin() + index;
131 if (b->fStyle.hasBackground()) {
132 fHasBackground = true;
133 }
134 if (b->fStyle.getDecorationType() != TextDecoration::kNoDecoration) {
135 fHasDecorations = true;
136 }
137 if (b->fStyle.getShadowNumber() > 0) {
138 fHasShadows = true;
139 }
140 }
141
142 // Get the logical order
143
144 // This is just chosen to catch the common/fast cases. Feel free to tweak.
145 constexpr int kPreallocCount = 4;
147 std::vector<RunIndex> placeholdersInOriginalOrder;
148 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 for (auto runIndex = start.runIndex(); runIndex <= end.runIndex(); ++runIndex) {
153 auto& run = fOwner->run(runIndex);
154 runLevels[runLevelsIndex++] = run.fBidiLevel;
155 fMaxRunMetrics.add(
156 InternalLineMetrics(run.correctAscent(), run.correctDescent(), run.fFontMetrics.fLeading));
157 if (run.isPlaceholder()) {
158 placeholdersInOriginalOrder.push_back(runIndex);
159 }
160 }
161 SkASSERT(runLevelsIndex == numRuns);
162
163 AutoSTArray<kPreallocCount, int32_t> logicalOrder(numRuns);
164
165 // TODO: hide all these logic in SkUnicode?
166 fOwner->getUnicode()->reorderVisual(runLevels.data(), numRuns, logicalOrder.data());
167 auto firstRunIndex = start.runIndex();
168 auto placeholderIter = placeholdersInOriginalOrder.begin();
169 for (auto index : logicalOrder) {
170 auto runIndex = firstRunIndex + index;
171 if (fOwner->run(runIndex).isPlaceholder()) {
172 fRunsInVisualOrder.push_back(*placeholderIter++);
173 } else {
174 fRunsInVisualOrder.push_back(runIndex);
175 }
176 }
177
178 // TODO: This is the fix for flutter. Must be removed...
179 for (auto cluster = &start; cluster <= &end; ++cluster) {
180 if (!cluster->run().isPlaceholder()) {
181 fShift += cluster->getHalfLetterSpacing();
182 break;
183 }
184 }
185}
186
188 if (fHasBackground) {
189 this->iterateThroughVisualRuns(false,
190 [painter, x, y, this]
191 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
192 *runWidthInLine = this->iterateThroughSingleRunByStyles(
193 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kBackground,
194 [painter, x, y, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
195 this->paintBackground(painter, x, y, textRange, style, context);
196 });
197 return true;
198 });
199 }
200
201 if (fHasShadows) {
202 this->iterateThroughVisualRuns(false,
203 [painter, x, y, this]
204 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
205 *runWidthInLine = this->iterateThroughSingleRunByStyles(
206 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kShadow,
207 [painter, x, y, this]
208 (TextRange textRange, const TextStyle& style, const ClipContext& context) {
209 this->paintShadow(painter, x, y, textRange, style, context);
210 });
211 return true;
212 });
213 }
214
216
217 for (auto& record : fTextBlobCache) {
218 record.paint(painter, x, y);
219 }
220
221 if (fHasDecorations) {
222 this->iterateThroughVisualRuns(false,
223 [painter, x, y, this]
224 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
225 *runWidthInLine = this->iterateThroughSingleRunByStyles(
226 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kDecorations,
227 [painter, x, y, this]
228 (TextRange textRange, const TextStyle& style, const ClipContext& context) {
229 this->paintDecorations(painter, x, y, textRange, style, context);
230 });
231 return true;
232 });
233 }
234}
235
237 if (fTextBlobCachePopulated) {
238 return;
239 }
240 if (fBlockRange.width() == 1 &&
241 fRunsInVisualOrder.size() == 1 &&
242 fEllipsis == nullptr &&
243 fOwner->run(fRunsInVisualOrder[0]).placeholderStyle() == nullptr) {
244 if (fClusterRange.width() == 0) {
245 return;
246 }
247 // Most common and most simple case
248 const auto& style = fOwner->block(fBlockRange.start).fStyle;
249 const auto& run = fOwner->run(fRunsInVisualOrder[0]);
250 auto clip = SkRect::MakeXYWH(0.0f, this->sizes().runTop(&run, this->fAscentStyle),
251 fAdvance.fX,
252 run.calculateHeight(this->fAscentStyle, this->fDescentStyle));
253
254 auto& start = fOwner->cluster(fClusterRange.start);
255 auto& end = fOwner->cluster(fClusterRange.end - 1);
256 SkASSERT(start.runIndex() == end.runIndex());
258 if (run.leftToRight()) {
259 glyphs = GlyphRange(start.startPos(),
260 end.isHardBreak() ? end.startPos() : end.endPos());
261 } else {
262 glyphs = GlyphRange(end.startPos(),
263 start.isHardBreak() ? start.startPos() : start.endPos());
264 }
265 ClipContext context = {/*run=*/&run,
266 /*pos=*/glyphs.start,
267 /*size=*/glyphs.width(),
268 /*fTextShift=*/-run.positionX(glyphs.start), // starting position
269 /*clip=*/clip, // entire line
270 /*fExcludedTrailingSpaces=*/0.0f, // no need for that
271 /*clippingNeeded=*/false}; // no need for that
272 this->buildTextBlob(fTextExcludingSpaces, style, context);
273 } else {
274 this->iterateThroughVisualRuns(false,
275 [this](const Run* run,
276 SkScalar runOffsetInLine,
277 TextRange textRange,
278 SkScalar* runWidthInLine) {
279 if (run->placeholderStyle() != nullptr) {
280 *runWidthInLine = run->advance().fX;
281 return true;
282 }
283 *runWidthInLine = this->iterateThroughSingleRunByStyles(
284 TextAdjustment::GlyphCluster,
285 run,
286 runOffsetInLine,
287 textRange,
289 [this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
290 this->buildTextBlob(textRange, style, context);
291 });
292 return true;
293 });
294 }
295 fTextBlobCachePopulated = true;
296}
297
298void TextLine::format(TextAlign align, SkScalar maxWidth) {
299 SkScalar delta = maxWidth - this->width();
300 if (delta <= 0) {
301 return;
302 }
303
304 // We do nothing for left align
305 if (align == TextAlign::kJustify) {
306 if (!this->endsWithHardLineBreak()) {
307 this->justify(maxWidth);
308 } else if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
309 // Justify -> Right align
310 fShift = delta;
311 }
312 } else if (align == TextAlign::kRight) {
313 fShift = delta;
314 } else if (align == TextAlign::kCenter) {
315 fShift = delta / 2;
316 }
317}
318
319void TextLine::scanStyles(StyleType styleType, const RunStyleVisitor& visitor) {
320 if (this->empty()) {
321 return;
322 }
323
325 false,
326 [this, visitor, styleType](
327 const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width) {
329 TextAdjustment::GlyphCluster,
330 run,
331 runOffset,
332 textRange,
333 styleType,
334 [visitor](TextRange textRange,
335 const TextStyle& style,
336 const ClipContext& context) {
337 visitor(textRange, style, context);
338 });
339 return true;
340 });
341}
342
344 SkRect result = context.clip;
345 result.fBottom += std::max(this->fMaxRunMetrics.height() - this->height(), 0.0f);
346 return result;
347}
348
349void TextLine::buildTextBlob(TextRange textRange, const TextStyle& style, const ClipContext& context) {
350 if (context.run->placeholderStyle() != nullptr) {
351 return;
352 }
353
354 fTextBlobCache.emplace_back();
355 TextBlobRecord& record = fTextBlobCache.back();
356
357 if (style.hasForeground()) {
358 record.fPaint = style.getForegroundPaintOrID();
359 } else {
360 std::get<SkPaint>(record.fPaint).setColor(style.getColor());
361 }
362 record.fVisitor_Run = context.run;
363 record.fVisitor_Pos = context.pos;
364
365 // TODO: This is the change for flutter, must be removed later
367 context.run->copyTo(builder, SkToU32(context.pos), context.size);
368 record.fClippingNeeded = context.clippingNeeded;
369 if (context.clippingNeeded) {
370 record.fClipRect = extendHeight(context).makeOffset(this->offset());
371 } else {
372 record.fClipRect = context.clip.makeOffset(this->offset());
373 }
374
375 SkASSERT(nearlyEqual(context.run->baselineShift(), style.getBaselineShift()));
376 SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() + 0.5);
377 record.fBlob = builder.make();
378 if (record.fBlob != nullptr) {
379 record.fBounds.joinPossiblyEmptyRect(record.fBlob->bounds());
380 }
381
382 record.fOffset = SkPoint::Make(this->offset().fX + context.fTextShift,
383 this->offset().fY + correctedBaseline);
384}
385
386void TextLine::TextBlobRecord::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
387 if (fClippingNeeded) {
388 painter->save();
389 painter->clipRect(fClipRect.makeOffset(x, y));
390 }
391 painter->drawTextBlob(fBlob, x + fOffset.x(), y + fOffset.y(), fPaint);
392 if (fClippingNeeded) {
393 painter->restore();
394 }
395}
396
397void TextLine::paintBackground(ParagraphPainter* painter,
398 SkScalar x,
399 SkScalar y,
400 TextRange textRange,
401 const TextStyle& style,
402 const ClipContext& context) const {
403 if (style.hasBackground()) {
404 painter->drawRect(context.clip.makeOffset(this->offset() + SkPoint::Make(x, y)),
405 style.getBackgroundPaintOrID());
406 }
407}
408
409void TextLine::paintShadow(ParagraphPainter* painter,
410 SkScalar x,
411 SkScalar y,
412 TextRange textRange,
413 const TextStyle& style,
414 const ClipContext& context) const {
415 SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() + 0.5);
416
417 for (TextShadow shadow : style.getShadows()) {
418 if (!shadow.hasShadow()) continue;
419
421 context.run->copyTo(builder, context.pos, context.size);
422
423 if (context.clippingNeeded) {
424 painter->save();
425 SkRect clip = extendHeight(context);
426 clip.offset(x, y);
427 clip.offset(this->offset());
428 painter->clipRect(clip);
429 }
430 auto blob = builder.make();
431 painter->drawTextShadow(blob,
432 x + this->offset().fX + shadow.fOffset.x() + context.fTextShift,
433 y + this->offset().fY + shadow.fOffset.y() + correctedBaseline,
434 shadow.fColor,
435 SkDoubleToScalar(shadow.fBlurSigma));
436 if (context.clippingNeeded) {
437 painter->restore();
438 }
439 }
440}
441
442void TextLine::paintDecorations(ParagraphPainter* painter, SkScalar x, SkScalar y, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
443 ParagraphPainterAutoRestore ppar(painter);
444 painter->translate(x + this->offset().fX, y + this->offset().fY + style.getBaselineShift());
445 Decorations decorations;
446 SkScalar correctedBaseline = SkScalarFloorToScalar(-this->sizes().rawAscent() + style.getBaselineShift() + 0.5);
447 decorations.paint(painter, style, context, correctedBaseline);
448}
449
450void TextLine::justify(SkScalar maxWidth) {
451 int whitespacePatches = 0;
452 SkScalar textLen = 0;
453 SkScalar whitespaceLen = 0;
454 bool whitespacePatch = false;
455 // Take leading whitespaces width but do not increment a whitespace patch number
456 bool leadingWhitespaces = false;
457 this->iterateThroughClustersInGlyphsOrder(false, false,
458 [&](const Cluster* cluster, ClusterIndex index, bool ghost) {
459 if (cluster->isWhitespaceBreak()) {
460 if (index == 0) {
461 leadingWhitespaces = true;
462 } else if (!whitespacePatch && !leadingWhitespaces) {
463 // We only count patches BETWEEN words, not before
464 ++whitespacePatches;
465 }
466 whitespacePatch = !leadingWhitespaces;
467 whitespaceLen += cluster->width();
468 } else if (cluster->isIdeographic()) {
469 // Whitespace break before and after
470 if (!whitespacePatch && index != 0) {
471 // We only count patches BETWEEN words, not before
472 ++whitespacePatches; // before
473 }
474 whitespacePatch = true;
475 leadingWhitespaces = false;
476 ++whitespacePatches; // after
477 } else {
478 whitespacePatch = false;
479 leadingWhitespaces = false;
480 }
481 textLen += cluster->width();
482 return true;
483 });
484
485 if (whitespacePatch) {
486 // We only count patches BETWEEN words, not after
487 --whitespacePatches;
488 }
489 if (whitespacePatches == 0) {
490 if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
491 // Justify -> Right align
492 fShift = maxWidth - textLen;
493 }
494 return;
495 }
496
497 SkScalar step = (maxWidth - textLen + whitespaceLen) / whitespacePatches;
498 SkScalar shift = 0.0f;
499 SkScalar prevShift = 0.0f;
500
501 // Deal with the ghost spaces
502 auto ghostShift = maxWidth - this->fAdvance.fX;
503 // Spread the extra whitespaces
504 whitespacePatch = false;
505 // Do not break on leading whitespaces
506 leadingWhitespaces = false;
507 this->iterateThroughClustersInGlyphsOrder(false, true, [&](const Cluster* cluster, ClusterIndex index, bool ghost) {
508
509 if (ghost) {
510 if (cluster->run().leftToRight()) {
511 this->shiftCluster(cluster, ghostShift, ghostShift);
512 }
513 return true;
514 }
515
516 if (cluster->isWhitespaceBreak()) {
517 if (index == 0) {
518 leadingWhitespaces = true;
519 } else if (!whitespacePatch && !leadingWhitespaces) {
520 shift += step;
521 whitespacePatch = true;
522 --whitespacePatches;
523 }
524 shift -= cluster->width();
525 } else if (cluster->isIdeographic()) {
526 if (!whitespacePatch && index != 0) {
527 shift += step;
528 --whitespacePatches;
529 }
530 whitespacePatch = false;
531 leadingWhitespaces = false;
532 } else {
533 whitespacePatch = false;
534 leadingWhitespaces = false;
535 }
536 this->shiftCluster(cluster, shift, prevShift);
537 prevShift = shift;
538 // We skip ideographic whitespaces
539 if (!cluster->isWhitespaceBreak() && cluster->isIdeographic()) {
540 shift += step;
541 whitespacePatch = true;
542 --whitespacePatches;
543 }
544 return true;
545 });
546
547 if (whitespacePatch && whitespacePatches < 0) {
548 whitespacePatches++;
549 shift -= step;
550 }
551
552 SkAssertResult(nearlyEqual(shift, maxWidth - textLen));
553 SkASSERT(whitespacePatches == 0);
554
555 this->fWidthWithSpaces += ghostShift;
556 this->fAdvance.fX = maxWidth;
557}
558
559void TextLine::shiftCluster(const Cluster* cluster, SkScalar shift, SkScalar prevShift) {
560
561 auto& run = cluster->run();
562 auto start = cluster->startPos();
563 auto end = cluster->endPos();
564
565 if (end == run.size()) {
566 // Set the same shift for the fake last glyph (to avoid all extra checks)
567 ++end;
568 }
569
570 if (run.fJustificationShifts.empty()) {
571 // Do not fill this array until needed
572 run.fJustificationShifts.push_back_n(run.size() + 1, { 0, 0 });
573 }
574
575 for (size_t pos = start; pos < end; ++pos) {
576 run.fJustificationShifts[pos] = { shift, prevShift };
577 }
578}
579
580void 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 SkScalar width = fAdvance.fX;
585 RunIndex lastRun = EMPTY_RUN;
586 std::unique_ptr<Run> ellipsisRun;
587 for (auto clusterIndex = fGhostClusterRange.end; clusterIndex > fGhostClusterRange.start; --clusterIndex) {
588 auto& cluster = fOwner->cluster(clusterIndex - 1);
589 // Shape the ellipsis if the run has changed
590 if (lastRun != cluster.runIndex()) {
591 ellipsisRun = this->shapeEllipsis(ellipsis, &cluster);
592 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 lastRun = EMPTY_RUN;
596 continue;
597 } else {
598 // We may need to continue
599 lastRun = cluster.runIndex();
600 }
601 }
602 // See if it fits
603 if (width + ellipsisRun->advance().fX > maxWidth) {
604 width -= cluster.width();
605 // Continue if the ellipsis does not fit
606 continue;
607 }
608 // We found enough room for the ellipsis
609 fAdvance.fX = width;
610 fEllipsis = std::move(ellipsisRun);
611 fEllipsis->setOwner(fOwner);
612
613 // Let's update the line
614 fClusterRange.end = clusterIndex;
615 fGhostClusterRange.end = fClusterRange.end;
616 fEllipsis->fClusterStart = cluster.textRange().start;
617 fText.end = cluster.textRange().end;
618 fTextIncludingNewlines.end = cluster.textRange().end;
619 fTextExcludingSpaces.end = cluster.textRange().end;
620 break;
621 }
622
623 if (!fEllipsis) {
624 // Weird situation: ellipsis does not fit; no ellipsis then
625 fClusterRange.end = fClusterRange.start;
626 fGhostClusterRange.end = fClusterRange.start;
627 fText.end = fText.start;
628 fTextIncludingNewlines.end = fTextIncludingNewlines.start;
629 fTextExcludingSpaces.end = fTextExcludingSpaces.start;
630 fAdvance.fX = 0;
631 }
632}
633
634std::unique_ptr<Run> TextLine::shapeEllipsis(const SkString& ellipsis, const Cluster* cluster) {
635
636 class ShapeHandler final : public SkShaper::RunHandler {
637 public:
638 ShapeHandler(SkScalar lineHeight, bool useHalfLeading, SkScalar baselineShift, const SkString& ellipsis)
639 : fRun(nullptr), fLineHeight(lineHeight), fUseHalfLeading(useHalfLeading), fBaselineShift(baselineShift), fEllipsis(ellipsis) {}
640 std::unique_ptr<Run> run() & { return std::move(fRun); }
641
642 private:
643 void beginLine() override {}
644
645 void runInfo(const RunInfo&) override {}
646
647 void commitRunInfo() override {}
648
649 Buffer runBuffer(const RunInfo& info) override {
650 SkASSERT(!fRun);
651 fRun = std::make_unique<Run>(nullptr, info, 0, fLineHeight, fUseHalfLeading, fBaselineShift, 0, 0);
652 return fRun->newRunBuffer();
653 }
654
655 void commitRunBuffer(const RunInfo& info) override {
656 fRun->fAdvance.fX = info.fAdvance.fX;
657 fRun->fAdvance.fY = fRun->advance().fY;
658 fRun->fPlaceholderIndex = std::numeric_limits<size_t>::max();
659 fRun->fEllipsis = true;
660 }
661
662 void commitLine() override {}
663
664 std::unique_ptr<Run> fRun;
665 SkScalar fLineHeight;
666 bool fUseHalfLeading;
667 SkScalar fBaselineShift;
668 SkString fEllipsis;
669 };
670
671 const Run& run = cluster->run();
672 TextStyle textStyle = fOwner->paragraphStyle().getTextStyle();
673 for (auto i = fBlockRange.start; i < fBlockRange.end; ++i) {
674 auto& block = fOwner->block(i);
675 if (run.leftToRight() && cluster->textRange().end <= block.fRange.end) {
676 textStyle = block.fStyle;
677 break;
678 } else if (!run.leftToRight() && cluster->textRange().start <= block.fRange.end) {
679 textStyle = block.fStyle;
680 break;
681 }
682 }
683
684 auto shaped = [&](sk_sp<SkTypeface> typeface, sk_sp<SkFontMgr> fallback) -> std::unique_ptr<Run> {
685 ShapeHandler handler(run.heightMultiplier(), run.useHalfLeading(), run.baselineShift(), ellipsis);
686 SkFont font(std::move(typeface), textStyle.getFontSize());
688 font.setHinting(SkFontHinting::kSlight);
689 font.setSubpixel(true);
690
691 std::unique_ptr<SkShaper> shaper = SkShapers::HB::ShapeDontWrapOrReorder(
692 fOwner->getUnicode(), fallback ? fallback : SkFontMgr::RefEmpty());
693
694 const SkBidiIterator::Level defaultLevel = SkBidiIterator::kLTR;
695 const char* utf8 = ellipsis.c_str();
696 size_t utf8Bytes = ellipsis.size();
697
698 std::unique_ptr<SkShaper::BiDiRunIterator> bidi = SkShapers::unicode::BidiRunIterator(
699 fOwner->getUnicode(), utf8, utf8Bytes, defaultLevel);
700 SkASSERT(bidi);
701
702 std::unique_ptr<SkShaper::LanguageRunIterator> language =
704 SkASSERT(language);
705
706 std::unique_ptr<SkShaper::ScriptRunIterator> script =
709
710 std::unique_ptr<SkShaper::FontRunIterator> fontRuns = SkShaper::MakeFontMgrRunIterator(
711 utf8, utf8Bytes, font, fallback ? fallback : SkFontMgr::RefEmpty());
712 SkASSERT(fontRuns);
713
714 shaper->shape(utf8,
715 utf8Bytes,
716 *fontRuns,
717 *bidi,
718 *script,
719 *language,
720 nullptr,
721 0,
723 &handler);
724 auto ellipsisRun = handler.run();
725 ellipsisRun->fTextRange = TextRange(0, ellipsis.size());
726 ellipsisRun->fOwner = fOwner;
727 return ellipsisRun;
728 };
729
730 // Check the current font
731 auto ellipsisRun = shaped(run.fFont.refTypeface(), nullptr);
732 if (ellipsisRun->isResolved()) {
733 return ellipsisRun;
734 }
735
736 // Check all allowed fonts
737 std::vector<sk_sp<SkTypeface>> typefaces = fOwner->fontCollection()->findTypefaces(
738 textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
739 for (const auto& typeface : typefaces) {
740 ellipsisRun = shaped(typeface, nullptr);
741 if (ellipsisRun->isResolved()) {
742 return ellipsisRun;
743 }
744 }
745
746 // Try the fallback
747 if (fOwner->fontCollection()->fontFallbackEnabled()) {
748 const char* ch = ellipsis.c_str();
750 ellipsis.c_str()
751 + 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 auto typeface = fOwner->fontCollection()->defaultFallback(
755 unicode,
756 textStyle.getFontStyle(),
757 textStyle.getLocale());
758 if (typeface) {
759 ellipsisRun = shaped(typeface, fOwner->fontCollection()->getFallbackManager());
760 if (ellipsisRun->isResolved()) {
761 return ellipsisRun;
762 }
763 }
764 }
765 return ellipsisRun;
766}
767
768TextLine::ClipContext TextLine::measureTextInsideOneRun(TextRange textRange,
769 const Run* run,
770 SkScalar runOffsetInLine,
771 SkScalar textOffsetInRunInLine,
772 bool includeGhostSpaces,
773 TextAdjustment textAdjustment) const {
774 ClipContext result = { run, 0, run->size(), 0, SkRect::MakeEmpty(), 0, false };
775
776 if (run->fEllipsis) {
777 // Both ellipsis and placeholders can only be measured as one glyph
778 result.fTextShift = runOffsetInLine;
779 result.clip = SkRect::MakeXYWH(runOffsetInLine,
780 sizes().runTop(run, this->fAscentStyle),
781 run->advance().fX,
782 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
783 return result;
784 } else if (run->isPlaceholder()) {
785 result.fTextShift = runOffsetInLine;
786 if (SkIsFinite(run->fFontMetrics.fAscent)) {
787 result.clip = SkRect::MakeXYWH(runOffsetInLine,
788 sizes().runTop(run, this->fAscentStyle),
789 run->advance().fX,
790 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
791 } else {
792 result.clip = SkRect::MakeXYWH(runOffsetInLine, run->fFontMetrics.fAscent, run->advance().fX, 0);
793 }
794 return result;
795 } else if (textRange.empty()) {
796 return result;
797 }
798
799 TextRange originalTextRange(textRange); // We need it for proportional measurement
800 // Find [start:end] clusters for the text
801 while (true) {
802 // Update textRange by cluster edges (shift start up to the edge of the cluster)
803 // TODO: remove this limitation?
804 TextRange updatedTextRange;
805 bool found;
806 std::tie(found, updatedTextRange.start, updatedTextRange.end) =
807 run->findLimitingGlyphClusters(textRange);
808 if (!found) {
809 return result;
810 }
811
812 if ((textAdjustment & TextAdjustment::Grapheme) == 0) {
813 textRange = updatedTextRange;
814 break;
815 }
816
817 // Update text range by grapheme edges (shift start up to the edge of the grapheme)
818 std::tie(found, updatedTextRange.start, updatedTextRange.end) =
819 run->findLimitingGraphemes(updatedTextRange);
820 if (updatedTextRange == textRange) {
821 break;
822 }
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 textRange = updatedTextRange;
827
828 // Move the start until it's on the grapheme edge (and glypheme, too)
829 }
830 Cluster* start = &fOwner->cluster(fOwner->clusterIndex(textRange.start));
831 Cluster* end = &fOwner->cluster(fOwner->clusterIndex(textRange.end - (textRange.width() == 0 ? 0 : 1)));
832
833 if (!run->leftToRight()) {
835 }
836 result.pos = start->startPos();
837 result.size = (end->isHardBreak() ? end->startPos() : end->endPos()) - start->startPos();
838 auto textStartInRun = run->positionX(start->startPos());
839 auto textStartInLine = runOffsetInLine + textOffsetInRunInLine;
840 if (!run->leftToRight()) {
842 }
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 result.clip =
859 sizes().runTop(run, this->fAscentStyle),
860 run->calculateWidth(result.pos, result.pos + result.size, false),
861 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 auto leftCorrection = start->sizeToChar(originalTextRange.start);
868 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 result.clippingNeeded = leftCorrection != 0 || rightCorrection != 0;
876 if (run->leftToRight()) {
877 result.clip.fLeft += leftCorrection;
878 result.clip.fRight -= rightCorrection;
879 textStartInLine -= leftCorrection;
880 } else {
881 result.clip.fRight -= leftCorrection;
882 result.clip.fLeft += rightCorrection;
883 textStartInLine -= rightCorrection;
884 }
885
886 result.clip.offset(textStartInLine, 0);
887 //SkDebugf("@%f[%f:%f)\n", textStartInLine, result.clip.fLeft, result.clip.fRight);
888
889 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 if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kLtr) {
894 // We only use this member for LTR
895 result.fExcludedTrailingSpaces = std::max(result.clip.fRight - fAdvance.fX, 0.0f);
896 result.clippingNeeded = true;
897 result.clip.fRight = fAdvance.fX;
898 }
899 }
900
901 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 result.clip.fRight = result.clip.fLeft;
905 }
906
907 // The text must be aligned with the lineOffset
908 result.fTextShift = textStartInLine - textStartInRun;
909
910 return result;
911}
912
913void TextLine::iterateThroughClustersInGlyphsOrder(bool reversed,
914 bool includeGhosts,
915 const ClustersVisitor& visitor) const {
916 // Walk through the clusters in the logical order (or reverse)
917 SkSpan<const size_t> runs(fRunsInVisualOrder.data(), fRunsInVisualOrder.size());
918 bool ignore = false;
919 ClusterIndex index = 0;
920 directional_for_each(runs, !reversed, [&](decltype(runs[0]) r) {
921 if (ignore) return;
922 auto run = this->fOwner->run(r);
923 auto trimmedRange = fClusterRange.intersection(run.clusterRange());
924 auto trailedRange = fGhostClusterRange.intersection(run.clusterRange());
925 SkASSERT(trimmedRange.start == trailedRange.start);
926
927 auto trailed = fOwner->clusters(trailedRange);
928 auto trimmed = fOwner->clusters(trimmedRange);
929 directional_for_each(trailed, reversed != run.leftToRight(), [&](Cluster& cluster) {
930 if (ignore) return;
931 bool ghost = &cluster >= trimmed.end();
932 if (!includeGhosts && ghost) {
933 return;
934 }
935 if (!visitor(&cluster, index++, ghost)) {
936
937 ignore = true;
938 return;
939 }
940 });
941 });
942}
943
944SkScalar TextLine::iterateThroughSingleRunByStyles(TextAdjustment textAdjustment,
945 const Run* run,
946 SkScalar runOffset,
947 TextRange textRange,
948 StyleType styleType,
949 const RunStyleVisitor& visitor) const {
950 auto correctContext = [&](TextRange textRange, SkScalar textOffsetInRun) -> ClipContext {
951 auto result = this->measureTextInsideOneRun(
952 textRange, run, runOffset, textOffsetInRun, false, textAdjustment);
953 if (styleType == StyleType::kDecorations) {
954 // Decorations are drawn based on the real font metrics (regardless of styles and strut)
955 result.clip.fTop = this->sizes().runTop(run, LineMetricStyle::CSS);
956 result.clip.fBottom = result.clip.fTop +
957 run->calculateHeight(LineMetricStyle::CSS, LineMetricStyle::CSS);
958 }
959 return result;
960 };
961
962 if (run->fEllipsis) {
963 // Extra efforts to get the ellipsis text style
964 ClipContext clipContext = correctContext(run->textRange(), 0.0f);
965 TextRange testRange(run->fClusterStart, run->fClusterStart + run->textRange().width());
966 for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
967 auto block = fOwner->styles().begin() + index;
968 auto intersect = intersected(block->fRange, testRange);
969 if (intersect.width() > 0) {
970 visitor(testRange, block->fStyle, clipContext);
971 return run->advance().fX;
972 }
973 }
974 SkASSERT(false);
975 }
976
977 if (styleType == StyleType::kNone) {
978 ClipContext clipContext = correctContext(textRange, 0.0f);
979 // The placehoder can have height=0 or (exclusively) width=0 and still be a thing
980 if (clipContext.clip.height() > 0.0f || clipContext.clip.width() > 0.0f) {
981 visitor(textRange, TextStyle(), clipContext);
982 return clipContext.clip.width();
983 } else {
984 return 0;
985 }
986 }
987
989 size_t size = 0;
990 const TextStyle* prevStyle = nullptr;
991 SkScalar textOffsetInRun = 0;
992
993 const BlockIndex blockRangeSize = fBlockRange.end - fBlockRange.start;
994 for (BlockIndex index = 0; index <= blockRangeSize; ++index) {
995
997 TextStyle* style = nullptr;
998 if (index < blockRangeSize) {
999 auto block = fOwner->styles().begin() +
1000 (run->leftToRight() ? fBlockRange.start + index : fBlockRange.end - index - 1);
1001
1002 // Get the text
1003 intersect = intersected(block->fRange, textRange);
1004 if (intersect.width() == 0) {
1005 if (start == EMPTY_INDEX) {
1006 // This style is not applicable to the text yet
1007 continue;
1008 } else {
1009 // We have found all the good styles already
1010 // but we need to process the last one of them
1012 index = fBlockRange.end;
1013 }
1014 } else {
1015 // Get the style
1016 style = &block->fStyle;
1017 if (start != EMPTY_INDEX && style->matchOneAttribute(styleType, *prevStyle)) {
1018 size += intersect.width();
1019 // RTL text intervals move backward
1020 start = std::min(intersect.start, start);
1021 continue;
1022 } else if (start == EMPTY_INDEX ) {
1023 // First time only
1024 prevStyle = style;
1025 size = intersect.width();
1026 start = intersect.start;
1027 continue;
1028 }
1029 }
1030 } else if (prevStyle != nullptr) {
1031 // This is the last style
1032 } else {
1033 break;
1034 }
1035
1036 // We have the style and the text
1037 auto runStyleTextRange = TextRange(start, start + size);
1038 ClipContext clipContext = correctContext(runStyleTextRange, textOffsetInRun);
1039 textOffsetInRun += clipContext.clip.width();
1040 if (clipContext.clip.height() == 0) {
1041 continue;
1042 }
1043 visitor(runStyleTextRange, *prevStyle, clipContext);
1044
1045 // Start all over again
1046 prevStyle = style;
1047 start = intersect.start;
1048 size = intersect.width();
1049 }
1050 return textOffsetInRun;
1051}
1052
1053void TextLine::iterateThroughVisualRuns(bool includingGhostSpaces, const RunVisitor& visitor) const {
1054
1055 // Walk through all the runs that intersect with the line in visual order
1056 SkScalar width = 0;
1057 SkScalar runOffset = 0;
1058 SkScalar totalWidth = 0;
1059 auto textRange = includingGhostSpaces ? this->textWithNewlines() : this->trimmedText();
1060
1061 if (this->ellipsis() != nullptr && fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
1062 runOffset = this->ellipsis()->offset().fX;
1063 if (visitor(ellipsis(), runOffset, ellipsis()->textRange(), &width)) {
1064 }
1065 }
1066
1067 for (auto& runIndex : fRunsInVisualOrder) {
1068
1069 const auto run = &this->fOwner->run(runIndex);
1070 auto lineIntersection = intersected(run->textRange(), textRange);
1071 if (lineIntersection.width() == 0 && this->width() != 0) {
1072 // TODO: deal with empty runs in a better way
1073 continue;
1074 }
1075 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 TextRange whitespaces = intersected(
1080 TextRange(fTextExcludingSpaces.end, fTextIncludingNewlines.end), run->fTextRange);
1081 if (whitespaces.width() > 0) {
1082 auto whitespacesLen = measureTextInsideOneRun(whitespaces, run, runOffset, 0, true, TextAdjustment::GlyphCluster).clip.width();
1083 runOffset -= whitespacesLen;
1084 }
1085 }
1086 runOffset += width;
1087 totalWidth += width;
1088 if (!visitor(run, runOffset, lineIntersection, &width)) {
1089 return;
1090 }
1091 }
1092
1093 runOffset += width;
1094 totalWidth += width;
1095
1096 if (this->ellipsis() != nullptr && fOwner->paragraphStyle().getTextDirection() == TextDirection::kLtr) {
1097 if (visitor(ellipsis(), runOffset, ellipsis()->textRange(), &width)) {
1098 totalWidth += width;
1099 }
1100 }
1101
1102 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 SkDEBUGFAILF("ASSERT: %f != %f\n", totalWidth, this->width());
1106 }
1107}
1108
1110 return fOffset + SkVector::Make(fShift, 0);
1111}
1112
1113LineMetrics TextLine::getMetrics() const {
1115 SkASSERT(fOwner);
1116
1117 // Fill out the metrics
1118 fOwner->ensureUTF16Mapping();
1119 result.fStartIndex = fOwner->getUTF16Index(fTextExcludingSpaces.start);
1120 result.fEndExcludingWhitespaces = fOwner->getUTF16Index(fTextExcludingSpaces.end);
1121 result.fEndIndex = fOwner->getUTF16Index(fText.end);
1122 result.fEndIncludingNewline = fOwner->getUTF16Index(fTextIncludingNewlines.end);
1123 result.fHardBreak = endsWithHardLineBreak();
1124 result.fAscent = - fMaxRunMetrics.ascent();
1125 result.fDescent = fMaxRunMetrics.descent();
1126 result.fUnscaledAscent = - fMaxRunMetrics.ascent(); // TODO: implement
1127 result.fHeight = fAdvance.fY;
1128 result.fWidth = fAdvance.fX;
1129 if (fOwner->getApplyRoundingHack()) {
1130 result.fHeight = littleRound(result.fHeight);
1131 result.fWidth = littleRound(result.fWidth);
1132 }
1133 result.fLeft = this->offset().fX;
1134 // This is Flutter definition of a baseline
1135 result.fBaseline = this->offset().fY + this->height() - this->sizes().descent();
1136 result.fLineNumber = this - fOwner->lines().begin();
1137
1138 // Fill out the style parts
1139 this->iterateThroughVisualRuns(false,
1140 [this, &result]
1141 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1142 if (run->placeholderStyle() != nullptr) {
1143 *runWidthInLine = run->advance().fX;
1144 return true;
1145 }
1146 *runWidthInLine = this->iterateThroughSingleRunByStyles(
1147 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kForeground,
1148 [&result, &run](TextRange textRange, const TextStyle& style, const ClipContext& context) {
1149 SkFontMetrics fontMetrics;
1150 run->fFont.getMetrics(&fontMetrics);
1151 StyleMetrics styleMetrics(&style, fontMetrics);
1152 result.fLineMetrics.emplace(textRange.start, styleMetrics);
1153 });
1154 return true;
1155 });
1156
1157 return result;
1158}
1159
1160bool TextLine::isFirstLine() const {
1161 return this == &fOwner->lines().front();
1162}
1163
1164bool TextLine::isLastLine() const {
1165 return this == &fOwner->lines().back();
1166}
1167
1168bool 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 return (fGhostClusterRange.width() > 0 && fOwner->cluster(fGhostClusterRange.end - 1).isHardBreak()) ||
1172 fEllipsis != nullptr ||
1173 fGhostClusterRange.end == fOwner->clusters().size() - 1;
1174}
1175
1176void TextLine::getRectsForRange(TextRange textRange0,
1177 RectHeightStyle rectHeightStyle,
1178 RectWidthStyle rectWidthStyle,
1179 std::vector<TextBox>& boxes) const
1180{
1181 const Run* lastRun = nullptr;
1182 auto startBox = boxes.size();
1183 this->iterateThroughVisualRuns(true,
1184 [textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox, this]
1185 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1186 *runWidthInLine = this->iterateThroughSingleRunByStyles(
1187 TextAdjustment::GraphemeGluster, run, runOffsetInLine, textRange, StyleType::kNone,
1188 [run, runOffsetInLine, textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox, this]
1189 (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& lineContext) {
1190
1191 auto intersect = textRange * textRange0;
1192 if (intersect.empty()) {
1193 return true;
1194 }
1195
1196 auto paragraphStyle = fOwner->paragraphStyle();
1197
1198 // Found a run that intersects with the text
1199 auto context = this->measureTextInsideOneRun(
1200 intersect, run, runOffsetInLine, 0, true, TextAdjustment::GraphemeGluster);
1201 SkRect clip = context.clip;
1202 clip.offset(lineContext.fTextShift - context.fTextShift, 0);
1203
1204 switch (rectHeightStyle) {
1205 case RectHeightStyle::kMax:
1206 // TODO: Change it once flutter rolls into google3
1207 // (probably will break things if changed before)
1208 clip.fBottom = this->height();
1209 clip.fTop = this->sizes().delta();
1210 break;
1211 case RectHeightStyle::kIncludeLineSpacingTop: {
1212 clip.fBottom = this->height();
1213 clip.fTop = this->sizes().delta();
1214 auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1215 if (isFirstLine()) {
1216 clip.fTop += verticalShift;
1217 }
1218 break;
1219 }
1220 case RectHeightStyle::kIncludeLineSpacingMiddle: {
1221 clip.fBottom = this->height();
1222 clip.fTop = this->sizes().delta();
1223 auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1224 clip.offset(0, verticalShift / 2.0);
1225 if (isFirstLine()) {
1226 clip.fTop += verticalShift / 2.0;
1227 }
1228 if (isLastLine()) {
1229 clip.fBottom -= verticalShift / 2.0;
1230 }
1231 break;
1232 }
1233 case RectHeightStyle::kIncludeLineSpacingBottom: {
1234 clip.fBottom = this->height();
1235 clip.fTop = this->sizes().delta();
1236 auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1237 clip.offset(0, verticalShift);
1238 if (isLastLine()) {
1239 clip.fBottom -= verticalShift;
1240 }
1241 break;
1242 }
1243 case RectHeightStyle::kStrut: {
1244 const auto& strutStyle = paragraphStyle.getStrutStyle();
1245 if (strutStyle.getStrutEnabled()
1246 && strutStyle.getFontSize() > 0) {
1247 auto strutMetrics = fOwner->strutMetrics();
1248 auto top = this->baseline();
1249 clip.fTop = top + strutMetrics.ascent();
1250 clip.fBottom = top + strutMetrics.descent();
1251 }
1252 }
1253 break;
1254 case RectHeightStyle::kTight: {
1255 if (run->fHeightMultiplier <= 0) {
1256 break;
1257 }
1258 const auto effectiveBaseline = this->baseline() + this->sizes().delta();
1259 clip.fTop = effectiveBaseline + run->ascent();
1260 clip.fBottom = effectiveBaseline + run->descent();
1261 }
1262 break;
1263 default:
1264 SkASSERT(false);
1265 break;
1266 }
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 SkRect trailingSpaces = SkRect::MakeEmpty();
1271 if (this->trimmedText().end <this->textWithNewlines().end && // Line has trailing space
1272 this->textWithNewlines().end == intersect.end && // Range is at the end of the line
1273 this->trimmedText().end > intersect.start) // Range has more than just spaces
1274 {
1275 auto delta = this->spacesWidth();
1276 trailingSpaces = SkRect::MakeXYWH(0, 0, 0, 0);
1277 // There are trailing spaces in this run
1278 if (paragraphStyle.getTextAlign() == TextAlign::kJustify && isLastLine())
1279 {
1280 // TODO: this is just a patch. Make it right later (when it's clear what and how)
1281 trailingSpaces = clip;
1282 if(run->leftToRight()) {
1283 trailingSpaces.fLeft = this->width();
1284 clip.fRight = this->width();
1285 } else {
1286 trailingSpaces.fRight = 0;
1287 clip.fLeft = 0;
1288 }
1289 } else if (paragraphStyle.getTextDirection() == TextDirection::kRtl &&
1290 !run->leftToRight())
1291 {
1292 // Split
1293 trailingSpaces = clip;
1294 trailingSpaces.fLeft = - delta;
1295 trailingSpaces.fRight = 0;
1296 clip.fLeft += delta;
1297 } else if (paragraphStyle.getTextDirection() == TextDirection::kLtr &&
1298 run->leftToRight())
1299 {
1300 // Split
1301 trailingSpaces = clip;
1302 trailingSpaces.fLeft = this->width();
1303 trailingSpaces.fRight = trailingSpaces.fLeft + delta;
1304 clip.fRight -= delta;
1305 }
1306 }
1307
1308 clip.offset(this->offset());
1309 if (trailingSpaces.width() > 0) {
1310 trailingSpaces.offset(this->offset());
1311 }
1312
1313 // Check if we can merge two boxes instead of adding a new one
1314 auto merge = [&lastRun, &context, &boxes](SkRect clip) {
1315 bool mergedBoxes = false;
1316 if (!boxes.empty() &&
1317 lastRun != nullptr &&
1318 context.run->leftToRight() == lastRun->leftToRight() &&
1319 lastRun->placeholderStyle() == nullptr &&
1320 context.run->placeholderStyle() == nullptr &&
1321 nearlyEqual(lastRun->heightMultiplier(),
1322 context.run->heightMultiplier()) &&
1323 lastRun->font() == context.run->font())
1324 {
1325 auto& lastBox = boxes.back();
1326 if (nearlyEqual(lastBox.rect.fTop, clip.fTop) &&
1327 nearlyEqual(lastBox.rect.fBottom, clip.fBottom) &&
1328 (nearlyEqual(lastBox.rect.fLeft, clip.fRight) ||
1329 nearlyEqual(lastBox.rect.fRight, clip.fLeft)))
1330 {
1331 lastBox.rect.fLeft = std::min(lastBox.rect.fLeft, clip.fLeft);
1332 lastBox.rect.fRight = std::max(lastBox.rect.fRight, clip.fRight);
1333 mergedBoxes = true;
1334 }
1335 }
1336 lastRun = context.run;
1337 return mergedBoxes;
1338 };
1339
1340 if (!merge(clip)) {
1341 boxes.emplace_back(clip, context.run->getTextDirection());
1342 }
1343 if (!nearlyZero(trailingSpaces.width()) && !merge(trailingSpaces)) {
1344 boxes.emplace_back(trailingSpaces, paragraphStyle.getTextDirection());
1345 }
1346
1347 if (rectWidthStyle == RectWidthStyle::kMax && !isLastLine()) {
1348 // Align the very left/right box horizontally
1349 auto lineStart = this->offset().fX;
1350 auto lineEnd = this->offset().fX + this->width();
1351 auto left = boxes[startBox];
1352 auto right = boxes.back();
1353 if (left.rect.fLeft > lineStart && left.direction == TextDirection::kRtl) {
1354 left.rect.fRight = left.rect.fLeft;
1355 left.rect.fLeft = 0;
1356 boxes.insert(boxes.begin() + startBox + 1, left);
1357 }
1358 if (right.direction == TextDirection::kLtr &&
1359 right.rect.fRight >= lineEnd &&
1360 right.rect.fRight < fOwner->widthWithTrailingSpaces()) {
1361 right.rect.fLeft = right.rect.fRight;
1362 right.rect.fRight = fOwner->widthWithTrailingSpaces();
1363 boxes.emplace_back(right);
1364 }
1365 }
1366
1367 return true;
1368 });
1369 return true;
1370 });
1371 if (fOwner->getApplyRoundingHack()) {
1372 for (auto& r : boxes) {
1373 r.rect.fLeft = littleRound(r.rect.fLeft);
1374 r.rect.fRight = littleRound(r.rect.fRight);
1375 r.rect.fTop = littleRound(r.rect.fTop);
1376 r.rect.fBottom = littleRound(r.rect.fBottom);
1377 }
1378 }
1379}
1380
1381PositionWithAffinity TextLine::getGlyphPositionAtCoordinate(SkScalar dx) {
1382
1383 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 auto utf16Index = fOwner->getUTF16Index(this->fTextExcludingSpaces.end);
1387 return { SkToS32(utf16Index) , kDownstream };
1388 }
1389
1391 this->iterateThroughVisualRuns(true,
1392 [this, dx, &result]
1393 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1394 bool keepLooking = true;
1395 *runWidthInLine = this->iterateThroughSingleRunByStyles(
1396 TextAdjustment::GraphemeGluster, run, runOffsetInLine, textRange, StyleType::kNone,
1397 [this, run, dx, &result, &keepLooking]
1398 (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context0) {
1399
1400 SkScalar offsetX = this->offset().fX;
1401 ClipContext context = context0;
1402
1403 // Correct the clip size because libtxt counts trailing spaces
1404 if (run->leftToRight()) {
1405 context.clip.fRight += context.fExcludedTrailingSpaces; // extending clip to the right
1406 } else {
1407 // Clip starts from 0; we cannot extend it to the left from that
1408 }
1409 // However, we need to offset the clip
1410 context.clip.offset(offsetX, 0.0f);
1411
1412 // This patch will help us to avoid a floating point error
1413 if (SkScalarNearlyEqual(context.clip.fRight, dx, 0.01f)) {
1414 context.clip.fRight = dx;
1415 }
1416
1417 if (dx <= context.clip.fLeft) {
1418 // All the other runs are placed right of this one
1419 auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos));
1420 if (run->leftToRight()) {
1421 result = { SkToS32(utf16Index), kDownstream};
1422 keepLooking = false;
1423 } else {
1424 result = { SkToS32(utf16Index + 1), kUpstream};
1425 // If we haven't reached the end of the run we need to keep looking
1426 keepLooking = context.pos != 0;
1427 }
1428 // For RTL we go another way
1429 return !run->leftToRight();
1430 }
1431
1432 if (dx >= context.clip.fRight) {
1433 // We have to keep looking ; just in case keep the last one as the closest
1434 auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos + context.size));
1435 if (run->leftToRight()) {
1436 result = {SkToS32(utf16Index), kUpstream};
1437 } else {
1438 result = {SkToS32(utf16Index), kDownstream};
1439 }
1440 // For RTL we go another way
1441 return run->leftToRight();
1442 }
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 size_t found = context.pos;
1448 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 auto end = context.run->positionX(index) + context.fTextShift + offsetX;
1451 if (fOwner->getApplyRoundingHack()) {
1452 end = littleRound(end);
1453 }
1454 if (end > dx) {
1455 break;
1456 } 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 found = index;
1459 break;
1460 }
1461 found = index;
1462 }
1463
1464 SkScalar glyphemePosLeft = context.run->positionX(found) + context.fTextShift + offsetX;
1465 SkScalar glyphemesWidth = context.run->positionX(found + 1) - context.run->positionX(found);
1466
1467 // Find the grapheme range that contains the point
1468 auto clusterIndex8 = context.run->globalClusterIndex(found);
1469 auto clusterEnd8 = context.run->globalClusterIndex(found + 1);
1470 auto graphemes = fOwner->countSurroundingGraphemes({clusterIndex8, clusterEnd8});
1471
1472 SkScalar center = glyphemePosLeft + glyphemesWidth / 2;
1473 if (graphemes.size() > 1) {
1474 // Calculate the position proportionally based on grapheme count
1475 SkScalar averageGraphemeWidth = glyphemesWidth / graphemes.size();
1476 SkScalar delta = dx - glyphemePosLeft;
1477 int graphemeIndex = SkScalarNearlyZero(averageGraphemeWidth)
1478 ? 0
1479 : SkScalarFloorToInt(delta / averageGraphemeWidth);
1480 auto graphemeCenter = glyphemePosLeft + graphemeIndex * averageGraphemeWidth +
1481 averageGraphemeWidth / 2;
1482 auto graphemeUtf8Index = graphemes[graphemeIndex];
1483 if ((dx < graphemeCenter) == context.run->leftToRight()) {
1484 size_t utf16Index = fOwner->getUTF16Index(graphemeUtf8Index);
1485 result = { SkToS32(utf16Index), kDownstream };
1486 } else {
1487 size_t utf16Index = fOwner->getUTF16Index(graphemeUtf8Index + 1);
1488 result = { SkToS32(utf16Index), kUpstream };
1489 }
1490 // Keep UTF16 index as is
1491 } else if ((dx < center) == context.run->leftToRight()) {
1492 size_t utf16Index = fOwner->getUTF16Index(clusterIndex8);
1493 result = { SkToS32(utf16Index), kDownstream };
1494 } else {
1495 size_t utf16Index = context.run->leftToRight()
1496 ? fOwner->getUTF16Index(clusterEnd8)
1497 : fOwner->getUTF16Index(clusterIndex8) + 1;
1498 result = { SkToS32(utf16Index), kUpstream };
1499 }
1500
1501 return keepLooking = false;
1502
1503 });
1504 return keepLooking;
1505 }
1506 );
1507 return result;
1508}
1509
1510void TextLine::getRectsForPlaceholders(std::vector<TextBox>& boxes) {
1511 this->iterateThroughVisualRuns(
1512 true,
1513 [&boxes, this](const Run* run, SkScalar runOffset, TextRange textRange,
1514 SkScalar* width) {
1515 auto context = this->measureTextInsideOneRun(
1516 textRange, run, runOffset, 0, true, TextAdjustment::GraphemeGluster);
1517 *width = context.clip.width();
1518
1519 if (textRange.width() == 0) {
1520 return true;
1521 }
1522 if (!run->isPlaceholder()) {
1523 return true;
1524 }
1525
1526 SkRect clip = context.clip;
1527 clip.offset(this->offset());
1528
1529 if (fOwner->getApplyRoundingHack()) {
1530 clip.fLeft = littleRound(clip.fLeft);
1531 clip.fRight = littleRound(clip.fRight);
1532 clip.fTop = littleRound(clip.fTop);
1533 clip.fBottom = littleRound(clip.fBottom);
1534 }
1535 boxes.emplace_back(clip, run->getTextDirection());
1536 return true;
1537 });
1538}
1539} // namespace textlayout
1540} // namespace skia
static int step(int x, SkScalar min, SkScalar max)
Definition: BlurTest.cpp:215
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
uint16_t glyphs[5]
Definition: FontMgrTest.cpp:46
SkAssertResult(font.textToGlyphs("Hello", 5, SkTextEncoding::kUTF8, glyphs, std::size(glyphs))==count)
static bool intersect(const SkPoint &p0, const SkPoint &n0, const SkPoint &p1, const SkPoint &n1, SkScalar *t)
SkPoint pos
static void merge(const uint8_t *SK_RESTRICT row, int rowN, const SkAlpha *SK_RESTRICT srcAA, const int16_t *SK_RESTRICT srcRuns, SkAlpha *SK_RESTRICT dstAA, int16_t *SK_RESTRICT dstRuns, int width)
Definition: SkAAClip.cpp:1691
#define SkDEBUGFAILF(fmt,...)
Definition: SkAssert.h:119
#define SkASSERT(cond)
Definition: SkAssert.h:116
static bool SkIsFinite(T x, Pack... values)
@ kSlight
minimal modification to improve constrast
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
Definition: SkPath.cpp:3892
static bool left(const SkPoint &p0, const SkPoint &p1)
static bool right(const SkPoint &p0, const SkPoint &p1)
void swap(sk_sp< T > &a, sk_sp< T > &b)
Definition: SkRefCnt.h:341
#define SkScalarFloorToScalar(x)
Definition: SkScalar.h:30
static bool SkScalarNearlyZero(SkScalar x, SkScalar tolerance=SK_ScalarNearlyZero)
Definition: SkScalar.h:101
static bool SkScalarNearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
Definition: SkScalar.h:107
#define SkDoubleToScalar(x)
Definition: SkScalar.h:64
#define SkScalarRoundToScalar(x)
Definition: SkScalar.h:32
#define SkScalarFloorToInt(x)
Definition: SkScalar.h:35
#define SkScalarAbs(x)
Definition: SkScalar.h:39
constexpr int32_t SkToS32(S x)
Definition: SkTo.h:25
constexpr uint32_t SkToU32(S x)
Definition: SkTo.h:26
int32_t SkUnichar
Definition: SkTypes.h:175
static SkScalar center(float pos0, float pos1)
uint8_t Level
Definition: SkUnicode.h:46
static sk_sp< SkFontMgr > RefEmpty()
Definition: SkFontMgr.cpp:154
Definition: SkFont.h:35
@ kAntiAlias
may have transparent pixels on glyph edges
void offset(SkScalar dx, SkScalar dy, SkPath *dst) const
Definition: SkPath.cpp:1691
virtual void commitLine()=0
Definition: shape.cpp:201
virtual void commitRunBuffer(const RunInfo &)=0
Definition: shape.cpp:176
virtual void commitRunInfo()=0
Definition: shape.cpp:150
virtual void runInfo(const RunInfo &)=0
Definition: shape.cpp:142
virtual void beginLine()=0
Definition: shape.cpp:135
virtual Buffer runBuffer(const RunInfo &)=0
Definition: shape.cpp:154
static std::unique_ptr< FontRunIterator > MakeFontMgrRunIterator(const char *utf8, size_t utf8Bytes, const SkFont &font, sk_sp< SkFontMgr > fallback)
Definition: SkShaper.cpp:187
static std::unique_ptr< LanguageRunIterator > MakeStdLanguageRunIterator(const char *utf8, size_t utf8Bytes)
Definition: SkShaper.cpp:204
size_t size() const
Definition: SkString.h:131
const char * c_str() const
Definition: SkString.h:133
virtual void reorderVisual(const BidiLevel runLevels[], int levelsCount, int32_t logicalFromVisual[])=0
TextRange textRange() const
Definition: Run.h:325
Run & run() const
Definition: Run.cpp:336
Run & run(RunIndex runIndex)
Cluster & cluster(ClusterIndex clusterIndex)
const ParagraphStyle & paragraphStyle() const
Block & block(BlockIndex blockIndex)
sk_sp< SkUnicode > getUnicode()
bool isPlaceholder() const
Definition: Run.h:103
size_t globalClusterIndex(size_t pos) const
Definition: Run.h:105
SkScalar positionX(size_t pos) const
Definition: Run.cpp:308
const SkFont & font() const
Definition: Run.h:95
SkScalar heightMultiplier() const
Definition: Run.h:99
PlaceholderStyle * placeholderStyle() const
Definition: Run.cpp:312
size_t size() const
Definition: Run.h:78
bool leftToRight() const
Definition: Run.h:96
SkRect extendHeight(const ClipContext &context) const
Definition: TextLine.cpp:343
SkVector offset() const
Definition: TextLine.cpp:1109
void scanStyles(StyleType style, const RunStyleVisitor &visitor)
Definition: TextLine.cpp:319
std::function< bool(const Cluster *cluster, ClusterIndex index, bool ghost)> ClustersVisitor
Definition: TextLine.h:100
bool endsWithHardLineBreak() const
Definition: TextLine.cpp:1168
std::function< bool(const Run *run, SkScalar runOffset, TextRange textRange, SkScalar *width)> RunVisitor
Definition: TextLine.h:89
void format(TextAlign align, SkScalar maxWidth)
Definition: TextLine.cpp:298
SkScalar iterateThroughSingleRunByStyles(TextAdjustment textAdjustment, const Run *run, SkScalar runOffset, TextRange textRange, StyleType styleType, const RunStyleVisitor &visitor) const
Definition: TextLine.cpp:944
void iterateThroughClustersInGlyphsOrder(bool reverse, bool includeGhosts, const ClustersVisitor &visitor) const
Definition: TextLine.cpp:913
SkScalar width() const
Definition: TextLine.h:78
std::function< void(TextRange textRange, const TextStyle &style, const ClipContext &context)> RunStyleVisitor
Definition: TextLine.h:92
std::vector< TextBlobRecord > fTextBlobCache
Definition: TextLine.h:210
void paint(ParagraphPainter *painter, SkScalar x, SkScalar y)
Definition: TextLine.cpp:187
SkScalar baseline() const
Definition: TextLine.h:86
InternalLineMetrics sizes() const
Definition: TextLine.h:73
void iterateThroughVisualRuns(bool includingGhostSpaces, const RunVisitor &runVisitor) const
Definition: TextLine.cpp:1053
bool hasForeground() const
Definition: TextStyle.h:168
bool matchOneAttribute(StyleType styleType, const TextStyle &other) const
SkScalar getBaselineShift() const
Definition: TextStyle.h:257
ParagraphPainter::SkPaintOrID getForegroundPaintOrID() const
Definition: TextStyle.h:173
SkColor getColor() const
Definition: TextStyle.h:165
const T * data() const
Definition: SkTemplates.h:251
int size() const
Definition: SkTArray.h:421
const Paint & paint
Definition: color_source.cc:38
static const char * begin(const StringSlice &s)
Definition: editor.cpp:252
@ kBackground
Suitable for threads that shouldn't disrupt high priority work.
Definition: embedder.h:260
float SkScalar
Definition: extension.cpp:12
static bool b
struct MyStruct a[10]
if(end==-1)
glong glong end
switch(prop_id)
GAsyncResult * result
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static float min(float r, float g, float b)
Definition: hsl.cpp:48
Definition: dart.idl:550
std::u16string text
double y
double x
skia_private::AutoTArray< sk_sp< SkImageFilter > > filters TypedMatrix matrix TypedMatrix matrix SkScalar dx
Definition: SkRecords.h:208
SKSHAPER_API std::unique_ptr< SkShaper > ShapeDontWrapOrReorder(sk_sp< SkUnicode > unicode, sk_sp< SkFontMgr > fallback)
SKSHAPER_API std::unique_ptr< SkShaper::ScriptRunIterator > ScriptRunIterator(const char *utf8, size_t utf8Bytes)
SKSHAPER_API std::unique_ptr< SkShaper::BiDiRunIterator > BidiRunIterator(sk_sp< SkUnicode > unicode, const char *utf8, size_t utf8Bytes, uint8_t bidiLevel)
SK_SPI SkUnichar NextUTF8WithReplacement(const char **ptr, const char *end)
Definition: SkUTF.cpp:154
@ kNone
Definition: layer.h:53
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets Path to the Flutter assets directory enable service port fallback
Definition: switches.h:154
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
font
Font Metadata and Metrics.
Definition: run.py:1
def run(cmd)
Definition: run.py:14
size_t BlockIndex
Definition: TextStyle.h:355
const size_t EMPTY_INDEX
Definition: DartTypes.h:91
TextRange operator*(const TextRange &a, const TextRange &b)
const SkRange< size_t > EMPTY_TEXT
Definition: TextStyle.h:338
UnaryFunction directional_for_each(C &c, bool forwards, UnaryFunction f)
Definition: DartTypes.h:85
size_t TextIndex
Definition: TextStyle.h:336
SkRange< size_t > TextRange
Definition: TextStyle.h:337
SkRange< GlyphIndex > GlyphRange
Definition: Run.h:44
size_t ClusterIndex
Definition: Run.h:35
static bool nearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
Definition: TextStyle.h:31
static bool nearlyZero(SkScalar x, SkScalar tolerance=SK_ScalarNearlyZero)
Definition: TextStyle.h:24
const size_t EMPTY_RUN
Definition: Run.h:33
size_t RunIndex
Definition: Run.h:32
Definition: DartTypes.h:13
SkScalar offsetX
int32_t height
int32_t width
SeparatedVector2 offset
float fX
x-axis value
Definition: SkPoint_impl.h:164
void offset(float dx, float dy)
Definition: SkPoint_impl.h:269
static constexpr SkPoint Make(float x, float y)
Definition: SkPoint_impl.h:173
constexpr float x() const
Definition: SkPoint_impl.h:181
static constexpr SkRect MakeEmpty()
Definition: SkRect.h:595
constexpr SkRect makeOffset(float dx, float dy) const
Definition: SkRect.h:965
SkScalar fLeft
smaller x-axis bounds
Definition: extension.cpp:14
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition: SkRect.h:659
SkScalar fRight
larger x-axis bounds
Definition: extension.cpp:16
void offset(float dx, float dy)
Definition: SkRect.h:1016
constexpr float height() const
Definition: SkRect.h:769
constexpr float width() const
Definition: SkRect.h:762
SkScalar fTop
smaller y-axis bounds
Definition: extension.cpp:15
TextDirection getTextDirection() const