feat: initial release — pdf-toolkit v1.0.0
Core operations: - Compress PDF (3 presets: screen, ebook, printer, prepress) - PDF/A detection and conversion to regular PDF - Compress while preserving PDF/A structure - Merge multiple PDFs - Split/extract pages by range - Rotate pages - Extract text and images Interface: - Streamlit web GUI (app.py) - FastAPI REST API with auto OpenAPI docs (api.py) Docs: - README.md, ARCHITECTURE.md, API.md - Unit tests (15/15 passing)
This commit is contained in:
240
docs/API.md
Normal file
240
docs/API.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# API do pdf-toolkit
|
||||
|
||||
Documentação interativa disponível em:
|
||||
- **Swagger UI**: http://localhost:8555/docs
|
||||
- **Redoc**: http://localhost:8555/redoc
|
||||
|
||||
---
|
||||
|
||||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:8555
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Endpoints
|
||||
|
||||
### `GET /`
|
||||
|
||||
Raiz informativa.
|
||||
|
||||
**Resposta:**
|
||||
```json
|
||||
{
|
||||
"app": "pdf-toolkit API",
|
||||
"version": "1.0.0",
|
||||
"docs": "/docs",
|
||||
"redoc": "/redoc"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `GET /health`
|
||||
|
||||
Health check.
|
||||
|
||||
**Resposta:**
|
||||
```json
|
||||
{"status": "ok", "version": "1.0.0"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `POST /inspect`
|
||||
|
||||
Inspeciona metadados de um PDF.
|
||||
|
||||
**Parâmetros:**
|
||||
| Nome | Tipo | Obrigatório |
|
||||
|---|---|---|
|
||||
| `file` | arquivo PDF | ✅ |
|
||||
|
||||
**Resposta:**
|
||||
```json
|
||||
{
|
||||
"valid": true,
|
||||
"pages": 42,
|
||||
"error": "",
|
||||
"is_pdfa": false,
|
||||
"file_size_mb": 2.34,
|
||||
"filename": "relatorio.pdf",
|
||||
"level": null,
|
||||
"method": "none",
|
||||
"confidence": 0.0
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `POST /compress`
|
||||
|
||||
Comprime um PDF re-amostrando imagens.
|
||||
|
||||
**Parâmetros:**
|
||||
| Nome | Tipo | Padrão | Obrigatório |
|
||||
|---|---|---|---|
|
||||
| `file` | arquivo | — | ✅ |
|
||||
| `preset` | string | `"ebook"` | ❌ |
|
||||
|
||||
**Presets válidos:** `screen`, `ebook`, `printer`, `prepress`
|
||||
|
||||
**Resposta:** PDF comprimido (download)
|
||||
|
||||
---
|
||||
|
||||
### `POST /pdfa-to-regular`
|
||||
|
||||
Remove restrições PDF/A gerando PDF comum.
|
||||
|
||||
**Parâmetros:**
|
||||
| Nome | Tipo | Obrigatório |
|
||||
|---|---|---|
|
||||
| `file` | arquivo PDF | ✅ |
|
||||
|
||||
**Resposta:** PDF comum (download)
|
||||
|
||||
---
|
||||
|
||||
### `POST /compress-pdfa`
|
||||
|
||||
Comprime imagens mantendo a estrutura PDF/A.
|
||||
|
||||
**Parâmetros:**
|
||||
| Nome | Tipo | Padrão | Obrigatório |
|
||||
|---|---|---|---|
|
||||
| `file` | arquivo | — | ✅ |
|
||||
| `quality` | int (10-100) | 60 | ❌ |
|
||||
|
||||
**Resposta:** PDF otimizado (download)
|
||||
|
||||
---
|
||||
|
||||
### `POST /merge`
|
||||
|
||||
Junta múltiplos PDFs em um único arquivo.
|
||||
|
||||
**Parâmetros:**
|
||||
| Nome | Tipo | Obrigatório |
|
||||
|---|---|---|
|
||||
| `files` | lista de arquivos | ✅ (mín: 2) |
|
||||
|
||||
**Resposta:** PDF mesclado (download)
|
||||
|
||||
---
|
||||
|
||||
### `POST /split`
|
||||
|
||||
Divide um PDF em múltiplos arquivos por intervalos de páginas.
|
||||
|
||||
**Parâmetros:**
|
||||
| Nome | Tipo | Obrigatório |
|
||||
|---|---|---|
|
||||
| `file` | arquivo PDF | ✅ |
|
||||
| `ranges` | string | ✅ |
|
||||
|
||||
**Exemplo de `ranges`:** `"1-3,5,7-9"` (1-indexado)
|
||||
|
||||
**Resposta:** ZIP com os PDFs extraídos
|
||||
|
||||
---
|
||||
|
||||
### `POST /extract-pages`
|
||||
|
||||
Extrai páginas específicas em ordem para um único PDF.
|
||||
|
||||
**Parâmetros:**
|
||||
| Nome | Tipo | Obrigatório |
|
||||
|---|---|---|
|
||||
| `file` | arquivo PDF | ✅ |
|
||||
| `pages` | string | ✅ |
|
||||
|
||||
**Exemplo de `pages`:** `"1,3,5"` (1-indexado)
|
||||
|
||||
**Resposta:** PDF com as páginas selecionadas (download)
|
||||
|
||||
---
|
||||
|
||||
### `POST /rotate`
|
||||
|
||||
Rotaciona páginas de um PDF.
|
||||
|
||||
**Parâmetros:**
|
||||
| Nome | Tipo | Padrão | Obrigatório |
|
||||
|---|---|---|---|
|
||||
| `file` | arquivo | — | ✅ |
|
||||
| `pages` | string | — | ✅ |
|
||||
| `angle` | int | 90 | ❌ |
|
||||
|
||||
**Exemplo de `pages`:** `"1,3,5"` ou `"todas"`
|
||||
|
||||
**Resposta:** PDF rotacionado (download)
|
||||
|
||||
---
|
||||
|
||||
### `POST /extract-text`
|
||||
|
||||
Extrai texto de todas as páginas do PDF.
|
||||
|
||||
**Parâmetros:**
|
||||
| Nome | Tipo | Obrigatório |
|
||||
|---|---|---|
|
||||
| `file` | arquivo PDF | ✅ |
|
||||
|
||||
**Resposta:** arquivo `.txt` (download)
|
||||
|
||||
---
|
||||
|
||||
### `POST /extract-images`
|
||||
|
||||
Extrai todas as imagens embutidas no PDF.
|
||||
|
||||
**Parâmetros:**
|
||||
| Nome | Tipo | Obrigatório |
|
||||
|---|---|---|
|
||||
| `file` | arquivo PDF | ✅ |
|
||||
|
||||
**Resposta:** ZIP com as imagens extraídas
|
||||
|
||||
---
|
||||
|
||||
## Exemplos com curl
|
||||
|
||||
### Comprimir
|
||||
```bash
|
||||
curl -X POST http://localhost:8555/compress \
|
||||
-F "file=@documento.pdf" \
|
||||
-F "preset=ebook" \
|
||||
-o comprimido.pdf
|
||||
```
|
||||
|
||||
### Merge
|
||||
```bash
|
||||
curl -X POST http://localhost:8555/merge \
|
||||
-F "files=@parte1.pdf" \
|
||||
-F "files=@parte2.pdf" \
|
||||
-o merged.pdf
|
||||
```
|
||||
|
||||
### Split
|
||||
```bash
|
||||
curl -X POST http://localhost:8555/split \
|
||||
-F "file=@documento.pdf" \
|
||||
-F "ranges=1-3,5,7-9" \
|
||||
-o split.zip
|
||||
```
|
||||
|
||||
### Inspect
|
||||
```bash
|
||||
curl -s -X POST http://localhost:8555/inspect \
|
||||
-F "file=@documento.pdf" | jq .
|
||||
```
|
||||
|
||||
### Extract text
|
||||
```bash
|
||||
curl -X POST http://localhost:8555/extract-text \
|
||||
-F "file=@documento.pdf" \
|
||||
-o texto.txt
|
||||
```
|
||||
149
docs/ARCHITECTURE.md
Normal file
149
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Arquitetura do pdf-toolkit
|
||||
|
||||
## Visão geral
|
||||
|
||||
O pdf-toolkit é um sistema de duas faces: **GUI (Streamlit)** e **API REST (FastAPI)**, ambas compartilhando o mesmo núcleo (`core/`) e configurações (`config.py`).
|
||||
|
||||
```
|
||||
┌─────────────────────┐ ┌──────────────────────┐
|
||||
│ Streamlit (app.py) │ │ FastAPI (api.py) │
|
||||
│ ─ Porta 8550 │ │ ─ Porta 8555 │
|
||||
│ ─ Upload drag-drop │ │ ─ Swagger /docs │
|
||||
│ ─ Preview visual │ │ ─ Redoc /redoc │
|
||||
└────────┬────────────┘ └──────────┬───────────┘
|
||||
│ │
|
||||
└──────────┬──────────────────┘
|
||||
│
|
||||
┌──────────▼───────────┐
|
||||
│ core/ (engine) │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ engine.py │ │← Orquestrador (facade)
|
||||
│ │ validators.py │ │← Valida entradas
|
||||
│ │ compress.py │ │← Compressão/redução
|
||||
│ │ pdfa.py │ │← PDF/A detection & strip
|
||||
│ │ operations.py │ │← Merge, split, rotate...
|
||||
│ └─────────────────┘ │
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
### Fluxo de dados
|
||||
|
||||
1. **Entrada**: bytes do PDF via upload (Streamlit) ou HTTP multipart (API)
|
||||
2. **Validação**: `validators.validate_pdf()` checa magic number, lê metadados
|
||||
3. **Detecção PDF/A**: `pdfa.detect_pdfa()` verifica XMP metadata e OutputIntents
|
||||
4. **Transformação**: operação específica (compressão, split, etc.)
|
||||
5. **Saída**: bytes transformados → download (Streamlit) ou Response (API)
|
||||
|
||||
---
|
||||
|
||||
## Módulo `core/engine.py`
|
||||
|
||||
`PDFEngine` é uma classe de fachada que unifica todos os submódulos. Cada método:
|
||||
- Recebe `bytes` (ou lista de bytes)
|
||||
- Retorna `bytes` (ou metadados dict)
|
||||
- Propaga exceções para o caller (Streamlit ou FastAPI)
|
||||
|
||||
```python
|
||||
engine = PDFEngine()
|
||||
info = engine.inspect(data, "doc.pdf")
|
||||
comprimido = engine.compress(data, preset="ebook")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Módulo `core/compress.py`
|
||||
|
||||
### Estratégia de compressão
|
||||
|
||||
1. Para cada página, varre `/Resources/XObject` em busca de `/Subtype == /Image`
|
||||
2. Extrai dados brutos da imagem
|
||||
3. Redimensiona proporcionalmente se exceder `max_dim` (LANCZOS)
|
||||
4. Re-comprime em JPEG com `quality` definida
|
||||
5. Substitui `_data` do objeto XObject
|
||||
6. Re-escreve o PDF com `PdfWriter.write_stream()` (já faz compressão de streams)
|
||||
|
||||
### Por que não usamos linearização?
|
||||
|
||||
`pypdf` não suporta linearização true (fast web view). A opção `linearize_pdf()` apenas re-escreve o PDF, o que já melhora a estrutura. Para linearização real, seria necessário Ghostscript ou qpdf.
|
||||
|
||||
---
|
||||
|
||||
## Módulo `core/pdfa.py`
|
||||
|
||||
### Detecção de PDF/A
|
||||
|
||||
Dois métodos complementares:
|
||||
1. **Metadados XMP**: busca padrão `pdfaid:part` + `pdfaid:conformance` via regex
|
||||
2. **OutputIntents**: verifica `/OutputIntents` no dicionário raiz do PDF (obrigatório em PDF/A)
|
||||
|
||||
### Conversão PDF/A → PDF comum
|
||||
|
||||
- Remove metadados XMP que declaram PDF/A
|
||||
- Remove dicionário `/OutputIntents`
|
||||
- Preserva conteúdo visual integralmente
|
||||
- Mantém metadados não-PDF/A (autor, título, etc.)
|
||||
|
||||
---
|
||||
|
||||
## Módulo `core/operations.py`
|
||||
|
||||
Operações independentes que não dependem entre si:
|
||||
|
||||
| Função | Descrição |
|
||||
|---|---|
|
||||
| `merge_pdfs()` | Concatena PDFs em ordem |
|
||||
| `split_pdf()` | Extrai intervalos → múltiplos PDFs |
|
||||
| `extract_pages()` | Extrai páginas específicas → 1 PDF |
|
||||
| `rotate_pages()` | Rotaciona páginas (90/180/270) |
|
||||
| `extract_text()` | Extrai texto de todas as páginas |
|
||||
| `extract_images()` | Extrai imagens embutidas |
|
||||
|
||||
---
|
||||
|
||||
## API (FastAPI)
|
||||
|
||||
A API usa `File` (multipart) para upload e `Response` para download.
|
||||
|
||||
### Validação automática via FastAPI:
|
||||
- Tipo dos parâmetros
|
||||
- Limite de tamanho (`_read_file()`)
|
||||
- Presets válidos
|
||||
|
||||
### Documentação: `/docs` (Swagger) e `/redoc` (Redoc) são gerados automaticamente.
|
||||
|
||||
---
|
||||
|
||||
## Streamlit (GUI)
|
||||
|
||||
A GUI organiza-se em:
|
||||
1. **Sidebar**: upload + seleção de operação + parâmetros + botão executar
|
||||
2. **Área principal**: preview dos arquivos ou resultados
|
||||
|
||||
### Estados:
|
||||
- **Antes de executar**: exibe metadados dos PDFs carregados
|
||||
- **Executando**: barra de progresso com descrição
|
||||
- **Resultado**: download individual ou ZIP com múltiplos arquivos
|
||||
|
||||
---
|
||||
|
||||
## Dependências
|
||||
|
||||
| Pacote | Uso |
|
||||
|---|---|
|
||||
| `streamlit` | Interface web |
|
||||
| `fastapi` + `uvicorn` | API REST |
|
||||
| `pypdf` | Leitura/escrita de PDF |
|
||||
| `Pillow` | Processamento de imagens |
|
||||
| `python-multipart` | Upload na API |
|
||||
|
||||
---
|
||||
|
||||
## Próximas melhorias possíveis
|
||||
|
||||
- [ ] Integração com Ghostscript para compressão mais agressiva
|
||||
- [ ] Preview visual de páginas via `pdf2image` + `poppler-utils`
|
||||
- [ ] Suporte a PDFs criptografados (senha)
|
||||
- [ ] OCR em PDFs scaneados (Tesseract)
|
||||
- [ ] Concorrência em lote com `asyncio`/`ThreadPoolExecutor`
|
||||
- [ ] Autenticação na API (API keys)
|
||||
- [ ] Dockerfile para deploy
|
||||
Reference in New Issue
Block a user