Flutter Engine
The Flutter Engine
Classes | Public Types | Public Member Functions | Public Attributes | List of all members
skia::textlayout::TextLine Class Reference

#include <TextLine.h>

Classes

struct  ClipContext
 

Public Types

enum  TextAdjustment { GlyphCluster = 0x01 , GlyphemeCluster = 0x02 , Grapheme = 0x04 , GraphemeGluster = 0x05 }
 
using RunVisitor = std::function< bool(const Run *run, SkScalar runOffset, TextRange textRange, SkScalar *width)>
 
using RunStyleVisitor = std::function< void(TextRange textRange, const TextStyle &style, const ClipContext &context)>
 
using ClustersVisitor = std::function< bool(const Cluster *cluster, ClusterIndex index, bool ghost)>
 

Public Member Functions

 TextLine ()=default
 
 TextLine (const TextLine &)=delete
 
TextLineoperator= (const TextLine &)=delete
 
 TextLine (TextLine &&)=default
 
TextLineoperator= (TextLine &&)=default
 
 ~TextLine ()=default
 
 TextLine (ParagraphImpl *owner, SkVector offset, SkVector advance, BlockRange blocks, TextRange textExcludingSpaces, TextRange text, TextRange textIncludingNewlines, ClusterRange clusters, ClusterRange clustersWithGhosts, SkScalar widthWithSpaces, InternalLineMetrics sizes)
 
TextRange trimmedText () const
 
TextRange textWithNewlines () const
 
TextRange text () const
 
ClusterRange clusters () const
 
ClusterRange clustersWithSpaces () const
 
Runellipsis () const
 
InternalLineMetrics sizes () const
 
bool empty () const
 
SkScalar spacesWidth () const
 
SkScalar height () const
 
SkScalar width () const
 
SkScalar widthWithoutEllipsis () const
 
SkVector offset () const
 
SkScalar alphabeticBaseline () const
 
SkScalar ideographicBaseline () const
 
SkScalar baseline () const
 
void iterateThroughVisualRuns (bool includingGhostSpaces, const RunVisitor &runVisitor) const
 
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
 
void format (TextAlign align, SkScalar maxWidth)
 
void paint (ParagraphPainter *painter, SkScalar x, SkScalar y)
 
void visit (SkScalar x, SkScalar y)
 
void ensureTextBlobCachePopulated ()
 
void createEllipsis (SkScalar maxWidth, const SkString &ellipsis, bool ltr)
 
void scanStyles (StyleType style, const RunStyleVisitor &visitor)
 
void setMaxRunMetrics (const InternalLineMetrics &metrics)
 
InternalLineMetrics getMaxRunMetrics () const
 
bool isFirstLine () const
 
bool isLastLine () const
 
void getRectsForRange (TextRange textRange, RectHeightStyle rectHeightStyle, RectWidthStyle rectWidthStyle, std::vector< TextBox > &boxes) const
 
void getRectsForPlaceholders (std::vector< TextBox > &boxes)
 
PositionWithAffinity getGlyphPositionAtCoordinate (SkScalar dx)
 
ClipContext measureTextInsideOneRun (TextRange textRange, const Run *run, SkScalar runOffsetInLine, SkScalar textOffsetInRunInLine, bool includeGhostSpaces, TextAdjustment textAdjustment) const
 
LineMetrics getMetrics () const
 
SkRect extendHeight (const ClipContext &context) const
 
void shiftVertically (SkScalar shift)
 
void setAscentStyle (LineMetricStyle style)
 
void setDescentStyle (LineMetricStyle style)
 
bool endsWithHardLineBreak () const
 

Public Attributes

std::vector< TextBlobRecord > fTextBlobCache
 

Detailed Description

Definition at line 28 of file TextLine.h.

Member Typedef Documentation

◆ ClustersVisitor

using skia::textlayout::TextLine::ClustersVisitor = std::function<bool(const Cluster* cluster, ClusterIndex index, bool ghost)>

Definition at line 100 of file TextLine.h.

◆ RunStyleVisitor

using skia::textlayout::TextLine::RunStyleVisitor = std::function<void( TextRange textRange, const TextStyle& style, const ClipContext& context)>

Definition at line 91 of file TextLine.h.

◆ RunVisitor

using skia::textlayout::TextLine::RunVisitor = std::function<bool( const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width)>

Definition at line 88 of file TextLine.h.

Member Enumeration Documentation

◆ TextAdjustment

Enumerator
GlyphCluster 
GlyphemeCluster 
Grapheme 
GraphemeGluster 

Definition at line 41 of file TextLine.h.

41 {
42 GlyphCluster = 0x01, // All text producing glyphs pointing to the same ClusterIndex
43 GlyphemeCluster = 0x02, // base glyph + all attached diacritics
44 Grapheme = 0x04, // Text adjusted to graphemes
45 GraphemeGluster = 0x05, // GlyphCluster & Grapheme
46 };

Constructor & Destructor Documentation

◆ TextLine() [1/4]

skia::textlayout::TextLine::TextLine ( )
default

◆ TextLine() [2/4]

skia::textlayout::TextLine::TextLine ( const TextLine )
delete

◆ TextLine() [3/4]

skia::textlayout::TextLine::TextLine ( TextLine &&  )
default

◆ ~TextLine()

skia::textlayout::TextLine::~TextLine ( )
default

◆ TextLine() [4/4]

skia::textlayout::TextLine::TextLine ( ParagraphImpl owner,
SkVector  offset,
SkVector  advance,
BlockRange  blocks,
TextRange  textExcludingSpaces,
TextRange  text,
TextRange  textIncludingNewlines,
ClusterRange  clusters,
ClusterRange  clustersWithGhosts,
SkScalar  widthWithSpaces,
InternalLineMetrics  sizes 
)

Definition at line 93 of file TextLine.cpp.

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}
#define SkASSERT(cond)
Definition: SkAssert.h:116
virtual void reorderVisual(const BidiLevel runLevels[], int levelsCount, int32_t logicalFromVisual[])=0
Run & run(RunIndex runIndex)
sk_sp< SkUnicode > getUnicode()
bool isPlaceholder() const
Definition: Run.h:103
SkVector offset() const
Definition: TextLine.cpp:1109
ClusterRange clusters() const
Definition: TextLine.h:70
TextRange text() const
Definition: TextLine.h:69
InternalLineMetrics sizes() const
Definition: TextLine.h:73
static bool b
glong glong end
Definition: run.py:1
size_t BlockIndex
Definition: TextStyle.h:355

Member Function Documentation

◆ alphabeticBaseline()

SkScalar skia::textlayout::TextLine::alphabeticBaseline ( ) const
inline

Definition at line 84 of file TextLine.h.

84{ return fSizes.alphabeticBaseline(); }
SkScalar alphabeticBaseline() const
Definition: Run.h:491

◆ baseline()

SkScalar skia::textlayout::TextLine::baseline ( ) const
inline

Definition at line 86 of file TextLine.h.

86{ return fSizes.baseline(); }

◆ clusters()

ClusterRange skia::textlayout::TextLine::clusters ( ) const
inline

Definition at line 70 of file TextLine.h.

70{ return fClusterRange; }

◆ clustersWithSpaces()

ClusterRange skia::textlayout::TextLine::clustersWithSpaces ( ) const
inline

Definition at line 71 of file TextLine.h.

71{ return fGhostClusterRange; }

◆ createEllipsis()

void skia::textlayout::TextLine::createEllipsis ( SkScalar  maxWidth,
const SkString ellipsis,
bool  ltr 
)

Definition at line 580 of file TextLine.cpp.

580 {
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}
Cluster & cluster(ClusterIndex clusterIndex)
SkScalar width() const
Definition: TextLine.h:78
Run * ellipsis() const
Definition: TextLine.h:72
float SkScalar
Definition: extension.cpp:12
const size_t EMPTY_RUN
Definition: Run.h:33
size_t RunIndex
Definition: Run.h:32
float fX
x-axis value
Definition: SkPoint_impl.h:164

◆ ellipsis()

Run * skia::textlayout::TextLine::ellipsis ( ) const
inline

Definition at line 72 of file TextLine.h.

72{ return fEllipsis.get(); }

◆ empty()

bool skia::textlayout::TextLine::empty ( ) const
inline

Definition at line 74 of file TextLine.h.

74{ return fTextExcludingSpaces.empty(); }

◆ endsWithHardLineBreak()

bool skia::textlayout::TextLine::endsWithHardLineBreak ( ) const

Definition at line 1168 of file TextLine.cpp.

1168 {
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}
bool isHardBreak() const
Definition: Run.h:310
SkSpan< Cluster > clusters()

◆ ensureTextBlobCachePopulated()

void skia::textlayout::TextLine::ensureTextBlobCachePopulated ( )

Definition at line 236 of file TextLine.cpp.

236 {
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}
uint16_t glyphs[5]
Definition: FontMgrTest.cpp:46
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
Definition: SkPath.cpp:3892
Block & block(BlockIndex blockIndex)
PlaceholderStyle * placeholderStyle() const
Definition: Run.cpp:312
void iterateThroughVisualRuns(bool includingGhostSpaces, const RunVisitor &runVisitor) const
Definition: TextLine.cpp:1053
int size() const
Definition: SkTArray.h:421
def run(cmd)
Definition: run.py:14
SkRange< size_t > TextRange
Definition: TextStyle.h:337
SkRange< GlyphIndex > GlyphRange
Definition: Run.h:44
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition: SkRect.h:659

◆ extendHeight()

SkRect skia::textlayout::TextLine::extendHeight ( const ClipContext context) const

Definition at line 343 of file TextLine.cpp.

343 {
344 SkRect result = context.clip;
345 result.fBottom += std::max(this->fMaxRunMetrics.height() - this->height(), 0.0f);
346 return result;
347}
GAsyncResult * result
static float max(float r, float g, float b)
Definition: hsl.cpp:49

◆ format()

void skia::textlayout::TextLine::format ( TextAlign  align,
SkScalar  maxWidth 
)

Definition at line 298 of file TextLine.cpp.

298 {
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}
const ParagraphStyle & paragraphStyle() const
bool endsWithHardLineBreak() const
Definition: TextLine.cpp:1168
TextDirection getTextDirection() const

◆ getGlyphPositionAtCoordinate()

PositionWithAffinity skia::textlayout::TextLine::getGlyphPositionAtCoordinate ( SkScalar  dx)

Definition at line 1381 of file TextLine.cpp.

1381 {
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
1390 PositionWithAffinity result(0, Affinity::kDownstream);
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}
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 SkScalarFloorToInt(x)
Definition: SkScalar.h:35
constexpr int32_t SkToS32(S x)
Definition: SkTo.h:25
skia_private::TArray< TextIndex > countSurroundingGraphemes(TextRange textRange) const
size_t getUTF16Index(TextIndex index) const
SkScalar spacesWidth() const
Definition: TextLine.h:76
SkScalar iterateThroughSingleRunByStyles(TextAdjustment textAdjustment, const Run *run, SkScalar runOffset, TextRange textRange, StyleType styleType, const RunStyleVisitor &visitor) const
Definition: TextLine.cpp:944
skia_private::AutoTArray< sk_sp< SkImageFilter > > filters TypedMatrix matrix TypedMatrix matrix SkScalar dx
Definition: SkRecords.h:208
@ kNone
Definition: layer.h:53
SkScalar offsetX

◆ getMaxRunMetrics()

InternalLineMetrics skia::textlayout::TextLine::getMaxRunMetrics ( ) const
inline

Definition at line 116 of file TextLine.h.

116{ return fMaxRunMetrics; }

◆ getMetrics()

LineMetrics skia::textlayout::TextLine::getMetrics ( ) const

Definition at line 1113 of file TextLine.cpp.

1113 {
1114 LineMetrics result;
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}
SkSpan< TextLine > lines()
SkScalar height() const
Definition: TextLine.h:77
float fY
y-axis value
Definition: SkPoint_impl.h:165

◆ getRectsForPlaceholders()

void skia::textlayout::TextLine::getRectsForPlaceholders ( std::vector< TextBox > &  boxes)

Definition at line 1510 of file TextLine.cpp.

1510 {
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}
void offset(SkScalar dx, SkScalar dy, SkPath *dst) const
Definition: SkPath.cpp:1691
ClipContext measureTextInsideOneRun(TextRange textRange, const Run *run, SkScalar runOffsetInLine, SkScalar textOffsetInRunInLine, bool includeGhostSpaces, TextAdjustment textAdjustment) const
Definition: TextLine.cpp:768

◆ getRectsForRange()

void skia::textlayout::TextLine::getRectsForRange ( TextRange  textRange,
RectHeightStyle  rectHeightStyle,
RectWidthStyle  rectWidthStyle,
std::vector< TextBox > &  boxes 
) const

Definition at line 1176 of file TextLine.cpp.

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) {
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;
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 }
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 }
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 }
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;
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}
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)
Definition: SkAAClip.cpp:1691
SkScalar rawAscent() const
Definition: Run.h:498
InternalLineMetrics strutMetrics() const
TextRange textWithNewlines() const
Definition: TextLine.h:68
TextRange trimmedText() const
Definition: TextLine.h:67
SkScalar baseline() const
Definition: TextLine.h:86
switch(prop_id)
static float min(float r, float g, float b)
Definition: hsl.cpp:48
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
static constexpr SkRect MakeEmpty()
Definition: SkRect.h:595
SkScalar fLeft
smaller x-axis bounds
Definition: extension.cpp:14
SkScalar fRight
larger x-axis bounds
Definition: extension.cpp:16
void offset(float dx, float dy)
Definition: SkRect.h:1016
constexpr float width() const
Definition: SkRect.h:762

◆ height()

SkScalar skia::textlayout::TextLine::height ( ) const
inline

Definition at line 77 of file TextLine.h.

77{ return fAdvance.fY; }

◆ ideographicBaseline()

SkScalar skia::textlayout::TextLine::ideographicBaseline ( ) const
inline

Definition at line 85 of file TextLine.h.

85{ return fSizes.ideographicBaseline(); }
SkScalar ideographicBaseline() const
Definition: Run.h:492

◆ isFirstLine()

bool skia::textlayout::TextLine::isFirstLine ( ) const

Definition at line 1160 of file TextLine.cpp.

1160 {
1161 return this == &fOwner->lines().front();
1162}

◆ isLastLine()

bool skia::textlayout::TextLine::isLastLine ( ) const

Definition at line 1164 of file TextLine.cpp.

1164 {
1165 return this == &fOwner->lines().back();
1166}

◆ iterateThroughClustersInGlyphsOrder()

void skia::textlayout::TextLine::iterateThroughClustersInGlyphsOrder ( bool  reverse,
bool  includeGhosts,
const ClustersVisitor visitor 
) const

Definition at line 913 of file TextLine.cpp.

915 {
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}
if(end==-1)
UnaryFunction directional_for_each(C &c, bool forwards, UnaryFunction f)
Definition: DartTypes.h:85
size_t ClusterIndex
Definition: Run.h:35
SkRange< size_t > intersection(SkRange< size_t > other) const
Definition: DartTypes.h:119

◆ iterateThroughSingleRunByStyles()

SkScalar skia::textlayout::TextLine::iterateThroughSingleRunByStyles ( TextAdjustment  textAdjustment,
const Run run,
SkScalar  runOffset,
TextRange  textRange,
StyleType  styleType,
const RunStyleVisitor visitor 
) const

Definition at line 944 of file TextLine.cpp.

949 {
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 +
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
1011 intersect = TextRange(start, start + size);
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}
SkScalar runTop(const Run *run, LineMetricStyle ascentStyle) const
Definition: Run.h:471
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
const size_t EMPTY_INDEX
Definition: DartTypes.h:91
size_t TextIndex
Definition: TextStyle.h:336

◆ iterateThroughVisualRuns()

void skia::textlayout::TextLine::iterateThroughVisualRuns ( bool  includingGhostSpaces,
const RunVisitor runVisitor 
) const

Definition at line 1053 of file TextLine.cpp.

1053 {
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}
#define SkDEBUGFAILF(fmt,...)
Definition: SkAssert.h:119
SkVector offset() const
Definition: Run.h:88

◆ measureTextInsideOneRun()

TextLine::ClipContext skia::textlayout::TextLine::measureTextInsideOneRun ( TextRange  textRange,
const Run run,
SkScalar  runOffsetInLine,
SkScalar  textOffsetInRunInLine,
bool  includeGhostSpaces,
TextAdjustment  textAdjustment 
) const

Definition at line 768 of file TextLine.cpp.

773 {
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()) {
834 std::swap(start, end);
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()) {
841 std::swap(start, end);
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
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}
static bool SkIsFinite(T x, Pack... values)
void swap(sk_sp< T > &a, sk_sp< T > &b)
Definition: SkRefCnt.h:341
ClusterIndex clusterIndex(TextIndex textIndex)

◆ offset()

SkVector skia::textlayout::TextLine::offset ( ) const

Definition at line 1109 of file TextLine.cpp.

1109 {
1110 return fOffset + SkVector::Make(fShift, 0);
1111}
static constexpr SkPoint Make(float x, float y)
Definition: SkPoint_impl.h:173

◆ operator=() [1/2]

TextLine & skia::textlayout::TextLine::operator= ( const TextLine )
delete

◆ operator=() [2/2]

TextLine & skia::textlayout::TextLine::operator= ( TextLine &&  )
default

◆ paint()

void skia::textlayout::TextLine::paint ( ParagraphPainter painter,
SkScalar  x,
SkScalar  y 
)

Definition at line 187 of file TextLine.cpp.

187 {
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}
std::vector< TextBlobRecord > fTextBlobCache
Definition: TextLine.h:210
@ kBackground
Suitable for threads that shouldn't disrupt high priority work.
Definition: embedder.h:260
double y
double x

◆ scanStyles()

void skia::textlayout::TextLine::scanStyles ( StyleType  style,
const RunStyleVisitor visitor 
)

Definition at line 319 of file TextLine.cpp.

319 {
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}

◆ setAscentStyle()

void skia::textlayout::TextLine::setAscentStyle ( LineMetricStyle  style)
inline

Definition at line 140 of file TextLine.h.

140{ fAscentStyle = style; }

◆ setDescentStyle()

void skia::textlayout::TextLine::setDescentStyle ( LineMetricStyle  style)
inline

Definition at line 141 of file TextLine.h.

141{ fDescentStyle = style; }

◆ setMaxRunMetrics()

void skia::textlayout::TextLine::setMaxRunMetrics ( const InternalLineMetrics metrics)
inline

Definition at line 115 of file TextLine.h.

115{ fMaxRunMetrics = metrics; }

◆ shiftVertically()

void skia::textlayout::TextLine::shiftVertically ( SkScalar  shift)
inline

Definition at line 138 of file TextLine.h.

138{ fOffset.fY += shift; }

◆ sizes()

InternalLineMetrics skia::textlayout::TextLine::sizes ( ) const
inline

Definition at line 73 of file TextLine.h.

73{ return fSizes; }

◆ spacesWidth()

SkScalar skia::textlayout::TextLine::spacesWidth ( ) const
inline

Definition at line 76 of file TextLine.h.

76{ return fWidthWithSpaces - width(); }

◆ text()

TextRange skia::textlayout::TextLine::text ( ) const
inline

Definition at line 69 of file TextLine.h.

69{ return fText; }

◆ textWithNewlines()

TextRange skia::textlayout::TextLine::textWithNewlines ( ) const
inline

Definition at line 68 of file TextLine.h.

68{ return fTextIncludingNewlines; }

◆ trimmedText()

TextRange skia::textlayout::TextLine::trimmedText ( ) const
inline

Definition at line 67 of file TextLine.h.

67{ return fTextExcludingSpaces; }

◆ visit()

void skia::textlayout::TextLine::visit ( SkScalar  x,
SkScalar  y 
)

◆ width()

SkScalar skia::textlayout::TextLine::width ( ) const
inline

Definition at line 78 of file TextLine.h.

78 {
79 return fAdvance.fX + (fEllipsis != nullptr ? fEllipsis->fAdvance.fX : 0);
80 }

◆ widthWithoutEllipsis()

SkScalar skia::textlayout::TextLine::widthWithoutEllipsis ( ) const
inline

Definition at line 81 of file TextLine.h.

81{ return fAdvance.fX; }

Member Data Documentation

◆ fTextBlobCache

std::vector<TextBlobRecord> skia::textlayout::TextLine::fTextBlobCache

Definition at line 210 of file TextLine.h.


The documentation for this class was generated from the following files: