Metadata-Version: 2.4
Name: receitapdf
Version: 0.4.1
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).

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 oito modelos.
- 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 | Exatamente `0000.0-00.0000000`: 4 dígitos, ponto, 1 dígito, hífen, 2 dígitos, ponto, 7 dígitos. |
| `numero_sipeagro` | Não, somente RCE | Registro SIPEAGRO no formato `00000-000000-00` (o primeiro bloco aceita de uma a cinco letras ou dígitos), impresso à direita no fim do bloco Prescrição. |
| `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 na RCE é 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 | Espécie do animal, combinada com raça em uma linha. |
| `raca` | A/B veterinários | Raça do animal. |
| `porte` | A/B veterinários | 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) |

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

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

Na RCE, 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 oito modelos.

## 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), marca_dagua: None };
    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; ambos 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.
- 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` ou `simples_digital` |
| `titulo` | Título em maiúsculas abaixo do paciente, por exemplo `Receita digital` |
| `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`; opcionais `cnpj`, `endereco`, `contato` e `logo` |
| `emissor` | `nome`, `especialidade` (opcional) e `conselho` com `sigla`, `numero` e `uf` |
| `paciente` | `nome`; opcionais `documento` já rotulado (`CPF: ...`) e `endereco` (até duas linhas) |
| `corpo` | Lista de blocos `{"paragrafo": "..."}`, `{"medicamento": {...}}` ou `{"exame": {...}}` |
| `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` |

- `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.

### Assinatura

- `simples_carimbo`: reserva 64 pt 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`: 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, num termo em `simples_digital`, `assinatura_paciente`. Em `simples_digital` 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. Em `simples_carimbo` a área é o espaço do carimbo do emissor; em `simples_digital` o emissor não tem área (a caixa já mostra a assinatura) e, num termo, 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 em `examples/`.
- Dezesseis 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.

