PDF Library for Delphi akzeptiert die Schriften, die ein Webprojekt bereits mitbringt. Sie importiert WOFF2-Container, zählt die Designachsen einer variablen Schrift als JSON auf und instanziiert einen gewählten Satz Achsenkoordinaten zu einer statischen Schrift, die die gewöhnlichen Einbettungs-, Subsetting- und Extraktionspfade handhaben, ohne irgendetwas über Variationen zu wissen. Sowohl TrueType- als auch CFF2-Umrisse werden abgedeckt
Die Situation, die dies adressiert, ist häufig und ärgerlich. Die Typografie einer Marke ist in einem Design-System definiert, das Design-System liefert variable WOFF2-Dateien, und der PDF-Generator kann weder den Container noch die Variationsdaten lesen. Der übliche Workaround — die Schriftgießerei um statische TTF-Instanzen jedes tatsächlich verwendeten Schriftschnitts zu bitten — dauert Wochen und erzeugt Dateien, die niemand synchron hält
Warum ist WOFF2 nicht einfach eine komprimierte Schrift?
Behandelt man WOFF2 als bloßen Brotli-Wrapper und dekomprimiert ihn, erhält man Bytes, die kein gültiges sfnt sind. Das Format erlaubt außerdem transformierte und umsortierte Tabellen: glyf, loca und hmtx können in umgeordneter Form gespeichert sein, die rückgängig gemacht werden muss, bevor die Schrift parsbar ist. Ein Allzweck-Dekompressor kann das nicht
Unter Windows stellt die Plattform seit Windows 10 Version 1703 Container-Analyse und -Entpackung bereit, daher verwendet die Bibliothek die Systemimplementierung, statt einen weiteren Decoder mitzuliefern. Diese Entscheidung hält die Bereitstellung frei von einer zusätzlichen DLL und bedeutet, dass die Transformationsumkehr vom selben Code gehandhabt wird, den der eigene Textstack des Betriebssystems verwendet. Auf anderen Plattformen läuft WOFF1 weiterhin über einen reinen Pascal-Pfad, und WOFF2 wird explizit als nicht unterstützt gemeldet, was eine ehrliche Antwort ist statt einer still beschädigten Schrift
Der Importpfad validiert vor und nach dem Dekodieren: Der 48-Byte-Header, die Containerlänge, die reservierten Felder, die deklarierte sfnt-Größe und eine Obergrenze von 512 MiB werden alle zuerst geprüft, und die dekodierte Stream-Größe wird anschließend mit der Deklaration verglichen. Ein Webfont ist heruntergeladener Inhalt, und die zwei Fehlermodi, die es zu blockieren lohnt, sind eine Größenabweichung und eine Dekompressionsbombe
uses
PDFlibrary;
var
Lib: TPDFlib;
FontID, InstanceID: Integer;
AxesJSON: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.NewDocument;
Lib.SetPageSize('A4');
Lib.NewPage;
// WOFF2, WOFF1, TTF, OTF und TTC laufen alle über denselben Aufruf
FontID := Lib.AddOpenTypeFontFromFile('Inter.woff2', 0);
if FontID <= 0 then
raise Exception.Create('font could not be imported');
// Was kann diese Schrift tatsächlich?
if Lib.GetFontVariationAxes(FontID, AxesJSON) = 1 then
Log(AxesJSON); // Tags, Minimum, Standard, Maximum je Achse
// Eine Design-Position in eine statische Schrift backen
InstanceID := Lib.CreateOpenTypeFontVariation(FontID,
'wght=620,wdth=87.5');
Lib.DrawOpenTypeText(56, 760, InstanceID, 24, 'Quarterly results', 0);
Lib.SaveToFile('report.pdf');
finally
Lib.Free;
end;
end;
Was Instancing neu aufbauen muss, und was es entfernt
Eine variable Schrift speichert einen Standardumriss pro Glyphe plus Deltas, indiziert nach Position im Designraum. Eine statische Instanz zu erzeugen bedeutet, diese Deltas an den angeforderten Koordinaten zu interpolieren, die Punkte zu verschieben und dann alles davon Abgeleitete neu zu berechnen
Für TrueType-Umrisse bedeutet das, fvar, avar, gvar, HVAR und MVAR zu parsen, Tupel-Deltas mit gemeinsamer und privater Punktnummerierung anzuwenden und die Punkte zu interpolieren, die kein explizites Delta tragen. Horizontale Phantompunkte bestimmen zusammen mit HVAR Vorschubbreite und linke Seitenlast. Bei zusammengesetzten Glyphen müssen zuerst ihre Komponentenabhängigkeiten und Punktzuordnungen aufgelöst werden, dann Translation, Skalierung, Matrix und Anbindungs-Offsets angewendet werden — mit Tiefenlimits und Zyklenerkennung, denn ein bösartiger Komponentengraph würde sonst endlos rekursieren
Metadaten wandern ebenfalls mit, und das ist der Teil, den die meisten Implementierungen auslassen. MVAR aktualisiert die globalen Metriken in hhea, OS/2 und post, sodass Zeilenabstand, Unterstreichungsposition und Kursivwinkel zur Instanz statt zum Standard passen. Die Achsen wght, wdth, ital und slnt aktualisieren zusätzlich Gewichtsklasse, Breitenklasse, Auswahl-Flags, macStyle und Kursivwinkel, sodass ein die statische Schrift untersuchendes Werkzeug den angeforderten Stil meldet. Eine Schrift, deren Geometrie fett ist und deren Metadaten regulär sagen, wird überall nachgelagert falsch beschriftet
Die Ausgabe glättet dann zusammengesetzte Umrisse und entfernt die nicht mehr geltenden Tabellen: die Variationstabellen, Hints, Device-Tabellen und jede Signatur. Glyphengrenzen, hhea-Zusammenfassungsmetriken, die maxp-Hint-Obergrenzen, Prüfsummen pro Tabelle und die Gesamtschrift-Prüfsumme werden alle neu berechnet
CFF2 nimmt einen anderen Weg zum selben Ziel
CFF2 kann nicht direkt als PDF-Font-Programm eingebettet werden: Seine Charstrings tragen keine Breiten, und es verlässt sich für Glyphenreihenfolge und Metriken auf die umgebenden OpenType-Tabellen. Das Instanziieren einer CFF2-Schrift erzeugt daher stattdessen eine CID-verschlüsselte CFF-OpenType-Schrift, die die bestehenden Zeichen-, Einbettungs- und Subsetting-Pfade bereits handhaben
Das Laden einer CFF2-Schrift erzeugt sofort eine statische Instanz mit Standardkoordinaten für das aktuelle Dokument, während das ursprüngliche CFF2-sfnt am Schriftobjekt erhalten bleibt, sodass Achsenaufzählung und spätere Instanziierung weiterhin den vollen Designraum sehen. Jede Glyphe wird beim Instanziieren einzeln de-subroutinisiert, was die Variationsarithmetik vollständig entfernt und den bestehenden Subsetter unverändert arbeiten lässt. Der Kompromiss besteht darin, dass eine statische Instanz etwas größer sein kann als eine, die gemeinsame Subroutinen behalten hat — ein fairer Preis dafür, keine zweite Subsetting-Implementierung zu benötigen
Eine Instanz verifizieren, bevor Sie ihr vertrauen
Instancing ist die Art von Feature, das auf subtile Weise falsch sein kann, ohne dass eine Exception das meldet. Drei Prüfungen fangen fast alles ab
Exportieren Sie die Instanz und öffnen Sie sie in einem Schriftwerkzeug. SaveFontToFile schreibt das erhaltene Font-Programm, sodass sich eine Instanz im Speicher ausgeben lässt, ohne zuvor ein PDF zu erzeugen. Die exportierte Datei sollte sauber parsen, keine Variationstabellen enthalten und die angeforderten Stilmetadaten melden
Vergleichen Sie Vorschübe an einer nicht-standardmäßigen Position. Kann Ihre Pipeline dieselbe Instanz auf andere Weise erzeugen, vergleichen Sie Vorschubbreiten und Kurvengrenzen Glyphe für Glyphe; die Referenzimplementierung hier stimmte mit einer unabhängigen Instanziierung einer 313-Glyphen-Zwei-Achsen-Schrift mit einer maximalen Abweichung von null über jede Glyphe hinweg überein
Und extrahieren Sie den Text aus dem erzeugten PDF zurück. Eine Instanz, die sich einbettet, aber keinen extrahierbaren Text erzeugt, hat ihre ToUnicode-Zuordnung verloren, was für Suche und Barrierefreiheit lange nach bestandener visueller Prüfung noch von Bedeutung ist. Der breitere Extraktionspfad ist unter Text-, Bild- und Schriftextraktion behandelt
// Eine erzeugte Instanz zur Inspektion oder Wiederverwendung ausgeben
Lib.SaveFontToFile('Inter-620-87.ttf');
// Fordern Sie Koordinaten außerhalb des deklarierten Bereichs an, werden sie geklemmt,
// lesen Sie also das Achsen-JSON, statt die Grenzen einer Schrift anzunehmen
// {"axes":[{"tag":"wght","min":100,"default":400,"max":900}, ...]}
Praktische Hinweise für eine Marken-Font-Pipeline
Cachen Sie Instanzen nach Koordinatensatz. Jeder eigenständige Koordinatensatz ist ein eigenständiges Font-Programm, sodass ein Dokument, das Schnitt 400 und Schnitt 620 mischt, zwei Subsets statt eines gemeinsamen Schriftschnitts einbettet. Dieselbe Instanz wiederholt zu erzeugen ist verschwendete Arbeit; Dutzende fast identischer Schnitte zu erzeugen ist verschwendete Dateigröße
Messen Sie mit der Instanz, mit der Sie auch zeichnen werden. Eine schmale (Condensed) Instanz hat schmalere Vorschübe als der Standard, sodass jedes Layout, das vor dem Instancing gemessen hat, die falschen Breiten gemessen hat. Das ist die mit Abstand häufigste Ursache für Text, der nur in der Produktion aus seiner Box überläuft
Halten Sie einen Fallback-Pfad für die Glyphen bereit, die Ihrer Markenschrift fehlen. Eine variable Markenschrift deckt typischerweise Latein und wenig sonst ab, und ein Bericht mit einem kyrillischen Kundennamen zeigt sonst fehlende Glyphen; der automatische Fallback-Mechanismus ist unter automatischer Font-Fallback für fehlende Glyphen beschrieben. Und wenn das Ziel eine kleinere Datei statt eines bestimmten Schnitts ist, leistet Subsetting mehr Arbeit als Instancing, wie unter PDF-Dateigrößenoptimierung und Font-Subsetting behandelt
Webfont-Import, variables Instancing, Subsetting und Fallback sind Teil derselben Font-Pipeline für Delphi, C++Builder und Free Pascal; die vollständige Funktionsliste finden Sie auf der Seite der PDF Library for Delphi