Files
pdf-toolkit/core/engine.py
CM Consultor e97adca143 fix: compressão real p/ stream DCTDecode + auth c/ chave vazia
- Substitui xobj._data por page.images[].replace() em compress.py e pdfa.py
- Adiciona compress_with_stats() com feedback honesto na UI
- Trata PDFTOOLKIT_API_KEY vazia como modo dev (auth desabilitado)
- Atualiza engine.py e app.py para exibir redução real ou 'já otimizado'
2026-08-09 05:01:16 +00:00

115 lines
3.8 KiB
Python

"""
Orquestrador principal de transformações de PDF.
Coordena validação, compressão, operações e conversão PDF/A
encadeando as funções dos submódulos.
"""
import io
from typing import List, Tuple
from .validators import validate_pdf, assert_safe_filename
from .compress import compress_pdf, compress_pdf_with_stats
from .pdfa import detect_pdfa, strip_pdfa, compress_pdfa
from .operations import merge_pdfs, split_pdf, extract_pages, rotate_pages
from .operations import extract_text, extract_images, preview_pages
class PDFEngine:
"""
Interface única para todas as operações do toolkit.
Cada método recebe bytes e retorna bytes (ou metadados).
"""
@staticmethod
def inspect(data: bytes, filename: str = "") -> dict:
"""Valida e retorna metadados do PDF."""
info = validate_pdf(data, filename)
if info["valid"]:
pdfa_info = detect_pdfa(data)
info.update(pdfa_info)
return info
@staticmethod
def compress(data: bytes, preset: str = "ebook") -> bytes:
"""Comprime PDF sem retornar um resultado maior que o original.
A regravação com pypdf pode aumentar PDFs que já estão comprimidos
ou que não possuem imagens adequadas para reamostragem. Nesse caso,
preservamos o original em vez de entregar um arquivo maior.
"""
result, stats = compress_pdf_with_stats(data, preset=preset)
if stats["reduced"]:
return result
return data
@staticmethod
def compress_with_stats(data: bytes, preset: str = "ebook") -> Tuple[bytes, dict]:
"""Comprime e retorna (bytes, estatísticas) para feedback na UI."""
result, stats = compress_pdf_with_stats(data, preset=preset)
if stats["reduced"]:
return result, stats
return data, stats
@staticmethod
def pdfa_to_regular(data: bytes) -> bytes:
"""Remove restrições PDF/A gerando PDF comum."""
return strip_pdfa(data)
@staticmethod
def compress_pdfa(data: bytes, quality: int = 60) -> bytes:
"""Comprime mantendo a estrutura PDF/A."""
return compress_pdfa(data, quality=quality)
@staticmethod
def detect_pdfa(data: bytes) -> dict:
"""Detecta se é PDF/A e qual nível."""
return detect_pdfa(data)
@staticmethod
def merge(files: List[Tuple[bytes, str]]) -> bytes:
"""Junta múltiplos PDFs."""
return merge_pdfs(files)
@staticmethod
def split(data: bytes, ranges: List[Tuple[int, int]], base: str = "pagina"):
"""Divide PDF em intervalos de páginas."""
return split_pdf(data, ranges, base)
@staticmethod
def extract(data: bytes, pages: List[int]) -> bytes:
"""Extrai páginas específicas."""
return extract_pages(data, pages)
@staticmethod
def rotate(data: bytes, rotations: List[Tuple[int, int]]) -> bytes:
"""Rotaciona páginas."""
return rotate_pages(data, rotations)
@staticmethod
def text(data: bytes) -> str:
"""Extrai texto de todas as páginas."""
return extract_text(data)
@staticmethod
def images(data: bytes):
"""Extrai imagens do PDF."""
return extract_images(data)
@staticmethod
def get_info_string(info: dict) -> str:
"""Formata metadados para exibição."""
lines = [
f"📄 **{info.get('filename', 'documento')}**",
f" - Tamanho: {info['file_size_mb']:.2f} MB",
f" - Páginas: {info['pages']}",
]
if info.get("valid"):
if info["is_pdfa"]:
level = info.get("level", "PDF/A")
lines.append(f" - 📋 Tipo: {level}")
else:
lines.append(" - 📋 Tipo: PDF comum")
else:
lines.append(f" - ⚠️ Erro: {info['error']}")
return "\n".join(lines)