refactor(pdf-compress): replace Ghostscript with qpdf + iLovePDF API

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>
This commit is contained in:
AI Assistant
2026-03-13 17:50:46 +02:00
parent d75fcb1d1c
commit f5deccd8ea
4 changed files with 358 additions and 203 deletions
@@ -1638,7 +1638,7 @@ function formatBytes(bytes: number) {
function PdfReducer() {
const [file, setFile] = useState<File | null>(null);
const [mode, setMode] = useState<"extreme" | "max" | "balanced">("extreme");
const [mode, setMode] = useState<"cloud-extreme" | "cloud-recommended" | "local">("cloud-recommended");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [result, setResult] = useState<{
@@ -1662,10 +1662,21 @@ function PdfReducer() {
try {
const formData = new FormData();
formData.append("fileInput", file);
// All modes use the GS endpoint with a level parameter
formData.append("level", mode === "extreme" ? "extreme" : mode === "max" ? "high" : "balanced");
const res = await fetch("/api/compress-pdf/extreme", {
let endpoint: string;
if (mode === "local") {
// qpdf-only: lossless structural optimization
endpoint = "/api/compress-pdf/extreme";
} else {
// iLovePDF cloud compression
endpoint = "/api/compress-pdf/cloud";
formData.append(
"level",
mode === "cloud-extreme" ? "extreme" : "recommended",
);
}
const res = await fetch(endpoint, {
method: "POST",
body: formData,
});
@@ -1755,22 +1766,26 @@ function PdfReducer() {
<select
value={mode}
onChange={(e) =>
setMode(e.target.value as "extreme" | "max" | "balanced")
setMode(e.target.value as "cloud-extreme" | "cloud-recommended" | "local")
}
className="mt-1 w-full rounded-md border bg-background px-3 py-2 text-sm"
>
<option value="extreme">
Extremă imagini 100 DPI, calitate scăzută
<option value="cloud-recommended">
iLovePDF compresie recomandat
</option>
<option value="cloud-extreme">
iLovePDF compresie extremă
</option>
<option value="local">
💻 Local optimizare structurală (fără pierderi)
</option>
<option value="max">Puternică imagini 150 DPI, calitate medie (recomandat)</option>
<option value="balanced">Echilibrată imagini 200 DPI, calitate bună</option>
</select>
<p className="text-xs text-muted-foreground">
{mode === "extreme"
? "Reduce maxim dimensiunea. Imaginile pot pierde detalii fine."
: mode === "max"
? "Balanță bună între dimensiune și calitate. Recomandat pentru majoritatea fișierelor."
: "Pierdere minimă de calitate. Ideal pentru documente cu grafice detaliate."}
{mode === "cloud-recommended"
? "Compresie cloud cu calitate bună. Reduce semnificativ imaginile păstrând lizibilitatea."
: mode === "cloud-extreme"
? "Compresie maximă cloud. Imagini reduse agresiv — ideal pentru arhivare."
: "Optimizare locală cu qpdf (lossless). Zero pierdere de calitate, reducere 5-30%."}
</p>
</div>