renderToString method

String renderToString({
  1. CellSize? size,
  2. String emptyMark = '·',
  3. String imageMark = '#',
})

Renders the current tree and stringifies the buffer with one row per line. Leading cells write their grapheme, continuation cells contribute nothing (their leading cell handles them), and empty cells are rendered with emptyMark (default · for visibility against runs of whitespace).

Overlay (inline-image) cells are stamped one per column by an image placement (unlike a continuation, whose leading cell already contributed the pair's width), so each occupies a real column. The presenter draws them as pixels — never as text — but the snapshot must still hold their columns or every later column shifts left, encoding wrong geometry in goldens and hiding text that overlaps the image. They render as imageMark (default #) so the footprint is visible and text painted over the region breaks the mark's run. # is a real glyph, so a snapshot that also renders a widget which paints literal # (a ProgressBar, say) can't tell the two apart — override imageMark with a glyph that subject doesn't emit for those cases.

Trailing empty cells on a row are trimmed for readability; image marks are content and are never trimmed.

Implementation

String renderToString({
  CellSize? size,
  String emptyMark = '·',
  String imageMark = '#',
}) {
  final buffer = render(size: size);
  final out = StringBuffer();
  for (var row = 0; row < buffer.size.rows; row++) {
    // Trim by cell, not by text: a glyph that happens to equal the mark
    // is content, and a mark of any length (even empty) trims the same.
    var end = buffer.size.cols;
    while (end > 0 && buffer.atColRow(end - 1, row).role == CellRole.empty) {
      end--;
    }
    final line = StringBuffer();
    for (var col = 0; col < end; col++) {
      final cell = buffer.atColRow(col, row);
      switch (cell.role) {
        case CellRole.empty:
          line.write(emptyMark);
        case CellRole.leading:
          line.write(cell.grapheme);
        case CellRole.overlay:
          // An image-owned column: mark it so geometry is preserved and the
          // footprint stays visible (see doc above).
          line.write(imageMark);
        case CellRole.continuation:
          // The second column of a wide grapheme; its leading cell already
          // wrote the whole glyph, so this contributes nothing.
          break;
      }
    }
    out.writeln(line.toString());
  }
  return out.toString();
}