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:
CM Consultor
2026-08-09 02:15:22 +00:00
commit 84d4a1eab5
17 changed files with 1998 additions and 0 deletions

78
core/validators.py Normal file
View File

@@ -0,0 +1,78 @@
"""
Validação de arquivos PDF recebidos.
"""
import io
import struct
from pathlib import Path
from typing import Union
from pypdf import PdfReader
PDF_MAGIC = b"%PDF"
def is_pdf_stream(data: bytes) -> bool:
"""Verifica se o buffer começa com a magic number de PDF."""
return data[:4] == PDF_MAGIC
def is_pdf_file(path: Union[str, Path]) -> bool:
"""Verifica se o arquivo é um PDF válido."""
path = Path(path)
if not path.exists() or not path.is_file():
return False
with open(path, "rb") as f:
header = f.read(4)
return header == PDF_MAGIC
def validate_pdf(data: bytes, filename: str = "") -> dict:
"""
Valida um PDF em memória. Retorna dict com:
- valid: bool
- pages: int (se válido)
- error: str (se inválido)
- is_pdfa: bool (provável PDF/A)
- file_size_mb: float
"""
result = {
"valid": False,
"pages": 0,
"error": "",
"is_pdfa": False,
"file_size_mb": round(len(data) / (1024 * 1024), 2),
"filename": filename,
}
if not is_pdf_stream(data):
result["error"] = "Arquivo não é um PDF (magic number inválido)"
return result
try:
reader = PdfReader(io.BytesIO(data))
result["pages"] = len(reader.pages)
result["valid"] = True
# Detecção básica de PDF/A via metadados XMP
if reader.metadata:
xmp = str(reader.metadata.raw_data or "")
if "/pdfaid:part" in xmp.lower() or "pdfa" in xmp.lower():
result["is_pdfa"] = True
except Exception as e:
result["error"] = f"Erro ao ler PDF: {str(e)}"
return result
def assert_safe_filename(name: str) -> str:
"""Remove caracteres perigosos de nomes de arquivo."""
import re
safe = re.sub(r'[^\w\-. ]', "_", name)
if not safe:
safe = "documento"
if not safe.lower().endswith(".pdf"):
safe += ".pdf"
return safe