lib_pdf 0.1.0
lib_pdf: ^0.1.0 copied to clipboard
Gere relatórios PDF escrevendo widgets Flutter comuns: texto real, gráficos vetoriais, paginação automática e visualizador com impressão.
lib_pdf #
Relatórios PDF escritos com widgets comuns do Flutter, com visualizador (zoom, páginas, impressão e salvar) incluído.
import 'package:flutter/material.dart';
import 'package:lib_pdf/lib_pdf.dart';
final doc = PdfDocument(title: 'Vendas');
doc.addPage(PdfMultiPage(
header: (ctx) => const Text('Relatório de Vendas', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
footer: (ctx) => Text('Página ${ctx.pageNumber} de ${ctx.pagesCount}'),
build: (ctx) => [
const Card(child: ListTile(title: Text('Total'), trailing: Text('R\$ 65.900'))),
const PdfKeepTogether(child: SizedBox(height: 240, child: GraficoBarras(dados: vendas))),
PdfTable(header: TableRow(children: [...]), rows: [...]),
],
));
final Uint8List bytes = await doc.save();
A geração do PDF é implementada do zero, sem bibliotecas de PDF. O visualizador usa o pacote printing para renderizar as páginas, imprimir e compartilhar.
Plataformas: Android e iOS.
Não há sintaxe nova para aprender: Text, Row, Column, Container, Card, Table, Icon, Image, CustomPaint, widgets do app e de pacotes (como fl_chart) funcionam como na tela. Só as classes específicas do PDF têm o prefixo Pdf.
Como funciona #
- A árvore de widgets é montada fora da tela e passa pelo layout normal do Flutter, com largura igual à da página.
- A paginação procura pontos de corte seguros na árvore já calculada.
- A pintura de cada página passa por um
Canvasque escreve operadores PDF em vez de pixels.
O resultado é o seguinte:
| Conteúdo | No PDF |
|---|---|
Text, RichText, Text.rich |
Texto real (selecionável e pesquisável), com a fonte embutida em subset |
Formas, bordas, CustomPainter, gráficos (fl_chart etc.) |
Vetores |
BoxDecoration com LinearGradient/RadialGradient |
Gradiente vetorial |
Icon (MaterialIcons e outras fontes OTF/CFF) |
Contornos vetoriais |
Image |
Imagem embutida (a mesma imagem repetida em várias páginas é gravada uma só vez) |
Opacity, clips, Transform |
Nativos do PDF |
Texto desenhado com TextPainter dentro de painters, sombras, ShaderMask, ImageFiltered, BackdropFilter |
Imagem em alta resolução, no lugar exato |
O último caso existe porque o Flutter não expõe o conteúdo de um ui.Paragraph nem os parâmetros de um Shader. Os rótulos de eixo do fl_chart, por exemplo, viram pequenas imagens (3× a resolução), enquanto barras, linhas e fatias continuam vetoriais.
Instalação #
dependencies:
lib_pdf:
git:
url: https://github.com/ArthurACarvalho/lib_flutter_pdf.git
ref: main
Documento e páginas #
PdfDocument(
title: 'Relatório', // metadados: title, author, subject, keywords, creator
theme: ThemeData(colorSchemeSeed: Colors.indigo),
textDirection: TextDirection.ltr, // padrão
fontFamily: 'Inter', // opcional; o padrão é a Roboto embutida
fonts: [PdfFont.asset('fonts/Inter-Regular.ttf', family: 'Inter')],
locale: const Locale('pt', 'BR'), // padrão: en_US
localizationsDelegates: GlobalMaterialLocalizations.delegates, // se usar widgets com textos do Material
rasterPixelRatio: 3, // resolução do que precisa virar imagem
imageTimeout: const Duration(seconds: 10),
settleDuration: Duration.zero, // > 0 liga as animações e espera antes de capturar
);
PdfMultiPage: o conteúdo debuildflui por quantas páginas forem necessárias. Aceitaheader,footer,background,foreground,format,margin(padrão: 36 pontos, 1,27 cm),theme,crossAxisAlignmentemaxPages(padrão: 1000, proteção contra conteúdo que nunca termina).PdfPage: uma página única.buildrecebe exatamente a área interna às margens, como uma tela.PdfContext: chega aos builders (pageNumber,pagesCount,format) e também pode ser lido em qualquer widget comPdfContext.of(context)(oumaybeOf, que devolve nulo fora do PDF).- Formatos:
PdfPageFormat.a4(padrão),a3,a5,letter,legal, além de.landscape,.portraitePdfPageFormat(largura, altura)em pontos. 1 pixel lógico equivale a 1 ponto;PdfPageFormat.cme.mmajudam nas margens.
Visualizador #
O jeito mais simples é gerar e abrir numa nova tela:
PdfDocumentViewer.open(context, document: doc, title: 'Relatório de Vendas', fileName: 'vendas.pdf');
Para embutir em uma tela sua:
PdfDocumentViewer.document(doc) // gera o PDF ao abrir
PdfDocumentViewer.bytes(bytes) // PDF já gerado (ou qualquer outro PDF)
PdfDocumentViewer(build: () => doc.save())
O visual é claro, com duas barras:
- Barra superior: nome do arquivo (ou
title) e os botões imprimir, ajustar à página/largura, diminuir zoom, aumentar zoom, salvar e fechar. Com o celular em pé ficam só imprimir, salvar e fechar; os outros aparecem em tablets e com o celular deitado. - Barra inferior: setas de página anterior e próxima, a caixa "3 / 15" (digite o número e confirme, ou toque fora, para ir à página) e o menu de zoom ("Ajustar à página" e de 25% a 500%).
O que ele oferece:
- Rolagem contínua entre as páginas.
- Zoom por botões, menu, pinça e toque duplo. 100% é a página ajustada à largura (até 900 px).
- Impressão pelo diálogo nativo do sistema.
- Salvar: abre o compartilhamento do sistema, que inclui "Salvar em Arquivos", Google Drive, e-mail e WhatsApp.
- Fechar: o botão aparece quando há
onClose. Oopenjá passa um que fecha a tela. - Nitidez sob demanda: as páginas aparecem primeiro em baixa resolução e são renderizadas de novo, nítidas, conforme ficam visíveis ou o zoom muda.
Parâmetros úteis:
title,fileName,allowPrinting,allowSaving,showToolbar,onCloseebackgroundColor;toolbarActions, para botões extras. UsePdfViewerToolbarButtonpara manter o estilo.
Com um PdfDocumentViewerController dá para montar a própria barra: zoomIn(), zoomOut(), setZoom(z), fitWidth(), fitPage(), goToPage(n), nextPage(), previousPage(), printDocument(), saveDocument() e reload(). O estado fica em isReady, pageNumber, pageCount, zoom, fitsPage, isPrinting, isSaving e bytes. As barras prontas também são widgets públicos: PdfDocumentViewerToolbar e PdfDocumentViewerNavigationBar.
Configuração #
Nada a configurar no Android e no iOS.
Paginação #
O corte entre páginas acontece apenas em pontos que não atravessam conteúdo:
- entre filhos de
Column,Row,Wrap,Stacketc. (em qualquer nível de aninhamento); - entre linhas de texto de um parágrafo;
- entre linhas de uma
Table.
Todo o resto é atômico: imagens, CustomPaint, widgets rotacionados ou escalados.
Widgets de controle:
PdfKeepTogether(child:): nunca corta o filho. Se ele não couber, começa na próxima página. Use em gráficos, cards e blocos de assinatura.PdfPageBreak(): força uma nova página e funciona em qualquer nível da árvore.PdfTable(header:, rows:, ...): umaTabledo Flutter que repete o cabeçalho no topo de cada página de continuação (desligue comrepeatHeader: false). Aceita os mesmos parâmetros deTable(columnWidths,borderetc.).PdfRasterize(child:): força uma subárvore a virar imagem.pixelRatiotroca a resolução só dela.
Se um bloco sem pontos de corte for maior que a página inteira, ele é cortado na altura da página e um aviso é emitido no console.
Gráficos #
Qualquer widget de gráfico serve. Duas recomendações:
- Desligue as animações de entrada:
duration: Duration.zeronofl_chart,animationDuration: 0no Syncfusion. Sem isso, a captura mostra o primeiro quadro da animação. Outra saída é usarsettleDuration. - Dê altura definida: por exemplo
SizedBox(height: 240, child: ...), porque o conteúdo de umPdfMultiPagetem altura livre, como dentro de umaListView.
Fontes #
- Sem configuração, os textos usam a Roboto embutida na lib, que cobre todos os acentos do português.
- Fontes declaradas no
pubspec.yamldo app (inclusive de pacotes) são procuradas noFontManifestquando umTextStyleusafontFamily. Esse caminho ainda não tem teste automatizado; na dúvida, registre a fonte comPdfFont. - Fontes fora do bundle são registradas com
PdfFont.memory(bytes, family:)ouPdfFont.asset(key, family:).
Formatos suportados: TrueType (.ttf) vira texto real, e OpenType/CFF (.otf) vira contornos. Coleções .ttc não são suportadas. Se um texto usa uma família cujo arquivo não é encontrado (por exemplo, uma fonte do sistema), aquele parágrafo vira imagem.
Imagens #
Image.memory é coberto pelos testes; Image.asset, Image.file e Image.network usam o mesmo caminho (RawImage), e o save() espera as imagens carregarem por até imageTimeout. Os pixels são gravados sem perdas (Flate): para relatórios com muitas fotos grandes, reduza as imagens antes de usá-las.
Testes #
Em testWidgets, gere o PDF dentro de tester.runAsync, porque a captura de imagens é assíncrona de verdade:
testWidgets('relatório', (tester) async {
final bytes = (await tester.runAsync(doc.save))!;
expect(bytes, isNotEmpty);
});
Nos testes, o flutter_test desenha sombras de Material como contornos (debugDisableShadows). Fontes do app, como MaterialIcons, precisam ser carregadas com FontLoader.
Os testes do pacote validam os PDFs com qpdf/poppler quando estão disponíveis. Por padrão eles são procurados no PATH; também dá para apontar a variável LIB_PDF_TOOLS para um script que exporte PATH/LD_LIBRARY_PATH. Para pular o teste de desempenho, use flutter test --exclude-tags perf. Para testar telas com o visualizador, passe um PdfViewerBackend falso em backend: (veja test/viewer_test.dart).
Limitações conhecidas #
- Direção do texto: texto RTL vira imagem. Scripts com shaping complexo (árabe, devanágari) não são suportados como texto real: com a Roboto padrão eles viram imagem, mas com uma fonte que tenha esses glifos sairiam sem shaping.
- Ligaduras e kerning: são aproximados. Cada palavra é posicionada exatamente onde o Flutter a colocou, mas as letras internas usam as larguras da fonte ajustadas à largura da palavra.
- Execução: a geração roda na thread principal (ela depende do motor de layout do Flutter). Como referência, uma tabela de 2.000 linhas (cerca de 50 páginas) leva de 1,5 a 2,5 s num computador; em celulares, conte com mais tempo.
GlobalKeys: são compartilhadas com o app, então não reutilize a mesma chave na tela e no relatório ao mesmo tempo.- Aparelhos reais: o visualizador foi validado em testes automatizados. Impressão e compartilhamento ainda não foram testados em aparelhos.
Exemplo #
example/ tem um app com um relatório de vendas completo: indicadores, gráfico CustomPainter, gráficos de linha e pizza do fl_chart, tabela de 120 linhas paginada e quebra de página.
cd example
flutter test # gera build/relatorio_vendas.pdf
flutter run # botão "Abrir relatório" abre o visualizador