@marsaude/paper
v1.2.4
Published
Motor client-side de paginação e exportação de PDF a partir de DOM arbitrário — escreva HTML/Angular como se a página fosse infinita; o motor mede o conteúdo real, decide cada quebra sem cortar nada no meio e exporta com diagnósticos auditáveis.
Downloads
1,359
Maintainers
Readme
@marsaude/paper
Motor client-side de paginação e exportação de PDF a partir de DOM arbitrário para Angular. Escreva HTML/Angular comum, como se a página fosse infinita — o motor mede o conteúdo renderizado de verdade, decide onde cada quebra de página acontece sem cortar nada no meio e exporta, explicando cada decisão que tomou.
conteúdo → [MEASURE] → [PLAN] → [COMMIT] → [EXPORT]
(leitura) (puro) (escrita) (plugável)- Nada cortado no meio: o que não cabe desce inteiro ou quebra entre os filhos.
- Decisões auditáveis: todo corte fica no decision log; problemas viram diagnósticos
MARPDF-*com sugestão de correção. - Plano puro e determinístico: paginação é função pura sobre medições — testável sem browser, com hash de reprodutibilidade.
- Zero conhecimento de domínio: o motor fragmenta DOM; seus templates ficam nos apps.
Instalação
npm install @marsaude/paper
# opcional, só para o export raster (lazy):
npm install jspdf @zumer/snapdomRequer Angular ≥ 22 (standalone/zoneless + Signals).
Uso mínimo
<mar-pdf #pdf format="A4" [margins]="16" [headerHeightPx]="84" [footerHeightPx]="52">
<div *marPdfHeader="let page">Meu relatório</div>
<div *marPdfFooter="let page">{{ page.number }} de {{ page.total }}</div>
<h1>Meu relatório</h1>
<p>Qualquer HTML aqui. Zero metadados obrigatórios.</p>
</mar-pdf>
<button (click)="pdf.print('Meu relatório')">Exportar PDF</button>Hints opcionais (o motor funciona sem nenhum)
| Hint | Efeito |
|---|---|
| pdfAtomic | Nunca corta; se não cabe, vai inteiro. Fatal se for maior que a página |
| pdfKeepTogether | Suave: inteiro se couber numa página; se for maior, divide (nunca fatal) |
| pdfSplittable | Força divisível (sobrepõe a heurística de card) |
| pdfGridTable | Grid CSS pagina como tabela: corte só entre linhas, colunas congeladas |
| pdfKeepWithNext / pdfKeepWithPrevious | Vínculo com o irmão seguinte/anterior |
| pdfBreakBefore / pdfBreakAfter | Força quebra de página |
| pdfRepeatOnFragment | Re-clona o elemento (ex.: cabeçalho) no topo de cada continuação |
Aliases CSS também são lidos: break-inside: avoid → atômico.
Conteúdo de tamanho desconhecido (dinâmico)
Se você não sabe o tamanho do que vem, não use pdfAtomic — ele afirma "isto é
pequeno e nunca quebra" e vira MARPDF-001 fatal / conteúdo truncado quando cresce.
| Situação | Use |
|---|---|
| Bloco que pode crescer (card, seção) | divisível (default) ou pdfKeepTogether |
| Grade de chips/itens (3 ou 300) | pdfGridTable |
| Texto longo (laudo) | quebre em blocos-filhos (<p> por parágrafo) — v1 não corta dentro de um parágrafo |
| Algo realmente pequeno e indivisível | pdfAtomic |
O portão de export é automático (v1.2): print(), exportRaster() e prepareRaster()
re-medem o palco antes de exportar (plano defasado nunca exporta) e lançam
MarPdfExportBlockedError quando há diagnóstico fatal (sempre) ou error (com
[strict]="true"). Trate na UI:
import { MarPdfExportBlockedError, blocksExport } from '@marsaude/paper';
try {
await pdf.exportRaster({ title: 'Relatório' });
} catch (err) {
if (err instanceof MarPdfExportBlockedError) mostrarErros(err.diagnostics);
}
// para desabilitar o botão preventivamente, blocksExport continua disponível:
const bloqueado = blocksExport(pdf.plan()?.diagnostics ?? [], /* strict */ true);Exportadores (não há bala de prata por browser)
pdf.print('Relatório'); // NativePrint — texto vivo, pesquisável
await pdf.exportRaster({ title: 'Relatório' }); // Raster — idêntico em todo browser
pdf.printEnvironment.recommended; // 'native' | 'raster' para o browser atual| Browser | Recomendado | Por quê |
|---|---|---|
| Chrome/Edge/Firefox (+ Chrome Android) | Nativo | respeitam @page; texto vivo/leve |
| Safari desktop e iOS | Raster | Safari ignora @page, força margens/cromo, gera branco |
- NativePrint (
print()):reservePrintMargins(defaulttrue) — o cromo do browser convive na margem sem gerar páginas extras. - Raster (
exportRaster()): sem diálogo, sem cromo, sem branco (texto vira imagem). Opções:dpi,format,quality,download,shareFirst(share sheet no mobile). No iOS use[autoPrepareRaster]="true"para o share abrir no toque (gera antes).
Segurança de conteúdo clínico
Elemento atômico maior que a página útil nunca é encolhido até caber: dentro da
tolerância (≤ ~8%) escala com aviso; acima dela, hint explícito gera MARPDF-001
fatal, que bloqueia o export independente de configuração (ADR-011).
Documentação (acompanha o pacote)
- docs/getting-started.md — primeiro PDF em 5 min.
- docs/conceitos.md — atômico vs. divisível, grid-tabela, conteúdo dinâmico.
- docs/exportadores.md — NativePrint × Raster e suporte por browser.
- docs/catalogo-de-erros.md — todos os códigos
MARPDF-*. - docs/adr.md — decisões de arquitetura.
Escopo da v1
MEASURE in-document, <mar-pdf> + directives, NativePrint e RasterExporter. Roadmap:
palco em iframe (spike S1), páginas especiais out-of-flow (capa/TOC) e quebra de parágrafo
por linha (Range API).
Licença
MIT © 2026 Mar Saúde
