glyphsWithEllipsis method

Iterable<LineRunGlyph> glyphsWithEllipsis(
  1. double width, {
  2. required Paragraph paragraph,
  3. required List<TextShapeResult> cleanupShapes,
  4. String ellipsis = '...',
  5. bool isLastLine = false,
})

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);
  }
}