Ghostscript -sDEVICE=pdfwrite fundamentally re-encodes fonts, causing
garbled text regardless of parameters. This cannot be fixed.
New approach:
- Local: qpdf-only lossless structural optimization (5-30% savings,
zero corruption risk — fonts and images completely untouched)
- Cloud: iLovePDF API integration (auth → start → upload → process →
download) with 3 levels (recommended/extreme/low), proper image
recompression without font corruption
Frontend: 3 modes (cloud recommended, cloud extreme, local lossless).
Docker: ILOVEPDF_PUBLIC_KEY env var added.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The -dPDFSETTINGS=/screen GS preset overwrites font encoding tables,
producing garbled text in output PDFs. Replace with individual params
that ONLY compress images while preserving fonts intact.
Three quality levels via GS (no Stirling dependency):
- extreme: 100 DPI, QFactor 1.2 (~quality 35)
- high: 150 DPI, QFactor 0.76 (~quality 50)
- balanced: 200 DPI, QFactor 0.4 (~quality 70)
Route all UI modes through the GS endpoint with level parameter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
formData() fails with "Failed to parse body as FormData" on large PDFs
in Next.js route handlers. Switch to req.arrayBuffer() which reliably
reads the full body, then manually extract the PDF from multipart.
Extreme mode: arrayBuffer + multipart extraction + GS + qpdf pipeline.
Stirling mode: arrayBuffer forwarding to Stirling with proper headers.
Revert serverActions.bodySizeLimit (doesn't apply to route handlers).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extreme mode: replace fragile manual multipart boundary parsing (which
extracted only a fraction of large files, producing empty PDFs) with
standard req.formData(). Add GS output validation + stderr capture.
Stirling mode: parse formData first then build fresh FormData for
Stirling instead of raw body passthrough (which lost data on large
files). Add 5min timeout + original/compressed size headers.
next.config: add 250MB body size limit for server actions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The multipart body parser was using the first \r\n\r\n as the file
content start, but this could miss the actual file part. Now properly
iterates through parts to find the one with filename= header, and
uses lastIndexOf for the closing boundary to avoid false matches
inside PDF binary data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Registratura: re-allocate number when company/direction changes on update,
recalculate old company's sequence counter from actual entries
- Extreme PDF: stream body to temp file instead of req.formData() to support large files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extreme PDF compression via direct Ghostscript + qpdf pipeline
(PassThroughJPEGImages=false, QFactor 1.5, 72 DPI downsample)
- DWG→DXF converter via libredwg (Docker only)
- PDF unlock in-app via Stirling PDF proxy
- Removed PDF/A tab (unused)
- Paste (Ctrl+V) on all file drop zones
- Mouse drag-drop reordering on thermal layers
- Tabs reorganized into 2 visual rows
- Dockerfile: added ghostscript, qpdf, libredwg