rasterize method

Future<void> rasterize({
  1. required int startLine,
  2. required int endLine,
})

Since the Paragraph rasterizing to the Canvas, and the getting of the Image bytes are async functions, there needs to be an async function not just the constructor. Plus we want the caller to decide how many lines of a long paragraph to rasterize, and when. Text lines as TxSprites are accumulated in this object. startLine and endLine are inclusive

Implementation

Future<void> rasterize({required int startLine, required int endLine}) async {
  if (isNotEmpty) {

    if (startLine < 0 || startLine > _lineMetrics.length - 1) throw Exception('startLine must be > 0 and < ${_lineMetrics.length}');
    if (endLine < startLine || endLine > _lineMetrics.length - 1) throw Exception('endLine must be >= startLine and < ${_lineMetrics.length}');

    // Calculate the top and bottom boundaries for the selected lines
    double topBoundary = 0;
    double bottomBoundary = 0;

    // work out a clip rectangle so we only draw the lines between startLine and endLine
    topBoundary = _lineMetrics[startLine].baseline - _lineMetrics[startLine].ascent;
    bottomBoundary = _lineMetrics[endLine].baseline + _lineMetrics[endLine].descent;

    // Define the area to clip: a window over the selected lines
    final clipRect = Rect.fromLTWH(
      0, // Start from the left edge of the canvas
      topBoundary, // Start clipping from the top of the startLine
      width.toDouble(), // Full width of the paragraph
      bottomBoundary - topBoundary, // Height of the selected lines
    );

    final pictureRecorder = ui.PictureRecorder();
    final canvas = ui.Canvas(pictureRecorder);
    canvas.clipRect(clipRect);

    canvas.drawParagraph(_paragraph, ui.Offset.zero);
    final ui.Picture picture = pictureRecorder.endRecording();

    final int totalHeight = (bottomBoundary - topBoundary).toInt();
    final int topOffset = topBoundary.toInt();

    final ui.Image image = await picture.toImage(_width, totalHeight);

    var byteData =
        (await image.toByteData(format: ui.ImageByteFormat.rawUnmodified))!;

    // loop over each requested line of text in the paragraph and create a TxSprite
    for (var line in _lineMetrics.sublist(startLine, endLine + 1)) {
      final int tlX = line.left.toInt();
      final int tlY = (line.baseline - line.ascent).toInt();
      final int tlyShifted = tlY - topOffset;
      int lineWidth = line.width.toInt();
      int lineHeight = (line.ascent + line.descent).toInt();

      // check for non-blank lines
      if (lineWidth > 0 && lineHeight > 0) {
        var linePixelData = Uint8List(lineWidth * lineHeight);

        for (int i = 0; i < lineHeight; i++) {
          // take one row of the source image byteData, remembering it's in RGBA so 4 bytes per pixel
          // and remembering the origin of the image is the top of the startLine, so we need to
          // shift all the top-left Ys by that first Y offset.
          var sourceRow = byteData.buffer
              .asUint8List(((tlyShifted + i) * _width + tlX) * 4, lineWidth * 4);

          for (int j = 0; j < lineWidth; j++) {
            // take only every 4th byte because the source buffer is RGBA
            // and map it to palette index 1 if it's 128 or bigger (monochrome palette only, and text rendering will be anti-aliased)
            linePixelData[i * lineWidth + j] = sourceRow[4 * j] >= 128 ? 1 : 0;
          }
        }

        // make a Sprite out of the line and add to the list
        _sprites.add(TxSprite(
            msgCode: _msgCode,
            width: lineWidth,
            height: lineHeight,
            numColors: 2,
            paletteData: _getPalette().data,
            pixelData: linePixelData));
      }
      else {
        // zero-width line, a blank line in the text block
        // so we make a 1x1 px sprite in the void color
        _sprites.add(TxSprite(
            msgCode: _msgCode,
            width: 1,
            height: 1,
            numColors: 2,
            paletteData: _getPalette().data,
            pixelData: Uint8List(1)));

      }
    }
  }
}