- Wenn man Textinhalte in einer PDF ohne kommerzielle PDF-Tools ändern will, muss man komprimierte Streams entpacken, die fontbezogene Kodierung nachvollziehen und Original- sowie Ersatztext auf dieselbe Weise austauschen
- Viele PDFs speichern den eigentlichen Dokumenttext nicht als Klartext, sondern in komprimierten Streams und in Zeichenfolgen, die wie Hexadezimalwerte aussehen; mit
qpdf --qdf --object-streams=disable lässt sich das in eine Form entpacken, die einer bearbeitbaren Darstellung näherkommt
- Textblöcke bestehen aus PDF-Befehlen wie
BT, Tf, Td, Tj und ET, und Werte wie <002a004800570003003600480057> Tj sind möglicherweise keine einfachen Unicode-Codepoints
- Wenn die PDF eine fontbezogene ToUnicode-Zuordnung enthält, kann man mit
pdffonts prüfen, ob emb und uni auf yes stehen, und über Referenzen wie /ToUnicode 19 0 R die Kodierungstabelle finden
- Wegen der Komplexität des PDF-Standards lässt sich diese Methode nicht auf alle Dateien verallgemeinern, aber bei bestimmten PDFs kann man die ursprüngliche Kodierung ermitteln und den Text per find-and-replace ersetzen
Komprimierte PDF-Textstreams entpacken
- Öffnet man eine PDF in einem Texteditor, ist ein Teil der Struktur lesbar, der eigentliche Dokumenttext aber möglicherweise nicht sichtbar
- Viele PDFs speichern Textdaten in komprimierter Form
- Mit
qpdf lassen sich komprimierte Textstreams zur Prüfung entpacken
- Auf dem Mac gibt es eine Homebrew-Formel
- Auch in Linux-Distributionen sind entsprechende Tools verfügbar
qpdf --qdf --object-streams=disable in.pdf out.pdf
- Nach der Bearbeitung lassen sich die Streams wieder komprimieren
qpdf out-edited.pdf out-recompressed.pdf
- Der Re-Komprimierungsbefehl erzeugte zwar einen Fehler, die resultierende PDF ließ sich in Preview aber lesen
Text innerhalb von PDF-Befehlen finden
- Auch nach dem Entpacken bleibt der Text in PDF-Befehlsblöcken eingebettet
- Ein grundlegender Textblock sieht etwa so aus
BT
/Font_0 12 Tf
288 720 Td
<002a004800570003003600480057> Tj
ET
- Laut PDF Reference führt dieser Block nacheinander folgende Schritte aus
- Ein Textobjekt beginnen
- Die verwendete Schriftart und Größe festlegen
- Die Startposition auf der Seite festlegen
- Die Glyphen der Zeichenfolge an dieser Position zeichnen
- Das Textobjekt beenden
Warum der Text nicht direkt lesbar ist
- Werte wie
<002a004800570003003600480057> sehen zwar wie Hex-Strings aus, sind aber möglicherweise keine einfache Liste von Unicode-Codepoints
- PDFs bieten viele Arten, Kodierungen anzugeben, und können benutzerdefinierte Kodierungen in der Datei enthalten
- Solche Kodierungen lassen sich meist auf Unicode-Codepoints abbilden
- Dieses Vorgehen setzt voraus, dass die bearbeitete PDF eine eingebettete Kodierung enthält
- Andere Fälle werden hier nicht behandelt
Schriftarten und Unicode-Zuordnung prüfen
- Die Textkodierung einer PDF ist mit einer bestimmten Schriftart verknüpft
pdffonts zeigt Informationen zu Schriftarten und Kodierung in einer PDF an
$ pdffonts sample.pdf
name type emb sub uni prob object ID
------------------------------------ ----------------- --- --- --- ---- ---------
CLDQZB+TrebuchetMS,Bold CID TrueType yes yes yes 9 0
YQBAIZ+TrebuchetMS CID TrueType yes yes yes 10 0
- Relevante Felder sind hier
emb: bedeutet, dass die Schriftart in die PDF eingebettet ist
uni: bedeutet, dass eine Unicode-Zuordnung vorhanden ist
- Wenn beide Werte
yes sind, besteht eine gute Chance, die nötige Zuordnung zu finden
- Ein Font-Identifier wie
/Font_0 aus dem Beispiel entspricht je nach Fall dem Schriftnamen aus der pdffonts-Ausgabe
- Im gezeigten Fall verwies
/Font_0 auf CLDQZB+TrebuchetMS,Bold
Der ToUnicode-Zuordnungstabelle folgen
- Nachdem man den vollständigen Namen der im Text verwendeten Schriftart gefunden hat, sucht man in der PDF intern nach diesem Namen
- An einer bestimmten Stelle kann man dann auf eine Referenz wie diese stoßen
/ToUnicode 19 0 R
- Dieser Wert verweist auf die Objekt-ID der Kodierungstabelle
- Anschließend ließ sich die entsprechende Tabelle durch die Suche nach
19 0 obj finden
Text mithilfe der Kodierungstabelle ersetzen
- Der zentrale Teil der Zuordnungstabelle kann etwa so aussehen
38 beginbfrange^M
<0036><0036><0053>^M
<0057><0057><0074>^M
<0044><0044><0061>^M
<0048><0048><0065>^M
<0050><0050><006D>^M
...
- Dieses Beispiel ordnet Bereiche benutzerdefinierter Kodierungspunkte Unicode-Punkten zu
- Hier besteht jeder Bereich nur aus einem einzelnen Zeichen
0036 wird auf den Unicode-Punkt 0053 abgebildet
- Falls das Tabellenformat anders aussieht, hilft das Tutorial zu ToUnicode-Mapping-Dateien
- Für die automatische Umwandlung wurde die Tabelle in Python in ein Dictionary überführt und eine einfache Kodier-/Dekodierfunktion geschrieben
- Der abschließende Ablauf für den Austausch ist folgender
- Den benutzerdefinierten Kodierungswert des zu ersetzenden Originaltexts ermitteln
- Den Ersatztext auf dieselbe Weise benutzerdefiniert kodieren
- In der PDF per find-and-replace den alten Wert durch den neuen ersetzen
1 Kommentare
Hacker-News-Kommentare
Die PDF-Spezifikation ist ziemlich rau. Mein derzeit liebstes Trivia-Wissen ist, dass sie beim Rendern überlappender Elemente alle Layer-Blend-Modi von Photoshop unterstützt.
Mein zweitliebstes ist, dass nachträglich angehängte Inhalte frühere Inhalte ändern können, sodass man forensisch alle unterschiedlichen Versionen untersuchen muss, die in einer Datei repräsentiert sind.
Es ist auch ein Beispiel für die Sinnlosigkeit von DRM. Die Spezifikation enthält passwortbasierte Verschlüsselung, kann getrennte „Owner“- und „User“-Passwörter haben und hat auch Bitfelder wie „Drucken verboten“ und „Textkopieren verboten“. Aber um ein Dokument zu lesen, muss es letztlich entschlüsselt werden; wenn man also ein verschlüsseltes PDF mit dem „User“-Passwort in einem nicht konformen Tool öffnet und als unverschlüsselte Version speichert, erhält man ein bearbeitbares Äquivalent.
[1] Abschnitt „More than just transparency“ von https://blog.adobe.com/en/publish/2022/01/31/20-years-of-tra...
[2] https://blog.didierstevens.com/2008/05/07/solving-a-little-p...
[3] Seite 61 von https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandard...
[4] Zum Beispiel ein Skript, das die pypdf-Bibliothek verwendet
Eines der damals bekannten Cracking-Tutorials entfernte das DRM des offiziellen Readers, indem es die Funktion patchte, die Berechtigungen prüft.
Dieses Thema kommt regelmäßig wieder auf; die meisten halten PDF für ein schwer zu knackendes Binärformat, tatsächlich ist es das aber nicht.
PDF ist ein Objektgraph aus unterschiedlichen Typen, und die Typen selbst sind in der offiziellen Spezifikation gut beschrieben. Ich habe die schmerzhafte Neigung, sie zum Spaß zu lesen.
Was ich immer empfehle, ist wie der Autor das PDF in eine Version ohne komprimierte Daten umzuwandeln. Mein bevorzugtes Tool ist mutool; mit
mutool clean -d in.pdf out.pdfgeht das. Wenn man danach darin herumstöbert, ist man überrascht, wie viel man nachvollziehen kann.Im Artikel fehlt der Schritt, sich das Page-Objekt anzusehen und die Ressourcen zu prüfen. Dort werden die im Content-Stream verwendeten Schriftnamen mit den tatsächlichen Unterobjekten verknüpft.
Ein weiterer wichtiger fehlender Punkt ist, dass die meisten Schriftarten als Subsets in PDFs eingebettet sind. Nur die benötigten Glyphen bleiben in der Schrift erhalten. Re-Encoding scheint normalerweise hier zu passieren, und ToUnicode wird beibehalten, um Textkopieren oder PDF-Suche zu ermöglichen. Für Nutzer ist das ein nettes Extra, und meiner Erfahrung nach ist es meistens vorhanden und korrekt.
Schon die Designphilosophie, ein Nur-Lese-Format schaffen zu wollen, war von Anfang an falsch. Es ist doch offensichtlich, welche Funktion die Leute als Erstes verlangen werden.
Ich frage mich, ob es weitere Erkenntnisse dazu gibt, wie man das ohne Cloud-Anbieter nativ gut hinbekommt. Besonders bei Tabellen.
So geht’s:
Mit
pdf2ps a.pdfin PostScripta.psumwandelnMit
vim a.psdas PostScript direkt bearbeitenMit
ps2pdf a.pswieder in PDF umwandelnKomplexe PDFs mit eingebettetem JavaScript oder Animationen funktionieren nach diesem Roundtrip manchmal nicht mehr richtig. Bei gewöhnlichen Dokumenten klappt es aber ziemlich gut. Wasserzeichen entfernen sowie Wörter und Zahlen ändern geht einfach; Abstände anzupassen ist schwieriger. Natürlich muss man sich mit PostScript einigermaßen auskennen
Selbst wenn die Ausgabe stimmte, mussten Seiten neu sortiert, Barcodes für die Verarbeitung durch Kuvertier-/Postmaschinen und für die Postsortierung angebracht sowie Berichte erzeugt werden. Üblicherweise haben wir mit Perl und anderen Tools Text von den Seiten abgegriffen, um Adressen zu erhalten; mit PS war das im Allgemeinen viel einfacher als mit PDF, aber es gab auch sehr fragile Fälle, etwa wenn „sichere“ PDFs keine korrekten Glyph-Mappings hatten
Im schlimmsten Fall schickte der Kunde einzelne Dokument-PDFs, und wenn man diese zusammenführte, explodierte in der Ausgabe die Zahl der Subset-Fonts, füllte den Druckerspeicher und brachte ihn zum Absturz. Als ich den Job aufgab, gab es meines Wissens immer noch kein nützliches Tool, das Subset-Fonts, die aus derselben Originalschrift stammten, vereinheitlichen und zusammenführen konnte. Das müsste eigentlich möglich sein und wäre sehr nützlich gewesen, aber mir fehlten Zeit und Wissen, um es mir anzusehen
Ehrlich gesagt wirkt es so, als würden von der Komplexität von PDF nur Malware-Autoren profitieren
Ein wichtiger Punkt scheint zu fehlen. Am Ende eines PDFs gibt es eine Tabelle, die Byte-Offsets der verschiedenen Objekte in der Datei speichert, die sogenannte Cross-Reference-Tabelle
Wenn man die Datei bearbeitet, ändern sich diese Offsets normalerweise, und die Datei geht kaputt. Im Artikel ging es offenbar nur darum, eine Zahl durch eine andere zu ersetzen, sodass sich die Position vielleicht nicht geändert hat
Im Allgemeinen muss man aber bei Einfügungen, Löschungen oder Änderungen in der Mitte der Datei die xref-Tabelle neu berechnen; daher ist es viel einfacher, eine Bibliothek zu verwenden, statt direkt im Texteditor zu arbeiten
qpdf rekonstruiert die ursprüngliche PDF-Struktur und scheint zu versuchen, auch die Objektnummern beizubehalten, aber die Offsets werden komplett neu berechnet
Falls die Autoren irgendwann darüber sprechen, würde ich gern lesen, warum das Format so geworden ist. Es wirkt, als sei es anfangs als vollständig textbasiertes Format gedacht gewesen und man habe später entschieden, dass Binärdaten effizienter sind. Allerdings passt die Anforderung, dass xref-Tabelleneinträge eine feste Länge haben müssen, etwas merkwürdig zu dieser Annahme
mutool cleanreparieren zu lassen. Da sie vollständig aus dem Inhalt abgeleitet werden kann, ist das sicherSolange man die Länge beibehält, kann man Binärdateien nach Belieben ändern
Eine Software, die ich früher verwendete, authentifizierte sich beim Server, aber die gesamte Verarbeitung fand auf dem Client statt. Der Client führte direkt SQL auf dem Server aus, und der Server prüfte nur, ob dieser Client die Zahl der gekauften Lizenzen überschritt
Man konnte sie durch einen Disassembler jagen, die Prüfungsstelle finden, sie direkt durch ein JMP ersetzen und den verbleibenden Platz mit NOPs auffüllen
Bei der Verwendung des
--qdf-Modus von qpdf scheint ein wichtiger Schritt zu fehlen. Nach dem Bearbeiten muss man die Datei durch das Dienstprogramm fix-pdf laufen lassen, damit alle Objekt-Offsets neu berechnet und die Cross-Reference-Tabelle am Dateiende neu aufgebaut werden. Ausnahme: Man hat nur an Ort und Stelle geändert, ohne Bytes hinzuzufügen oder zu entfernenDie Top 3 der interessanten PDF-Fakten sind diese
[0] Beispiel: https://lab6.com/2
Wenn du deine PDF-Reise fortsetzen und mehr lernen möchtest, kannst du diese kürzlich zusammengestellte Dateisezierungs-Dokumentation lesen:
https://pdfsyntax.dev/introduction_pdf_syntax.html
Was Leute bei PDFs oft übersehen: In gewisser Hinsicht sind sie eher ein Bildformat als ein Word-Dokument. Bei der Dokumentbearbeitung entsprechen Word-Dokumente, PDFs und Bilder ungefähr DAW-Projekten, MIDI und MP3 in der Musik, oder Java-Quellcode, JVM-Bytecode und reinem x86-Maschinencode in der Software
Der Hauptzweck einer PDF-Datei ist es, vollkommen eindeutig festzulegen, was angezeigt oder gedruckt werden soll – mit deutlich weniger Bytes als ein echtes Bild. Sie nutzt aus, dass der Dokumentautor die Muster in der Dokumentstruktur kennt, und komprimiert bei richtiger Darstellung viel besser als ein echter Bildkompressionsalgorithmus. Wenn man zum Beispiel Zugriff auf die eigentliche Schriftart hat, ist es besser zu sagen: „Setze diese Zeichen an diese Koordinaten mit diesem Abstand“, statt jedes Auftreten eines Buchstabens als Teil eines Bildes abzulegen und darauf zu hoffen, dass der Kompressionsalgorithmus die Wiederholungen schon reduziert. Welches Zeichen zu welchem Wort gehört oder sogar welcher Unicode-Codepoint welchem Font-Glyphen entspricht, ist im Grunde nicht wichtig, wenn das einzige Ziel darin besteht, ein Dokumentbild effizient zu übertragen
Bei einem editierbaren Dokument ist neben der Darstellung die semantische Struktur des Inhalts viel wichtiger. Es spielt eine Rolle, ob ein bestimmter Abstand im Text mehrere Leerzeichen sind, die nächste Spalte einer Tabelle oder eine merkwürdige Seitenanordnung wegen eines Bildes. Es ist auch wichtig, ob der Text unten auf jeder Seite vom Autor mehrfach eingefügt wurde oder ob er einmal eingegeben und als Fußzeile eingerichtet wurde. Wenn ein neuer Absatz hinzugefügt wird und sich dadurch das Seitenlayout ändern muss, ist es wichtig zu wissen, dass der letzte Absatz dieser Seite eine Fußnote ist und nicht auf die nächste Seite rutschen darf. Wenn eine Abschnittsüberschrift auf eine andere Seite wandert, muss sich das Inhaltsverzeichnis automatisch aktualisieren; es darf nicht bloß manuell eingegebener Text des Autors sein
Aus Sicht eines Druckers oder Bildschirms ist all das egal. Er druckt oder zeigt einfach an, was ihm angewiesen wurde. In PDF müssen Fußnoten, Abschnittsüberschriften, Fußzeilen und Inhaltsverzeichnisse nichts Besonderes sein; sie können auch einfach bedeutungslos formatierter Text sein. Deshalb kann es nie zu 100 % korrekt werden, wenn man PDF für Zwecke verwendet, die nicht Anzeige oder Druck sind. Natürlich gibt es Bemühungen, das auszugleichen, und PDF-Erzeugungsprogramme können beliebige Metadaten einfügen, aber verpflichtend ist das nicht
Das ist vielleicht nicht das mentale Modell, das die PDF-Autoren im Sinn hatten, aber es ist eine nützliche Perspektive, um zu verstehen, warum PDF so aufgebaut ist
Auf einem PostScript-Drucker sah die Ausgabe exakt gleich aus, und mit Type-1-Schriften sogar noch besser
Wer das tun will, verfehlt den Kern von PDF. PDF ist ein Seitenbeschreibungsformat und beschreibt daher nicht die Dokumentstruktur, sondern nur die sichtbaren Spuren auf der Seite
Man sollte nicht versuchen, das PDF zu bearbeiten, sondern das Originaldokument, aus dem das PDF erzeugt wurde
Irgendwie ist es „unprofessionell“ geworden, einfach das Dokument zu schicken, das zum Bearbeiten gedacht ist, und so landen wir am Ende in genau dieser Situation
Es ist optional, wird aber für Barrierefreiheit empfohlen. Ein Beispiel ist PDF/UA. Siehe dazu Kapitel 14.7–14.8 in [1]
[1] https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandard...
Den PDF-Text kann man nicht direkt ändern, denn der Setzer würde diese Änderung nicht sehen. Die Markup-Werkzeuge von Acrobat sind nicht schrecklich, aber sie reichen nicht an die Zeiten von Papier und rotem Bleistift heran. Es sei denn, man benutzt Acrobats „pencil“-Werkzeug. Ich wünschte, dieser Teil würde verbessert
Vor 20 Jahren habe ich bei einer Zeitung als Druckplatten-Verantwortlicher gearbeitet. Wir hatten zwei Kodak-„Drucker“ für Platten im Wert von je einer Million Dollar, wobei „Drucker“ nicht wirklich stimmt. Wenn ich mich richtig erinnere, wurde die Emulsion auf der Platte mit UV-Licht belichtet und dann in einem chemischen Bad herausgelöst
Die Kodak-Geräte fielen häufig aus, und mein Chef öffnete dann direkt PostScript- oder EPS-Dateien, um Header oder fehlerhafte Stellen zu korrigieren, die die Layoutsoftware geschickt hatte. Danach lief es wieder. Nebenbei: Unsere riesige deutsche Offset-Rotationsmaschine lief unter Linux
Mein Chef hieß, glaube ich, Bill. Ein guter Mensch, der mich mit 17 zu einem Sigur-Rós-Konzert mitgenommen hat. Jetzt sind es gleich zwei Geschichten, die nichts mit PDF zu tun haben
Guter Artikel. In den letzten etwa fünf Jahren habe ich beim Bau von DocSpring [1] viel in der PDF-Spezifikation gelesen, und trotzdem fühlt es sich an, als hätte ich nur an der Oberfläche gekratzt. qpdf ist ein hervorragendes Tool, und ein weiteres meiner Lieblingswerkzeuge ist RUPS [2], mit dem man tief in die PDF-Struktur eintauchen kann
[1] https://docspring.com
[2] https://github.com/itext/i7j-rups