[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"articolo-generare-pdf-in-flutter-con-i-pacchetti-pdf-e-printing":3,"comments-article-generare-pdf-in-flutter-con-i-pacchetti-pdf-e-printing":26},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"status":16,"published_at":17,"meta_title":18,"meta_description":19,"category":20,"author":24},81,"Generare PDF in Flutter con i pacchetti pdf e printing","generare-pdf-in-flutter-con-i-pacchetti-pdf-e-printing","Fatture, report e ricevute direttamente dall'app: guida pratica alla creazione di documenti PDF in Flutter con i pacchetti pdf e printing, tra layout multipagina, tabelle, font personalizzati, anteprima, stampa e condivisione.","## Perché generare PDF lato client\n\nFatture, ricevute, report di attività, schede tecniche: prima o poi quasi ogni app gestionale deve produrre un documento stampabile. La soluzione più diffusa è delegare tutto al backend, ma non sempre è la scelta migliore: significa una chiamata di rete in più, un servizio da mantenere e nessuna possibilità di generare documenti offline.\n\nIn Flutter esiste un'alternativa matura: il pacchetto **`pdf`** (scritto interamente in Dart, quindi funzionante su Android, iOS, Web, desktop) affiancato da **`printing`**, che aggiunge anteprima, invio alla stampante e condivisione del file. In questa guida vediamo come costruire un documento reale — una fattura multipagina con logo, tabella e numerazione — e come integrarlo nel flusso dell'app.\n\n## Setup\n\n```yaml\ndependencies:\n  pdf: ^3.11.1\n  printing: ^5.13.4\n  path_provider: ^2.1.4\n```\n\nUn dettaglio fondamentale da chiarire subito: **i widget del pacchetto `pdf` non sono i widget di Flutter**. Vivono in un namespace separato e hanno un motore di layout proprio, orientato alla pagina stampata. Per questo la convenzione è importarli con un prefisso:\n\n```dart\nimport 'package:pdf\u002Fpdf.dart';\nimport 'package:pdf\u002Fwidgets.dart' as pw;\n```\n\nI nomi sono familiari (`pw.Column`, `pw.Row`, `pw.Container`, `pw.Text`) ma non aspettarti `MediaQuery`, `setState` o animazioni: qui si descrive un documento statico, non una UI interattiva.\n\n## Il primo documento\n\nLa struttura minima è sempre la stessa: si crea un `Document`, si aggiungono una o più pagine, si serializza in `Uint8List`.\n\n```dart\nimport 'dart:typed_data';\n\nFuture\u003CUint8List> buildSimplePdf() async {\n  final doc = pw.Document();\n\n  doc.addPage(\n    pw.Page(\n      pageFormat: PdfPageFormat.a4,\n      margin: const pw.EdgeInsets.all(32),\n      build: (context) => pw.Center(\n        child: pw.Text(\n          'Ciao Flutter!',\n          style: pw.TextStyle(fontSize: 32, fontWeight: pw.FontWeight.bold),\n        ),\n      ),\n    ),\n  );\n\n  return doc.save();\n}\n```\n\n`doc.save()` restituisce i byte del PDF: da lì puoi salvarli su disco, caricarli su un server o passarli a `printing`.\n\n## Font: il primo scoglio da risolvere\n\nI font di default incorporati (Helvetica e simili) coprono solo un sottoinsieme di caratteri Latin-1. Se il documento contiene simboli particolari, accenti insoliti o l'euro in certe posizioni, rischi i famigerati rettangoli vuoti. La soluzione è caricare un font TrueType dagli asset e impostarlo come tema del documento.\n\n```yaml\nflutter:\n  assets:\n    - assets\u002Ffonts\u002FRoboto-Regular.ttf\n    - assets\u002Ffonts\u002FRoboto-Bold.ttf\n```\n\n```dart\nimport 'package:flutter\u002Fservices.dart' show rootBundle;\n\nFuture\u003Cpw.ThemeData> buildTheme() async {\n  final regular = pw.Font.ttf(\n    await rootBundle.load('assets\u002Ffonts\u002FRoboto-Regular.ttf'),\n  );\n  final bold = pw.Font.ttf(\n    await rootBundle.load('assets\u002Ffonts\u002FRoboto-Bold.ttf'),\n  );\n\n  return pw.ThemeData.withFont(base: regular, bold: bold);\n}\n```\n\nIn alternativa `printing` espone `PdfGoogleFonts`, che scarica i font da Google Fonts a runtime (`await PdfGoogleFonts.robotoRegular()`): comodo in prototipazione, meno adatto se l'app deve funzionare offline.\n\n## Documenti multipagina con `MultiPage`\n\nPer i report reali si usa `MultiPage`: distribuisce automaticamente il contenuto su più fogli, ripetendo header e footer. È il widget che userai nel 90% dei casi.\n\n```dart\nclass InvoiceItem {\n  const InvoiceItem(this.description, this.qty, this.unitPrice);\n  final String description;\n  final int qty;\n  final double unitPrice;\n  double get total => qty * unitPrice;\n}\n\nFuture\u003CUint8List> buildInvoice({\n  required String number,\n  required String customer,\n  required List\u003CInvoiceItem> items,\n  required Uint8List logoBytes,\n}) async {\n  final doc = pw.Document(title: 'Fattura $number');\n  final theme = await buildTheme();\n  final logo = pw.MemoryImage(logoBytes);\n\n  final grandTotal = items.fold\u003Cdouble>(0, (s, i) => s + i.total);\n\n  doc.addPage(\n    pw.MultiPage(\n      theme: theme,\n      pageFormat: PdfPageFormat.a4,\n      margin: const pw.EdgeInsets.symmetric(horizontal: 32, vertical: 40),\n      header: (context) => pw.Row(\n        mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,\n        crossAxisAlignment: pw.CrossAxisAlignment.start,\n        children: [\n          pw.SizedBox(height: 48, width: 48, child: pw.Image(logo)),\n          pw.Column(\n            crossAxisAlignment: pw.CrossAxisAlignment.end,\n            children: [\n              pw.Text('FATTURA $number',\n                  style: pw.TextStyle(\n                      fontSize: 18, fontWeight: pw.FontWeight.bold)),\n              pw.Text(customer,\n                  style: const pw.TextStyle(color: PdfColors.grey700)),\n            ],\n          ),\n        ],\n      ),\n      footer: (context) => pw.Align(\n        alignment: pw.Alignment.centerRight,\n        child: pw.Text(\n          'Pagina ${context.pageNumber} di ${context.pagesCount}',\n          style: const pw.TextStyle(fontSize: 10, color: PdfColors.grey600),\n        ),\n      ),\n      build: (context) => [\n        pw.SizedBox(height: 24),\n        _itemsTable(items),\n        pw.SizedBox(height: 16),\n        pw.Align(\n          alignment: pw.Alignment.centerRight,\n          child: pw.Text(\n            'Totale: € ${grandTotal.toStringAsFixed(2)}',\n            style: pw.TextStyle(fontSize: 16, fontWeight: pw.FontWeight.bold),\n          ),\n        ),\n      ],\n    ),\n  );\n\n  return doc.save();\n}\n```\n\nDa notare: il parametro `build` di `MultiPage` restituisce una **lista di widget**, non un widget singolo. Ogni elemento della lista può essere spezzato tra due pagine, ed è proprio questo che rende il layout scalabile su documenti lunghi.\n\n## Tabelle leggibili con `TableHelper`\n\nCostruire una tabella a mano con `pw.Table` e `pw.TableRow` è possibile ma verboso. Per i dati tabellari conviene `TableHelper.fromTextArray`, che genera intestazione, bordi e allineamenti in poche righe e — cosa cruciale — supporta la ripetizione dell'header sulle pagine successive.\n\n```dart\npw.Widget _itemsTable(List\u003CInvoiceItem> items) {\n  return pw.TableHelper.fromTextArray(\n    headers: ['Descrizione', 'Q.tà', 'Prezzo', 'Totale'],\n    headerStyle: pw.TextStyle(fontWeight: pw.FontWeight.bold),\n    headerDecoration: const pw.BoxDecoration(color: PdfColors.grey300),\n    cellHeight: 28,\n    cellAlignments: {\n      0: pw.Alignment.centerLeft,\n      1: pw.Alignment.center,\n      2: pw.Alignment.centerRight,\n      3: pw.Alignment.centerRight,\n    },\n    data: items\n        .map((i) => [\n              i.description,\n              '${i.qty}',\n              '€ ${i.unitPrice.toStringAsFixed(2)}',\n              '€ ${i.total.toStringAsFixed(2)}',\n            ])\n        .toList(),\n  );\n}\n```\n\n## Anteprima, stampa e condivisione con `printing`\n\nIl pacchetto `printing` offre tre strumenti che coprono praticamente ogni esigenza.\n\n**Anteprima integrata** — `PdfPreview` è un widget Flutter a tutti gli effetti: mostra le pagine, permette lo zoom e include già i pulsanti di stampa, condivisione e salvataggio.\n\n```dart\nclass InvoicePreviewPage extends StatelessWidget {\n  const InvoicePreviewPage({super.key, required this.items});\n  final List\u003CInvoiceItem> items;\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: const Text('Anteprima fattura')),\n      body: PdfPreview(\n        build: (format) => buildInvoiceFor(format, items),\n        canDebug: false,\n        pdfFileName: 'fattura-2024-001.pdf',\n      ),\n    );\n  }\n}\n```\n\nIl callback `build` riceve il `PdfPageFormat` scelto dall'utente: se lo propaghi al documento, il layout si adatta automaticamente ad A4, Letter o formati custom.\n\n**Stampa diretta**\n\n```dart\nawait Printing.layoutPdf(\n  onLayout: (format) => buildInvoice(\u002F* ... *\u002F),\n  name: 'fattura-2024-001',\n);\n```\n\n**Condivisione**\n\n```dart\nfinal bytes = await buildInvoice(\u002F* ... *\u002F);\nawait Printing.sharePdf(bytes: bytes, filename: 'fattura.pdf');\n```\n\n## Salvare il file su disco\n\nSe il documento va archiviato localmente, combina i byte con `path_provider`:\n\n```dart\nimport 'dart:io';\nimport 'package:path_provider\u002Fpath_provider.dart';\n\nFuture\u003CFile> saveInvoice(Uint8List bytes, String name) async {\n  final dir = await getApplicationDocumentsDirectory();\n  final file = File('${dir.path}\u002F$name.pdf');\n  return file.writeAsBytes(bytes);\n}\n```\n\nAttenzione: su Web `dart:io` non esiste. Se il target include il browser, isola questa logica dietro un'interfaccia o usa direttamente `Printing.sharePdf`, che sul Web attiva il download del file.\n\n## Da widget Flutter a PDF\n\nA volte serve replicare esattamente una schermata già disegnata (un grafico, una card di riepilogo). `printing` mette a disposizione `Printing.convertHtml` per l'HTML e, per la UI Flutter, la strada più affidabile è catturare il widget con `RepaintBoundary` e inserire l'immagine risultante nel PDF:\n\n```dart\nfinal boundary = key.currentContext!.findRenderObject() as RenderRepaintBoundary;\nfinal image = await boundary.toImage(pixelRatio: 3);\nfinal byteData = await image.toByteData(format: ui.ImageByteFormat.png);\nfinal png = byteData!.buffer.asUint8List();\n\ndoc.addPage(pw.Page(build: (_) => pw.Center(child: pw.Image(pw.MemoryImage(png)))));\n```\n\nÈ una soluzione pragmatica, ma ricorda che il risultato è un'immagine raster: niente testo selezionabile e file più pesanti. Usala solo per contenuti grafici, mai per intere pagine di testo.\n\n## Performance: genera in un isolate\n\nLa generazione di un PDF è calcolo puro in Dart, e su documenti di decine di pagine può bloccare il thread UI per centinaia di millisecondi. Poiché il pacchetto `pdf` non dipende dai binding di Flutter, puoi spostare il lavoro pesante con `compute`:\n\n```dart\nfinal bytes = await compute(_renderReport, reportData);\n```\n\nL'unico vincolo è che i font e le immagini vanno caricati **prima** (con `rootBundle`, che richiede il thread principale) e passati all'isolate già come `Uint8List`, insieme al resto dei dati.\n\n## Best practice finali\n\n- **Separa i dati dal layout**: costruisci il documento a partire da modelli immutabili, così potrai testare la generazione senza UI.\n- **Definisci un tema una volta sola** (font, colori, stili di testo) e riusalo in tutti i documenti: evita duplicazioni e garantisce coerenza visiva.\n- **Fissa il formato di pagina** solo quando ha senso: passando il `PdfPageFormat` dal `PdfPreview` ottieni documenti adattivi senza sforzo.\n- **Verifica il rendering su un lettore reale** (Acrobat, Anteprima di macOS): `PdfPreview` è affidabile, ma i visualizzatori esterni possono evidenziare problemi di font mancanti.\n- **Comprimi le immagini** prima di inserirle: una foto a piena risoluzione può far esplodere il peso del file di diversi megabyte.\n\nCon poche centinaia di righe di codice hai un generatore di documenti completo, offline e multipiattaforma: per fatture, ricevute e report è spesso tutto ciò che serve, senza aggiungere un solo endpoint al backend.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002Fef57a29e-db2f-4e46-9f31-d5a04d7c957e.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1586691853971-f6395ac36928?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODc5NzYwNzl8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Annie Spratt","https:\u002F\u002Funsplash.com\u002F@anniespratt","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fwhite-printer-paper-on-brown-wooden-table-l22PZDR1rS0",null,"published","2026-08-29T04:01:19+00:00","Generare PDF in Flutter con pdf e printing","Guida pratica per creare, visualizzare, stampare e condividere PDF in Flutter con i pacchetti pdf e printing: layout multipagina, tabelle, font e performance.",{"id":21,"name":22,"slug":23},1,"Guide","guide",{"id":21,"name":25},"Flutter Bot",[],1789205510361]