# Engines de PDF

Todo relatório migrado precisa de **um** engine para transformar o HTML Vash em PDF.

**Vantagem central:** a API de entrada é a mesma em todos — trocar de engine é trocar o `require`, sem reescrever o controller:

```js
await reportEngine.renderReportPdf(model, res, {
  reportDir: __dirname,
  pdf: { /* margens, página, etc. */ }
});
```

Escolha o engine conforme o ambiente da aplicação hospedeira. As dependências **não** vêm embutidas neste pacote: instale-as na sua app.

## Comparativo

| Engine | Módulo | Quando usar | Status |
|---|---|---|---|
| **Devbox** | `report-engine-devbox` | App com adapter `res.pdf` | Pronto |
| **Puppeteer** | `report-engine-puppeteer` | Node genérico / Chromium | Pronto |
| **wkhtmltopdf** | `report-engine-wkhtmltopdf` | Binário wkhtmltopdf no PATH | Pronto |
| **WeasyPrint** | `report-engine-weasyprint` | Python 3 + WeasyPrint | Pronto |

## Dependências por engine

### Comum a Puppeteer e wkhtmltopdf

| Dependência | Tipo | Instalação | Link |
|---|---|---|---|
| `vash` | pacote npm | `npm install vash` | https://www.npmjs.com/package/vash |

Templates `report.html` / `report_header.html` / `report_footer.html` são compilados com Vash **dentro** desses engines.

### Devbox (`report-engine-devbox`)

| Dependência | Tipo | Como obter |
|---|---|---|
| `res.pdf` | API do framework hospedeiro | Já existe em apps Devbox / stacks com esse adapter |
| templates Vash | no framework | em geral o próprio `res.pdf` renderiza Vash |

Não exige `puppeteer` nem o binário `wkhtmltopdf`.

```bash
# apenas o runtime CrystalNode
npm install @crystal-node/runtime
```

```js
const reportEngine = require('@crystal-node/runtime/report-engine-devbox');
```

### Puppeteer (`report-engine-puppeteer`)

| Dependência | Tipo | Instalação | Link |
|---|---|---|---|
| `puppeteer` | pacote npm (baixa Chromium) | `npm install puppeteer` | https://www.npmjs.com/package/puppeteer |
| `vash` | pacote npm | `npm install vash` | https://www.npmjs.com/package/vash |

```bash
# individual
npm install @crystal-node/engine-puppeteer @crystal-node/controller puppeteer vash
# ou meta-pacote
npm install @crystal-node/runtime puppeteer vash
```

Em CI/Docker, o Puppeteer pode precisar de libs do sistema (fonte, sandbox). Consulte:  
https://pptr.dev/troubleshooting

Alternativa mais leve (Chromium externo): `puppeteer-core` — nesse caso você precisa passar o browser/`executablePath` via opções do engine (`puppeteer` / `puppeteerLaunch`). Link: https://www.npmjs.com/package/puppeteer-core

```js
const reportEngine = require('@crystal-node/runtime/report-engine-puppeteer');

await reportEngine.renderReportPdf(model, res, {
  reportDir: __dirname,
  pdf: {
    marginTop: 28,
    marginBottom: 14,
    marginLeft: 0,
    marginRight: 0,
    pageWidth: '215.9mm',
    pageHeight: '279.4mm'
  },
  // opcional: escala se o HTML veio com outro coeficiente de twips
  // twipsToPxCoefficient: 1.25,
  // debug: true
});
```

Debug:

```bash
REPORT_ENGINE_PUPPETEER_DEBUG=1
```

Artefatos em `<reportDir>/puppeteer-debug/<timestamp>/`.

### wkhtmltopdf (`report-engine-wkhtmltopdf`)

| Dependência | Tipo | Instalação | Link |
|---|---|---|---|
| `wkhtmltopdf` | **binário do sistema** (não é pacote npm obrigatório) | instalar no SO / PATH | https://wkhtmltopdf.org/downloads.html |
| `vash` | pacote npm | `npm install vash` | https://www.npmjs.com/package/vash |

```bash
npm install @crystal-node/engine-wkhtmltopdf @crystal-node/controller vash
# ou: npm install @crystal-node/runtime vash
# + instalar o binário wkhtmltopdf no sistema
```

Caminho do binário (opcional):

- opção `wkhtmltopdfPath` em `renderReportPdf`
- ou variável de ambiente `WKHTMLTOPDF_PATH`

```js
const reportEngine = require('@crystal-node/runtime/report-engine-wkhtmltopdf');

await reportEngine.renderReportPdf(model, res, {
  reportDir: __dirname,
  pdf: {
    marginTop: 28,
    marginBottom: 14,
    marginLeft: 0,
    marginRight: 0,
    pageWidth: '215.9mm',
    pageHeight: '279.4mm',
    dpi: 96
  },
  // wkhtmltopdfPath: '/usr/local/bin/wkhtmltopdf',
  // debug: true
});
```

Pacotes npm auxiliares (opcionais, se preferir gerenciar via npm):  
https://www.npmjs.com/package/wkhtmltopdf — este runtime chama o **binário** diretamente; o wrapper npm não é obrigatório.

### WeasyPrint (`report-engine-weasyprint`)

| Dependência | Tipo | Link |
|---|---|---|
| WeasyPrint | CLI Python (`weasyprint` ou `py -m weasyprint`) | https://doc.courtbouillon.org/weasyprint/ |
| vash | pacote npm (render dos templates) | https://www.npmjs.com/package/vash |

**Status:** implementado. Header/footer via `position: running()` + `@page` margins; numeração com `counter(page)` / `counter(pages)`. Binário: `options.weasyprintPath` → `WEASYPRINT_PATH` → `weasyprint` → fallback `python -m weasyprint`.

```js
const reportEngine = require('@crystal-node/runtime/report-engine-weasyprint');
await reportEngine.renderReportPdf(model, res, {
  reportDir: __dirname,
  pdf: { marginTop: 26, marginBottom: 30, pageWidth: '210mm', pageHeight: '297mm' },
  weasyprintPath: process.env.WEASYPRINT_PATH // opcional
});
```

## Trocar de engine no controller

A troca é só o `require` — o restante do controller permanece igual:

```js
// Pacotes individuais (recomendado)
const reportEngine = require('@crystal-node/engine-devbox/report-engine-devbox');
const reportEngine = require('@crystal-node/engine-puppeteer');
const reportEngine = require('@crystal-node/engine-wkhtmltopdf');
const reportEngine = require('@crystal-node/engine-weasyprint');

// Ou via meta-pacote / subpaths (compat 1.0)
const reportEngine = require('@crystal-node/runtime/report-engine-devbox');
const reportEngine = require('@crystal-node/runtime/report-engine-puppeteer');
const reportEngine = require('@crystal-node/runtime/report-engine-wkhtmltopdf');
const reportEngine = require('@crystal-node/runtime/report-engine-weasyprint');
```

Também disponível pelo entrypoint principal:

```js
const {
  reportEngineDevbox,
  reportEnginePuppeteer,
  reportEngineWkhtmltopdf,
  reportEngineWeasyprint
} = require('@crystal-node/runtime');
```

## Header e footer

Se `report_header.html` / `report_footer.html` existirem e tiverem conteúdo, os engines os usam automaticamente.

Numeração de página nos templates (marcadores compatíveis):

```html
@{
  crystalFunctions = Object.assign({}, crystalFunctions, {
    pageNumber: function () { return crystalFunctions.pdfPageChrome('page'); },
    totalPageCount: function () { return crystalFunctions.pdfPageChrome('sitepages'); }
  });
}
```

- **wkhtmltopdf:** preenche `data-cr-pdf-chrome` via script + `?page=` / `?topage=` (não depende do token `[page]` do binário)
- **Puppeteer:** converte para classes `pageNumber` / `totalPages` do `headerTemplate`/`footerTemplate`
- **WeasyPrint:** classes `weasy-page-number` / `weasy-pages-number` + `counter(page|pages)`

### Suppress condicional por página (`data-cr-suppress-if`)

Secções de page header/footer cuja fórmula Crystal cita `PageNumber` / `TotalPageCount` saem com
`data-cr-suppress-if="page >= …"` (sem `@if` Vash). Avaliação:

| Engine | Mecanismo |
|--------|-----------|
| wkhtmltopdf / devbox | Script no HTML + query `?page=` / `?topage=` |
| Puppeteer / WeasyPrint | `pdf-chrome-suppress.js` filtra por página; faixas + merge via **pdf-lib** |

Instale `pdf-lib` no projeto hospedeiro se for usar Puppeteer ou WeasyPrint com esses marcadores.

## Qual escolher na migração manual?

1. **Já está em Devbox** → `report-engine-devbox`
2. **App Node nova / sem `res.pdf`** → `report-engine-puppeteer` (mais fácil de instalar via npm)
3. **Infra já tem wkhtmltopdf** → `report-engine-wkhtmltopdf`
4. **Fidelidade tipográfica / CSS** → `report-engine-weasyprint` (CLI WeasyPrint + peer `pdf-lib` se houver suppress por página)
