Die losLab PDF Library kann fehlende Schriftart-Programme eines bereits geladenen PDFs mit einem einzigen Aufruf einbetten: EmbedMissingFonts durchläuft jedes Schriftart-Verzeichnis im Dokument, lokalisiert die passende installierte Systemschriftart anhand ihres BaseFont-Namens und schreibt das Schriftart-Programm zurück in die Datei. Für Teams, die Dokumente von Drittanbietern reparieren müssen, die die PDF/A-Validierung aufgrund fehlender Schrifteinbettung nicht bestehen, ist dies die Lösung, mit der der Preflight-Fehler 00030 behoben wird
Das Szenario ist deprimierend häufig: Eine Archivierungs-Pipeline empfängt PDFs von Lieferanten, Kunden oder einem Scandienstleister; die Dokumente werden an jedem Arbeitsplatz im Gebäude einwandfrei gerendert; und dann lehnt der PDF/A-Validator den gesamten Stapel mit derselben Fehlermeldung ab, die einmal pro Datei wiederholt wird: Mindestens eine Schriftart ist nicht eingebettet. Niemand in der Kette wird die Dateien neu generieren, also muss die Pipeline sie reparieren. Dieser Artikel befasst sich mit diesem Reparaturweg. Er ist das Gegenstück zum Preflight-Artikel, der die Erkennung von PDF/A- und PDF/UA-Verletzungen behandelt: jener Text sagt Ihnen, welche Dokumente fehlerhaft sind, dieser repariert die häufigste Ursache dafür
Warum erfordert PDF/A, dass jede Schriftart eingebettet ist?
ISO 19005-1 §6.3.4 verlangt, dass jede von einem konformen Dokument verwendete Schriftart ihr Schriftart-Programm in der Datei mitführt. Das gesamte Versprechen von PDF/A ist die Reproduzierbarkeit: Das Dokument muss auf einer Maschine in fünfzig Jahren, die keine der Schriftarten des ursprünglichen Erstellungssystems besitzt, exakt identisch gerendert werden. Eine nicht eingebettete Schriftart ist eine Anweisung, Arial irgendwo auf dem Anzeigesystem zu suchen, und der Standard vertritt den Standpunkt, dass „irgendwo auf dem Anzeigesystem“ keine Archivgarantie darstellt. Welche Glyphen, Metriken und Abdeckungen die Ersatzschriftart auch immer hat, das ist es, was der Leser erhält, und es entspricht möglicherweise nicht dem, was der Autor sah
Der historische Verursacher ist die Konvention der „Standard 14“. PDF 1.0 versprach, dass jeder Viewer Helvetica, Times, Courier, Symbol und ZapfDingbats mitliefert. Daher lernten Generatoren, diese Schriftarten namentlich zu referenzieren und nichts einzubetten – eine Praxis, die von dreißig Jahren Werkzeugentwicklung immer noch genau so gepflegt wird. Die losLab PDF Library nimmt diese Anforderung so ernst, dass im PDF/A-Erstellungsmodus AddStandardFont bewusst wirkungslos ist: Die Bibliothek liefert die Standard-14-Schriftartprogramme nicht mit, kann nichts einbetten, was sie nicht hat, und weigert sich, eine nicht eingebettete Referenz in ein Dokument zu schreiben, das Konformität beansprucht. Sie gibt 0 zurück, ohne eine Schriftart auszuwählen, sodass ein PDF/A-Dokument stattdessen AddTrueTypeFont mit Einbettung verwenden muss, und jede Embed=0-Anforderung wird bei aktivem PDF/A-Modus stillschweigend zu Embed=1 aufgewertet. Das ist die Schreiberseite. Das schwierigere Problem liegt auf der Leserseite: ein Dokument, das jemand anderes bereits geschrieben hat, voller Schriftart-Verzeichnisse, die Sie nicht erstellt haben
Wie repariert EmbedMissingFonts ein geladenes Dokument?
Die losLab PDF Library repariert Schriftarten direkt an Ort und Stelle, anstatt sie neu aufzubauen. Wenn ein PDF-Generator eine nicht eingebettete TrueType-Schriftart schreibt, ist das von ihm erzeugte FontDescriptor-Verzeichnis bereits vollständig: FontName, FontBBox, Flags, Ascent, Descent, StemV sind alle vorhanden. Das Einzige, was sie von einer eingebetteten Schriftart unterscheidet, ist das Fehlen eines Eintrags, nämlich der /FontFile2-Stream-Referenz, die das tatsächliche Schriftart-Programm enthält. Daher rührt EmbedMissingFonts weder das Schriftart-Verzeichnis, die Kodierung, das Breiten-Array noch einen Inhaltsstrom an, der die Schriftart über den Ressourcennamen referenziert. Es liest das passende Schriftart-Programm aus dem System, komprimiert es in ein neues Stream-Objekt und hängt eine einzelne /FontFile2-Referenz (oder /FontFile3 für CIDFontType0-Schriftarten) an den bereits vorhandenen FontDescriptor an. Alles, worauf die Seiten des Dokuments verweisen, bleibt genau dort, wo es war, was die Ausführung dieser Operation bei Dateien, die Sie nicht kontrollieren, sicher macht
Die Abdeckung umfasst beide Schriftartarchitekturen, die Ihnen in der Praxis begegnen: einfache TrueType-Schriftarten und zusammengesetzte Type0/CID-Schriftarten, wie sie für CJK-Texte und moderne Unicode-Ausgaben erzeugt werden. Der Durchlauf zählt bewusst jedes Font-Verzeichnis im Objektbaum des Dokuments auf, anstatt sich auf einen seitenweisen Ressourcendurchlauf zu verlassen, sodass auch Schriftarten erfasst werden, die aus Anmerkungen referenziert oder seitenübergreifend gemeinsam genutzt werden. Die API ist ein einziger Aufruf des geladenen Dokuments:
var
PDF: TPDFlib;
Repaired: Integer;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('supplier-invoice.pdf', '') <> 1 then
raise Exception.Create('Could not load PDF');
// Durchläuft jedes Font-Verzeichnis; gibt zurück, wie viele Schriftarten
// ein Schriftart-Programm erhalten haben. Schriftarten, deren Programm
// nicht auf dem System gefunden werden kann, werden übersprungen, führen aber nicht zum Fehler.
Repaired := PDF.EmbedMissingFonts;
Writeln(Format('%d font program(s) embedded', [Repaired]));
PDF.SaveToFile('supplier-invoice-repaired.pdf');
finally
PDF.Free;
end;
end;
Ein Detail, das man kennen sollte, weil es erklärt, warum der Namensabgleich besser funktioniert als ein naiver String-Vergleich: Die Bibliothek normalisiert BaseFont-Namen, bevor sie nach ihnen sucht. Subset-Präfixe (das ABCDEF+-Muster aus sechs Großbuchstaben und einem Pluszeichen) werden entfernt, PostScript-Suffixe wie ArialMT werden zu Arial aufgelöst, und TrueType-Collection-Dateien werden erkannt und entpackt, sodass eine Schriftart, die sich in einer .ttc-Datei befindet, dennoch korrekt eingebettet wird
Überprüfung der Reparatur mit einem Preflight-Bericht
CreatePreflightReport ist der Verifizierungsschritt, und der Kreis schließt sich bewusst: Dieselbe Überprüfung, die die Datei bemängelt hat, sollte auch diejenige sein, die sie freigibt. Der Fehlercode 00030 ist das Ergebnis der tiefen PDF/A-Prüfung und lautet „At least one font is not embedded (FontFile/FontFile2/FontFile3 missing)“. Er wird für die Datei als Ganzes gemeldet, sodass eine einzige übersehene Schriftart ihn bestehen lässt. Führen Sie den Bericht für die Quelldatei aus, reparieren Sie sie, speichern Sie sie und führen Sie ihn erneut für die Ausgabe aus:
function HasFontEmbeddingViolation(PDF: TPDFlib;
const FileName: string): Boolean;
var
Report: string;
begin
// ComplianceTests = 1 wählt die PDF/A-Prüfungen aus
Report := PDF.CreatePreflightReport(FileName, '', 1, 0);
Result := Pos('00030', Report) > 0;
end;
Für eine Ansicht pro Schriftart anstelle eines Urteils pro Datei laden Sie das reparierte Dokument erneut und listen Sie es auf: FindFonts gefolgt von SelectFont und GetFontIsEmbedded meldet den Einbettungsstatus Schriftart für Schriftart. Dies ist das richtige Werkzeug, wenn ein Batch-Job protokollieren muss, welche Schriftart in welcher Datei genau nicht repariert werden konnte. Dasselbe Aufzählungsmuster wird im Artikel über das Extrahieren von Text, Bildern und Schriftarten aus geladenen PDFs verwendet, wo es der Extraktion anstelle der Reparatur dient
Was passiert, wenn die Schriftart nicht auf dem System installiert ist?
EmbedMissingFonts überspringt jede Schriftart, deren Programm es nicht finden kann, und meldet das Überspringen über seinen Rückgabewert: Wenn die Anzahl niedriger ausfällt als die Anzahl der von Ihnen gezählten, nicht eingebetteten Schriftarten, liegt der Unterschied in den Schriftarten, die das System nicht besitzt. Dies ist das ehrliche Fehlerszenario und es ist besser als die Alternativen, denn das Erfinden eines Ersatzprogramms für eine im Dokument benannte Schriftart würde das Rendering verändern, was eine Archivreparatur niemals tun darf. Für diese Fälle bietet die losLab PDF Library EmbedFontProgramFromFile, das eine vom Aufrufer bereitgestellte .ttf oder .otf in die benannte Schriftart einbettet, sodass eine Pipeline die erwarteten Unternehmensschriftarten mitliefern und gezielt auf diese zurückgreifen kann:
var
I, FontID: Integer;
begin
PDF.FindFonts;
for I := 1 to PDF.FontCount do
begin
FontID := PDF.GetFontID(I);
if (FontID > 0) and (PDF.SelectFont(FontID) = 1) then
if PDF.GetFontIsEmbedded = 0 then
// Versuchen Sie zuerst die installierte Systemschriftart, und greifen Sie dann
// auf eine Schriftdatei zurück, die zusammen mit der Anwendung geliefert wird
if PDF.EmbedFontProgram(PDF.FontName) = 0 then
PDF.EmbedFontProgramFromFile(PDF.FontName,
'fonts\CorporateSans.ttf');
end;
end;
Zwei Grenzen sollten klar benannt werden. Erstens werden Type1-Schriftarten in der aktuellen Implementierung nicht repariert: Ihr /FontFile-Eintrag erfordert die dreiteilige PFB-Struktur mit expliziten Längenschlüsseln, und die Bibliothek überspringt sie, anstatt einen fehlerhaften Stream zu schreiben; sie sind in modernen Dokumenten selten, kommen aber in alten Archiven vor. Zweitens ist das Einbetten einer Schriftart ein Lizenzierungsvorgang. Die Einbettungsberechtigungen einer TrueType-Schriftart gehören dem jeweiligen Schrifthersteller (Foundry). Bei einer Reparatur-Pipeline, die lizenzierte Schriftart-Programme in Dokumente packt, die die Organisation verlassen, sollte jemand bestätigen, dass die Schriftartlizenzen dies tatsächlich zulassen. Die Bibliothek tut, worum Sie bitten; ob Sie darum bitten dürfen, ist eine Frage für Ihre Rechtsabteilung, nicht für Ihren Compiler
Einbettung ist notwendig, aber nicht ausreichend
Das Reparieren von Schriftarten behebt den Fehler 00030, und sonst nichts. Ein Dokument, das die PDF/A-Prüfung aufgrund von Verschlüsselung, fehlenden XMP-Metadaten, einem geräteabhängigen Farbraum ohne OutputIntent oder fehlenden ToUnicode-Tabellen nicht besteht, wird auch nach dem Einbetten jeder Schriftart weiterhin durchfallen. Deshalb gehört die Reparatur in eine Preflight-gesteuerte Schleife, anstatt eine solche zu ersetzen. Führen Sie den vollständigen Bericht aus, beheben Sie, was dort genannt wird, und lassen Sie sich vom Bericht sagen, wann Sie fertig sind. Es gibt auch eine Kostendimension: Ein vollständiges CJK-Schriftartprogramm kann mehrere Megabyte groß sein, sodass das Einbetten mehrerer solcher Programme ein kleines Dokument drastisch vergrößern kann. Das Gegengewicht dazu ist das Subsetting, das im Artikel über PDF-Dateigrößenoptimierung und Schriftarten-Subsetting behandelt wird, wodurch jedes eingebettete Programm auf die tatsächlich im Dokument gerenderten Glyphen reduziert wird
Verhindern, dass neue Dokumente wieder fehlerhaft werden
SetEmbedAllFonts ist die vorbeugende Hälfte derselben Funktion: ein Schutz auf der Schreiberseite, der verhindert, dass Ihr eigener Code Dokumente erzeugt, wie sie in diesem Artikel repariert werden. Wenn SetEmbedAllFonts(1) aktiv ist, wird jeder nachfolgende AddTrueTypeFont-Aufruf, der Embed=0 anfordert, zu einer eingebetteten Referenz aufgewertet, was die Garantie, die der PDF/A-Modus bereits erzwingt, auf jedes Dokument ausdehnt. Dies betrifft Schriftarten, die nach dem Aufruf hinzugefügt werden, nicht jedoch Schriftarten, die sich bereits in einer geladenen Datei befinden. So bleibt die Arbeitsteilung sauber: SetEmbedAllFonts für Dokumente, die Sie erstellen, EmbedMissingFonts für Dokumente, die Sie übernehmen
PDF.NewDocument;
PDF.SetEmbedAllFonts(1);
// Ab hier verhält sich AddTrueTypeFont(Name, 0) wie
// AddTrueTypeFont(Name, 1): Es kann keine nicht eingebettete
// Referenz in die Ausgabedatei gelangen
Beide Hälften, der Schutz auf Schreiberseite und der Pfad „Laden-Reparieren-Speichern“, sind Teil der losLab PDF Library für Delphi, C# und VB.NET, zusammen mit der Preflight-Engine, die das Ergebnis überprüft. Die Produktseite enthält die vollständige Schriftart-API-Referenz einschließlich der Einbettungs- und Subsetting-Aufrufe pro Schriftart