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