layoutPdf method

  1. @override
Future<bool> layoutPdf(
  1. Printer? printer,
  2. LayoutCallback onLayout,
  3. String name,
  4. PdfPageFormat format,
  5. bool dynamicLayout,
  6. bool usePrinterSettings,
  7. OutputType outputType,
  8. bool forceCustomPrintPaper,
)

Prints a Pdf document to a local printer using the platform UI the Pdf document is re-built in a LayoutCallback each time the user changes a setting like the page format or orientation.

returns a future with a bool set to true if the document is printed and false if it is canceled. throws an exception in case of error

Implementation

@override
Future<bool> layoutPdf(
  Printer? printer,
  LayoutCallback onLayout,
  String name,
  PdfPageFormat format,
  bool dynamicLayout,
  bool usePrinterSettings,
  OutputType outputType,
  bool forceCustomPrintPaper,
) async {
  late Uint8List result;
  try {
    result = await onLayout(format);
  } catch (e, s) {
    InformationCollector? collector;

    assert(() {
      collector = () sync* {
        yield StringProperty('PageFormat', format.toString());
      };
      return true;
    }());

    FlutterError.reportError(
      FlutterErrorDetails(
        exception: e,
        stack: s,
        stackFilter: (input) => input,
        library: 'printing',
        context: ErrorDescription('while generating a PDF'),
        informationCollector: collector,
      ),
    );

    rethrow;
  }

  if (result.isEmpty) {
    return false;
  }

  final userAgent = web.window.navigator.userAgent;
  final isChrome = web.window['chrome'] != null;
  final isSafari = web.window['safari'] != null &&
      !userAgent.contains(RegExp(r'Version/14\.1\.'));
  final isMobile = userAgent.contains('Mobile');
  final isFirefox = userAgent.contains('Firefox');

  // Chrome, Safari, and Firefox on a desktop computer
  if ((isChrome || isSafari || isFirefox) && !isMobile) {
    final completer = Completer<bool>();
    final pdfFile = web.Blob(
      [result.toJS].toJS,
      web.BlobPropertyBag(type: 'application/pdf'),
    );
    final pdfUrl = web.URL.createObjectURL(pdfFile);
    final doc = web.window.document;

    final script =
        doc.getElementById(_scriptId) ?? doc.createElement('script');
    script.setAttribute('id', _scriptId);
    script.setAttribute('type', 'text/javascript');
    script.innerHTML =
        '''function ${_frameId}_print(){var f=document.getElementById('$_frameId');f.focus();f.contentWindow.print();}'''
            .toJS;
    doc.body!.append(script);

    final frame = doc.getElementById(_frameId) ?? doc.createElement('iframe');
    if (isFirefox) {
      // Set the iframe to be is visible on the page (guaranteed by fixed position) but hidden using opacity 0, because
      // this works in Firefox. The height needs to be sufficient for some part of the document other than the PDF
      // viewer's toolbar to be visible in the page
      frame.setAttribute(
        'style',
        'width: 1px; height: 100px; position: fixed; left: 0; top: 0; opacity: 0; border-width: 0; margin: 0; padding: 0',
      );
    } else {
      // Hide the iframe in other browsers
      frame.setAttribute(
        'style',
        'visibility: hidden; height: 0; width: 0; position: absolute;',
        // 'height: 400px; width: 600px; position: absolute; z-index: 1000',
      );
    }

    frame.setAttribute('id', _frameId);
    frame.setAttribute('src', pdfUrl);
    final stopWatch = Stopwatch();

    web.EventListener? load;
    load = (web.Event event) {
      frame.removeEventListener('load', load);
      Timer(Duration(milliseconds: isSafari ? 500 : 0), () {
        try {
          stopWatch.start();
          web.window.callMethod('${_frameId}_print'.toJS);
          stopWatch.stop();
          completer.complete(true);
        } catch (e) {
          assert(() {
            // ignore: avoid_print
            print('Error: $e');
            return true;
          }());
          completer.complete(_getPdf(result));
        }
      });
    }.toJS;

    frame.addEventListener('load', load);

    doc.body!.append(frame);

    final res = await completer.future;
    // If print() is synchronous
    if (stopWatch.elapsedMilliseconds > 1000) {
      frame.remove();
      script.remove();
    }
    return res;
  }

  return _getPdf(result);
}