PDFlibPas résout les caractères que la police sélectionnée ne peut pas dessiner en parcourant une chaîne de secours de polices installées, cluster par cluster, tout en préservant la forme des glyphes et l'ordre des passages bidirectionnels. Vous l'activez avec SetAutomaticFontFallback, vous étendez la chaîne avec AddFontFallback, et seules les polices de secours réellement utilisées pour la sortie sont incorporées dans le fichier
Le problème qu'elle résout est celui que rencontre tout générateur de documents la première fois qu'un nom de client arrive dans une écriture que la police du modèle n'avait jamais anticipée. L'échec est silencieux, ce qui le rend coûteux
Pourquoi un texte non pris en charge disparaît-il au lieu de déclencher une erreur ?
Parce que le format PDF n'a aucune notion de police incapable de dessiner un caractère. Une police simple associe des codes d'octets à des noms de glyphes via un encodage ; une police composite associe des codes à des indices de glyphes via une CMap. Demandez un glyphe que la police ne contient pas et vous obtenez l'indice de glyphe zéro, .notdef, que la plupart des polices dessinent comme rien ou comme une case vide. Le fichier est structurellement valide, l'opérateur de texte est bien formé, et la page s'affiche. C'est simplement vide à l'endroit où le nom aurait dû apparaître
Rien dans ISO 32000-1 n'oblige un producteur à s'en apercevoir. Un générateur qui écrit du texte sans vérifier la couverture produit un PDF techniquement conforme qui a silencieusement perdu du contenu, et la perte n'apparaît sur l'écran d'un client que des semaines plus tard. C'est pourquoi la fonction de repli et le rapport de glyphes manquants sont livrés ensemble : résoudre ce qui peut l'être n'est que la moitié du travail, signaler ce qui n'a pas pu l'être en est l'autre moitié
Le repli s'applique par cluster, pas par point de code
La granularité est le détail qui sépare une implémentation qui fonctionne d'une implémentation seulement plausible. Le texte n'est pas une suite de caractères indépendants. Une syllabe devanagari, un emoji avec un modificateur de teint, une lettre de base avec des signes combinants : chacun forme un cluster qui doit être rendu par une seule police, car les décisions de formation des glyphes en son sein dépendent des tables de cette police
PDFlibPas résout au niveau du cluster, si bien qu'un cluster couvert par une police de secours est dessiné entièrement par cette police. Couper au milieu d'un cluster et dessiner une moitié avec la police principale et l'autre avec une police de secours produirait un résultat techniquement présent mais visiblement cassé, sans doute pire que le vide de départ. L'ordre des passages est préservé lui aussi, si bien qu'un repli à l'intérieur d'un passage de droite à gauche ne réordonne pas le texte environnant ; le même mécanisme sous-tend la mise en page verticale décrite dans l'écriture verticale pour le japonais et le chinois
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.SetAutomaticFontFallback(1);
// Ordre de recherche : la première correspondance l'emporte, mettez donc les polices les plus larges en dernier
Lib.AddFontFallback('Microsoft YaHei'); // Chinois simplifié
Lib.AddFontFallback('Meiryo'); // Japonais
Lib.AddFontFallback('Segoe UI Symbol');
Lib.AddFontFallback('Segoe UI Emoji');
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_REPORT);
Lib.AddTrueTypeFont('Arial', 1); // 1 = incorporer la police
Lib.SetTextSize(11);
Lib.DrawText(72, 720, 'Invoice for 北京示例科技有限公司');
Lib.DrawText(72, 700, 'Delivery status: on time');
Lib.SaveToFile('invoice.pdf');
finally
Lib.Free;
end;
end;
Ordonnez la chaîne de façon délibérée. La résolution retient la première police qui couvre le cluster, donc une police pan-Unicode large placée en premier l'emportera presque partout et vos polices spécifiques à une écriture, pourtant choisies avec soin, ne seront jamais consultées. Placez les polices spécifiques en premier et la police généraliste en dernier
Signaler ou interrompre : quel type d'échec voulez-vous ?
SetMissingGlyphPolicy accepte PDF_MISSING_GLYPH_REPORT, la valeur par défaut compatible, ou PDF_MISSING_GLYPH_ABORT. Avec la politique de signalement, l'opération de texte se poursuit, les points de code non résolus sont abandonnés comme avant, et chacun est enregistré. Avec la politique d'interruption, l'opération de texte est rejetée avant qu'aucun contenu ne soit écrit et LastErrorCode prend la valeur 521
Choisissez selon l'usage du document. Un lot de rapports internes doit continuer à s'afficher et journaliser les lacunes, car un rapport légèrement incomplet aujourd'hui vaut mieux qu'aucun rapport du tout. Un contrat juridiquement contraignant, une facture, ou tout document portant un nom, doit s'interrompre, car un caractère silencieusement supprimé dans le nom d'une partie est un défaut que vous préférez découvrir dans votre propre processus plutôt que dans un litige. La politique d'interruption échoue avant l'écriture, si bien qu'aucun flux de contenu à moitié formé ne subsiste
var
Lib: TPDFlib;
Report: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_ABORT);
// ... construire le document ...
if Lib.DrawText(72, 660, CustomerName) <> 1 then
if Lib.LastErrorCode = PDFLIB_ERROR_MISSING_GLYPH then
begin
Report := Lib.GetMissingGlyphReportJSON;
// {"valid":false,"policy":1,"eventCount":1,"events":[
// {"sequence":1,"documentIndex":0,"page":1,"utf16Index":12,
// "codePoint":21271,"unicode":"U+5317","fontName":"Arial",
// "fontType":"TrueType","operation":"DrawText"}]}
EscalateToOperator(Report);
end;
finally
Lib.Free;
end;
end;
Le rapport est délibérément lisible par machine et borné. Chaque événement porte la page, l'indice UTF-16 dans la chaîne, le point de code sous forme numérique et sous forme U+XXXX, la police sélectionnée, son type et l'opération qui a rencontré le problème, si bien qu'un ticket de support peut nommer le caractère exact plutôt que de décrire un symptôme. Le traqueur conserve les 256 événements les plus récents, ce qui suffit pour diagnostiquer un document et reste assez restreint pour qu'une exécution pathologique ne transforme pas le diagnostic en problème de mémoire
La mesure et le dessin doivent concorder
La mesure de largeur utilise les mêmes décisions de repli conscientes des clusters que le dessin. Cela paraît évident et c'est pourtant ce que la plupart des couches de repli maison ratent : elles corrigent le chemin de dessin, laissent la mesure sur la police principale, et chaque zone de texte, alignement à droite et colonne de tableau finit par être calculé à partir de largeurs qui ne correspondent pas à ce qui a été rendu
Comme les deux chemins partagent la résolution, une chaîne mesurée avant d'être dessinée occupe exactement la largeur mesurée, y compris les passages en police de secours. C'est ce qui permet d'activer le repli globalement, plutôt que seulement dans les endroits que vous avez audités à la main
Seul ce que vous avez utilisé est incorporé
Les polices de secours sont incorporées de façon paresseuse : une police de la chaîne qui n'a jamais résolu de cluster ne contribue en rien à la sortie. Un document contenant un caractère chinois et 5 000 caractères latins ne transporte pas une police CJK complète ; il transporte ce que la passe de sous-ensembles a produit pour ce seul glyphe, comportement décrit dans l'optimisation de la taille des fichiers et le sous-ensemblage de polices
Cette paresse rend une chaîne large peu coûteuse à configurer. Enregistrez les polices dont votre ensemble de documents pourrait avoir besoin dans chaque locale que vous desservez, et chaque PDF individuel ne paie que pour ce qu'il a réellement utilisé. Pour des documents que vous n'avez pas générés, où les polices manquantes sont déjà à l'intérieur d'un fichier existant, le chemin de réparation est différent et couvert dans l'incorporation de polices manquantes dans un PDF existant
Une réserve de déploiement mérite d'être posée clairement : le repli se résout par rapport aux polices installées sur la machine qui exécute le code. Un serveur sans polices CJK installées n'a rien vers quoi se replier, et le rapport vous le dira dès le premier document plutôt qu'après la première réclamation. Livrez les polices dont vous dépendez, et vérifiez les conditions de licence pour leur incorporation
PDFlibPas est une bibliothèque PDF pour Delphi, C++Builder et Lazarus, avec des interfaces DLL et ActiveX correspondantes, si bien que les API de repli et de glyphes manquants sont aussi accessibles depuis des appelants non-Pascal. La documentation complète se trouve sur la page PDFlibPas Delphi PDF library