glyphsWithEllipsis method
Implementation
Iterable<LineRunGlyph> glyphsWithEllipsis(
double width, {
required Paragraph paragraph,
required List<TextShapeResult> cleanupShapes,
String ellipsis = '...',
bool isLastLine = false,
}) sync* {
var displayRuns = <GlyphRun>[];
// Iterate in logical order to add ellipsis at overflow.
var glyphRuns = paragraph.runs;
Font? ellipsisFont;
TextShapeResult? ellipsisShape;
GlyphRun? ellipsisRun;
double ellipsisWidth = 0;
double x = 0;
double ellipsisFontSize = 0;
int startGIndex = startIndex;
GlyphRun? endRunRef;
bool addedEllipsis = false;
var actualEndIndex = endIndex;
// If it's the last line we can actually early out if the whole things fits,
// so check that first with no extra shaping.
if (isLastLine) {
bool fits = true;
measuringLastLine:
for (int i = startRun; i < endRun + 1; i++) {
var run = glyphRuns[i];
int endGIndex = i == endRun ? endIndex : run.glyphCount;
for (int j = startGIndex; j != endGIndex; j++) {
x += run.advanceAt(j);
if (x > width) {
fits = false;
break measuringLastLine;
}
}
startGIndex = 0;
}
if (fits) {
// It fits, just get the regular glyphs.
yield* glyphs(paragraph);
return;
}
}
startGIndex = startIndex;
for (int i = startRun; i < endRun + 1; i++) {
var run = glyphRuns[i];
if (run.font != ellipsisFont && run.fontSize != ellipsisFontSize) {
// Track the latest we've checked (even if we discard it so we don't try
// to do this again for this ellipsis).
ellipsisFont = run.font;
ellipsisFontSize = run.fontSize;
// Get the next shape so we can check if it fits, otherwise keep using
// the last one.
var nextEllipsisShape = run.font.shape(
'...',
[
TextRun(
font: run.font,
fontSize: run.fontSize,
unicharCount: 3,
),
],
);
// Hard assumption one run and para
var para = nextEllipsisShape.paragraphs.first;
var nextEllipsisRun = para.runs.first;
double nextEllipsisWidth = 0;
for (int j = 0; j < nextEllipsisRun.glyphCount; j++) {
nextEllipsisWidth += nextEllipsisRun.advanceAt(j);
}
if (ellipsisShape == null || x + nextEllipsisWidth <= width) {
// This ellipsis still fits, go ahead and use it. Otherwise stick with
// the old one.
ellipsisWidth = nextEllipsisWidth;
ellipsisRun = nextEllipsisRun;
ellipsisShape?.dispose();
ellipsisShape = nextEllipsisShape;
}
}
int endGIndex = i == endRun ? endIndex : run.glyphCount;
for (int j = startGIndex; j != endGIndex; j++) {
var advance = run.advanceAt(j);
if (x + advance + ellipsisWidth > width) {
actualEndIndex = j;
addedEllipsis = true;
break;
}
x += advance;
}
endRunRef = run;
startGIndex = 0;
displayRuns.add(run);
if (addedEllipsis) {
if (ellipsisRun != null) {
displayRuns.add(ellipsisRun);
}
break;
}
}
// There was enough space for it, so let's add the ellipsis. Note that we
// already checked if this is the last line and found that the whole text
// didn't fit.
if (!addedEllipsis && ellipsisRun != null) {
displayRuns.add(ellipsisRun);
}
var startRunRef =
ellipsisRun == displayRuns.first ? null : displayRuns.first;
for (final run in paragraph.orderVisually(displayRuns)) {
int startGIndex = startRunRef == run ? startIndex : 0;
int endGIndex = endRunRef == run ? actualEndIndex : run.glyphCount;
int j, end, inc;
if (run.direction == TextDirection.rtl) {
j = endGIndex - 1;
end = startGIndex - 1;
inc = -1;
} else {
j = startGIndex;
end = endGIndex;
inc = 1;
}
while (j != end) {
yield LineRunGlyph(run, j);
j += inc;
}
}
if (ellipsisShape != null) {
cleanupShapes.add(ellipsisShape);
}
}