Greboca  

Suport technique et veille technologique

Aujourd’hui, les grandes entreprises et administrations publiques hésitent entre continuer à utiliser des logiciels propriétaires ou basculer vers les Logiciels Libres. Pourtant, la plupart des logiciels libres sont capables de bien traiter les données issues des logiciels propriétaire, et parfois avec une meilleur compatibilité.

C’est alors la barrière de la prise en main qui fait peur, et pourtant...

Les logiciels libres

L’aspect « Logiciel Libre » permet une évolution rapide et une plus grande participation des utilisateurs. Les aides et tutoriels foisonnent sur Internet ou sont directement inclus dans le logiciel lui-même.

Enfin, les concepteurs sont plus proches des utilisateurs, ce qui rend les logiciels libres plus agréable à utiliser et conviviaux.

Grâce à la disponibilité des logiciels libres, vous trouverez facilement des services de support techniques et la licence n’est plus un frein à l’utilisation de ces logiciels par votre personnel.

Notre support technique concerne essentiellement les logiciels libres, que ce soit sous forme de services ponctuels ou de tutoriels.

LinuxFr.org : les journaux  -  PDF, mais que fait la police

 -  Août 2021 - 

Sommaire

Salutations

Le PDF, quel format merveilleux. On génère un fichier et on est sûrs qu'il va s'afficher correctement partout, que ce soit avec des lecteurs libres (Poppler et donc Okular/Evince, mupdf, pdf.js, pdfium et donc Chromium…) ou propriétaires.
Ou pas.
«tiens, je me demandais, ton okular aussi il fait un rendu dégueulasse des fonts sur une attestation d'assurance maladie ameli ?»

En voilà une question qu'elle est bonne. Et oui, il fait un rendu dégueulasse. Bon, le document de test utilisé ici sera une attestation de mutuelle, mais l'idée est la même. Alors plongeons dans la fosse à purin qu'est le rendu de texte en PDF…

1) Regard d'utilisateur

Bon. Ouvrons le PDF avec Okular (une version anonymisée est disponible sur https://rock.pinaraf.info/~pinaraf/dlfp-pdf/test-pdf-fonts-anon.pdf). Le texte de l'adresse est effectivement dégueulasse. Le MA de MADAME est tout compressé, avec le M qui mord sur le A (ou l'inverse, je n'ai jamais étudié la férocité relative des lettres). De même sur le DA ou le D mord le A. C'est très sale.
Okular a le bon goût, dans la fenêtre des propriétés du document (menu Fichier), d'indiquer les polices utilisées et leurs substitutions, et même le chemin du fichier de police sur le disque.
On observe que 6 polices sont utilisées, deux Arial (dispo gratuitement avec une licence flexible), deux Calibri (coucou Microsoft qui interdit d'installer ces polices sur Linux sans licence), une Monospace821BT-Roman inconnue au bataillon et une UNWXZK+Windings-Regular, du monde de microsoft à nouveau. Sur toutes ces polices, seule la Wingdings est embarquée dans le document. Pour toutes les autres, le moteur de rendu a du trouver la police sur le système, ou procéder à une substitution, plus ou moins heureuse…
Arial BoldMT et ArialMT sont chez moi remplacées par Liberation Sans Bold et DejaVu Sans. Le remplacement de ArialMT par DejaVu Sans n'est pas le plus heureux qu'il soit, c'est incohérent avec Arial BoldMT, mais admettons.
Les Calibri sont remplacés par DejaVu Sans. C'est la même famille (sans-serif, les hors la loi se font plaisir), mais par contre il n'y a pas de garantie de compatibilité de chasse, donc le résultat risque d'être médiocre.
La dernière, Monospace821BT-Roman, est remplacée par DejaVu Sans. Et là, ça pique. Monospace821BT est une police à chasse fixe (tous les caractères ont la même taille). DejaVu Sans ne l'est pas du tout. Et c'est fort probablement cette police qui est utilisée pour l'adresse, ce qui explique l'apparence affreuse de l'adresse sur le document.

Quand on ouvre le document avec mupdf, on observe que l'adresse n'est pas très belle, les caractères semblent déformés, ce qui est particulièrement visible sur le I de MACHIN qui est extrêmement large. Mais le document est globalement plus propre qu'avec poppler.

Avec pdf.js et pdfium par contre (donc Firefox et Chromium), le résultat est correct. Et pdf.js faisant du rendu sur un document HTML, on peut utiliser l'inspecteur de Firefox pour regarder ce qui est fait ! On observe que l'adresse utilise une police à chasse fixe (font-family monospace), ce qui permet un rendu tout à fait correct du texte.

Pour achever ce tour du point de vue de l'utilisateur, regardons ce que fait la concurrence propriétaire, Acrobat Reader (dans une VM sous Windows 10, avec uniquement les polices de base). L'adresse apparait comme sur mupdf, avec des espacement entre caratères assez désagréable, mais elle est lisible, et le caractère I n'est pas sur-gras. Dans les propriétés du document, la police Monospace821BT-Roman a été remplacée par Adobe Sans MM, erreur similaire à Poppler.

2) Plus technique, le contenu du PDF…

Un PDF est un flux constitué d'une succession d'objets. Cela permet notamment des fonctionnalités très pratiques en cas de contraintes de place ou de bande passante : si vous avez un fichier de 1 GB (avec plein d'images ou de vidéos par exemple), nul besoin de lire tout le document pour afficher la première page (si le PDF est bien construit).
Pour regarder le contenu d'un PDF, le meilleur outil que je connaisse est podofobrowser (https://github.com/KubaO/podofobrowser).
La vue par défaut est arborescente, ce qui sera plus pratique pour nous.
L'élément de départ est le catalogue, un dictionnaire. Ce qui nous intéresse dedans est la liste des polices de caractères. Pour cela, on va se promener dans Pages, qui contient une liste de 1 élément (normal, il n'y a qu'une page). L'objet Page (position 5 0 dans le document) est un dictionnaire, qui contient des Resources, dont les polices utilisées.
On y retrouve bien nos 6 polices de caractères, et la cinquième est la Monospace821BT-Roman. Dans les propriétés listées ici, on observe des informations sur la chasse des caractères (commune à chaque caractère, 602), des flags (valeur 32), un type (Type1)… Rien de transcendant pour nous pour le moment.
Mais que contient ce flags ? Le code source de mupdf est assez clair là dessus :

enum
{
        PDF_FD_FIXED_PITCH = 1 << 0,
        PDF_FD_SERIF = 1 << 1,
        PDF_FD_SYMBOLIC = 1 << 2,
        PDF_FD_SCRIPT = 1 << 3,
        PDF_FD_NONSYMBOLIC = 1 << 5,
        PDF_FD_ITALIC = 1 << 6,
        PDF_FD_ALL_CAP = 1 << 16,
        PDF_FD_SMALL_CAP = 1 << 17,
        PDF_FD_FORCE_BOLD = 1 << 18
};

La police est donc juste indiquée comme «non symbolique» (rappel, 2**5 = 32). Et pas comme ayant une chasse fixe. Du coup, forcément, ça complique le remplacement…

Avec podofobrowser, je change donc ce flag pour y mettre la valeur 33, je sauvegarde, je recharge dans okular… et rien, nada, toujours la même police… Bigre… Mais si je change le BaseFont (le nom donc) en «/Monospace», là le remplacement est bien fait et le texte s'affiche correctement. Sur mupdf, le flag est suffisant par contre pour qu'une belle police à chasse fixe soit prise.

Bigre bigre. Il va donc falloir regarder comment fait chaque implémentation libre alors.

3) Commençons avec mupdf

mupdf a un code assez clair, même s'il est très peu commenté. Tout se passe dans https://github.com/ArtifexSoftware/mupdf/blob/master/source/pdf/pdf-font.c
La fonction qui déclenche tout est pdf_load_font_descriptor. Elle va lire les différents éléments du PDF, puis appeler pdf_load_system_font qui passe la balle à pdf_load_substitute_font. Si la police n'est pas trouvée sur le système, alors pdf_lookup_substitute_font prend le relais, avec les informations sur monospace/serif/gras/italique. Si la police est mono, alors la police Courier est prise, en serif ce sera Times et sinon Helvetica.

mupdf suppose donc que les flags sont correctement renseignés. Sinon, la police de substitution choisie sera nécessairement mauvaise. Pour compenser, le code de dessin utilise la largeur des caractères pour contraindre le rendu de chaque glyphe (fonction fz_adjust_ft_glyph_width appelée par do_ft_render_glyph dans source/fitz/font.c)
C'est donc pour cela que le rendu de mupdf n'est pas parfait, mais reste acceptable : les caractères ne sont pas prévus pour être en chasse fixe, et sont individuellement redimensionnés. D'où le I qui semble bien trop large.

4) Que fait pdf.js ?

Nous avons vu que la police n'a pas le bon flag, et pourtant pdf.js choisit bien une police en chasse fixe. Un petit coup d'inspecteur web montre un font-family: monospace.
Donc regardons le code source de pdf.js.

projects/pdf.js/src $ git grep monospace
core/evaluator.js:    // Heuristic: detection of monospace font by checking all non-zero widths
core/evaluator.js:    let monospace = false;
core/evaluator.js:      monospace = true;
core/evaluator.js:      monospace,
core/evaluator.js:          (metrics.monospace ? FontFlags.FixedPitch : 0) |
core/fonts.js:      fallbackName = "monospace";

C'est tentant ce commentaire, regardons… https://github.com/mozilla/pdf.js/blob/master/src/core/evaluator.js#L3552
Le code vérifie la largeur de chaque caractère de la police. Si toutes les largeurs non-nulles sont égales, alors la police est considérée comme étant à chasse fixe, même en l'absence du flag FixedPitch. Simple et efficace. Le code traduit ensuite ce flag en fallbackName "monospace" qui correspondra à la famille de police en CSS, https://github.com/mozilla/pdf.js/blob/master/src/core/fonts.js#L871

5) Quid de poppler

Dans le cas de poppler, la gestion de la substitution est… différente. Tout d'abord, un algorithme similaire à celui de pdf.js est bien présent pour identifier un flag FixedWidth manquant : https://gitlab.freedesktop.org/poppler/poppler/-/blob/master/poppler/GfxFont.cc#L1362. Le reste de la substitution a lieu dans https://gitlab.freedesktop.org/poppler/poppler/-/blob/master/poppler/FontInfo.cc#L189
Si la police n'est pas embarquée dans le PDF, alors il appelle findSystemFontFile, https://gitlab.freedesktop.org/poppler/poppler/-/blob/master/poppler/GlobalParams.cc#L873
Cette fonction va construire une requête fontconfig pour trouver la police qui correspond. Fontconfig est en quelque sorte le gestionnaire de polices : il mantient un cache des polices installées, de leurs propriétés, et est capable de trouver une police en fonction d'un ensemble de critères (type de police, nom, langues supportées…) Ces critères sont regroupés dans ce que Fontconfig appelle un pattern.
En posant dans gdb un breakpoint sur la fonction FcConfigSubstitute, on peut extraire le pattern et l'afficher avec un appel à FcPatternPrint. On obtient alors le pattern suivant:

Pattern has 3 elts (size 16)
        family: "Monospace821BT"(s)
        spacing: 100(i)(s)
        lang: "xx"(s)

Si on fait cette recherche en ligne de commande, à l'aide de fc-match, on obtient ce résultat :

% fc-match "Monospace821BT:spacing=100:lang=xx" file family
DejaVu Sans:file=/usr/share/fonts/TTF/DejaVuSans.ttf

Et c'est… inattendu. Le critère spacing: 100 correspond pourtant bien à une demande de police à chasse fixe si je comprends bien la signification de la constante dans le code (cf https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/main/fontconfig/fontconfig.h#L178) et différentes personnes sur internet. Mais la police donnée ici ne l'est pas du tout.

Si je regarde mon catalogue de police complet :

% fc-list : family spacing | grep "DejaVu Sans"     
DejaVu Sans Mono:spacing=100
DejaVu Sans,DejaVu Sans Light
DejaVu Sans
DejaVu Sans,DejaVu Sans Condensed

Donc l'info de chasse fixe est bien présente, et uniquement sur la police DejaVu Sans Mono.

Bon… ben… on va creuser ce que fait fontconfig, ça a l'air rigolo…

En lisant un peu le code source, je suis tombé sur la sympatique variable d'environnement FC_DEBUG, qui contient un ensemble de flag afin d'activer plus ou moins de messages de debug : https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/main/src/fcint.h#L99
La subtilité n'étant pas mon truc, je lance donc fc-match avec en variable d'environnement FC_DEBUG=8191. «On sait jamais».

Ça produit un log particulièrement volumineux, mais on aura au moins toutes les informations. Je vous l'ai mis ici à disposition : https://rock.pinaraf.info/~pinaraf/dlfp-pdf/log-fc.txt
Afin de sauter toute la configuration, cherchons où la substitution commence en cherchant notre police, Monospace821BT. La première occurence est à la ligne 46849 (oui, c'est beaucoup de messages de debug). En continuant les itérations, on observe à la ligne 47403 que la famille a été changée en family: "Monospace821BT"(s) "sans-serif"(w), ce qui est évidemment faux. Ce remplacement a été fait par le fichier de règles /etc/fonts/conf.d/49-sansserif.conf recopié ici:

<?xml version="1.0"?>
</span>

  Add sans-serif to the family when no generic name

         target="pattern">
                 qual="all" name="family" compare="not_eq">
                        sans-serif
                
                 qual="all" name="family" compare="not_eq">
                        serif
                
                 qual="all" name="family" compare="not_eq">
                        monospace
                
                 name="family" mode="append_last">
                        sans-serif
                
        

Si aucune famille générique n'a été trouvée, alors on ajoute la famille sans-serif… J'avoue ne pas comprendre la logique puisque cela ignore complètement le critère spacing: 100.

J'ai donc ajouté le fichier de configuration suivant, dans /etc/fonts/conf.d/48-spacing.conf :

<?xml version="1.0"?>
</span>

  Add mono to the family when no generic name and spacing is 100

         target="pattern">
                 qual="all" name="family" compare="not_eq">
                        sans-serif
                
                 qual="all" name="family" compare="not_eq">
                        serif
                
                 qual="all" name="family" compare="not_eq">
                        monospace
                
                 qual="all" name="spacing" compare="eq">
                        100
                
                 name="family" mode="append_last">
                        monospace
                
        

Et là, miracle…

% fc-match "Monospace821BT:spacing=100:lang=xx" file family 
DejaVu Sans Mono:file=/usr/share/fonts/TTF/DejaVuSansMono.ttf

Et le PDF s'affiche correctement dans okular !

La merge request a bien sûr été réalisée pour le projet fontconfig, parce que même si c'est pas un bug (ce dont je doute), c'est un comportement piégeux qui a eu même des «grands» logiciels. https://gitlab.freedesktop.org/fontconfig/fontconfig/-/merge_requests/195

Il restera encore à creuser le code de dessin des caractères de poppler pour qu'il respecte systématiquement la largeur demandée dans la police, mais ça, c'est pour une prochaine fois…

Conclusion

C'est pas parce que le PDF est lisible qu'il faut ignorer un problème. J'avais observé ce bug sur mes premières attestation de carte vitale il y a plus de 10 ans, mais je n'avais jamais pris la peine de l'explorer, en supposant qu'il était lié à ma frilosité sur l'installation de polices. C'est uniquement quand j'ai appris que d'autres avaient le problème que j'ai creusé… J'aurais du creuser il y a 10 ans…

Commentaires : voir le flux Atom ouvrir dans le navigateur

par Pinaraf

LinuxFr.org : les journaux

LinuxFr.org : Journaux

La version 2.0 de WhosWho est sortie

 -  15 mai - 

Bonjour Nal,Je viens de publier la version 2.0 de WhosWho, mon logiciel pour faire des trombinoscopes, dont j'avais parlé il y a longtemps dans (...)


décrire une une image avec une iA locale

 -  8 mai - 

Aujourd'hui c'est fourien™, petit tuto sans prétention!Pour décrire des images en utilisant une iA localement j'utilise LLaVA qui fait partie de (...)


antistress adventure in Flatpak land

 -  30 avril - 

Hello nal, ça faisait un bail !Certain (il se reconnaîtra) m'a demandé de le tenir au courant lorsque j'aurai basculé sur un usage de Firefox (...)


Téléphone sous Linux ?

 -  25 avril - 

Aujourd'hui, avoir un téléphone avec un Android libéré, c'est possible, on pense en particulier à Murena.Avoir un téléphone sous GNU/Linux, c'est (...)


Quand votre voiture vous espionne… et vous le fait payer

 -  23 avril - 

Ceci se passe aux États-Unis, pour l’instant, aucune preuve qu’une telle fuite existe en Europe. Mais… si votre assurance augmente brutalement, (...)