buildPdf method

Future<Uint8List> buildPdf({
  1. String orient = 'P',
  2. String paper = 'A4',
  3. String fontAsset = 'assets/fonts/NotoSansTC-Regular.ttf',
})

@@@ 2026-08-21 How PDFs are generated: native WebView + PrintDocumentAdapter.

Dead ends tried along the way, kept here so nobody circles back to them later:

  1. Printing.layoutPdf() -- brings up Android's native print preview, which depends on the system's Print Spooler service. This device doesn't have that service, so it hangs forever on "preparing preview" -- our own code never even gets called.
  2. Printing.convertHtml() -- a known bug in the printing package (github.com/DavBfr/dart_pdf/issues/1517): on some devices, the call neither returns nor throws -- it just hangs. Tested shrinking the HTML from 9.48 million characters down to 9,853 characters and it still hung, so it isn't a content-size issue.
  3. htmltopdfwidgets's HTMLToPdf() -- pure-Dart parsing, doesn't hang, but it doesn't handle : a 6,097-character detail table's HTML only parsed into 2 blocks -- the table effectively disappeared, and it also blew past MultiPage's page-count limit.

    The current approach: hand the HTML to the system's native WebView to lay out, then write a vector PDF (selectable text, scales without quality loss) via createPrintDocumentAdapter(). An independent implementation from (2), so it doesn't share the code path that hangs; unlike (3), layout is handled by a real browser engine, so

    s and CSS render exactly as written. Chinese fonts don't need to be embedded -- the WebView can see the system's built-in Noto Sans CJK.

Implementation

Future<Uint8List> buildPdf({
  String orient = 'P',
  String paper = 'A4',
  String fontAsset = 'assets/fonts/NotoSansTC-Regular.ttf',
}) async {
  // The Web platform doesn't support this path; uses _webPrint instead
  if (kIsWeb) {
    await _webPrintBody(buildHtml(), paper, orient);
    return Uint8List(0);
  }
  debugPrint('[buildPdf] starting (native WebView + PrintDocumentAdapter)');
  // @@@ 2026-08-21 PDF now reuses exactly the same CSS as the on-screen
  // preview.
  //
  //   PDF used to go through reportCssPrint(), a separate stylesheet
  //   from the on-screen preview's reportCssScreen(), which resulted in
  //   fields wrapping heavily. Tried shrinking the font size and
  //   locking a 760px canvas with proportional scaling (printFitCss) to
  //   fix it -- both were guesses at "what actually fits."
  //
  //   Testing then found: a phone in portrait's WebView preview width
  //   is about 720px, nearly identical to A4 portrait's printable width
  //   (718px), and it laid out with zero wrapping, ruler line intact.
  //   Which means the width was never the problem -- the two sides just
  //   had different CSS. No more guessing needed: just reuse the
  //   stylesheet already proven to lay out correctly.
  //
  //   reportCssScreen carries screen-only decoration (gray background,
  //   shadow, inter-page gaps); @media print strips that out for
  //   printing, with the layout/font size/column widths left untouched.
  final html = _fullHtmlLikeScreen(buildHtml(), paper, orient);
  debugPrint('[buildPdf] step 1/2 HTML assembled (${html.length} chars '
      'total, reusing the on-screen preview CSS, print font size derived '
      'from the $kRulerChars-character ruler line), handing off to the '
      'native WebView for PDF conversion...');
  final result = await HtmlToPdfConverter().convertHtmlToPdfBytes(html: html);
  debugPrint(
      '[buildPdf] step 2/2 PDF generation complete (${result.length} bytes)');
  return result;
}