PDFlibPas wandelt PDF-Inhalte ohne Office-Automatisierung in zwei editierbare Formate um. ExportPageMarkdown und ExportDocumentMarkdown liefern semantisches Markdown mit erkannten Überschriften, geordneten und ungeordneten Listen und Pipe-Tabellen, während SaveDOCXToFile und SaveDOCXToStream ein WordprocessingML-Paket schreiben, das Absätze, Überschriften, native Listennummerierung, erkannte Tabellen, Schriftformatierung, Seitenumbrüche und positionierte PNG-Bilder enthält
Beide laufen vollständig in Pascal, auf einem Server, ohne installiertes Word und ohne COM. Genau diese Vorgabe ist der Grund, warum das Feature in einer PDF-Bibliothek steckt und nicht in einem Desktop-Tool
Warum ist "PDF zu Word" wirklich schwierig?
Weil eine PDF-Seite keine Absätze enthält. Sie enthält textzeigende Operatoren, die Glyphenläufe an Koordinaten platzieren, in welcher Reihenfolge auch immer der Erzeuger sie ausgegeben hat, ohne jede Verpflichtung anzugeben, dass zwei Läufe zum selben Satz gehören, geschweige denn zum selben Listenelement. Das Format wurde entworfen, um eine gedruckte Seite exakt zu beschreiben, und es gelingt ihm dies, indem es genau die Struktur verwirft, die die Seite hervorgebracht hat
Also muss jeder Konverter rekonstruieren, was der Generator weggeworfen hat. Zeilengruppierung ergibt sich aus vertikalem Abstand und Grundlinienausrichtung. Absatzgrenzen ergeben sich aus Abstandsänderungen und Einrückung. Eine Überschrift ist eine Zeile, deren Schrift größer oder fetter als der Fließtext ist und die sich vom Folgenden abhebt. Eine Liste ist eine Folge von Absätzen, die mit einem Aufzählungszeichen oder einem Nummernmuster beginnen. Eine Tabelle ist ein Raster aus Textblöcken, deren Kanten sich über Zeilen und Spalten hinweg ausrichten. Jeder dieser Punkte ist eine Vermutung, und Vermutung bedeutet ein gutes Ergebnis bei Dokumenten, die gewöhnlichen typografischen Konventionen folgen, und ein mittelmäßiges bei Dokumenten, die das nicht tun
Getaggte PDFs sind die Ausnahme, und eine bedeutende. Trägt das Dokument einen Strukturbaum, werden die Rollen für Absatz, Überschrift, Liste und Tabelle erfasst statt geraten, weshalb sich die in Barrierefreiheitsstruktur getaggter PDFs beschriebene Arbeit auch bei der Konvertierungsqualität auszahlt. Wer den Erzeuger selbst kontrolliert, tut mit dem Taggen der eigenen Ausgabe das mit dem größten Hebel für jeden, der sie später konvertieren muss
Markdown-Export, Seite für Seite
Der Markdown-Pfad ist der richtige, wenn das Ziel eine Text-Pipeline ist: eine Dokumentations-Site, ein Suchindex, ein Retrieval-Korpus für einen Assistenten. Die Optionen sind eine Bitmaske: PDF_MARKDOWN_INCLUDE_PAGE_MARKERS, PDF_MARKDOWN_DETECT_HEADINGS, PDF_MARKDOWN_PRESERVE_STYLES, wobei PDF_MARKDOWN_DEFAULT alle drei kombiniert
var
Pdf: TPDFlib;
Md: WideString;
begin
Pdf := TPDFlib.Create;
try
Pdf.LoadFromFile('handbook.pdf', '');
// Eine Seite, als String
Md := Pdf.ExportPageMarkdown(1, PDF_MARKDOWN_DEFAULT);
// Ein Seitenbereich, als UTF-8 ohne BOM auf die Platte gestreamt
Pdf.SaveMarkdownToFile('1-40',
PDF_MARKDOWN_DETECT_HEADINGS or PDF_MARKDOWN_PRESERVE_STYLES,
'handbook.md');
finally
Pdf.Free;
end;
end;
Seitenmarker verdienen sich ihren Platz bei Retrieval-Arbeit. Ein Textabschnitt, der die Seite trägt, von der er stammt, lässt sich präzise zitieren, und ein Leser, der dem Zitat folgt, landet dort, wo die Aussage tatsächlich steht. Schalten Sie sie aus, wenn das Markdown für menschliches Lesen bestimmt ist, wo Seitengrenzen aus dem Quelllayout nur Rauschen sind
Die Streaming-Einstiegspunkte zählen bei großen Dokumenten. SaveMarkdownToStream und SaveMarkdownToFile schreiben UTF-8 Seite für Seite und puffern nicht die komplette Ausgabe, sodass ein 900-seitiges Handbuch nicht zuerst zu einem 900-seitigen String im Speicher wird. Auch das Fehlen einer Byte-Order-Mark ist Absicht: Ein BOM in einer Markdown-Datei bringt eine überraschende Zahl statischer Site-Generatoren und Diff-Tools durcheinander
DOCX ohne Office auf der Maschine
Der DOCX-Writer erzeugt das Paket selbst: ZIP-Einträge, geschrieben als rohes Deflate mit CRC-Prüfungen, die WordprocessingML-Teile und die Beziehungen, die sie verbinden. Nichts ruft Word auf, was bedeutet, dass die Konvertierung auf einem Headless-Server läuft, in einem Service-Account, in einem Container, überall dort, wo Office-Automatisierung entweder unlizenziert, instabil oder verboten ist
var
Pdf: TPDFlib;
Target: TFileStream;
begin
Pdf := TPDFlib.Create;
Target := TFileStream.Create('handbook.docx', fmCreate);
try
Pdf.LoadFromFile('handbook.pdf', '');
Pdf.SaveDOCXToStream('1-40',
PDF_DOCX_INCLUDE_IMAGES or PDF_DOCX_DETECT_HEADINGS or
PDF_DOCX_PRESERVE_STYLES or PDF_DOCX_PRESERVE_PAGE_BREAKS,
Target);
finally
Target.Free;
Pdf.Free;
end;
end;
Bilddaten werden geschrieben, während jede Seite verarbeitet wird, statt gesammelt und am Ende angehängt zu werden, sodass der Spitzenverbrauch einer Seite entspricht statt dem gesamten Dokument. Die explizite Seitenreihenfolge bleibt erhalten, und die zuvor ausgewählte PDF-Seite wird danach wiederhergestellt, was zählt, wenn der Export nur ein Schritt innerhalb eines längeren Jobs ist, der aus anderen Gründen eine Seite ausgewählt hatte
Was bringt deterministisches Verpacken?
Byte-für-Byte-Reproduzierbarkeit. Zwei Konvertierungen derselben Eingabe mit denselben Optionen erzeugen dasselbe Paket, was bedeutet, dass man die Ausgabe hashen kann, um Änderungen zu erkennen, zwei Builds eines generierten Dokuments diffen kann und aggressiv cachen kann, ohne befürchten zu müssen, dass eine identische Eingabe ein unterschiedliches Artefakt erzeugt hat
Office-Automatisierung kann das nicht versprechen. Sie bettet Zeitstempel, Revisionskennungen und maschinenabhängige Metadaten ein, sodass dasselbe Dokument zweimal konvertiert auf Weisen abweicht, die Hashing zunichtemachen. Dieselbe Überlegung treibt die in deterministischen PDF-IDs für reproduzierbare Builds besprochenen Kennungen an: Ist die Ausgabe reproduzierbar, wird Verifikation zu einem Vergleich statt zu einer Inspektion
Wo die Ausgabe gut ist, und wo nicht
Seien Sie Ihren Nutzern gegenüber ehrlich darüber, denn die Konvertierungsqualität schwankt stärker mit der Eingabe als mit dem Konverter. Getaggte PDFs und sauber erzeugte Geschäftsdokumente, Rechnungen, Berichte, Verträge, konvertieren gut: Überschriften landen als Überschriften, Tabellen überleben, Listen werden in Word korrekt neu nummeriert. Zweispaltige akademische Layouts konvertieren akzeptabel, wenn die Spaltengeometrie regelmäßig ist. Tabellen, die über Seitenumbrüche hinausreichen, werden durch Vermutung wieder zusammengesetzt und manchmal geteilt. Stark gestaltetes Marketingmaterial, bei dem Text um der visuellen Wirkung willen platziert wird statt in Lesereihenfolge, konvertiert schlecht, und keine noch so gute Vermutung behebt das
Gescannte Dokumente sind ein eigener Fall. Eine Seite, die ein einziges großes Bild ist, enthält keine Textobjekte, also gibt es nichts zu exportieren, bis eine Textebene existiert; der OCR-Pfad, der eine solche erzeugt, ist eine Voraussetzung, keine Option. Bevor Sie einen großen Stapel verarbeiten, prüfen Sie ein Dutzend repräsentativer Dateien und schauen Sie sich die Ausgabe an, und erwägen Sie, zunächst Seitenelemente aufzulisten, wie in Textsuche und Seitenelement-Aufzählung beschrieben, um zu sehen, was die Seiten tatsächlich enthalten
Für Assistenten- und Retrieval-Pipelines ist der Markdown-Pfad meist das bessere Ziel: Überschriften werden zu Chunk-Grenzen, Tabellen bleiben als Pipe-Tabellen lesbar, und Seitenmarker geben jedem Chunk eine zitierbare Position. Für menschliches Bearbeiten ist DOCX die Antwort, denn was der Nutzer will, ist nicht der Text, sondern die Möglichkeit, ihn zu ändern
PDFlibPas ist eine Delphi-, C++Builder- und Lazarus-PDF-Bibliothek mit passenden DLL- und ActiveX-Schnittstellen, sodass dieselben Export-Aufrufe auch aus C#, C++ oder Skript-Hosts verfügbar sind. Die vollständige Dokumentation und ein Testbuild stehen auf der PDFlibPas Delphi PDF-Bibliothek-Seite