Metadata-Version: 2.4
Name: receitapdf
Version: 0.9.2
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Rust
Classifier: Topic :: Office/Business
License-File: LICENSE
License-File: LICENSE-DejaVu.txt
License-File: LICENSE-Quicksand.txt
Summary: Geração de PDFs dos modelos eletrônicos de receituários da Anvisa
License-Expression: MIT
Requires-Python: >=3.13
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM

# receitapdf

Biblioteca Rust, CLI e extensão Python que recebem uma prescrição estruturada e devolvem os bytes do PDF. Os oito modelos usam os layouts ajustados e aprovados em `examples/`: A, B, B2, A/B veterinários, retinoides, talidomida e Receita de Controle Especial (RCE). A RCE também tem a variação `receita_controle_especial_veterinaria`, para prescrever medicamento humano a animais, com paciente/proprietário no padrão do NRA veterinário.

Também gera receita simples, pedidos de exame e documentos (atestado, declaração, termo, encaminhamento) em layouts nativos; veja [Documentos nos layouts nativos](#documentos-nos-layouts-nativos).

## Layouts usados na geração

A CLI e as APIs Rust/Python usam o mesmo renderizador Rust. Os cabeçalhos aprovados estão incorporados em `assets/modelos/`; os boxes, rótulos, dados, rodapé e QR são desenhados na geração. Não é necessário instalar PyMuPDF, executar scripts Python ou acessar a rede para gerar uma receita.

Os [PDFs originais](assets/anvisa-v2/) continuam como referência. Os layouts ajustados são derivados desses arquivos; não são os arquivos oficiais intactos. A consulta feita durante os ajustes está em [endereço e modelo oficial](examples/modelo-a/endereco-e-modelo.md).

Padrões compartilhados:

- Página de 595,5 × 842,25 pt; espaçamento de 10 pt entre os boxes.
- Caixa de numeração com as mesmas dimensões nos modelos e na variação veterinária da RCE.
- Rótulos de 7,5 pt acima dos valores de 10 pt; entrelinha de 14 pt.
- Nomes com até duas linhas; endereços de emitente/paciente com até três.
- Emitente e paciente com a mesma altura, calculada pelos dados.
- Prescrição com quebra automática e limite definido pelo espaço disponível.
- Rodapé alinhado pela base, com entrelinha de 20 pt. Plataforma e assinatura aceitam até duas linhas cada, sem reservar uma linha vazia.
- Últimas três instruções em itálico, URLs em negrito itálico ao lado dos rótulos.
- Token e código acima do QR, alinhados ao seu lado esquerdo; base visível do QR alinhada à última linha do rodapé.
- Na talidomida, nome/CPF/telefone do responsável ficam lado a lado, com endereço abaixo e espaço para duas linhas. Dose e tempo de tratamento ficam lado a lado.

A prescrição utiliza o espaço restante acima do rodapé: textos adicionais no rodapé reduzem sua altura quando necessário. Conteúdo que excede a capacidade retorna erro; não é truncado nem sobreposto.

## CLI

Rust está declarado em `mise.toml`:

```sh
mise install
mise exec rust -- cargo run --release -- examples/notificacao-a.json saida.pdf
```

O executável recebe `<entrada.json> <saida.pdf> [--preview <pasta>]`. JSON com `modelo` gera uma receita Anvisa; JSON com `layout` gera um [documento nativo](#documentos-nos-layouts-nativos). Com `--preview`, grava também a [prévia](#prévia-em-webp) de cada página em `<pasta>/pagina-N.webp`. Para entrada padrão:

```sh
cat examples/notificacao-a.json | mise exec rust -- cargo run --release -- - saida.pdf
```

## API Rust

```rust
use receitapdf::{gerar_pdf, Receita};

fn gerar(receita: &Receita) -> Result<Vec<u8>, receitapdf::Erro> {
    gerar_pdf(receita)
}
```

`gerar_pdf` valida os campos obrigatórios, número, telefones e capacidade do layout. Retorna os bytes do PDF ou `Erro`. A assinatura criptográfica, a obtenção do número SNCR e as decisões clínicas são responsabilidades da integração chamadora.

## API Python

CPython 3.13 ou superior. A extensão usa PyO3 com ABI estável `abi3`; Rust/Cargo são necessários para compilar a partir do código-fonte:

```sh
python3.13 -m pip install .
```

```python
import json
from pathlib import Path
from receitapdf import gerar_pdf, url_qr

receita = json.loads(Path("examples/notificacao-a.json").read_text(encoding="utf-8"))
Path("saida.pdf").write_bytes(gerar_pdf(receita))
print(url_qr(receita))
```

`gerar_pdf` retorna `bytes`. Entrada ausente, inválida ou que exceda o espaço disponível gera `ValueError`; falhas internas de geração geram `RuntimeError`. Para construir o wheel: `maturin build --release`.

## JSON: campos e formatos

CLI e APIs aceitam a mesma estrutura. Todos os exemplos declaram todos os campos da API; opcionais não usados recebem `null`. Os arquivos `dados-curtos.json` e `dados-longos.json` em cada pasta de modelo também são entradas válidas da CLI. Dados são totalmente fictícios.

### Campos da receita

| Campo | Obrigatório | Formato / uso |
| --- | --- | --- |
| `modelo` | Sim | Um dos identificadores da tabela de modelos abaixo. |
| `numero` | Sim, inclusive RCE | `0000.0-00.0000000` ou `0000.0-00.000000000`: 4 dígitos, ponto, 1 dígito, hífen, 2 dígitos, ponto, 7 ou 9 dígitos (14 ou 16 no total), sempre com a máscara. |
| `prescritor` | Sim | Objeto da tabela de emitente. |
| `paciente` | Sim | Objeto da tabela de paciente. |
| `medicamento` | Sim | Objeto da tabela de prescrição. |
| `plataforma` | Sim | Texto livre, até duas linhas no PDF. |
| `data` | Sim | Texto já formatado; padrão dos exemplos: `DD/MM/AAAA`. |
| `assinatura` | Não | Texto pronto, até duas linhas. Ausente, `null` ou em branco: `Assinado digitalmente por {prescritor.nome} em {data}`. Não cria assinatura criptográfica. |
| `link_verificacao` | Sim | URL fornecida pela plataforma. Deve caber em uma linha junto ao rótulo. |
| `token_verificacao` | Não | Texto pronto para consultar em `link_verificacao`, até duas linhas; não é extraído da URL. |
| `codigo_verificacao` | Não | Texto pronto para consultar em `link_verificacao`, até duas linhas; não é extraído da URL. |
| `dados_especificos` | Não, exceto campos exigidos na talidomida | Objeto com `quantidade_por_extenso` e `responsavel`. Pode ser omitido nos demais modelos. |

Campos obrigatórios não aceitam texto em branco. Os limites de linhas são medidos pela fonte e largura efetivas do PDF; uma palavra que não caiba na largura é rejeitada. `\n` cria uma quebra explícita. Datas, CPF/passaporte, CID e valores clínicos são textos fornecidos pela integração; o componente não valida sua semântica.

### Emitente (`prescritor`)

| Campo | Obrigatório | Formato / uso |
| --- | --- | --- |
| `nome` | Sim | Até duas linhas. |
| `conselho` | Sim | Sigla do conselho, como `CRM`, `CRMV` ou `CRO`. |
| `uf` | Sim | UF, como `BA`; fornecida pela integração. |
| `registro` | Não | Número de inscrição, como `000000`; exibido como `CRM/BA 000000`. Campo novo, opcional para compatibilidade; os exemplos sempre o informam. |
| `registro_mapa` | Não | Registro do profissional no MAPA; nos modelos veterinários A/B e nas duas RCEs é exibido após a inscrição, como `CRMV/BA 000000 (MAPA: 000000)`. |
| `documento` | Talidomida | CPF em texto, como `000.000.000-00`. |
| `cadastro` | Talidomida | Número do cadastro do prescritor, em texto. |
| `especialidade` | Talidomida | Texto em uma linha. |
| `endereco` | Sim | Endereço completo em até três linhas. |
| `telefone` | Sim | E.164, como `+557130000000`; exibido no padrão brasileiro. |

### Paciente / proprietário (`paciente`)

| Campo | Obrigatório | Formato / uso |
| --- | --- | --- |
| `nome` | Sim | Até duas linhas; nos modelos veterinários é o nome do proprietário. |
| `documento` | Sim | CPF ou passaporte em texto, uma linha. |
| `endereco` | Sim | Endereço completo, até três linhas. |
| `telefone` | Não | E.164; impresso no modelo de talidomida. |
| `data_nascimento` | Retinoides e talidomida | Texto já formatado; exemplos em `DD/MM/AAAA`, uma linha. |
| `sexo` | Retinoides e talidomida | Texto em uma linha. |
| `cid` | Talidomida | Texto em uma linha. |
| `especie` | A/B veterinários e RCE veterinária | Espécie do animal, combinada com raça em uma linha. |
| `raca` | A/B veterinários e RCE veterinária | Raça do animal. |
| `porte` | A/B veterinários e RCE veterinária | Texto em uma linha. |
| `responsavel` | Não | Objeto de responsável; alternativa a `dados_especificos.responsavel` na talidomida. |

Campos específicos de um modelo são impressos somente nele. Telefones preenchidos são validados mesmo quando o campo não é impresso.

### Prescrição (`medicamento`)

| Campo | Obrigatório | Formato / uso |
| --- | --- | --- |
| `nome` | Sim | Nome do medicamento/substância em texto. |
| `concentracao` | Sim | Concentração com unidade, como `10 mg`. |
| `forma_farmaceutica` | Sim | Como `Comprimido`. |
| `quantidade` | Sim | Texto; na talidomida, quantidade em algarismos, como `30`. |
| `posologia` | Sim | Texto com quebra automática, limitado pela altura disponível. |
| `dose_por_unidade` | Talidomida | Texto com unidade, como `100 mg`. |
| `tempo_tratamento` | Talidomida | Texto, como `30 dias`. |
| `outras_orientacoes` | Não | Texto impresso na talidomida; em branco quando ausente. |

Na talidomida, o box usa quantidade, dose, tempo de tratamento, posologia e outras orientações. Nome, concentração e forma farmacêutica continuam obrigatórios na entrada estruturada, embora não sejam impressos nesse modelo.

### Dados específicos e responsável

| Campo | Obrigatório | Formato / uso |
| --- | --- | --- |
| `dados_especificos.quantidade_por_extenso` | Talidomida | Como `trinta comprimidos`. O PDF combina `30 (trinta comprimidos)`. |
| `dados_especificos.responsavel` | Não | Objeto abaixo, quando aplicável à talidomida. Tem prioridade sobre `paciente.responsavel`. |
| `responsavel.nome` | Se houver responsável | Até duas linhas na primeira coluna. |
| `responsavel.documento` | Se houver responsável | CPF ou passaporte em uma linha. |
| `responsavel.telefone` | Não | E.164, uma linha. |
| `responsavel.endereco` | Se houver responsável | Endereço completo, até duas linhas na largura do box. |

Sem responsável, o box permanece vazio. O endereço dele reserva duas linhas conforme o layout aprovado.

### Telefones e endereços

Telefones devem usar E.164 sem espaços ou pontuação: `+5571930000000`. Outros formatos, como `(71) 93000-0000`, são rejeitados. No PDF, números brasileiros aparecem como `(71) 93000-0000` ou `(71) 3000-0000`; telefones de outros países mantêm E.164.

Endereços são strings completas. Padrão usado nos exemplos: `logradouro, número, complemento, bairro, cidade/UF, CEP 00000-000`. A ordem é uma convenção dos exemplos, não uma validação normativa implementada. Veja a [consulta sobre endereço](examples/modelo-a/endereco-e-modelo.md).

## Modelos, exemplos e QR

| `modelo` | JSON completo | PDF gerado |
| --- | --- | --- |
| `notificacao_a` | [A](examples/visual-review/notificacao_a.json) | [PDF](examples/notificacao_a.pdf) |
| `notificacao_b` | [B](examples/visual-review/notificacao_b.json) | [PDF](examples/notificacao_b.pdf) |
| `notificacao_b2` | [B2](examples/visual-review/notificacao_b2.json) | [PDF](examples/notificacao_b2.pdf) |
| `notificacao_a_veterinaria` | [A veterinário](examples/visual-review/notificacao_a_veterinaria.json) | [PDF](examples/notificacao_a_veterinaria.pdf) |
| `notificacao_b_veterinaria` | [B veterinário](examples/visual-review/notificacao_b_veterinaria.json) | [PDF](examples/notificacao_b_veterinaria.pdf) |
| `retinoides` | [Retinoides](examples/visual-review/retinoides.json) | [PDF](examples/retinoides.pdf) |
| `talidomida` | [Talidomida](examples/visual-review/talidomida.json) | [PDF](examples/talidomida.pdf) |
| `receita_controle_especial` | [RCE](examples/visual-review/receita_controle_especial.json) | [PDF](examples/receita_controle_especial.pdf) |
| `receita_controle_especial_veterinaria` | [RCE veterinária](examples/visual-review/receita_controle_especial_veterinaria.json) | [PDF](examples/receita_controle_especial_veterinaria.pdf) |

Nas Notificações, o QR usa a consulta SNCR:

```text
https://sncr.anvisa.gov.br/receita/consultar?numero={numero}
```

Nas duas RCEs, o QR usa exatamente `link_verificacao`, conforme o layout aprovado neste projeto. O número da RCE segue o mesmo padrão textual do A, mas seu QR é da plataforma. `url_qr` retorna a URL correspondente nos modelos e na variação veterinária da RCE.

## Documentos nos layouts nativos

Os layouts `simples_carimbo` e `simples_digital` geram receita simples, fórmula manipulada, pedidos de exame, atestados, declarações, termos e encaminhamentos. Diferem só no bloco de assinatura: espaço para carimbo ou caixa de assinatura digital. A geometria é fixa, como nos modelos Anvisa: texto que excede o limite do campo gera erro. Cabeçalho, paciente e título se repetem em todas as páginas; assinatura e "Emitido em" ficam só na última.

```sh
mise exec rust -- cargo run --release -- examples/documentos/receita-simples.json receita.pdf
```

```rust
use receitapdf::{gerar_documento, Documento, Imagens};

fn gerar(documento: &Documento, logo_png: &[u8]) -> Result<Vec<u8>, receitapdf::Erro> {
    let imagens = Imagens { logo: Some(logo_png), ..Imagens::default() };
    Ok(gerar_documento(documento, imagens)?.pdf)
}
```

```python
import json
from pathlib import Path
from receitapdf import gerar_documento

pasta = Path("examples/documentos")
documento = json.loads((pasta / "receita-simples.json").read_text(encoding="utf-8"))
gerado = gerar_documento(
    documento,
    logo=(pasta / "logo-exemplo.png").read_bytes(),
    marca_dagua=(pasta / "marca-dagua-exemplo.png").read_bytes(),
)
Path("documento.pdf").write_bytes(gerado.pdf)
print(gerado.areas_assinatura)  # [], ou as áreas com assinatura.coordenadas
```

A biblioteca Rust recebe as imagens em bytes (`Imagens`) e não lê arquivos nem acessa a rede. `clinica.logo` (cabeçalho) e `marca_dagua` (rodapé, a 10% de opacidade) aceitam PNG de até 2 MB; imagens do corpo também são fornecidas em `Imagens.corpo`, associadas ao texto de `origem`. Todos são opcionais, e sem marca d'água o rodapé fica sem marca:

- CLI: caminho relativo ao JSON (ou ao diretório atual, com stdin) ou endereço `https://`, baixado com tempo limite de 10 s, sem redirecionamento para HTTP. O download depende da feature `rede`, padrão na CLI e ausente na extensão Python. Blocos de imagem no corpo usam caminhos de PNG locais.
- Python: só endereços `https://` são baixados automaticamente; para arquivos locais, passe os bytes em `logo=` e `marca_dagua=`.

### Campos do documento

| Campo | Conteúdo |
| --- | --- |
| `layout` | `simples_carimbo`, `simples_digital`, `documento_carimbo` ou `documento_digital` |
| `titulo` | Título em maiúsculas abaixo do paciente, por exemplo `Receita digital` |
| `numero` | Opcional: número exibido numa caixa à direita do título. SNCR da Receita Sujeita a Retenção (RET), no formato `0000.0-00.0000000` ou `0000.0-00.000000000`, com o QR do cabeçalho consultando `https://sncr.anvisa.gov.br/receita/consultar?numero={numero}`; ou SIPEAGRO, no formato `MV00000000000 / 0000 / 00` (`MV` e 11 dígitos, 4 dígitos e 2 dígitos; aceito também sem os espaços em volta das barras e sempre exibido com eles), com o QR do cabeçalho em `verificacao.url_qr`. SIPEAGRO inválido gera `NumeroSipeagroInvalido` |
| `assinatura` | Opcional: `campo_sig` e `coordenadas` (padrão `false`) e `url_qr` (QR da caixa de assinatura digital; padrão `verificacao.url_qr`) |
| `clinica` | `nome`, exibido no cabeçalho (por exemplo o nome fantasia), sem limite de linhas: o cabeçalho cresce quando o texto passa do QR; opcionais `razao_social` (nome do rodapé, uma linha de até 294 pt; sem ela, o rodapé usa `nome`), `cnpj` (rodapé, abaixo do nome), `endereco`, `contato` e `logo` |
| `emissor` | `nome`, `especialidade` (opcional) e `conselho` com `sigla`, `numero` e `uf` |
| `contingencia` | Opcional (padrão `false`): emissão em contingência. A faixa do paciente fica com uma linha só, com o nome e o selo "Emitido em contingência", e o título e o corpo sobem; o endereço do paciente não é impresso |
| `paciente` | `nome`; opcionais `documento` já rotulado (`CPF: ...`) e `endereco`. Sem limite de linhas: a faixa do paciente cresce além das três linhas do design e empurra o título e o corpo. Na receita veterinária, o animal, com os opcionais `especie`, `raca`, `sexo`, `idade` e `peso` já formatados (`6 anos`, `32,4 kg`) |
| `tutor` | Opcional: `nome`; opcionais `documento` e `endereco`. Com ele, a faixa do paciente segue o modelo veterinário: o nome do animal com espécie, raça, sexo, idade e peso separados por " · ", "Tutor:" com o nome e o documento do tutor, e o endereço do tutor (sem ele, o do paciente). Em contingência, só a linha do animal, ao lado do selo |
| `corpo` | Lista de blocos `{"paragrafo": "..."}`, `{"medicamento": {...}}`, `{"exame": {...}}` ou `{"imagem": {"origem": "imagem.png", "largura": 300, "altura": 150}}`. Nos layouts `documento_*`, a imagem PNG é centralizada na caixa indicada; largura e altura são pontos |
| `conteudo_rico` | Envelope TipTap v1 opcional: `schema`, `doc`, `fields` e `images`. Substitui `corpo` quando presente, que pode então ser omitido. Veja abaixo |
| `cids` | Opcional: códigos CID-10 exibidos após o corpo (até duas linhas) |
| `orientacoes` | Opcional: orientações ao paciente após o corpo, uma por item (até quatro linhas cada) |
| `signatarios_extras` | Até um assinante além do emissor, como o paciente num termo. Em `simples_carimbo` assina à mão; em `simples_digital` assina digitalmente, ao lado da caixa, que fica mais alta para o texto do emissor ter três linhas |
| `emitido_em` | Data e hora já formatadas |
| `marca_dagua` | Opcional: caminho ou URL do PNG da marca d'água do rodapé |
| `verificacao` | `url_qr` (QR do cabeçalho), `site`, `token` e `codigo`. O `site` fica numa linha própria do rodapé, como link (`https://` é acrescentado quando falta), com até 205 pt: cerca de 60 caracteres típicos de endereço e, no pior caso (só letras largas como `m`), 37 |

- `medicamento`: `nome` (até duas linhas), `quantidade`, `detalhe` (apresentação, até duas linhas) e `observacao` (posologia, até quatro linhas).
- `exame`: `nome` (até duas linhas), `codigos` (lista de `rotulo`/`valor`, até duas linhas) e `instrucao` (até três linhas).

Medicamentos e exames são numerados em sequências próprias e nunca se dividem entre páginas; parágrafos quebram por linha. CID-10 e orientações aparecem depois do corpo, abaixo de um tracejado, com o rótulo sempre junto da primeira linha.

### Conteúdo rico (TipTap 3, contrato v1)

Os layouts `documento_carimbo` e `documento_digital` removem toda a faixa do
paciente e seu espaço. Clínica, emissor, título, assinatura (inclusive
`signatarios_extras`), data e verificação continuam em `Documento`. O corpo do
editor entra em `conteudo_rico`:

```json
{
  "conteudo_rico": {
    "schema": 1,
    "doc": {
      "type": "doc",
      "content": [{
        "type": "paragraph",
        "attrs": {"alignment": "justify"},
        "content": [
          {"type": "text", "text": "Atesto o comparecimento de "},
          {"type": "structuredField", "attrs": {"field": "paciente.nome"}, "marks": [{"type": "bold"}]}
        ]
      }]
    },
    "fields": {"paciente.nome": "Maria Helena Duarte"},
    "images": {}
  }
}
```

O exemplo acima é o trecho a acrescentar ao objeto `Documento`, não um documento
completo. Rust, CLI e Python interpretam o mesmo envelope, sem acesso a arquivos
ou rede para resolver campos e imagens ricos.

- Nós: `paragraph`, `heading` (nível 3), `text`, `hardBreak`, listas
  `bulletList`/`orderedList` com `listItem` aninhados, `structuredField` e
  `clinicalImage`. Listas numeradas sempre começam em 1 (`start`/`type` ignorados).
- Marcas: `bold`, `italic` e `underline`, combináveis. Itálico usa inclinação
  tipográfica da mesma fonte Quicksand. Títulos internos usam negrito de 14 pt;
  texto usa 12 pt. Parágrafos vazios e quebras explícitas preservam linhas vazias.
- Alinhamento: `attrs.alignment` ausente/`null` = esquerda; `center`, `right` ou
  `justify`. A última linha do parágrafo e linhas com quebra explícita não são
  justificadas. Espaços não separáveis (NBSP) não criam quebras de palavra.
- Campos: o valor vem exclusivamente de `fields[attrs.field]`, preservando
  marcas e `\n`; `label` nunca é impresso como valor. Campo ausente gera erro.
- Imagem: `{"type":"clinicalImage","attrs":{"image":"id","width":"50","float":"left"}}`.
  Larguras: `"25"`, `"50"` (padrão), `"100"`, em percentual da largura útil
  da página. `float` ausente/`null` centraliza; `left`/`right` fazem as linhas
  seguintes contornarem a imagem até sua base. Largura `100` não flutua.
  `images[id]` contém `content_type`, `width`/`height` em pixels e `data_base64`.
  São aceitos PNG, JPEG, GIF e WebP (primeiro quadro nos formatos animados).
  Dimensões declaradas são conferidas com os bytes, sem EXIF/reorientação.
- Margens de imagens: 12 pt acima/abaixo em bloco; floats têm 4 pt acima,
  20 pt entre imagem/texto e 12 pt abaixo. Cantos de 4 pt. Como nos layouts
  existentes, 1 px de geometria do editor corresponde a 1 pt no PDF.
- Imagens não se dividem entre páginas; float e linhas ao lado permanecem juntos.
  Floats adjacentes compartilham a linha quando há espaço; imagens em bloco
  começam depois dos floats ativos. Conteúdo que não
  cabe inteiro em sua área gera erro, nunca truncamento. Os limites de segurança
  por imagem são 16 MiB de bytes, 40 milhões de pixels e 16000 px por dimensão.
- Versão diferente de 1, atributos inválidos, campo/imagem faltante ou bytes
  inválidos retornam erro (`ValueError` em Python). Nós desconhecidos preservam
  o texto dos filhos; marcas desconhecidas são ignoradas. Profundidade máxima: 64.

Os 21 JSONs de referência estão em `tests/fixtures/tiptap-v1.json`. Exemplo
completo com imagens incorporadas e formatação: `examples/documentos/documento-tiptap.json`.

Imagens legadas de blocos `corpo[].imagem` continuam em `Imagens.corpo` (Rust)
ou `imagens_corpo={"origem": bytes_png}` (Python). Isso não é necessário para
`conteudo_rico.images`, que já transporta os bytes em base64.

### Assinatura

- `simples_carimbo` e `documento_carimbo`: reservam espaço acima da linha do emissor para carimbo e assinatura manual ou carimbo visual do serviço de assinatura. O rodapé só afirma a assinatura ICP-Brasil com `campo_sig` ou `coordenadas`.
- `simples_digital` e `documento_digital`: caixa com QR e texto de assinatura ICP-Brasil. O QR usa `assinatura.url_qr` (por exemplo, o validador da assinatura) ou, sem ele, `verificacao.url_qr`.
- `campo_sig`: cria campos de assinatura vazios (`/FT /Sig`) na última página, onde serviços PAdES assinam pelo nome: `assinatura_emissor` e, quando há `signatarios_extras`, `assinatura_paciente`, tanto no carimbo quanto no digital. Nos layouts digitais o campo do emissor é invisível.
- `coordenadas`: retorna `areas_assinatura`, uma lista com `campo`, `pagina`, `x`, `y`, `largura`, `altura`, `largura_pagina` e `altura_pagina`, em pontos com origem no canto inferior esquerdo (o mesmo espaço do `/Rect` do PDF). A CLI imprime a lista em JSON. Nos layouts de carimbo, retorna o espaço do emissor e, quando há `signatarios_extras`, também o do paciente à direita (ambos com 220 × 64 pt). Nos digitais, o emissor não tem área (a caixa já mostra a assinatura) e o paciente tem a área acima da linha dele, ao lado da caixa.

| Documento | JSON | PDFs |
| --- | --- | --- |
| Receita simples | [JSON](examples/documentos/receita-simples.json) | [carimbo](examples/documentos/receita-simples-carimbo.pdf), [digital](examples/documentos/receita-simples-digital.pdf) |
| Fórmula manipulada | [JSON](examples/documentos/formula-manipulada.json) | [carimbo](examples/documentos/formula-manipulada-carimbo.pdf), [digital](examples/documentos/formula-manipulada-digital.pdf) |
| Exames laboratoriais | [JSON](examples/documentos/exames-laboratoriais.json) | [carimbo](examples/documentos/exames-laboratoriais-carimbo.pdf), [digital](examples/documentos/exames-laboratoriais-digital.pdf) |
| Atestado | [JSON](examples/documentos/atestado.json) | [carimbo](examples/documentos/atestado-carimbo.pdf), [digital](examples/documentos/atestado-digital.pdf) |
| Termo de consentimento | [JSON](examples/documentos/termo-consentimento.json) | [carimbo](examples/documentos/termo-consentimento-carimbo.pdf), [digital](examples/documentos/termo-consentimento-digital.pdf) |

Os demais exemplos, inclusive documentos de várias páginas, estão em [examples/documentos](examples/documentos/), com prévias e [contato visual](examples/documentos/png/contato.png).

## Prévia em WebP

`gerar_preview` rasteriza todas as páginas de um PDF gerado (receita Anvisa ou documento nativo) e devolve uma imagem WebP sem perdas por página, com a marca d'água diagonal "PRÉVIA — SEM VALIDADE" em cinza translúcido. O PDF não muda: a marca existe só nas imagens.

```rust
let paginas: Vec<Vec<u8>> = receitapdf::gerar_preview(&pdf, 144.0)?;
```

```python
from receitapdf import gerar_preview

for numero, webp in enumerate(gerar_preview(gerado.pdf, dpi=144), start=1):
    Path(f"pagina-{numero}.webp").write_bytes(webp)
```

- `dpi` vai de 36 a 300 (144 dpi gera A4 com 1190 × 1684 px). Fora da faixa, ou com PDF ilegível, gera `Erro::PreviaInvalida` (`ValueError` em Python).
- Usa o `hayro`, rasterizador em Rust puro, sem PyMuPDF nem bibliotecas nativas. Fica na feature `preview`, padrão na CLI e incluída na wheel, onde acrescenta cerca de 2,5 MB. No Python, a função libera o GIL.
- [Exemplo](examples/documentos/previa/) de uma receita de três páginas.

## Gerar todos os exemplos

```sh
python -m pip install pymupdf pillow
python examples/gerar_exemplos.py
```

O script compila a CLI Rust e gera:

- Os oito PDFs principais e a variação veterinária da RCE em `examples/`.
- Dezoito PDFs e PNGs, com dados curtos e longos, em [modelos-gerados](examples/modelos-gerados/).
- Prévias de 144 dpi e [contato visual](examples/visual-review/contato.png).
- Os quatro exemplos de rodapé com plataforma/assinatura em uma ou duas linhas, em `examples/rodape-plataforma-*.pdf`.
- Cada documento de `examples/documentos/` nos layouts `simples_carimbo` e `simples_digital`, com PNGs de todas as páginas em `examples/documentos/png/` e as prévias WebP da receita longa, geradas pela CLI, em `examples/documentos/previa/`.

PyMuPDF e Pillow são usados apenas nas prévias. Os PDFs são produzidos pela mesma CLI que o consumidor usa.

Os scripts `examples/recriar_*.py` mantêm as referências de ajuste visual e usam os mesmos JSONs da API. Após alterar os cabeçalhos nesses estudos, execute `python examples/preparar_modelos.py` para atualizar `assets/modelos/` e recompile a biblioteca. As fontes Open Sans e os avisos vetoriais vêm dos originais; a composição Rust usa `printpdf` e `lopdf`. DejaVu Sans é incorporada nos dados e DejaVu Sans Bold Oblique nas URLs. Os layouts `simples_*` usam Quicksand ([OFL](assets/fonts/Quicksand-OFL.txt)).

## Compilação e testes

```sh
mise exec rust -- cargo check --locked --all-targets --features python
mise exec rust -- cargo test --locked
mise exec rust -- cargo test --locked --features python
maturin build --release
```

O código é MIT ([LICENSE](LICENSE)); fontes e modelos de origem mantêm seus próprios termos.

### Publicar no PyPI

O workflow [publish-pypi.yml](.github/workflows/publish-pypi.yml) cria o sdist e o wheel Linux x86_64, executa os testes e publica via Trusted Publishing. Para habilitar a publicação, cadastre um publisher pendente nas configurações do PyPI com estes valores:

- Projeto PyPI: `receitapdf`
- Owner do GitHub: `ramon`
- Repositório: `receitapdf`
- Workflow: `.github/workflows/publish-pypi.yml`
- Environment do GitHub: `pypi`

Depois desse cadastro, a publicação pode ser iniciada manualmente em `master` ou ao publicar uma release do GitHub com tag `vX.Y.Z` igual à versão em `pyproject.toml`. O job usa OIDC; não é necessário guardar token do PyPI como secret.

