Aller au contenu

Décortiquer un PDF avec PDFLang

À l’écran, un PDF a l’air d’un bloc plat, la photo d’une page. Il n’en est rien. C’est une boîte de pièces, et la plupart des questions qu’on pose sur un fichier — est-ce que ça va s’imprimer correctement ? est-ce qu’il manque quelque chose ? — portent en réalité sur l’une de ces pièces.

PDFLang vous donne cinq façons d’aller à l’intérieur. Quatre sont déjà là quand votre script démarre. La cinquième, vous la dessinez.

Tout script commence avec une variable appelée doc. Rien à ouvrir, rien à charger : doc est le fichier en cours de vérification.

check "Qu'est-ce que je regarde" {
print("fichier :", doc.filename)
print("pages :", doc.page_count)
print("titre :", doc.title)
}

doc.title et doc.author viennent des métadonnées du fichier — les informations qu’un PDF porte sur lui-même et qu’on ne voit jamais sur la page. Elles sont souvent vides, parce que la personne qui a exporté le fichier ne les a jamais remplies. Cela mérite une vérification en soi :

check "Le fichier dit ce qu'il est" {
assert doc.title != "", "le PDF n'a pas de titre dans ses métadonnées"
}

doc contient aussi trois listes : doc.pages, doc.fonts et doc.images. Ce sont des listes ordinaires, donc tout ce qu’on fait à une liste marche sur elles.

doc.pages vous donne les pages une par une. Chacune connaît sa taille, la quantité d’encre qu’elle porte, et lesquelles de ses boîtes sont définies.

Les tailles sont en points, l’unité dans laquelle un PDF raisonne : 72 points par pouce, donc une page A4 fait 595 × 842. Vous n’êtes pas obligé de penser en points : écrivez 210mm ou 8.5in et la comparaison fonctionne.

check "Tout est en A4" {
doc.pages.each { |page|
assert page.width > 200mm,
"la page #{page.number} est trop étroite : #{page.width}pt"
}
}

Notez page.number. Les pages se comptent à partir de 1, comme dans une conversation : le numéro affiché dans un message d’échec est donc celui de la page qu’une personne ira ouvrir.

Puis il y a les boîtes. Une page prête pour l’impression porte plusieurs rectangles qui disent quoi en faire : la TrimBox est l’endroit où le massicot coupe, la BleedBox est la zone légèrement plus grande où la couleur doit déborder de la coupe, pour qu’un millimètre de dérive ne laisse pas un liseré blanc. Un fichier sans elles peut sembler parfait à l’écran et rester inutilisable chez l’imprimeur :

check "Prêt pour le massicot" {
doc.pages.each { |page|
assert page.has_trim_box, "la page #{page.number} n'a pas de TrimBox"
assert page.has_bleed_box, "la page #{page.number} n'a pas de BleedBox"
}
}

Une page rapporte aussi page.tac : la quantité d’encre à son endroit le plus chargé, en pourcentage. Quatre encres à pleine puissance feraient 400 %, ce qu’aucune presse n’accepte ; 300 % est un plafond courant.

doc.fonts liste les caractères utilisés par le fichier, et chacun répond à une seule question importante : est-il intégré ?

Une police intégrée voyage à l’intérieur du PDF. Une police non intégrée n’est que nommée, et la machine qui ouvre le fichier lui substitue ce qu’elle a. Le texte ne disparaît pas : il change de forme. Les lignes se recoupent ailleurs, un titre qui tenait sur une ligne en prend deux, et personne ne s’en aperçoit avant l’impression.

check "Chaque police voyage avec le fichier" {
doc.fonts.each { |font|
assert font.is_embedded, "la police '#{font.name}' n'est pas intégrée"
}
}

doc.images donne toutes les images du fichier, et page.images celles d’une page précise. La propriété qui vaut d’être comprise, c’est dpi.

DPI, ce sont les points par pouce — la quantité de détail dans chaque pouce imprimé. PDFLang ne rapporte pas le chiffre stocké dans les métadonnées de l’image. Il rapporte la résolution effective : les pixels que l’image possède réellement, divisés par la taille à laquelle elle est imprimée. Étirez une photo de 1000 pixels sur 20 centimètres et c’est une image basse définition, quoi qu’elle prétende. C’est ce chiffre qui décide si l’impression sera nette.

check "Assez net pour l'impression" {
doc.images.each { |img|
assert img.dpi >= 300,
"image page #{img.page_number} : #{img.dpi} DPI, il en faut 300"
}
}

img.color_space est l’autre propriété courante. L’offset travaille en CMJN ; une image encore en RVB doit être convertie, et la conversion décale les couleurs. Autant s’en rendre compte avant de graver les plaques.

Les quatre précédentes viennent avec le fichier. La cinquième, non : une region est un rectangle que vous définissez, pour interroger une partie de la page plutôt que la page entière.

pied = region(0, 0, 595, 60, "pied de page")
bandeau = region(20mm, 250mm, 60mm, 15mm, "bandeau d'avertissement")

La seule chose à retenir est la position de l’origine. Dans un PDF, 0, 0 est le coin inférieur gauche de la page, pas le supérieur. Y croît vers le haut.

Les regions permettent d’écrire les règles qui comptent vraiment pour un travail. Non pas « ce fichier contient-il la mention légale » mais « la mention légale est-elle dans le bandeau qui lui est réservé » :

check "Bandeau d'avertissement" {
bandeau = region(0, 700, 595, 142, "bandeau")
contenu = text::extract_from_region(1, bandeau)
assert contenu.contains("SUR ORDONNANCE"),
"le bandeau ne porte pas la mention obligatoire"
}

Les regions se comparent aussi. pied.intersects(entete) dit si deux zones se chevauchent : c’est ainsi qu’on attrape un élément parti dans un espace réservé à autre chose. pied.expand(5mm) et pied.inset(3mm) renvoient une copie plus large ou plus serrée, pour la tolérance.

Le chapitre sur les types du document liste chaque propriété et méthode des cinq, avec des exemples. C’est de la référence : à consulter au besoin, pas à lire d’un bout à l’autre.

Pour voir les cinq utilisés ensemble sur de vrais travaux, les recettes et les exemples sont l’endroit.

DigitalOceanMerci à DigitalOcean d’héberger ce site.