exportPagePng function

Future<Uint8List> exportPagePng({
  1. required DraftDocument document,
  2. required PageComponent page,
  3. ExportDpi dpi = ExportDpi.d150,
  4. Set<Handle> omitOwners = const {},
  5. List<PageUnderlayFill> underlay = const [],
})

The page of document as a PNG at dpi (spec 13 D5).

  • The image is round(effW / 25.4 · dpi) × round(effH / 25.4 · dpi) pixels; the drawing goes through the page camera at u = dpi / 25.4, unrounded, so the last column or row is a part pixel at most.
  • An opaque white ground is filled first; the resolver's foreground is black and level of detail is off, as for exportPagePdf.
  • The drawing goes through a CanvasDrawSink, whose paths are anti-aliased, with a FlutterTextMeasurer of the export's own for its paragraphs, cleared before this returns. The painter lays every text box out with document.textMeasurer, as on the screen.
  • A leaf whose direct owner is in omitOwners is not drawn (spec D6).
  • underlay is drawn after the ground and before the document (plan colours spec PC-7); empty, the bytes are today's.
  • The PNG carries exactly one pHYs chunk, right after IHDR: pixels per metre round(dpi / 0.0254) on both axes, unit metre.

The export reads document and writes nothing to it: its codec bytes, its stateId and its dispatcher's mutation hooks are as they were (see _withExportIndex), on a throw as well. Only the paint runs under the export's index; toImage and the encoding run after it is gone.

Implementation

Future<Uint8List> exportPagePng({
  required DraftDocument document,
  required PageComponent page,
  ExportDpi dpi = ExportDpi.d150,
  Set<Handle> omitOwners = const {},
  List<PageUnderlayFill> underlay = const [],
}) async {
  final dotsPerInch = dpi.value;
  final camera = pageCamera(page, dotsPerInch / 25.4);
  final width = (page.effectiveWidthMm / 25.4 * dotsPerInch).round();
  final height = (page.effectiveHeightMm / 25.4 * dotsPerInch).round();
  final measurer = FlutterTextMeasurer();
  ui.Picture? picture;
  ui.Image? image;
  try {
    final recorder = ui.PictureRecorder();
    try {
      final canvas = ui.Canvas(recorder)
        ..drawPaint(ui.Paint()..color = const ui.Color(0xFFFFFFFF));
      _withExportIndex(document, (index) {
        final sink = CanvasDrawSink(
          canvas: canvas,
          pixelsPerPaperMm: camera.pixelsPerPaperMm,
          measurer: measurer,
          textStyleOf: document.textStyleOf,
        );
        if (underlay.isNotEmpty) {
          drawPageUnderlay(sink, camera.camera, underlay);
        }
        DraftPainter(
          document: document,
          index: index,
          resolver: DocumentStyleResolver(document, foreground: 0x000000),
          minTextCapPixels: 0.0,
          omitOwners: omitOwners,
        ).paint(sink, camera.camera, camera.size);
      });
    } finally {
      picture = recorder.endRecording();
    }
    image = await picture.toImage(width, height);
    final data = await image.toByteData(format: ui.ImageByteFormat.png);
    if (data == null) {
      throw StateError('the engine encoded no PNG');
    }
    return withPhysChunk(
      data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes),
      dotsPerInch,
    );
  } finally {
    image?.dispose();
    picture?.dispose();
    measurer.clear();
  }
}