47 if (
a.start ==
b.start &&
a.end ==
b.end)
return a;
59 if (
a.start ==
b.start &&
a.end ==
b.end)
return a;
80 if (applyRoundingHack) {
105 , fBlockRange(blocks)
106 , fTextExcludingSpaces(textExcludingSpaces)
108 , fTextIncludingNewlines(textIncludingNewlines)
109 , fClusterRange(clusters)
110 , fGhostClusterRange(clustersWithGhosts)
111 , fRunsInVisualOrder()
115 , fWidthWithSpaces(widthWithSpaces)
118 , fHasBackground(
false)
120 , fHasDecorations(
false)
123 , fTextBlobCachePopulated(
false) {
127 size_t numRuns =
end.runIndex() -
start.runIndex() + 1;
130 auto b = fOwner->
styles().begin() + index;
131 if (
b->fStyle.hasBackground()) {
132 fHasBackground =
true;
135 fHasDecorations =
true;
137 if (
b->fStyle.getShadowNumber() > 0) {
145 constexpr int kPreallocCount = 4;
147 std::vector<RunIndex> placeholdersInOriginalOrder;
148 size_t runLevelsIndex = 0;
152 for (
auto runIndex =
start.runIndex(); runIndex <=
end.runIndex(); ++runIndex) {
153 auto&
run = fOwner->
run(runIndex);
154 runLevels[runLevelsIndex++] =
run.fBidiLevel;
157 if (
run.isPlaceholder()) {
158 placeholdersInOriginalOrder.push_back(runIndex);
161 SkASSERT(runLevelsIndex == numRuns);
167 auto firstRunIndex =
start.runIndex();
168 auto placeholderIter = placeholdersInOriginalOrder.begin();
169 for (
auto index : logicalOrder) {
170 auto runIndex = firstRunIndex + index;
172 fRunsInVisualOrder.
push_back(*placeholderIter++);
179 for (
auto cluster = &
start; cluster <= &
end; ++cluster) {
180 if (!cluster->run().isPlaceholder()) {
181 fShift += cluster->getHalfLetterSpacing();
188 if (fHasBackground) {
190 [painter,
x,
y,
this]
195 this->paintBackground(painter,
x,
y, textRange, style, context);
203 [painter,
x,
y,
this]
207 [painter,
x,
y,
this]
209 this->paintShadow(painter,
x,
y, textRange, style, context);
218 record.paint(painter,
x,
y);
221 if (fHasDecorations) {
223 [painter,
x,
y,
this]
227 [painter,
x,
y,
this]
229 this->paintDecorations(painter,
x,
y, textRange, style, context);
237 if (fTextBlobCachePopulated) {
240 if (fBlockRange.
width() == 1 &&
241 fRunsInVisualOrder.
size() == 1 &&
242 fEllipsis ==
nullptr &&
244 if (fClusterRange.
width() == 0) {
249 const auto&
run = fOwner->
run(fRunsInVisualOrder[0]);
252 run.calculateHeight(this->fAscentStyle, this->fDescentStyle));
258 if (
run.leftToRight()) {
260 end.isHardBreak() ?
end.startPos() :
end.endPos());
272 this->buildTextBlob(fTextExcludingSpaces, style, context);
279 if (
run->placeholderStyle() !=
nullptr) {
280 *runWidthInLine = run->advance().fX;
283 *runWidthInLine = this->iterateThroughSingleRunByStyles(
284 TextAdjustment::GlyphCluster,
290 this->buildTextBlob(textRange, style, context);
295 fTextBlobCachePopulated =
true;
307 this->justify(maxWidth);
326 [
this, visitor, styleType](
329 TextAdjustment::GlyphCluster,
337 visitor(textRange, style, context);
349void TextLine::buildTextBlob(
TextRange textRange,
const TextStyle& style,
const ClipContext& context) {
350 if (context.run->placeholderStyle() !=
nullptr) {
360 std::get<SkPaint>(record.fPaint).setColor(style.
getColor());
362 record.fVisitor_Run = context.run;
363 record.fVisitor_Pos = context.pos;
368 record.fClippingNeeded = context.clippingNeeded;
369 if (context.clippingNeeded) {
378 if (record.fBlob !=
nullptr) {
379 record.fBounds.joinPossiblyEmptyRect(record.fBlob->bounds());
383 this->
offset().fY + correctedBaseline);
387 if (fClippingNeeded) {
389 painter->clipRect(fClipRect.makeOffset(
x,
y));
391 painter->drawTextBlob(fBlob,
x + fOffset.x(),
y + fOffset.y(), fPaint);
392 if (fClippingNeeded) {
397void TextLine::paintBackground(ParagraphPainter* painter,
402 const ClipContext& context)
const {
403 if (style.hasBackground()) {
404 painter->drawRect(context.clip.makeOffset(this->offset() +
SkPoint::Make(
x,
y)),
405 style.getBackgroundPaintOrID());
409void TextLine::paintShadow(ParagraphPainter* painter,
414 const ClipContext& context)
const {
417 for (TextShadow shadow : style.getShadows()) {
418 if (!shadow.hasShadow())
continue;
421 context.run->copyTo(
builder, context.pos, context.size);
423 if (context.clippingNeeded) {
428 painter->clipRect(
clip);
431 painter->drawTextShadow(blob,
432 x + this->
offset().fX + shadow.fOffset.
x() + context.fTextShift,
433 y + this->offset().fY + shadow.fOffset.y() + correctedBaseline,
436 if (context.clippingNeeded) {
443 ParagraphPainterAutoRestore ppar(painter);
444 painter->translate(
x + this->
offset().fX,
y + this->
offset().fY + style.getBaselineShift());
445 Decorations decorations;
447 decorations.paint(painter, style, context, correctedBaseline);
450void TextLine::justify(
SkScalar maxWidth) {
451 int whitespacePatches = 0;
454 bool whitespacePatch =
false;
456 bool leadingWhitespaces =
false;
458 [&](
const Cluster* cluster,
ClusterIndex index,
bool ghost) {
459 if (cluster->isWhitespaceBreak()) {
461 leadingWhitespaces = true;
462 }
else if (!whitespacePatch && !leadingWhitespaces) {
466 whitespacePatch = !leadingWhitespaces;
467 whitespaceLen += cluster->width();
468 }
else if (cluster->isIdeographic()) {
470 if (!whitespacePatch && index != 0) {
474 whitespacePatch =
true;
475 leadingWhitespaces =
false;
478 whitespacePatch =
false;
479 leadingWhitespaces =
false;
481 textLen += cluster->width();
485 if (whitespacePatch) {
489 if (whitespacePatches == 0) {
490 if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
492 fShift = maxWidth - textLen;
497 SkScalar step = (maxWidth - textLen + whitespaceLen) / whitespacePatches;
502 auto ghostShift = maxWidth - this->fAdvance.fX;
504 whitespacePatch =
false;
506 leadingWhitespaces =
false;
507 this->iterateThroughClustersInGlyphsOrder(
false,
true, [&](
const Cluster* cluster,
ClusterIndex index,
bool ghost) {
510 if (cluster->run().leftToRight()) {
511 this->shiftCluster(cluster, ghostShift, ghostShift);
516 if (cluster->isWhitespaceBreak()) {
518 leadingWhitespaces = true;
519 }
else if (!whitespacePatch && !leadingWhitespaces) {
521 whitespacePatch =
true;
524 shift -= cluster->width();
525 }
else if (cluster->isIdeographic()) {
526 if (!whitespacePatch && index != 0) {
530 whitespacePatch =
false;
531 leadingWhitespaces =
false;
533 whitespacePatch =
false;
534 leadingWhitespaces =
false;
536 this->shiftCluster(cluster, shift, prevShift);
539 if (!cluster->isWhitespaceBreak() && cluster->isIdeographic()) {
541 whitespacePatch =
true;
547 if (whitespacePatch && whitespacePatches < 0) {
555 this->fWidthWithSpaces += ghostShift;
556 this->fAdvance.fX = maxWidth;
559void TextLine::shiftCluster(
const Cluster* cluster,
SkScalar shift,
SkScalar prevShift) {
561 auto&
run = cluster->run();
562 auto start = cluster->startPos();
563 auto end = cluster->endPos();
570 if (
run.fJustificationShifts.empty()) {
572 run.fJustificationShifts.push_back_n(
run.size() + 1, { 0, 0 });
576 run.fJustificationShifts[
pos] = { shift, prevShift };
586 std::unique_ptr<Run> ellipsisRun;
587 for (
auto clusterIndex = fGhostClusterRange.end; clusterIndex > fGhostClusterRange.start; --clusterIndex) {
588 auto& cluster = fOwner->cluster(clusterIndex - 1);
590 if (lastRun != cluster.runIndex()) {
591 ellipsisRun = this->shapeEllipsis(ellipsis, &cluster);
592 if (ellipsisRun->advance().fX > maxWidth) {
599 lastRun = cluster.runIndex();
603 if (
width + ellipsisRun->advance().fX > maxWidth) {
604 width -= cluster.width();
610 fEllipsis = std::move(ellipsisRun);
611 fEllipsis->setOwner(fOwner);
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;
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;
634std::unique_ptr<Run> TextLine::shapeEllipsis(
const SkString& ellipsis,
const Cluster* cluster) {
639 : fRun(nullptr), fLineHeight(lineHeight), fUseHalfLeading(useHalfLeading), fBaselineShift(baselineShift), fEllipsis(ellipsis) {}
640 std::unique_ptr<Run>
run() & {
return std::move(fRun); }
645 void runInfo(
const RunInfo&)
override {}
651 fRun = std::make_unique<Run>(
nullptr,
info, 0, fLineHeight, fUseHalfLeading, fBaselineShift, 0, 0);
652 return fRun->newRunBuffer();
656 fRun->fAdvance.fX =
info.fAdvance.fX;
657 fRun->fAdvance.fY = fRun->advance().fY;
659 fRun->fEllipsis =
true;
664 std::unique_ptr<Run> fRun;
666 bool fUseHalfLeading;
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;
678 }
else if (!
run.leftToRight() && cluster->
textRange().
start <= block.fRange.end) {
679 textStyle = block.fStyle;
685 ShapeHandler handler(
run.heightMultiplier(),
run.useHalfLeading(),
run.baselineShift(), ellipsis);
686 SkFont font(std::move(typeface), textStyle.getFontSize());
689 font.setSubpixel(
true);
696 size_t utf8Bytes = ellipsis.
size();
699 fOwner->getUnicode(),
utf8, utf8Bytes, defaultLevel);
702 std::unique_ptr<SkShaper::LanguageRunIterator> language =
706 std::unique_ptr<SkShaper::ScriptRunIterator>
script =
724 auto ellipsisRun = handler.run();
726 ellipsisRun->fOwner = fOwner;
731 auto ellipsisRun = shaped(
run.fFont.refTypeface(),
nullptr);
732 if (ellipsisRun->isResolved()) {
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()) {
747 if (fOwner->fontCollection()->fontFallbackEnabled()) {
748 const char* ch = ellipsis.
c_str();
754 auto typeface = fOwner->fontCollection()->defaultFallback(
756 textStyle.getFontStyle(),
757 textStyle.getLocale());
759 ellipsisRun = shaped(typeface, fOwner->fontCollection()->getFallbackManager());
760 if (ellipsisRun->isResolved()) {
772 bool includeGhostSpaces,
776 if (
run->fEllipsis) {
778 result.fTextShift = runOffsetInLine;
780 sizes().runTop(
run, this->fAscentStyle),
782 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
784 }
else if (
run->isPlaceholder()) {
785 result.fTextShift = runOffsetInLine;
788 sizes().runTop(
run, this->fAscentStyle),
790 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
795 }
else if (textRange.
empty()) {
806 std::tie(found, updatedTextRange.
start, updatedTextRange.
end) =
807 run->findLimitingGlyphClusters(textRange);
812 if ((textAdjustment & TextAdjustment::Grapheme) == 0) {
813 textRange = updatedTextRange;
818 std::tie(found, updatedTextRange.
start, updatedTextRange.
end) =
819 run->findLimitingGraphemes(updatedTextRange);
820 if (updatedTextRange == textRange) {
826 textRange = updatedTextRange;
831 Cluster*
end = &fOwner->cluster(fOwner->clusterIndex(textRange.
end - (textRange.
width() == 0 ? 0 : 1)));
833 if (!
run->leftToRight()) {
838 auto textStartInRun =
run->positionX(
start->startPos());
839 auto textStartInLine = runOffsetInLine + textOffsetInRunInLine;
840 if (!
run->leftToRight()) {
859 sizes().runTop(
run, this->fAscentStyle),
861 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
867 auto leftCorrection =
start->sizeToChar(originalTextRange.
start);
868 auto rightCorrection =
end->sizeFromChar(originalTextRange.
end - 1);
875 result.clippingNeeded = leftCorrection != 0 || rightCorrection != 0;
876 if (
run->leftToRight()) {
877 result.clip.fLeft += leftCorrection;
878 result.clip.fRight -= rightCorrection;
879 textStartInLine -= leftCorrection;
881 result.clip.fRight -= leftCorrection;
882 result.clip.fLeft += rightCorrection;
883 textStartInLine -= rightCorrection;
886 result.clip.offset(textStartInLine, 0);
889 if (compareRound(
result.clip.fRight, fAdvance.fX, fOwner->getApplyRoundingHack()) > 0 && !includeGhostSpaces) {
893 if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kLtr) {
896 result.clippingNeeded =
true;
897 result.clip.fRight = fAdvance.fX;
901 if (
result.clip.width() < 0) {
908 result.fTextShift = textStartInLine - textStartInRun;
913void TextLine::iterateThroughClustersInGlyphsOrder(
bool reversed,
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);
927 auto trailed = fOwner->clusters(trailedRange);
928 auto trimmed = fOwner->clusters(trimmedRange);
931 bool ghost = &cluster >= trimmed.end();
932 if (!includeGhosts && ghost) {
935 if (!visitor(&cluster, index++, ghost)) {
951 auto result = this->measureTextInsideOneRun(
952 textRange,
run, runOffset, textOffsetInRun,
false, textAdjustment);
955 result.clip.fTop = this->sizes().runTop(
run, LineMetricStyle::CSS);
957 run->calculateHeight(LineMetricStyle::CSS, LineMetricStyle::CSS);
962 if (
run->fEllipsis) {
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);
970 visitor(testRange, block->fStyle, clipContext);
971 return run->advance().fX;
978 ClipContext clipContext = correctContext(textRange, 0.0f);
981 visitor(textRange,
TextStyle(), clipContext);
993 const BlockIndex blockRangeSize = fBlockRange.end - fBlockRange.start;
994 for (
BlockIndex index = 0; index <= blockRangeSize; ++index) {
998 if (index < blockRangeSize) {
999 auto block = fOwner->styles().begin() +
1000 (
run->leftToRight() ? fBlockRange.start + index : fBlockRange.end - index - 1);
1003 intersect = intersected(block->fRange, textRange);
1012 index = fBlockRange.end;
1016 style = &block->fStyle;
1030 }
else if (prevStyle !=
nullptr) {
1038 ClipContext clipContext = correctContext(runStyleTextRange, textOffsetInRun);
1039 textOffsetInRun += clipContext.
clip.
width();
1043 visitor(runStyleTextRange, *prevStyle, clipContext);
1050 return textOffsetInRun;
1053void TextLine::iterateThroughVisualRuns(
bool includingGhostSpaces,
const RunVisitor& visitor)
const {
1059 auto textRange = includingGhostSpaces ? this->textWithNewlines() : this->trimmedText();
1061 if (this->ellipsis() !=
nullptr && fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
1062 runOffset = this->ellipsis()->offset().fX;
1063 if (visitor(ellipsis(), runOffset, ellipsis()->textRange(), &
width)) {
1067 for (
auto& runIndex : fRunsInVisualOrder) {
1069 const auto run = &this->fOwner->run(runIndex);
1070 auto lineIntersection = intersected(
run->textRange(), textRange);
1071 if (lineIntersection.width() == 0 && this->width() != 0) {
1075 if (!
run->leftToRight() && runOffset == 0 && includingGhostSpaces) {
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;
1087 totalWidth +=
width;
1088 if (!visitor(
run, runOffset, lineIntersection, &
width)) {
1094 totalWidth +=
width;
1096 if (this->ellipsis() !=
nullptr && fOwner->paragraphStyle().getTextDirection() == TextDirection::kLtr) {
1097 if (visitor(ellipsis(), runOffset, ellipsis()->textRange(), &
width)) {
1098 totalWidth +=
width;
1102 if (!includingGhostSpaces && compareRound(totalWidth, this->
width(), fOwner->getApplyRoundingHack()) != 0) {
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();
1127 result.fHeight = fAdvance.fY;
1128 result.fWidth = fAdvance.fX;
1129 if (fOwner->getApplyRoundingHack()) {
1136 result.fLineNumber =
this - fOwner->lines().begin();
1139 this->iterateThroughVisualRuns(
false,
1142 if (
run->placeholderStyle() !=
nullptr) {
1143 *runWidthInLine = run->advance().fX;
1146 *runWidthInLine = this->iterateThroughSingleRunByStyles(
1149 SkFontMetrics fontMetrics;
1150 run->fFont.getMetrics(&fontMetrics);
1151 StyleMetrics styleMetrics(&style, fontMetrics);
1152 result.fLineMetrics.emplace(textRange.start, styleMetrics);
1160bool TextLine::isFirstLine()
const {
1161 return this == &fOwner->lines().front();
1164bool TextLine::isLastLine()
const {
1165 return this == &fOwner->lines().back();
1168bool TextLine::endsWithHardLineBreak()
const {
1171 return (fGhostClusterRange.width() > 0 && fOwner->cluster(fGhostClusterRange.end - 1).isHardBreak()) ||
1172 fEllipsis !=
nullptr ||
1173 fGhostClusterRange.end == fOwner->clusters().size() - 1;
1179 std::vector<TextBox>& boxes)
const
1181 const Run* lastRun =
nullptr;
1182 auto startBox = boxes.
size();
1183 this->iterateThroughVisualRuns(
true,
1184 [textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox,
this]
1186 *runWidthInLine = this->iterateThroughSingleRunByStyles(
1188 [
run, runOffsetInLine, textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox,
this]
1191 auto intersect = textRange * textRange0;
1196 auto paragraphStyle = fOwner->paragraphStyle();
1199 auto context = this->measureTextInsideOneRun(
1200 intersect,
run, runOffsetInLine, 0,
true, TextAdjustment::GraphemeGluster);
1204 switch (rectHeightStyle) {
1205 case RectHeightStyle::kMax:
1209 clip.
fTop = this->sizes().delta();
1211 case RectHeightStyle::kIncludeLineSpacingTop: {
1213 clip.
fTop = this->sizes().delta();
1214 auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1215 if (isFirstLine()) {
1216 clip.fTop += verticalShift;
1220 case RectHeightStyle::kIncludeLineSpacingMiddle: {
1222 clip.
fTop = this->sizes().delta();
1223 auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1225 if (isFirstLine()) {
1226 clip.fTop += verticalShift / 2.0;
1229 clip.fBottom -= verticalShift / 2.0;
1233 case RectHeightStyle::kIncludeLineSpacingBottom: {
1235 clip.
fTop = this->sizes().delta();
1236 auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1239 clip.fBottom -= verticalShift;
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();
1254 case RectHeightStyle::kTight: {
1255 if (
run->fHeightMultiplier <= 0) {
1258 const auto effectiveBaseline = this->baseline() + this->sizes().delta();
1259 clip.fTop = effectiveBaseline +
run->ascent();
1260 clip.fBottom = effectiveBaseline +
run->descent();
1271 if (this->trimmedText().end <this->textWithNewlines().
end &&
1273 this->trimmedText().end >
intersect.start)
1275 auto delta = this->spacesWidth();
1278 if (paragraphStyle.getTextAlign() == TextAlign::kJustify && isLastLine())
1281 trailingSpaces =
clip;
1282 if(
run->leftToRight()) {
1286 trailingSpaces.
fRight = 0;
1289 }
else if (paragraphStyle.getTextDirection() == TextDirection::kRtl &&
1290 !
run->leftToRight())
1293 trailingSpaces =
clip;
1295 trailingSpaces.
fRight = 0;
1297 }
else if (paragraphStyle.getTextDirection() == TextDirection::kLtr &&
1301 trailingSpaces =
clip;
1309 if (trailingSpaces.
width() > 0) {
1315 bool mergedBoxes =
false;
1316 if (!boxes.empty() &&
1317 lastRun !=
nullptr &&
1320 context.run->placeholderStyle() ==
nullptr &&
1322 context.run->heightMultiplier()) &&
1323 lastRun->
font() == context.run->font())
1325 auto& lastBox = boxes.back();
1331 lastBox.rect.fLeft =
std::min(lastBox.rect.fLeft,
clip.fLeft);
1332 lastBox.rect.fRight =
std::max(lastBox.rect.fRight,
clip.fRight);
1336 lastRun = context.run;
1341 boxes.emplace_back(
clip, context.run->getTextDirection());
1344 boxes.emplace_back(trailingSpaces, paragraphStyle.getTextDirection());
1347 if (rectWidthStyle == RectWidthStyle::kMax && !isLastLine()) {
1349 auto lineStart = this->
offset().fX;
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);
1358 if (
right.direction == TextDirection::kLtr &&
1359 right.rect.fRight >= lineEnd &&
1360 right.rect.fRight < fOwner->widthWithTrailingSpaces()) {
1362 right.rect.fRight = fOwner->widthWithTrailingSpaces();
1363 boxes.emplace_back(
right);
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);
1386 auto utf16Index = fOwner->getUTF16Index(this->fTextExcludingSpaces.end);
1391 this->iterateThroughVisualRuns(
true,
1394 bool keepLooking =
true;
1395 *runWidthInLine = this->iterateThroughSingleRunByStyles(
1404 if (
run->leftToRight()) {
1420 if (
run->leftToRight()) {
1422 keepLooking =
false;
1426 keepLooking = context.
pos != 0;
1429 return !
run->leftToRight();
1435 if (
run->leftToRight()) {
1441 return run->leftToRight();
1447 size_t found = context.
pos;
1448 for (
size_t index = context.
pos; index < context.
pos + context.
size; ++index) {
1451 if (fOwner->getApplyRoundingHack()) {
1470 auto graphemes = fOwner->countSurroundingGraphemes({clusterIndex8, clusterEnd8});
1473 if (graphemes.size() > 1) {
1475 SkScalar averageGraphemeWidth = glyphemesWidth / graphemes.size();
1480 auto graphemeCenter = glyphemePosLeft + graphemeIndex * averageGraphemeWidth +
1481 averageGraphemeWidth / 2;
1482 auto graphemeUtf8Index = graphemes[graphemeIndex];
1484 size_t utf16Index = fOwner->getUTF16Index(graphemeUtf8Index);
1487 size_t utf16Index = fOwner->getUTF16Index(graphemeUtf8Index + 1);
1492 size_t utf16Index = fOwner->getUTF16Index(clusterIndex8);
1496 ? fOwner->getUTF16Index(clusterEnd8)
1497 : fOwner->getUTF16Index(clusterIndex8) + 1;
1501 return keepLooking =
false;
1510void TextLine::getRectsForPlaceholders(std::vector<TextBox>& boxes) {
1511 this->iterateThroughVisualRuns(
1515 auto context = this->measureTextInsideOneRun(
1516 textRange,
run, runOffset, 0,
true, TextAdjustment::GraphemeGluster);
1517 *
width = context.clip.width();
1519 if (textRange.
width() == 0) {
1522 if (!
run->isPlaceholder()) {
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);
1535 boxes.emplace_back(
clip,
run->getTextDirection());
static int step(int x, SkScalar min, SkScalar max)
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
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)
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)
#define SkDEBUGFAILF(fmt,...)
static bool SkIsFinite(T x, Pack... values)
@ kSlight
minimal modification to improve constrast
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
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)
#define SkScalarFloorToScalar(x)
static bool SkScalarNearlyZero(SkScalar x, SkScalar tolerance=SK_ScalarNearlyZero)
static bool SkScalarNearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
#define SkDoubleToScalar(x)
#define SkScalarRoundToScalar(x)
#define SkScalarFloorToInt(x)
constexpr int32_t SkToS32(S x)
constexpr uint32_t SkToU32(S x)
static SkScalar center(float pos0, float pos1)
static sk_sp< SkFontMgr > RefEmpty()
@ kAntiAlias
may have transparent pixels on glyph edges
void offset(SkScalar dx, SkScalar dy, SkPath *dst) const
virtual void commitLine()=0
virtual void commitRunBuffer(const RunInfo &)=0
virtual void commitRunInfo()=0
virtual void runInfo(const RunInfo &)=0
virtual void beginLine()=0
virtual Buffer runBuffer(const RunInfo &)=0
static std::unique_ptr< FontRunIterator > MakeFontMgrRunIterator(const char *utf8, size_t utf8Bytes, const SkFont &font, sk_sp< SkFontMgr > fallback)
static std::unique_ptr< LanguageRunIterator > MakeStdLanguageRunIterator(const char *utf8, size_t utf8Bytes)
const char * c_str() const
virtual void reorderVisual(const BidiLevel runLevels[], int levelsCount, int32_t logicalFromVisual[])=0
TextRange textRange() const
Run & run(RunIndex runIndex)
Cluster & cluster(ClusterIndex clusterIndex)
const ParagraphStyle & paragraphStyle() const
Block & block(BlockIndex blockIndex)
sk_sp< SkUnicode > getUnicode()
bool isPlaceholder() const
size_t globalClusterIndex(size_t pos) const
SkScalar positionX(size_t pos) const
const SkFont & font() const
SkScalar heightMultiplier() const
PlaceholderStyle * placeholderStyle() const
SkRect extendHeight(const ClipContext &context) const
void scanStyles(StyleType style, const RunStyleVisitor &visitor)
std::function< bool(const Cluster *cluster, ClusterIndex index, bool ghost)> ClustersVisitor
bool endsWithHardLineBreak() const
std::function< bool(const Run *run, SkScalar runOffset, TextRange textRange, SkScalar *width)> RunVisitor
void format(TextAlign align, SkScalar maxWidth)
SkScalar iterateThroughSingleRunByStyles(TextAdjustment textAdjustment, const Run *run, SkScalar runOffset, TextRange textRange, StyleType styleType, const RunStyleVisitor &visitor) const
void iterateThroughClustersInGlyphsOrder(bool reverse, bool includeGhosts, const ClustersVisitor &visitor) const
std::function< void(TextRange textRange, const TextStyle &style, const ClipContext &context)> RunStyleVisitor
std::vector< TextBlobRecord > fTextBlobCache
void paint(ParagraphPainter *painter, SkScalar x, SkScalar y)
SkScalar baseline() const
void ensureTextBlobCachePopulated()
InternalLineMetrics sizes() const
void iterateThroughVisualRuns(bool includingGhostSpaces, const RunVisitor &runVisitor) const
bool hasForeground() const
bool matchOneAttribute(StyleType styleType, const TextStyle &other) const
SkScalar getBaselineShift() const
ParagraphPainter::SkPaintOrID getForegroundPaintOrID() const
static const char * begin(const StringSlice &s)
@ kBackground
Suitable for threads that shouldn't disrupt high priority work.
static float max(float r, float g, float b)
static float min(float r, float g, float b)
skia_private::AutoTArray< sk_sp< SkImageFilter > > filters TypedMatrix matrix TypedMatrix matrix SkScalar dx
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)
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
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
font
Font Metadata and Metrics.
TextRange operator*(const TextRange &a, const TextRange &b)
const SkRange< size_t > EMPTY_TEXT
UnaryFunction directional_for_each(C &c, bool forwards, UnaryFunction f)
SkRange< size_t > TextRange
SkRange< GlyphIndex > GlyphRange
static bool nearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
static bool nearlyZero(SkScalar x, SkScalar tolerance=SK_ScalarNearlyZero)
void offset(float dx, float dy)
static constexpr SkPoint Make(float x, float y)
constexpr float x() const
static constexpr SkRect MakeEmpty()
constexpr SkRect makeOffset(float dx, float dy) const
SkScalar fLeft
smaller x-axis bounds
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
SkScalar fRight
larger x-axis bounds
void offset(float dx, float dy)
constexpr float height() const
constexpr float width() const
SkScalar fTop
smaller y-axis bounds
TextDirection getTextDirection() const
SkScalar fExcludedTrailingSpaces