Skip to content

Document Output

David PHAM-VAN edited this page May 31, 2023 · 7 revisions

Document Output

Many options are offered to output the PDF document: save to disk, send to a printer, or share over other applications. In all cases, use the Document.save() only once, as it changes internal structures and won't be able to generate the document multiple times.

Save to disk

Using Document.save() generates a list of bytes representing the binary PDF document. To store on disk, assuming your Document instance is pdf, do:

var content = pdf.save();

var file = File("example.pdf");
await file.writeAsBytes(content);

Using Flutter, the file cannot be saved to any location, the operating system will allow writing only to a few base directory.

In your pubspec.yaml add the path_provider dependency:

dependencies:
  path_provider:

then you can use the available functions to get the temporary directory:

var content = pdf.save();

var output = await getTemporaryDirectory();
var file = File("${output.path}/example.pdf");
await file.writeAsBytes(content);

You can use file.path with other Flutter plugins, for example, flutter_full_pdf_viewer to display the result.

Send to a printer

Three options are available: print using the OS dialog, direct print, or print using another plugin.

Print using the OS dialog

Create a function to be used with an OnPressed callback. It's easier to have the document generation function in a dedicated Dart file to avoid namespace collisions with the Flutter Widgets:

void printDocument() async {
  await Printing.layoutPdf(
    onLayout: (PdfPageFormat format) async {
      var doc = Document();

      doc.addPage(
        Page(
          pageFormat: format,
          build: (Context context) =>
            Center(
              child: Text('Hello World'),
            ),
        ),
      );

      return doc.save();
    }
  );
}

Printing.layoutPdf opens the OS print dialog and start the printing process. When the user changes the paper format, it calls the onLayout callback with the new page dimensions and margins to properly reflow the layout. This is available only on select platforms, depending on the available API.

It returns a bool that indicates if the document has been printed or canceled by the user.

Direct print

Some platforms allow to print with minor user intervention. First select a printer using Printing.pickPrinter():

var myPrinter = await Printing.pickPrinter(bounds: bounds);
print('Selected printer: $myPrinter');

Then send the document directly:

await Printing.directPrintPdf(
  printer: myPrinter,
  onLayout: (PdfPageFormat format) async {
    var doc = Document();

    doc.addPage(
      Page(
        pageFormat: format,
        build: (Context context) =>
          Center(
            child: Text('Hello World'),
          ),
      ),
    );

    return doc.save();
  }
);

Using another plugin

Some Flutter plugins can use native printer API using Escape commands to draw basic text and bitmap images to thermal printers. This allows printing using a network, USB, or Bluetooth printer.

As an example, flutter_pos_printer_platform can print bitmap images loaded from the plugin image:

In your pubspec.yaml add:

dependencies:
  pdf:
  printing:
  image:
  flutter_pos_printer_platform:

Generate the PDF document:

Future<Uint8List> _generateTicket() async {
  final pdf = Document();

  pdf.addPage(
    Page(
      pageFormat: PdfPageFormat.roll80,
      build: (Context context) => SizedBox(
        height: 300 * PdfPageFormat.mm,
        child: Center(
          child: Text('Hello World'),
        ),
      ),
    ),
  );

  return pdf.save();
}

PdfPageFormat.roll80 is a special paper size with 80 millimeters wide and infinite height. Particularly suitable for roll papers found in thermal printers.

Next step is to connect to the printer. Then we raster the pages to bitmap images and print them one by one:

var printerManager = PrinterManager.instance;

Future _printTicket() async {
  
  var connectedTCP = await printerManager.connect(type: PrinterType.network, model: TcpPrinterInput(ipAddress: '192.168.0.123'));
  if (!connectedTCP) {
    throw Exception('Unable to connect to the printer');
  }

  final profile = await CapabilityProfile.load();
  final generator = Generator(PaperSize.mm80, profile);

  var pdfDoc = await _generateTicket();
  List<int> bytes = [];

  await for (var page
      in Printing.raster(await _generateTicket(), dpi: dpi)) {
    final image = page.asImage();
    ticket += generator.image(image);
    ticket += generator.feed(2);
    ticket += generator.cut();
  }

  printerManager.send(type: PrinterType.network, bytes: ticket);
}

Printing.raster gets a dpi parameter to generate the proper bitmap dimensions according to the chosen paper size. It returns an asynchronous stream of bitmap pages we can loop through using the await for structure.

203 dpi is usually the high-density setting used on 80 millimeters of thermal printers. This has to be adjusted depending on the printer model.