Seit Wochen baut ein Entwickler an Convertify, einem kostenlosen Bildkonverter auf Basis von Rust, Axum und libvips. Diese Woche stand ein spannendes Feature auf der Agenda: die Umwandlung von PDF-Dateien in JPG- oder PNG-Bilder. Was als scheinbar einfache Erweiterung begann, entwickelte sich zu einer kleinen Entdeckungsreise in die Tiefen von libvips und den verschiedenen PDF-Rendering-Engines. Die ersten Experimente zeigten schnell, dass PDFs nicht einfach wie herkömmliche Bilder behandelt werden können.
Warum eine naive Lösung nicht immer funktioniert
Die bestehende Bildverarbeitungspipeline des Entwicklers war simpel aufgebaut: Ein Bild wird über die Funktion VipsImage::new_from_file(path) geladen, bearbeitet und schließlich als Datei gespeichert. Diese Methode funktioniert einwandfrei bei gängigen Bildformaten wie PNG oder JPEG. Doch bei PDFs zeigte sich schnell, dass die Dinge anders gelagert sind.
Der erste Versuch, PDFs direkt zu laden, schien zunächst erfolgreich. Mit dem Befehl VipsImage::new_from_file(&format!("{}[dpi={}]", file_path, dpi))? konnte eine PDF-Datei geladen werden. Libvips akzeptiert dabei den Pfad zur PDF-Datei mit einem zusätzlichen Parameter für die DPI-Auflösung und gibt ein VipsImage-Objekt zurück. Bei einseitigen PDFs funktioniert diese Methode tatsächlich problemlos. Doch was passiert bei mehrseitigen Dokumenten?
Die Herausforderung: Multi-Page-PDFs und ihre Besonderheiten
Mehrseitige PDFs stellen eine besondere Herausforderung dar. Jede Seite muss separat gerendert und entweder als einzelne Dateien oder in einem ZIP-Archiv zusammengefasst werden. Der Entwickler implementierte eine Funktion load_pdf, die zunächst die Anzahl der Seiten in der PDF-Datei ermittelt. Dies geschieht durch das Laden der Datei mit VipsImage::new_from_file(&file_path)? und dem anschließenden Abruf der Seitenanzahl über probe.get_n_pages().
Für jede einzelne Seite wird dann ein neues VipsImage erstellt, wobei der Parameter page angibt, welche Seite gerendert werden soll. Wichtig ist dabei, dass die Seitennummerierung bei null beginnt. Die gerenderten Bilder werden zunächst als temporäre Dateien gespeichert und anschließend in einem ZIP-Archiv zusammengefasst. Dabei nutzt der Entwickler die ZipWriter-Bibliothek, um die Dateien zu bündeln und das Archiv zu erstellen.
Doch es gibt ein Detail, das leicht übersehen werden kann: Die temporären Dateien müssen nach dem Erstellen des ZIP-Archivs wieder gelöscht werden, um Speicherplatz freizugeben. Der Entwickler hatte zunächst einen Fehler in der Bereinigungslogik, bei dem die Anzahl der gelöschten Dateien falsch gezählt wurde. Dieser Bug führte dazu, dass das System fälschlicherweise behauptete, Hunderte von Dateien gelöscht zu haben, obwohl das Verzeichnis leer war.
Libvips und die Rendering-Engines: Poppler vs. PDFium
Ein zentrales Thema bei der Arbeit mit PDFs in libvips ist die Rendering-Engine. Libvips selbst enthält keinen eigenen PDF-Renderer, sondern delegiert die Arbeit an externe Bibliotheken wie Poppler oder PDFium. Je nach Systemkonfiguration wird eine dieser Bibliotheken genutzt. Auf den meisten Linux-Distributionen wird Poppler über das Paket libvips-dev eingebunden.
Doch hier beginnt das Problem: Poppler und PDFium erzeugen unterschiedliche Rendering-Ergebnisse, insbesondere bei komplexen Layouts. Der Entwickler stellte fest, dass einige PDFs in Adobe Acrobat korrekt dargestellt wurden, in seinem Converter jedoch leichte Abweichungen in der Textpositionierung aufwiesen. Die Ursache lag in den unterschiedlichen Rendering-Backends: Poppler nutzt den Splash-Rasterizer, während PDFium auf Skia setzt.
Der Unterschied wird besonders bei textlastigen PDFs sichtbar. Skia, mit seinem analytischen Abdeckungsrasterizer, erzeugt schärfere Kanten und eine bessere Textdarstellung im Vergleich zu Splash. Für den Einsatz in der Produktion empfiehlt der Entwickler daher, die genutzte Rendering-Engine zu prüfen. Mit dem Befehl vips --version kann die genutzte Version von libvips abgefragt werden. Um zu sehen, welche PDF-Lader verfügbar sind, hilft der Befehl vips -l | grep pdf. Falls PDFium verfügbar ist, kann es explizit genutzt werden, um eine bessere Rendering-Qualität zu erreichen.
DPI-Konfiguration: Balance zwischen Qualität und Performance
Ein weiterer wichtiger Aspekt ist die DPI-Einstellung. Der Entwickler hat eine serverseitige DPI-Begrenzung eingeführt, um die Qualität und Performance des Converters zu optimieren. Nutzer können zwar jede beliebige DPI-Zahl anfordern, doch der Server beschränkt den Wert auf einen Bereich zwischen 72 und 300 DPI. Der Grund dafür liegt in den Ressourcen, die bei höheren Auflösungen benötigt werden.
Bei einer 600-DPI-A4-Seite entsteht ein Rohpuffer von etwa 139 MB im RGBA-Format. Obwohl libvips diese Daten in Kacheln streamt und somit kein Speicherüberlauf droht, führt eine so hohe Auflösung zu einer erheblichen Verlangsamung des Prozesses und erzeugt extrem große Ausgabedateien. Für druckfähige Ergebnisse ist eine Auflösung von 300 DPI ideal. Bei dieser Einstellung entsteht für ein A4-Blatt eine Datei von etwa 2480×3508 Pixeln, die als JPG-Datei rund 900 KB groß ist.
Was kommt als Nächstes?
Die PDF-Frontend-Funktionalität ist nun live. Nutzer können zwischen verschiedenen Auflösungen (72, 150 oder 300 DPI) wählen und mehrseitige PDFs als ZIP-Datei herunterladen. Die Funktionen können unter den Adressen convertifyapp.net/pdf-to-png und convertifyapp.net/pdf-to-jpg ausprobiert werden.
Der Entwickler steht noch vor einer wichtigen Entscheidung: Soll die Nutzung von PDFium als explizite Option für Nutzer angeboten werden, um eine bessere Rendering-Qualität zu ermöglichen? Oder reicht es aus, das Verhalten von Poppler zu dokumentieren und auf die Standardkonfiguration zu setzen? Für die meisten PDFs, die Nutzer hochladen, funktioniert die aktuelle Lösung mit Poppler bereits zufriedenstellend.
KI-Zusammenfassung
Learn how Rust and libvips simplify PDF to image conversion, revealing rendering backends, DPI trade-offs, and cleanup pitfalls in a real-world project.