PDF Library for Delphi accepte les polices qu'un projet web utilise déjà. Elle importe les conteneurs WOFF2, énumère les axes de conception d'une police variable sous forme de JSON, et instancie un jeu choisi de coordonnées d'axe en une police statique que les voies ordinaires d'intégration, de sous-ensemblage et d'extraction traitent sans rien savoir des variations. Les contours TrueType comme les contours CFF2 sont couverts
La situation à laquelle cela répond est courante et irritante. La typographie d'une marque est définie dans un système de conception, ce système de conception livre des fichiers WOFF2 variables, et le générateur de PDF ne peut lire ni le conteneur ni les données de variation. La solution de contournement habituelle — demander à la fonderie des instances TTF statiques de chaque graisse réellement utilisée — prend des semaines et produit des fichiers que personne ne garde synchronisés
Pourquoi le WOFF2 n'est-il pas juste une police compressée ?
Traiter le WOFF2 comme une enveloppe Brotli et le décompresser donne des octets qui ne forment pas un sfnt valide. Le format permet aussi des tables transformées et réordonnées : glyf, loca et hmtx peuvent être stockées sous une forme réarrangée qui doit être inversée avant que la police ne soit analysable. Un décompresseur généraliste ne peut pas faire cela
Sous Windows, la plateforme fournit l'analyse et le décompactage de conteneurs depuis Windows 10 version 1703, si bien que la bibliothèque utilise l'implémentation système plutôt que d'embarquer un autre décodeur. Cette décision garde le déploiement libre d'une DLL supplémentaire et signifie que l'inversion des transformations est gérée par le même code que celui utilisé par la pile texte propre du système d'exploitation. Sur d'autres plateformes, le WOFF1 emprunte toujours une voie Pascal pur et le WOFF2 se déclare explicitement non pris en charge, ce qui est une réponse honnête plutôt qu'une police silencieusement corrompue
La voie d'import valide avant le décodage et après : l'en-tête de 48 octets, la longueur du conteneur, les champs réservés, la taille sfnt déclarée et un plafond de 512 Mio sont tous vérifiés en premier, et la taille du flux décodé est comparée à la déclaration ensuite. Une police web est un contenu téléchargé, et les deux modes d'échec qu'il vaut la peine de bloquer sont une incohérence de taille et une bombe de décompression
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 et TTC entrent tous par le même appel
FontID := Lib.AddOpenTypeFontFromFile('Inter.woff2', 0);
if FontID <= 0 then
raise Exception.Create('font could not be imported');
// Que peut réellement faire cette police ?
if Lib.GetFontVariationAxes(FontID, AxesJSON) = 1 then
Log(AxesJSON); // tags, minimum, valeur par défaut, maximum par axe
// Fige un emplacement de conception donné en une police statique
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;
Ce que l'instanciation doit reconstruire, et ce qu'elle retire
Une police variable stocke un contour par défaut par glyphe plus des deltas indexés par position dans l'espace de conception. Produire une instance statique signifie interpoler ces deltas aux coordonnées demandées, déplacer les points, puis recalculer tout ce qui en dérive
Pour les contours TrueType, cela signifie analyser fvar, avar, gvar, HVAR et MVAR, appliquer des deltas de tuple avec une numérotation de points partagée et privée, et interpoler les points qui ne portent aucun delta explicite. Les points fantômes horizontaux, combinés à HVAR, déterminent la largeur d'avance et l'approche gauche. Les glyphes composites nécessitent d'abord la résolution de leurs dépendances de composants et de leurs correspondances de points, puis l'application de la translation, de l'échelle, de la matrice et des décalages d'attachement — avec des limites de profondeur et une détection de cycle, car un graphe de composants malveillant récurrerait sinon sans fin
Les métadonnées se déplacent aussi, et c'est la partie que la plupart des implémentations sautent. MVAR met à jour les métriques globales dans hhea, OS/2 et post, si bien que l'interlignage, la position de soulignement et l'angle d'italique correspondent à l'instance plutôt qu'à la valeur par défaut. Les axes wght, wdth, ital et slnt mettent en outre à jour la classe de graisse, la classe de largeur, les indicateurs de sélection, macStyle et l'angle d'italique, si bien qu'un outil inspectant la police statique rapporte le style que vous avez demandé. Une police dont la géométrie est grasse et dont les métadonnées disent normale sera mal étiquetée partout en aval
La sortie aplatit ensuite les contours composites et retire les tables qui ne s'appliquent plus : les tables de variation, les hints, les tables de dispositifs et toute signature. Les limites de glyphe, les métriques récapitulatives de hhea, les plafonds de hints de maxp, les sommes de contrôle par table et la somme de contrôle de la police entière sont toutes recalculées
Le CFF2 emprunte une voie différente vers le même résultat
Le CFF2 ne peut pas être intégré directement comme programme de police PDF : ses charstrings ne portent aucune largeur, et il s'appuie sur les tables OpenType environnantes pour l'ordre des glyphes et les métriques. Instancier une police CFF2 produit donc à la place une police OpenType CFF à clé CID, que les voies existantes de dessin, d'intégration et de sous-ensemblage traitent déjà
Charger une police CFF2 produit immédiatement une instance statique aux coordonnées par défaut pour le document courant, tandis que le sfnt CFF2 original est conservé sur l'objet police afin que l'énumération des axes et l'instanciation ultérieure voient toujours l'espace de conception complet. Chaque glyphe est dé-subroutiné individuellement pendant l'instanciation, ce qui supprime entièrement l'arithmétique de variation et laisse le sous-ensembleur existant fonctionner sans modification. Le compromis est qu'une instance statique peut être légèrement plus volumineuse qu'une instance qui aurait conservé des subroutines partagées — un prix raisonnable pour ne pas avoir besoin d'une seconde implémentation de sous-ensemblage
Vérifier une instance avant de lui faire confiance
L'instanciation est le genre de fonctionnalité qui peut être subtilement fausse de façons qu'aucune exception ne signale. Trois vérifications attrapent presque tout
Exportez l'instance et ouvrez-la dans un outil de police. SaveFontToFile écrit le programme de police conservé, si bien qu'une instance en mémoire peut être extraite sans produire de PDF d'abord. Le fichier exporté doit s'analyser proprement, ne contenir aucune table de variation, et rapporter les métadonnées de style que vous avez demandées
Comparez les avances à un emplacement non par défaut. Si votre pipeline peut produire la même instance d'une autre façon, comparez les largeurs d'avance et les limites de courbe glyphe par glyphe ; l'implémentation de référence ici a fait correspondre une instanciation indépendante d'une police à deux axes et 313 glyphes avec une différence maximale de zéro sur chaque glyphe
Et extrayez le texte du PDF généré. Une instance qui s'intègre mais ne produit aucun texte extractible a perdu sa correspondance ToUnicode, ce qui compte pour la recherche et l'accessibilité bien après que la vérification visuelle a réussi. La voie d'extraction plus large est traitée dans l'extraction de texte, d'images et de polices
// Extrait une instance générée pour inspection ou réutilisation
Lib.SaveFontToFile('Inter-620-87.ttf');
// Demander des coordonnées hors de la plage déclarée les fait limiter,
// lisez donc le JSON des axes plutôt que de supposer les limites d'une police
// {"axes":[{"tag":"wght","min":100,"default":400,"max":900}, ...]}
Remarques pratiques pour un pipeline de police de marque
Mettez en cache les instances par jeu de coordonnées. Chaque jeu distinct de coordonnées est un programme de police distinct, si bien qu'un document mêlant graisse 400 et graisse 620 intègre deux sous-ensembles plutôt qu'une seule face partagée. Générer la même instance à répétition est du travail gaspillé ; générer des dizaines de graisses quasi identiques est de la taille de fichier gaspillée
Mesurez avec l'instance que vous allez dessiner. Une instance condensée a des avances plus étroites que celle par défaut, si bien que toute mise en page mesurée avant l'instanciation a mesuré les mauvaises largeurs. C'est la cause la plus courante de texte qui déborde de sa boîte uniquement en production
Gardez une voie de repli pour les glyphes que votre police de marque ne possède pas. Une police de marque variable couvre typiquement le latin et peu d'autre chose, et un rapport contenant un nom de client cyrillique montrera sinon des glyphes manquants ; le mécanisme de repli automatique est décrit dans le repli automatique de police pour les glyphes manquants. Et quand l'objectif est un fichier plus petit plutôt qu'une graisse spécifique, le sous-ensemblage accomplit davantage de travail que l'instanciation, comme traité dans l'optimisation de la taille de fichier PDF et le sous-ensemblage de polices
L'import de polices web, l'instanciation variable, le sous-ensemblage et le repli font partie du même pipeline de polices pour Delphi, C++Builder et Free Pascal ; la liste complète des fonctionnalités se trouve sur la page de PDF Library for Delphi