Les fichiers Adobe PDF sont devenus une norme pour les échanges de documents. Ces fichiers peuvent avoir un poids en octets conséquent notamment en raison de la présence d’images. S’il existe des solutions pour transmettre des fichiers lourds (WeTransfer par ex.) il peut s’avérer judicieux de réduire leur taille. Nous pouvons aussi avoir besoin de modifier les métadatas ou de joindre plusieurs fichiers ensembles. Pour cela nous disposons de nombreuses solutions. Celles que nous allons étudier reposent sur Ghostscript (logiciel gratuit) à partir d’un Terminal (bash, zsh, Powershell, etc.) soit à l’aide du code Python.
- Utilisation dans le terminal
- Automatisation
- Compiler les sources
Utilisation dans le terminal
Nous allons commencer par installer Ghostscript avant de voir les différents usages.
Installation
Windows
Nous installons Ghostscript à partir de la page de téléchargement. Sous Windows 11 nous choisissons la version 64 bit.
La version est installée dans le dossier C:\Program Files\gs
. A l’intérieur un sous-repertoire pour la version installée. Par exemple, un sous-répertoire gs10.02.1
.
Dans un sous-répertoire bin
, nous avons deux exécutables :
gswin64.exe
qui ouvre Ghostscript dans une fenêtregswin64c.exe
qui ouvre Ghostscript dans le terminal
Par exemple pour ouvrir Ghostscript dans Powershell nous utiliserons un de ces exécutables.
PS C:\Users\gilles> & 'C:\Program Files\gs\gs10.00.0\bin\gswin64c.exe'
GPL Ghostscript 10.0.0 (2022-09-21)
Copyright (C) 2022 Artifex Software, Inc. All rights reserved.
This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
see the file COPYING for details.
GS>
Pour une utilisation avec PowerShell il conviendra de prendre garde à la syntaxe. Par exemple pour le paramètre
CompatibilityLevel
nous écrirons-dCompatibilityLevel='1.4'
ou"-dCompatibilityLevel=1.4"
.
macOS
Pour installer Ghostscript dans sur macOS nous allons passer par le gestionnaire de package brew
% brew update
% brew upgrade
% brew install ghostscript
% brew brew cleanup
La mise à jour de Ghostscript se fait avec le paramètre upgrade
% brew upgrade ghostscript
A noter que pour afficher les fichiers PDF avec Ghostscript nous devons installer xquartz :
% brew install --cask xquartz
L’exécutable de Ghostscript est gs
. Pour connaître la version de Ghostscript :
% gs --version
9.56.1
Pour connaître l’emplacement de l’exécutable :
% which gs
/opt/local/bin/gs
Il est possible que le fichier à cet emplacement soit un lien symbolique. Pour vérifier cela nous tapons la ligne de commandes qui suit. Nous constatons que les informations du fichier commence par l
(link). ->
précise l’emplacement de l’exécutable lié et qui sera exécuté.
% ls -la /opt/local/bin/gs
lrwxr-xr-x 1 root wheel 47 15 déc 11:07 /opt/local/bin/gs -> /opt/homebrew/Cellar/ghostscript/10.02.1/bin/gs
Nous pouvons constater que par exemple la dernière version est installée par brew dans le dossier /opt/homebrew/Cellar/ghostscript
. A l’intérieur de cet emplacement nous avons un répertoire par version de ghostscript. Par exemple, nous avons un répertoire 10.01.2. A l’intérieur un sous-répertoire bin
avec l’exécutable gs
(pour Ghostscript).
% /opt/homebrew/Cellar/ghostscript/10.01.2/bin/gs
GPL Ghostscript 10.01.2 (2023-06-21)
Copyright (C) 2023 Artifex Software, Inc. All rights reserved.
This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
see the file COPYING for details.
Nous pouvons créer un lien symbolique vers l’exécutable pour un appel simplifié. Dans notre cas nous utiliserons `ghostscript` pour appeler l’exécutable `gs`. La commande `ln` avec l’option `-s` crée un lien symbolique vers un fichier.
% sudo rm /opt/local/bin/gs
% sudo ln -s /opt/homebrew/Cellar/ghostscript/10.01.2/bin/gs /opt/local/bin/ghostscript
% which ghostscript
/opt/local/bin/ghostscript
Linux
En fonction de la distribution utilisée, nous utiliserons les lignes de commandes qui suivent.
Pour les distributions à base de Debian
gilles@debian:~$ sudo apt update
gilles@debian:~$ sudo apt install ghostscript
Pour la distribution Arch Linux
[gilles@Archlinux ~]$ sudo pacman -U http://mirror.archlinuxarm.org/aarch64/extra/libpaper-2.1.1-1-aarch64.pkg.tar.xz
[gilles@Archlinux ~]$ sudo pacman -S ghostscript
Pour la distribution Alpine Linux
~ $ sudo apk update
~ $ sudo apk add ghostscript
Pour les distributions à base de Fedora
[gilles@fedora ~]$ sudo yum install ghostscript
L’exécutable de Ghostscript est gs
.
Pour construire l’application Ghostscript à partir des sources, nous procédons comme suit.
- Nous commençons par récupérer un URL valide vers l’archive contant les sources. Pour cela, nous allons sur la page de téléchargement et nous récupérons le lien (par ex. https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs10012/ghostscript-10.01.2.tar.gz]).
- Dans le terminal nous nous déplaçons dans le répertoire de téléchargement ou dans un répertoire temporaire quelconque. Nous allons décompresser dans ce répertoire les sources. Pour télécharger l’archive dans le terminal nous avons à notre disposition la commande
wget
. Si elle n’est pas disponible, tous les gestionnaires de paquets la répertorie dans leurs dépôts (par ex.sudo apt install wget
). - Nous dé-archivons le fichier téléchargé. A partir d’un gestionnaire de fenêtres, en double cliquant dessus nous allons pouvoir le faire automatiquement. Dans le terminal nous utiliserons une ligne de commandes pour dé-archiver :
tar -xzf ghostscript-10.01.2.tar.gz
. Un sous répertoire est créé. Les sources sont à l’intérieur - Nous définissons comme répertoire en cours la racine de ce répertoire. Par ex.
cd ghostscript-10.01.2
- A l’intérieur, nous exécutons le fichier de configuration
./configure
. Ce fichier va préparer la compilation à partir des données collectés dans le système (architecture par exemple). Un fichiermakefile
est créé. - Pour lancer la compilation du code nous tapons la commande
make
. Cette opération peut être plus ou moins longue en fonction de la puissance de l’ordinateur. - Enfin, pour installer le code compilé dans notre système, nous exécuterons en mode super utilisateur la commande
sudo make install
En résumé, nous pouvons exécuter les commandes suivantes :
[gilles@Archlinux Downloads]$ wget https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs10012/ghostscript-10.01.2.tar.gz
[gilles@Archlinux Downloads]$ tar -xzf ghostscript-10.01.2.tar.gz
[gilles@Archlinux Downloads]$ cd ghostscript-10.01.2
[gilles@Archlinux ghostscript-10.01.2]$ ./configure
[gilles@Archlinux ghostscript-10.01.2]$ make
[gilles@Archlinux ghostscript-10.01.2]$ sudo make install
Options
Ghostscript est une logiciel en ligne de commandes. Par défaut, il va s’exécuter en mode interactif. L’utilisateur doit entrer les commandes à la suite d’un prompt et les valider par appui sur la touche entrée. Pour sortir de ce mode, il faut taper la commande quit
.
Pour ce qui nous concerne il est plus intéressant de l’utiliser en mode shell, sans interaction. A la suite du nom de la commande Ghostscript (par ex. gs
) nous saisirons une suite de paramètres et d’arguments, ainsi que le nom des fichiers à traiter en entrée et en sortie.
Commandes | Descriptions |
---|---|
gs -h | Affiche l’aide et la configuration de base, notamment la liste des périphériques disponibles (device). |
-q ou -dQUIET | Supprime l’affichage des messages de démarrage (version, copyright) |
-f | Définir un fichier en entrée |
-d | Affecte une valeur à un paramètre interne. Cette valeur doit avoir un type de données reconnue par Ghostscript comme par exemple false , true ou null . Ce peut être également une valeur numérique. |
-s | Affecte sous forme de texte une valeur à un paramètre interne |
-dNODISPLAY | Utilise un périphérique NUL qui ne génère pas de sortie. Cela remplace le périphérique de sortie par défaut ou défini avec -sDEVICE= . A utiliser dans le cas d’une évaluation. |
-dPDFSETTINGS= | Profile Adobe Distiller pour traiter le fichier en sortie. Ils comportent des préréglages en fonction de la destination. Par ex. /screen . Les valeurs possibles sont : \screen (résolution basse, optimisé pour les écrans), \ebook (résolution moyenne, optimisé pour livre numérique), \printer (résolution élevée, optimisé pour impression), \prepress (optimisé prepress) et \default |
-sDEVICE= | Défini le périphérique de sortie. C’est par exemple un fichier Adobe PDF avec pdfwrite . |
-sPAPERSIZE= | Défini les dimensions par défaut des pages. Par ex. -sPAPERSIZE=a4 ou -sPAPERSIZE=legal |
-dFIXEDMEDIA | Fixe les dimensions des médias (images) pour les adapter |
-dPDFFitPage | Ajuste le contenu au dimension de la page |
-dDEVICEWIDTHPOINTS=w | Définir la largeur d’une page en DPI (point par pouce, 1 point = 1/72 de pouce ). Par exemple |
-dDEVICEHEIGHTPOINTS=h | Définir la hauteur d’une page en DPI (point par pouce, 1 point = 1/72 de pouce ). Par exemple -dDEVICEHEIGHTPOINTS=300 |
-dMonoImageResolution= | Résolution en DPI pour les images monochrome. Par ex. -dMonoImageResolution=100 |
-dColorImageResolution= | Résolution en DPI pour les images couleur. Par ex. 100 |
-dGrayImageResolution= | Résolution en DPI pour les images en niveaux de gris. Par ex. 150 |
-dDownsampleColorImages= | Autorise la modification de la résolution des images couleurs. true pour autoriser. |
-dColorImageDownsampleType= | Défini la méthode de transformation de la résolution des images. Les valeurs possibles sont : /Subsample , /Average et /Bicubic . |
-dAntiAliasColorImages= | Active le traitement anti-alias des images couleurs. true pour autoriser. |
-dDownsampleGrayImages= | Autorise la modification de la résolution des images en niveaux de gris. true pour autoriser. |
-dGrayImageDownsampleType= | Défini la méthode de transformation de la résolution des images. Les valeurs possibles sont : /Subsample , /Average et /Bicubic . |
-dAntiAliasGrayImages= | Active le traitement anti-alias des images en niveaux de gris. true pour autoriser. |
-dDownsampleMonoImages= | Autorise la modification de la résolution des images monochromes. true pour autoriser. |
-dMonoImageDownsampleType= | Défini la méthode de transformation de la résolution des images. Les valeurs possibles sont : /Subsample , /Average et /Bicubic . |
-dAntiAliasMonochromeImages= | Active le traitement anti-alias des images en monochrome. true pour autoriser. |
| Appliquer une couleur noire à tous les contenus textes. Cela ne concerne pas le texte dans les images. |
-dBlackVector | Appliquer une couleur noire à tous les contenus vectoriels. |
-sOCRLanguage= | Définir la langue utilisée pour la reconnaissance de caractères (OCR). Par ex. -sOCRLanguage="fra" . |
-dOCREngine= | Choisir le moteur OCR à utiliser (0 pour Legacy Tesseract et 1 pour LSTM/Neural Network). |
-dBATCH | N’ouvre pas la console de Ghostscript et traite directement le ou les fichiers en entrée. Ghostscript se ferme automatiquement quand toutes les opérations sont terminées. A utiliser pour l’utilisation dans des scripts |
-dNOPROMPT | En mode interactif, masque l’invite pour saisir de Ghostscript |
-dNOPAUSE | Désactive la pause automatique après chaque page. Il est utilisé conjointement avec Normalement, on -dBATCH . |
-sColorConversionStrategy= | Défini la stratégie de conversion des couleurs avec une chaîne de caractères. Les valeurs possibles sont : LeaveColorUnchanged , Gray , RGB , ou CMYK . |
-dColorConversionStrategy= | Défini la stratégie de conversion des couleurs. Les valeurs possibles sont : /LeaveColorUnchanged , /Gray , /RGB , ou /CMYK . |
-dCompatibilityLevel= | Niveau de compatibilité. Par ex. 1.4 |
-o ou -sOutputFile= | Définir le fichier de sortie avec les options -dBATCH et -dNOPAUSE . Par ex. -o out.pdf ou -sOutputFile=out.pdf |
-sCompression= | Type de compression None , LZW , Flate , jpeg et RLE |
-dDetectDuplicateImages= | Regroupe en une seule images les images ayant un hashage identique. La valeur par défaut est à true . |
-c "..." | Définir une commande Postscript et demander à Ghostscript de l’interpréter. La ou les commandes sont placées entre double guillemets. |
-dFirstPage= | Début de la page à traiter. Par ex. -dFirstPage=3 pour ignorer les 2 premières pages |
-dLastPage= | Dernière page à traiter. Par ex. -dLastPage=182 pour ignorer les pages après la page 182 |
-sPageList= | Définir les pages à traiter. Il peut y avoir trois types de valeurs : odd (toutes les pages impaires), even (toutes les pages paires) et une liste de pages. La liste de page peut être formalisée par une liste de numéros de page séparé par une virgule (par ex. -PageList=2,30,42 ), une plage de page avec le signe moins (par ex. -PageList=2-42 ), et une liste de pages et de plages de pages (par ex. -PageList=2,7,10-29,40 ). |
-sOwnerPassword= | Définir le mot de passe du propriétaire du document. Le document peut être ouvert, mais il sera limité aux permissions accordés par le propriétaire (modification, impression, etc.). |
-sUserPassword= | Définir le mot de passe de l’utilisateur lors de l’ouvrir de document. S’il n’est pas défini, le document peut être ouvert sans mot de passe. Par contre pour les permissions comme l’impression, il faut le mot de passe du propriétaire (-sOwnerPassword= ). |
-dEncryptionR= | Définir la méthode d’encryptage. Pour une utilisation avec les versions 1.4 et suivantes, nous utiliserons gestionnaire de sécurité révision 3 ( ) |
-dKeyLength= | Définir la longueur en bits de la clef d’encryption. Pour la révision 3 du gestionnaire de sécurité est de 128. |
-dPermissions= | Définir sous forme de bit les autorisations PDF (impression, copie du texte ou des graphiques, insertion, rotation, suppression de pages, ajout d’annotations ou de signatures, remplissage des champs de formulaire). Par ex. pour n’autoriser que l’impression -dPermissions=196 |
-sPDFPassword= | Définir le mot de passe pour ouvrir un document protégé. |
Afficher un PDF
Par exemple, sous Linux, nous indiquons à Ghostscript le fichier à afficher.
[gilles@Archlinux Downloads]$ gs cours-python.pdf
GPL Ghostscript 10.01.2 (2023-06-21)
Copyright (C) 2023 Artifex Software, Inc. All rights reserved.
This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
see the file COPYING for details.
Unknown .defaultpapersize: (A4).
Processing pages 1 through 298.
Page 1
>>showpage, press <return> to continue<<
Obtenir des informations
Pour récupérer les informations disponibles dans un fichier PDF et afficher les métadatas nous procéderons comme suit :
% gs -dQUIET -dBATCH -dNODISPLAY -dNOPAUSE -dPDFINFO "merge.pdf"
(null) has 15 pages
Title: ??
Author: Gilles
Subject:
Keywords:
Creator: ??
Producer: GPL Ghostscript 9.56.1
CreationDate: D:20230718174033+02'00'
ModDate: D:20230718174033+02'00'
La date et l’heure sont définies au format D:%Y%m%d%H%M%S
ou `D:%Y%m%d%H%M%S%z` (avec prise en compte de la Timezone). Pour convertir une date en utilisant le langage Python nous procéderons ainsi :
from datetime import datetime, timezone
from tzlocal import get_localzone
# PDF Date -> date
dateShortPDF=datetime.strptime('D:20230719101359','D:%Y%m%d%H%M%S')
print(dateShortPDF) # -> 2023-07-19 10:13:59
# PDF Date avec Timezone -> date
dateTimezonePDF = datetime.strptime("D:20231226132119+01'00'".replace("'",':')[:-1],'D:%Y%m%d%H%M%S%z')
print(dateTimezonePDF) # -> 2023-12-26 13:21:19+01:00
# date -> PDF Date
dateActuelle=datetime.strftime(datetime.now(), 'D:%Y%m%d%H%M%S')
print(dateActuelle) # -> 'D:20230719101359'
# date -> PDF Date avec TimeZone
dateTimezoneActuelle = datetime.strftime(datetime.now(timezone.utc).astimezone(get_localzone()), 'D:%Y%m%d%H%M%S%z')
dateTimezoneActuelle = f"{dateTimezoneActuelle[:-2]}'{dateTimezoneActuelle[19:]}'"
Pour afficher le détail des documents PDF il existe une alternative avec Poppler. C’est une bibliothèque open source spécialisée pour les documents PDF.
% brew install poppler
Pour afficher les informations d’un fichier PDF nous passerons son nom en paramètre à pdfinfo
.
% pdfinfo "/Users/gilles/Downloads/source.pdf"
Title:
Creator: Adobe Acrobat 22.1
Producer: Adobe Acrobat 22.1 Image Conversion Plug-in
CreationDate: Wed Nov 8 19:01:34 2023 CET
ModDate: Wed Nov 8 19:01:34 2023 CET
Custom Metadata: no
Metadata Stream: yes
Tagged: no
UserProperties: no
Suspects: no
Form: none
JavaScript: no
Pages: 567
Encrypted: no
Page size: 532.8 x 646.08 pts
Page rot: 0
File size: 438948393 bytes
Optimized: yes
PDF version: 1.6
Les dimensions de la page (page size) sont en points. Il y a 72 points dans un inch (dpi ou dot per inch) ou 72 pixels dans un inch (ppi ou pixel per inch). Un inch (pouce) mesure 2.54 centimètres.
Pour les convertir nous appliquons la formule suivante : valeur en point multipliée par 1/72 et multipliée par 2.54 pour obtenir une valeur en centimètres. Dans notre exemple, nous évaluons la largeur 532.8
soit 18.796 cm
. La hauteur sera de 22.792 cm
.
Pour connaître le détail des images d’un document PDF, nous exécuterons la ligne suivante :
% pdfimages -list "/Users/gilles/Downloads/source.pdf"
page num type width height color comp bpc enc interp object ID x-ppi y-ppi size ratio
--------------------------------------------------------------------------------------------
1 0 image 2220 2692 rgb 3 8 jpeg no 3564 0 300 300 945K 5.4%
2 1 image 2220 2692 rgb 3 8 jpeg no 4 0 300 300 497K 2.8%
3 2 image 2220 2692 rgb 3 8 jpeg no 8 0 300 300 581K 3.3%
4 3 image 2220 2692 rgb 3 8 jpeg no 12 0 300 300 666K 3.8%
...
Dans ce tableau nous identifions la largeur (pixels), hauteur (pixels), mode couleur (RGB ou niveaux de gris) et la densité de points par pouce (inch) en largeur (x) et en hauteur (y). Nous pouvons évaluer la largeur et la hauteur d’une image en centimètres :
\[largeur = {2220 * 2.54\;\text{cm} \over 300\;\text{dpi}} = 18.796\;\text{ cm}\]
\[hauteur = {2692 * 2.54\;\text{cm} \over 300\;\text{dpi}} = 22.792\;\text{ cm}\]
Pour connaître le nombre de points par pouce à partir d’une largeur en pixels et en centimètres, nous écrirons la formule suivante :
\[\text{dpi} = {\text{Nbr pixels} * 2.54\;\text{cm} \over \text{longueur en cm}} = {2220\;\text{pixels} * 2.54\;\text{cm} \over 18.796\;\text{cm}} = 300\;\text{dpi}\]Pour connaître le nombre de pixels à partir des DPI et de la longueur en centimètres.
\[\text{pixels} = {\text{dpi} * \text{longueur en cm} \over 2.54\;\text{cm}} = {300\;\text{dpi} * 18.796\;\text{cm} \over 2.54\;\text{cm}} = 2220\;\text{pixels}\]A partir de ces informations, nous pouvons ajuster un document PDF pour une impression optimale. Par exemple pour imprimer en 300 dpi une page (A4 21 cm x 29.7 cm) avec une image pleine plage, la résolution en pixels sera :
\[largeur = {300 * 21\;\text{cm} \over 2.54\;\text{cm}} = 2480\;\text{pixels}\]
\[hauteur = {300 * 29.7\;\text{cm} \over 2.54\;\text{cm}} = 3508\;\text{pixels}\]
Pour aller plus loin sur le sujet, pour une visualisation optimum sur un écran spécifique (je pense notamment à une liseuse ou une tablette), il faut évaluer la résolution idéale.
Par exemple pour un écran 24″ full hd (1920 x 1080) nous aurons une densité de pixels de 92 [PPI](https://en.wikipedia.org/wiki/Pixel_density) (Pixels per inch).
\[ppi = {\text{largeur en pixels} \over \text{largeur de l’écran en pouce (inch)}}\]Attention, il s’agit de la largeur d’un écran en pouce, et non pas de la diagonale. Par exemple pour un écran Huawei MateView 28.2″ nous avons approximativement une largeur de 23.95 pouces pour une résolution horizontale de 3840 pixels.
\[ppi = {3840\;\text{pixels} \over 23.95\;\text{pouce (inch)}} = 160 \;\text{ppi}\]Pour un liseuse, par exemple une Amazon Kindle Scribe 10.2″ (1860×2480 pixels), nous avons une densité de 300 PPI. Pour récupérer la largeur en pouce, nous appliquerons la formule suivante :
\[largeur en pouces = {\text{largeur en pixels} \over \text{ppi}} = {1860\over300} = 6.2 \text{ pouces}\]Pour obtenir une longueur en centimètre, nous devons multiplier une valeur en pouce par 2.54 cm.
Ces formules peuvent également servir pour évaluer les dimensions idéales en pixels d’une image pour une impression en 300 dpi sur du papier en 10 x 15 cm :
\[\text{largeur en pixels} = { 300 \text{ dpi} * 10 \text{ cm} \over 2.54 \text{ cm} } = 1181 \text{ pixels}\] \[\text{hauteur en pixels} = { 300 \text{ dpi} * 15 \text{ cm} \over 2.54 \text{ cm} } = 1772 \text{ pixels}\]La résolution en mégapixels de cette image est évaluée avec la formule suivante :
\[\text{résolution en mpx} = {{1181\text{ pixels} * 1772\text{ pixels}} \over 1000000\text{ pixels}} = 2.10 \text{ mégapixels}\]Inversement pour connaître à partir des dimensions en pixels d’une photo, nous pouvons calculer pour une valeur de DPI la dimension d’impression maximale. Par exemple pour un fichier JPEG issu d’un boitier hybride Sony ILCE-7M4 de 33 mpx (7008 × 4672), les dimensions à 200 DPI sont :
\[\text{largeur en cm} = { 7008 \text{ dpi} * 2.54\text{ cm} \over 200\text{ dpi} } = 89\text{ cm}\] \[\text{hauteur en cm} = { 4672 \text{ dpi} * 2.54\text{ cm} \over 200\text{ dpi} } = 59\text{ cm}\]
Modifier les métadatas
Les métadatas disponibles par défaut sont :
Métadatas | Descriptions |
---|---|
/Title | Titre du document |
/Author | Auteur du document |
/Subject | Sujet ou objet du document |
/Keywords | Mots-clefs séparés par une virgule |
/ModDate | Date de modification |
/Creator | Créateur |
/Producer | Outil de création du document |
/CreationDate | Date de création |
Le principe pour modifier les métadatas du document est le suivant :
- ajouter le paramètre
-c
- entre doubles guillemets, nous commençons par
[
- chaque métadata à modifier est insérée avec la nouvelle valeur placée entre parenthèses.
- avant les doubles guillemets de fin nous ajoutons
/DOCINFO pdfmark
Par exemple : -c "[/Title (Mon document PDF) /Producer (Gilles) /DOCINFO pdfmark"
% gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -sColorConversionStrategy=Gray -dDownsampleGrayImages=true -dGrayImageResolution=150 -sGrayImageDownsampleType=Subsample -dCompatibilityLevel=1.4 -sOutputFile=/Users/gilles/output.pdf /Users/gilles/input.pdf -c "[/Title (Mon document PDF) /Producer (Gilles) /DOCINFO pdfmark"
Déverrouiller
Un document PDF peut être protégé par un mot de passe en ouverture. Pour récupérer par exemple des informations sur ce fichier, nous devrons transmettre le mot de passe avec -sPDFPassword=
.
% gs -dQUIET -dBATCH -dNODISPLAY -dNOPAUSE -sPDFPassword=XXXXXXXX -dPDFINFO "crypte.pdf"
(null) has 2 pages
Title: /var/www/kalilab-tmp/ktag8f9l8f.rtf
Author: Laurent Schlegel
Creator: Ted: http://www.nllgg.nl/Ted
Producer: Ted: http://www.nllgg.nl/Ted
CreationDate: D:202108121018
ModDate: D:202109141311
Pour définir un mot de passe à l’ouverture d’un fichier nous utiliserons les options cumulées -sOwnerPassword=
(mot de passe administrateur pour permettre le contrôle de la lecture) et -sUserPassword=
(mot de passe qui sera demandé à l’ouverture).
gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -dCompatibilityLevel=2.0 -sOwnerPassword=test -sUserPassword=mot-de-passe -sOutputFile="/Users/gilles/Downloads/dossier-transforme.pdf" -f "/Users/gilles/Downloads/dossier.pdf"
Protéger
Les documents PDF peuvent être protégés en lecture, mais il est également possible certaines actions. Il est possible d’interdire l’impression d’un document (même si on peut toujours imprimer une copie d’écran) ou la modification.
Pour les possesseurs de MacOS ces permissions sont visibles dans l’inspecteur (⌘I).

Techniquement, pour protéger un document PDF en modulant les permissions en fonction des actions autorisées, nous devons assigner à la variable Permissions
une valeur sur 32 bits. Cette valeur est le résultat de l’activation ou non des bits. Le 1er bit est à droite.
Bits | Descriptions |
---|---|
1-2 | Mettre à 0 |
3 | Autoriser l’impression mettre à 1 |
4 | Autoriser les modifications (insertion, rotation, ou suppression de page) |
5 | Autoriser la copie/extraction non contrôlée par le bit 10 |
6 | Autoriser les annotations et le remplissage de formulaire. Bit 4 défini afin d’autoriser la forme interactive |
7-8 | Mettre à 1 (bits réservés) |
9 | Autoriser le remplissage des champs de formulaire existants |
10 | Autoriser la copie des textes et des graphiques |
11 | Mettre à 0 |
12 | Pour activer l’impression haute résolution mettre à 1 |
13-32 | Mettre à 0 pour le gestionnaire de sécurité révision 3, sinon à 1 pour la révision 2. |
Il est possible de construire la valeur à l’aide de Python. Dans le REPL, nous pouvons saisir une valeur binaire (les bits sont précédés des caractères 0b
). Par exemple ici nous allons autoriser tout en activant les bits 3 à 6, ainsi que les bits 9 et 10.
% python3
Python 3.12.0 (v3.12.0:0fb18b02c8, Oct 2 2023, 09:45:56) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 0b1111111100
1020
Une fois notre valeur définie de permissions, nous devons ajouter un mot de passe propriétaire et définir des paramètres d’encryption. Le mot de passe se définit avec -sOwnerPassword=
.
% /opt/local/bin/gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -dCompatibilityLevel=2.0 -sOwnerPassword=test -dEncryptionR=3 -dKeyLength=128 -dPermissions=1020 -sOutputFile="/Users/gilles/Downloads/pdf_reduce/0-Dossier vente Aquarelle.pdf" -f "/Users/gilles/Downloads/pdf1/0-Dossier vente Aquarelle.pdf"
Joindre plusieurs fichiers
Nous allons joindre plusieurs fichiers PDF en un seul. Nous en profitons pour convertir toutes les pages en niveau de gris.
% gs -sDEVICE=pdfwrite -dNOPAUSE -sColorConversionStrategy=Gray -dCompatibilityLevel=1.4 -sOutputFile='merge.pdf' -dBATCH 'source1.pdf' 'source2.pdf' 'source3.pdf'
Convertir en PDF/A
Un document PDF/A (Portable Document Format Archivable) est une variante normalisée ISO. Ce type de document a été créé pour réaliser un archivage long et apporter des garanties de conservation. Ghostscript propose d’ajouter le paramètre -dPDFA=1
.
% gs -dPDFA=1 -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -sColorConversionStrategy=Gray -dDownsampleGrayImages=true -dGrayImageResolution=150 -dGrayImageDownsampleType=/Bicubic -dCompatibilityLevel=1.4 -sOutputFile="/Users/gilles/Downloads/destination.pdf" "/Users/gilles/Downloads/source.pdf"
Orientation
Une propriété -dAutoRotatePages=
a pour mission d’activer ou non l’auto-rotation. Si nous lui passons en valeur /PageByPage
ou /All
, les pages seront redressées automatiquement, et ce quelque soit l’orientation, de manière à faciliter la lecture. Par contre, cela ne modifiera pas l’orientation de la page. Cela ne joue que sur l’affichage. Pour désactiver l’auto-rotation nous affecterons la valeur /None
. Dans ce cas la valeur affectée à setpagedevice
est utilisée.
Pour changer physiquement l’orientation des pages nous devons ajouter du code Postscript au document. Pour cela, nous allons utiliser la commande -c "<</Orientation {orientation}>> setpagedevice"
avec {orientation}
représentée par une valeur :
0
pour aucune rotation (portrait)1
pour 90° vers en sens inverse des aiguilles d’une montre (landscape ou paysage)2
pour 180° (upside down ou à l’envers)3
pour 90° dans le sens des aiguilles d’une montre (seascape)
/opt/local/bin/gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -dAutoRotatePages=/None -dCompatibilityLevel=2.0 -sOutputFile="/Users/gilles/Downloads/pdf_reduce/dossier.pdf" -c "<</Orientation 3>> setpagedevice" -f "/Users/gilles/Downloads/pdf/dossier.pdf"
L’injection du code Postscript doit se faire avant l’importation du fichier source. Le commutateur -f
doit précéder le fichier source.
Redimensionnement
Un document PDF peut comporter des pages de dimensions différentes. Si à l’affichage cela ne pose pas de problème, l’impression d’un document va générer des difficultés. Le bac d’alimentation ne comporte en général qu’un seul format de papier.
Avec Ghostscript nous avons deux approches pour redimensionner les pages.
La première consiste à utiliser un format pré-existant comme par exemple le format A4. Les formats sont transmis en argument de l’option -sDEFAULTPAPERSIZE=
.
% /opt/local/bin/gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -sPAPERSIZE=a4 -dFIXEDMEDIA -dPDFFitPage -dCompatibilityLevel=2.0 -sOutputFile="/Users/gilles/Downloads/pdf_reduce/dossier.pdf" "/Users/gilles/Downloads/pdf/dossier.pdf"
Formats | Largeurs | Hauteurs | Normes |
---|---|---|---|
a0 | 841 | 1189 | ISO STANDARD |
a1 | 594 | 841 | ISO STANDARD |
a2 | 420 | 594 | ISO STANDARD |
a3 | 297 | 420 | ISO STANDARD |
a4 | 210 | 297 | ISO STANDARD |
a4small | 210 | 297 | ISO STANDARD |
a5 | 148 | 210 | ISO STANDARD |
a6 | 105 | 148 | ISO STANDARD |
a7 | 74 | 105 | ISO STANDARD |
a8 | 52 | 74 | ISO STANDARD |
a9 | 37 | 52 | ISO STANDARD |
a10 | 26 | 37 | ISO STANDARD |
isob0 | 1000 | 1414 | ISO STANDARD |
isob1 | 707 | 1000 | ISO STANDARD |
isob2 | 500 | 707 | ISO STANDARD |
isob3 | 353 | 500 | ISO STANDARD |
isob4 | 250 | 353 | ISO STANDARD |
isob5 | 176 | 250 | ISO STANDARD |
isob6 | 125 | 176 | ISO STANDARD |
c0 | 917 | 1297 | ISO STANDARD |
c1 | 648 | 917 | ISO STANDARD |
c2 | 458 | 648 | ISO STANDARD |
c3 | 324 | 458 | ISO STANDARD |
c4 | 229 | 324 | ISO STANDARD |
c5 | 162 | 229 | ISO STANDARD |
c6 | 114 | 162 | ISO STANDARD |
11×17 | 279 | 432 | US STANDARD |
ledger | 432 | 279 | US STANDARD |
legal | 216 | 356 | US STANDARD |
letter | 216 | 279 | US STANDARD |
lettersmall | 216 | 279 | US STANDARD |
archE | 914 | 1219 | US STANDARD |
archD | 610 | 914 | US STANDARD |
archC | 457 | 610 | US STANDARD |
archB | 305 | 457 | US STANDARD |
archA | 229 | 305 | US STANDARD |
jisb0 | 1030 | 1456 | JIS STANDARD |
jisb1 | 728 | 1030 | JIS STANDARD |
jisb2 | 515 | 728 | JIS STANDARD |
jisb3 | 364 | 515 | JIS STANDARD |
jisb4 | 257 | 364 | JIS STANDARD |
jisb5 | 182 | 257 | JIS STANDARD |
jisb6 | 128 | 182 | JIS STANDARD |
flsa | 216 | 330 | Autres |
flse | 216 | 330 | Autres |
halfletter | 140 | 216 | Autres |
hagaki | 100 | 148 | Autres |
La deuxième approche consiste à définir les dimensions en utilisant les options -dDEVICEWIDTHPOINTS=<largeur>
et -dDEVICEHEIGHTPOINTS=<hauteur>
. Les dimensions sont en points (1 pouce/inch = 72 points et 1 pouce/inch = 2.54 centimètres). Par exemple, pour appliquer un format de page de 10 x 10 cm nous pour vous exécuter la commande qui suit :
% /opt/local/bin/gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -dDEVICEWIDTHPOINTS=283.46 -dDEVICEHEIGHTPOINTS=283.46 -dFIXEDMEDIA -dPDFFitPage -dCompatibilityLevel=2.0 -sOutputFile="/Users/gilles/Downloads/pdf_reduce/dossier.pdf" "/Users/gilles/Downloads/pdf/dossier.pdf"
Il faut pas oublier d’ajouter les options -dFIXEDMEDIA -dPDFFitPage
pour adapter les pages au format.
Réduction de la taille des documents
Profile Distiller
L’utilisation de profiles inspirés d’Adobe Distiller est une approche simple pour réduire le poids d’un document. Ces profiles utilisent des réglages adaptés en fonction de l’appareil qui affichera le PDF.
Pour afficher la liste des profiles supportés par l’option -dPDFSETTINGS=...
nous utiliserons la commande suivante sous macOS ou Linux :
% gs -dNODISPLAY -c ".distillersettings {exch ==only ( ) print ==} forall quit"
GPL Ghostscript 10.02.1 (2023-11-01)
Copyright (C) 2023 Artifex Software, Inc. All rights reserved.
This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
see the file COPYING for details.
/ebook -dict-
/printer -dict-
/default -dict-
/prepress -dict-
/screen -dict-
/PSL2Printer -dict-
Sous Windows, dans Powershell nous utiliserons la même commande.
PS C:\Users\gille> & 'C:\Program Files\gs\gs10.00.0\bin\gswin64c.exe' -dNODISPLAY -c ".distillersettings {exch ==only ( ) print ==} forall quit"
GPL Ghostscript 10.0.0 (2022-09-21)
Copyright (C) 2022 Artifex Software, Inc. All rights reserved.
This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
see the file COPYING for details.
/default -dict-
/prepress -dict-
/screen -dict-
/PSL2Printer -dict-
/ebook -dict-
/printer -dict-
Pour afficher les paramètres par défaut d’un profile comme par exemple /screen
:
% gs -q -dNODISPLAY -c ".distillersettings /screen get {exch ==only ( ) print ===} forall quit" | sort
/AutoRotatePages /PageByPage
/CannotEmbedFontPolicy /Warning
/ColorACSImageDict << /ColorTransform 1 /Blend 1 /HSamples [2 1 1 2] /VSamples [2 1 1 2] /QFactor 0.76 >>
/ColorConversionStrategy /sRGB
/ColorImageDownsampleType /Average
/ColorImageResolution 72
/CompatibilityLevel 1.5
/CreateJobTicket false
/DoThumbnails false
/EmbedAllFonts true
/GrayACSImageDict << /ColorTransform 1 /Blend 1 /HSamples [2 1 1 2] /VSamples [2 1 1 2] /QFactor 0.76 >>
/GrayImageDownsampleType /Average
/GrayImageResolution 72
/MonoImageDownsampleType /Subsample
/MonoImageResolution 300
/NeverEmbed [/Courier /Courier-Bold /Courier-Oblique /Courier-BoldOblique /Helvetica /Helvetica-Bold /Helvetica-Oblique /Helvetica-BoldOblique /Times-Roman /Times-Bold /Times-Italic /Times-BoldItalic /Symbol /ZapfDingbats]
/PreserveEPSInfo false
/PreserveOPIComments false
/PreserveOverprintSettings false
/UCRandBGInfo /Remove
A la suite d’un paramètre de sortie (ici screen
) nous pouvons modifier certains paramètres comme la résolution de sortie pour les images (ici pour les images monochrome, niveau de gris et couleur).
% gs -dPDFSETTINGS=/screen -sDEVICE=pdfwrite -dMonoImageResolution=150
-dColorImageResolution=100 -dGrayImageResolution=150
-dBATCH -dNOPROMPT -dNOPAUSE -sOutputFile="/Users/gilles/Documents/Archives/temporaire/pdf-out.pdf" "/Users/gilles/Documents/Archives/temporaire/pdf-in.pdf"
Niveaux de gris
Pour réduire le poids d’un document, une approche consiste à transformer toutes les images en niveaux de gris. En effet, une image avec cette colorimétrie utilise 256 niveaux de gris. Le nombre d’informations stockées pour une image sera réduit.
Nous personnaliserons la ligne de commandes pour forcer ce type de conversion.
-sColorConversionStrategy=Gray
pour appliquer une conversion vers du niveaux de gris à toutes les images.-dGrayImageResolution=100
pour modifier la densité de points par pouce (DPI ou nombre de points imprimés sur une ligne). Un pouce (inch) est égale à 2.54 cm. Une image de 10 cm x 5 cm aura pour une densité de 200 dpi aura environ 787 points en largeur (10 cm / 2.54 x 200 dpi). Plus la résolution en DPI est élevée, plus l’image sera volumineuse. A contrario, une résolution faible allégera le poids de l’image avec en contre-partie une détérioration de la qualité.-dDownsampleGrayImages=true
pour activer le changement de résolution.
Pour réduire la taille de l’image nous allons jouer sur le nombre de DPI.
% gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -sColorConversionStrategy=Gray -dDownsampleGrayImages=true -dGrayImageResolution=100 -dCompatibilityLevel=1.4 -sOutputFile="/Users/gilles/Downloads/destination.pdf" "/Users/gilles/Downloads/source.pdf"
Pour les utilisateurs de MacOS (et Linux) il est toujours possible d’intégrer une commande au shell qui
Pour les utilisateurs du shell ZSH nous nous plaçons à la racine de l’utilisateur, et nous ouvrons le fichier .zshrc
(pour Bash nous éditerons .bash_profile
).
% cd
% nano .zshrc

Nous ajoutons ensuite le code suivant à la fin du fichier. Pour sauvegarder nous faisons les touches CTRL+X puis Y et entrée.
pdfcompress()
{
gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -sColorConversionStrategy=Gray -dDownsampleGrayImages=true -dGrayImageResolution=100 -dCompatibilityLevel=2.0 -sOutputFile=${1%.*}_reduce.pdf $1
}
Pour essayer, nous ouvrons un nouveau terminal. Nous tapons simplement pdfcompress
suivi du nom du fichier à compresser.
Noir et blanc
Pour des documents PDF ne comportant que des objets textes, il est possible de forcer les couleurs au noir avec -dBlackText
. Cela n’affectera pas les images.
gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -dBlackText -dBlackVector -dCompatibilityLevel=2.0 -sOutputFile="/Users/gilles/Downloads/test-nb.pdf" -f "/Users/gilles/Downloads/test.pdf"
Pour les documents PDF comportant des images, la solution repose sur des opérations successives. La première consiste à utiliser le device
Device : pngmono, pngmonod, png16m, png16, png256, png48, pnggray
% mkdir img
% gs -q -dBATCH -dNOPAUSE -sDEVICE=png16m -r300 -sOutputFile=img/out_%03d.png -f "/Volumes/seagate/Download/Ebook/Informatique/eni csharp 10 et visual studio code.pdf"
Récupère dans une variable toutes les images
% files=$(find ./img -type f -name "*.png")
% sources="";for file in `find ./img -type f -name "*.jpg"`; do sources="${sources} (${file}) viewJPEG showpage" done
%
Reconnaissance de caractères (OCR)
Ghostscript intègre depuis la version 9.54 un moteur de reconnaissance de caractères basé sur Tesseract et Leptonica. La reconnaissance se fait sur des médias images (par exemple issues d’un scanner).
La reconnaissance est intégrée sous forme de devices :
ocr
pour une conversion d’un fichier PDF vers un fichier texte. Les images sont convertis en niveaux de gris pour être analysées par Tesseract. Le fichier texte est encodé en UTF-8.hocr
pour une conversion d’un fichier PDF vers un fichier XML au standard hOCRpdfocr24
(RGB),pdfocr32
(CMYK) oupdfocr8
(niveau de gris) pour une conversion d’un fichier PDF vers un fichier PDF sans modifier le fichier source. Les images sont conservées comme tel. Le texte reconnu est ajouté au fichier. Il ne sera pas disponible à l’affichage (invisible-text-only PDF). Il sera par contre reconnu pour la recherche et le copier/coller dans le document.
Tesseract offre plusieurs moteurs pour opérer la reconnaissance. Pour choisir un moteur avec Ghostscript nous avons à notre disposition l’option -dOCREngine=
. Par défaut le moteur LSTM/neural network est utilisé (-dOCREngine=1
). Nous pouvons choisir d’utiliser le moteur Legacy Tesseract (-dOCREngine=0
).
Pour fonctionner, Tesseract a besoin de fichiers de données spécifiques appelés traineddata. Ces fichiers sont essentiels pour adapter la reconnaissance en fonction de chaque langue. Il existe des variantes qui favorise la rapide ou la qualité.
- https://github.com/tesseract-ocr/tessdata_best pour les données assurants la meilleure qualité de reconnaissance avec le moteur LSTM ;
- https://github.com/tesseract-ocr/tessdata_fast pour les données assurants la meilleure vitesse de reconnaissance avec le moteur LSTM ;
- https://github.com/tesseract-ocr/tessdata pour les données assurants la meilleurs vitesse de reconnaissance avec les moteurs LSTM et legacy.
Les fichiers sont stockés dans un répertoire appelé tessdata
. Le chemin d’accès à ce répertoire doit être mémorisé dans un variable locale.
% export TESSDATA_PREFIX=/Users/gilles/tessdata
Alternativement, le chemin d’accès peut aussi être renseigné comme paramètre de Ghostscript avec l’option --permit-file-read=
.
% /opt/local/bin/gs -dNOPAUSE -dBATCH --permit-file-read=/Users/gilles/tessdata -sDEVICE=ocr ...
Par défaut, la langue anglaise est utilisée avec le fichier eng.traineddata
. Pour modifier la langue utilisée, nous utiliserons l’option -sOCRLanguage=
avec en valeur le code de la langue à utiliser.
% /opt/local/bin/gs -dNOPAUSE -dBATCH -sDEVICE=ocr -sOCRLanguage="fra" --permit-file-read=/Users/gilles/tessdata -sOutputFile="/Users/gilles/Downloads/rapport.txt" -f "/Users/gilles/Downloads/rapport.pdf"
L’utilisation de plusieurs langues est possible. L’option -sOCRLanguage=
sera alors écrite par exemple -sOCRLanguage="fra+eng"
pour utiliser le français et l’anglais.
Dans le répertoire tessdata
il n’est pas nécessaire de copier tous les fichiers avec extension traineddata
. Nous pouvons y placer que les fichiers pour les langues qu’il est envisagé de reconnaître.
Il est possible d’adapter la résolution des images utilisées avec le moteur OCR avec l’option -r<dpi>
ou -r<dpi horizontal>x<dpi vertical>
. -r<dpi>
applique la même résolution en horizontal et en vertical. Attention, plus les DPI seront élevés, plus la reconnaissance sera longue. Il faut appliquer une valeur équilibrée. Cette option est équivalente à -dDEVICEXRESOLUTION=<dpi>
et -dDEVICEYRESOLUTION=<dpi>
(la résolution horizontale et verticale sur le device utilisé).
% /opt/local/bin/gs -dNOPAUSE -dBATCH -sDEVICE=ocr -r200 --permit-file-read=/Users/gilles/tessdata -sOutputFile="/Users/gilles/Downloads/rapport.txt" -f "/Users/gilles/Downloads/rapport.pdf"
Automatisation
Il existe de nombreuses possibilités en matière d’automatisation. L’utilisation du terminal est une de ces solutions. Ce n’est pas une approche facile pour le néophyte, mais son utilisation peut s’avérer un puissant allié pour réduire la taille des fichiers.
Avec le terminal
Windows Powershell
Sous Windows, en utilisant PowerShell nous pouvons automatiser la réduction de la taille des fichiers. Nous créons un fichier texte reduction.ps1
dans lequel nous intégrons ces lignes. Sans faire un cours sur PowerShell, notons les points suivants :
Get-ChildItem
récupère dans un tableau tous les fichiers (y compris dans les sous-dossiers) avec extension.pdf
.$gs
précise l’emplacement de Ghostscript.$destinationFolder
identifie le dossier de destination où tous les fichiers réduits seront créés.ForEach
parcoure les éléments du tableau$files
. Chaque élément est placé dans une variable$file
.$destination
comporte le nom complet du fichier à créer (dossier et fichier).if
est utilisé pour ne réduire que les fichiers de 100 Mo et plus. C’est facultatif. Nous pouvons supprimer ce test et les accolades associées.& $gs ...
exécute Ghostscript avec les paramètres pour chaque élément de la liste$files
.
$files= Get-ChildItem -Path "D:\travail" -Filter *.pdf -Recurse -ErrorAction SilentlyContinue -Force
$gs = 'C:\Program Files\gs\gs10.02.1\bin\gswin64c.exe'
$destinationFolder = "d:\pdf_reduits"
ForEach ($file in $files) {
$destination = [io.path]::Combine($destinationFolder,$file)
If ((($file.Length) / 1024 / 1024) -ge 100) {
& $gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -sColorConversionStrategy=Gray -dDownsampleGrayImages=true -dGrayImageResolution=150 "-dCompatibilityLevel=1.4" -o $destination $file.FullName
}
}
Pour exécuter ce script dans PowerShell nous devons autoriser l’utilisateur actuel. Pour cela nous ouvrons le terminal en mode administrateur et nous tapons la commande suivante :
PS C:\Users\gilles> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Shell MacOS/Linux
Nous allons créer un fichier texte que nous nommerons reduction.sh
. Cela se fait automatiquement par la ligne de commandes qui suit :
% nano reduction.sh
A l’intérieur de ce fichier nous allons créer une fonction récursive pour parcourir tous les sous-répertoires d’un emplacement spécifié lors de l’appel de la fonction. L’écriture de script pour le terminal utilise un langage particulièrement abscon. Il faut être vigileant à bien reproduire la syntaxe.
#!/bin/bash
sourceFolder=/Users/gilles/Downloads/pdf
destinationFolder=/Users/gilles/Downloads/pdf_reduce/
recherche () {
for chemin in "$1"/*; do
if [ -d "$chemin" ]; then
walk_dir "$chemin"
elif [ -e "$chemin" ]; then
case "$chemin" in
*.pdf|*.PDF)
destination=$destinationFolder$(basename "$chemin")
gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -sColorConversionStrategy=Gray -dDownsampleGrayImages=true -dGrayImageResolution=150 -dCompatibilityLevel=1.4 -o "${destination}" "${chemin}"
esac
fi
done
}
recherche "$sourceFolder"
Le script pourra être exécuté tout simplement.
% bash reduction.sh
Pour rendre exécutable le script nous exécuterons cette commande.
% sudo chmod +x reduction.sh
L’exécution du script se fera de cette façon :
% ./reduction.sh
Nous pouvons modifier le script pour définir dans la ligne de commandes en paramètres les dossiers de recherche ($1
) et de destination ($2
).
#!/bin/bash
sourceFolder=$1
destinationFolder=$2
...
Nous lançons le script en ajoutant les chemins source et destination (avec un /
à la fin).
% ./reduce.sh /Users/gilles/Downloads/pdf_source /Users/gilles/Downloads/pdf_destination/
Pour afficher la liste de tous les fichiers PDF contenus dans un dossier et ses sous-dossiers nous avons à notre disposition la commande
find
.% find /Users/gilles/Downloads/pdf -type f -name '*.pdf' -exec printf "%s\n" {} +
Sous MacOS et Linux il existe de nombreuses autres possibilités pour automatiser en ligne de commandes la réduction de la taille des fichiers. Au lieu d’utiliser un script avec une boucle, nous pouvons le faire en une seule ligne de commandes en utilisant find
. Cette commande est capable d’exécuter une ou plusieurs commandes en communiquant chaque nom de fichier trouvé (récupérable avec {}
). basename
est utilisé pour extraire le nom du fichier sans extension.
% find /Users/gilles/Downloads/pdf_source -type f -name "*.pdf" -exec bash -c 'destination="/Users/gilles/Downloads/pdf_destination/"$(basename "$0" .pdf); gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -sColorConversionStrategy=Gray -dDownsampleGrayImages=true -dGrayImageResolution=150 -dCompatibilityLevel=1.4 -o $destination".reduce.pdf" "$0";' {} \;
En mode terminal, nous pouvons également utiliser des variables locales (destinationDossier
et sourceDossier
) pour renseigner la ligne de commandes.
% destinationDossier="/Users/gilles/Downloads/pdf_reduce/"
% sourceDossier="/Users/gilles/Downloads/pdf"
% find $sourceDossier -type f -name "*.pdf" -exec zsh -c 'destination=$1$(basename "$0" .pdf)".reduce.pdf"; gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -sColorConversionStrategy=Gray -dDownsampleGrayImages=true -dGrayImageResolution=150 -dCompatibilityLevel=1.4 -o $destination "$0";' {} $destinationDossier \;
Avec Python
Nous allons créer un programme Python pour automatiser la réduction de tous les fichiers PDF d’un dossier. Nous pouvons donner à cette application le nom reduce_gs
.
Sur mon dépôt GitHub.com nous pouvons retrouver un ensemble d’exemples notamment les interactions entre la librairie libgs et Python.
Installation d’un environnement
Windows
Pour exécuter cette application, il est fortement conseillé de créer un environnement virtuel.
PS C:\Users\gilles\Documents\Python Scripts\pdf> python -m venv .venv
Pour activer l’environnement dans Powershell nous écrirons les commandes suivantes :
PS C:\Users\gilles\Documents\Python Scripts\pdf> .\.venv\Scripts\Activate.ps1
(.venv) PS C:\Users\gilles\Documents\Python Scripts\pdf> pip list
Package Version
----------------- -------
pip 23.3.2
python-utils 3.8.1
setuptools 65.5.0
Pour fonctionner ce script a besoin de bibliothèques auxiliaires. Nous pouvons les installer directement par une commande pip
.
(.venv) PS C:\Users\gilles\Documents\Python Scripts\pdf> python install tqdm
Pour mettre à jour le package, nous ajouterons un paramètre --upgrade
:
(.venv) PS C:\Users\gilles\Documents\Python Scripts\pdf> pip install --upgrade tqdm
Nous pouvons aussi utiliser un fichier texte que nous appellerons requirements.txt
auquel nous ajouterons la ligne suivante :
tqdm==4.66.1
Pour installer les bibliothèques, nous utiliserons le fichier requirements.txt
avec la commande pip
.
(.venv) PS C:\Users\gilles\Documents\Python Scripts\pdf> pip install -r requirements.txt
Mac OS
Dans le système d’Apple nous allons utiliser l’environnement Python Conda. Nous pouvons utiliser les distributions Anaconda ou Miniconda pour utiliser Conda. Elles sont compatibles avec tous les systèmes d’exploitation (Windows, Mac Os ou Linux). Cela permet une meilleure intégration d’un environnement de travail Python.
Une fois installée, nous allons travailler dans des environnements virtuels. Pour obtenir la liste des environnements présents sur le système, nous utiliserons dans le Terminal les instructions qui suivent :
% conda env list
ou
% conda info --envs
Nous pouvons également obtenir des informations sur l’environnement. Ici, nous sommes en présence d’un environnement sous Mac OS Silicon (arm).
% conda info
active environment : base
active env location : /Users/gilles/opt/anaconda3
shell level : 1
user config file : /Users/gilles/.condarc
populated config files : /Users/gilles/.condarc
conda version : 4.13.0
conda-build version : 3.21.8
python version : 3.9.12.final.0
virtual packages : __osx=13.3.1=0
__unix=0=0
__archspec=1=arm64
base environment : /Users/gilles/opt/anaconda3 (writable)
conda av data dir : /Users/gilles/opt/anaconda3/etc/conda
conda av metadata url : None
channel URLs : https://repo.anaconda.com/pkgs/main/osx-arm64
https://repo.anaconda.com/pkgs/main/noarch
https://repo.anaconda.com/pkgs/r/osx-arm64
https://repo.anaconda.com/pkgs/r/noarch
package cache : /Users/gilles/opt/anaconda3/pkgs
/Users/gilles/.conda/pkgs
envs directories : /Users/gilles/opt/anaconda3/envs
/Users/gilles/.conda/envs
platform : osx-arm64
user-agent : conda/4.13.0 requests/2.27.1 CPython/3.9.12 Darwin/22.4.0 OSX/13.3.1
UID:GID : 501:20
netrc file : None
offline mode : False
L’environnement de développement Python peut être mis à jour.
% conda update conda
Si nous utilisons la distribution Anaconda, nous pouvons également utiliser la commande suivante :
% conda update anaconda
Pour développer, nous devons créer un environnement virtuel. Pour cela, nous utilisons la commande create
, suivi de l’argument -n
pour donner un nom à l’environnement. Optionnellement, nous pouvons définir la version de Python à installer.
% conda create -n reduce_pdf python=3.11
ou
% conda create --name reduce_pdf python=3.11
Nous pouvons également intégrer des bibliothèques à la création de l’environnement. -c conda-forge
est utilisé pour définir le dépôt de bibliothèque à utiliser.
% conda create -n reduce_pdf -c conda-forge python=3.11 tqdm
Une bibliothèque peut être installé par la suite lorsque l’environnement est en cours d’utilisation.
% conda install -c conda-forge tqdm
Si nous souhaitons mettre à jour un package spécifique nous utiliserons la commande update
suivi du ou des noms de packages.
% conda update tdqm
Les dépendances (packages) sont également mises à jour. Tout est automatique, il n’est pas nécessaire de s’en inquiéter. update propose quelques options supplémentaires :
--no-deps
pour ne pas mettre à jour les dépendances--force-reinstall
pour forcer la mise à jour même si le package est à jour--dry-run
pour simuler la mise à jour--yes
pour répondre automatiquement oui à toute question
La mise à jour de tous les packages d’un environnement se fait de cette manière.
% conda update -all
L’utilisation d’un environnement nécessite son activation. Pour faire cela, nous avons la commande :
% conda activate reduce_pdf
Pour quitter l’environnement nous procéderons comme suit :
% conda deactivate
Pour supprimer un environnement de développement :
% conda env remove --name reduce_pdf
Pour supprimer tous les environnements à un emplacement spécifique, nous utiliserons l’argument --prefix
.
% conda env remove --prefix /Users/gilles/envs
Code complet
Pour utiliser ce code nous devons l’insérer dans un fichier Python. Vous pouvez le faire avec n’importe quel éditeur texte ou de code. Personnellement je développe en Python sous Microsoft Visuel Code.
Nous pouvons le faire dans le Terminal de MacOS, en utilisant l’utilitaire Nano et en copier le code.
% nano reduce_gs.py
Pour sortir de Nano, appuyez sur le raccourci CTRL + X
puis validez par Y
et la touche Entrée
.
Pour exécuter le code dans Terminal, nous utiliserons l’exécutable Python avec en argument le chemin d’accès au fichier reduce_gs.py. Par exemple, pour lancer le script présent dans le répertoire actif.
% python ./reduce_gs.py
Le listing complet est reproduit ci-après. Il s’agit d’une version simplifiée. Sur le dépôt GitHub vous retrouvez une version étoffée avec des options supplémentaires.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
##################################################
## reduction de la taille des PDF
##################################################
## Author: Gilles BIHAN
## Copyright: Copyright 2024, Converio.fr
## Version: 1.0.0
## Email: gilles.bihan@converio.fr
## Status: Dev
##################################################
import os, subprocess, re, shutil, argparse
from platform import system
from tqdm import tqdm
if system() in [ 'Linux', 'Darwin']:
ghostscript = shutil.which("gs")
elif system() in ['Windows']:
ghostscript = shutil.which("gswin64c.exe")
def numberOfPage(source):
cmd = [ghostscript, "-dQUIET", "-dBATCH", "-dNODISPLAY", "-dNOPAUSE", "-dPDFINFO", "-dFirstPage=1","-dLastPage=1", source]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = proc.communicate()
ret = re.findall('([0-9]{1,})',str(stderr))
if len(ret)>0:
return re.search('([0-9]{1,})',str(stderr))[0]
return 0
def cli(cmd, source, pages):
fichier = "[{:^20s}] ".format(os.path.basename(source)[:20])
with tqdm(total=int(pages)) as bar:
bar.set_description_str(fichier)
p = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True)
while True:
chunk = p.stdout.readline()
if chunk=='':
break
m = re.search("Page (?P<page>[0-9]{1,})",chunk)
if m:
bar.update(1)
def reduction(dossierSource, dossierDestination, dpi=150, version="1.4", replace=False):
global ghostscript
source_size = 0
dest_size = 0
nbr_file = 0
for path, subdirs, files in os.walk(dossierSource):
fichiers = [file for file in files if os.path.splitext(file)[1]==".pdf"]
for fichier in fichiers:
source = os.path.join(path, fichier)
destination = source.replace(os.path.dirname(source),dossierDestination)
if os.path.exists(destination) and not replace:
continue
destinationFinale = destination
if replace:
destination=re.sub('(?i)'+re.escape(".pdf"), lambda m: ".tmp", destination)
cmd = [ghostscript, '-sDEVICE=pdfwrite', '-dNOPAUSE', '-dBATCH',
'-sColorConversionStrategy=Gray', '-dDownsampleGrayImages=true', f"-dGrayImageResolution={dpi}", f"-dCompatibilityLevel={version}",
f"-sOutputFile={destination}",
f"{source}"]
try:
cli(cmd,source=source,pages=numberOfPage(source=source))
source_size += os.stat(source).st_size
dest_size += os.stat(destination).st_size
if replace and os.path.exists(destinationFinale):
os.remove(destinationFinale)
shutil.move(destination,destinationFinale)
elif replace:
shutil.move(destination,destinationFinale)
except FileNotFoundError:
if os.path.exists(source):
shutil.copyfile(source,destination)
except Exception as e:
print(e)
nbr_file += 1
return nbr_file,source_size,dest_size
if __name__ == "__main__":
description="Réduction de la taille des fichiers PDF"
parser = argparse.ArgumentParser(description=description,exit_on_error=False)
parser.add_argument("source", help="Dossier source",type=str)
parser.add_argument("destination", help="Dossier de destination",type=str)
parser.add_argument("-d","--dpi", help="Points par pouce (défaut=150)",type=int,default=150)
parser.add_argument("-v","--version", help="Version du document PDF (défaut=1.4)",type=str,default="1.4",)
parser.add_argument("-r","--replace", help="Points par pouce (défaut=150)",type=str, choices=['o','oui','y', 'yes', 'n','non','no'],default='n')
try:
args = parser.parse_args()
replace = False if args.replace.startswith("n") else True
if ghostscript is None:
print("Ghostscript n'a pas été trouvé. Veuillez l'installer.")
elif os.path.exists(args.source):
if not os.path.exists(args.destination):
os.makedirs(args.destination)
nbr_file,source_size,dest_size = reduction(dossierSource=args.source,
dossierDestination=args.destination,
dpi=args.dpi, version=args.version, replace=replace)
print(f"Nbr de fichiers: {nbr_file}\tSources: {source_size / (1024*1024):.2f} mbytes\tDestinations: {dest_size / (1024*1024):.2f} mbytes soit gain de {max(0,1-(dest_size/source_size)):.2%}")
except argparse.ArgumentError as e:
print(e)
except ZeroDivisionError:
print("Aucun fichier n'a été traité")
except Exception as e:
print(e)
Utilisation
Pour afficher l’aide de la commande nous ajoutons le paramètre -h
.
(.venv) PS C:\Users\gilles\Documents\Python Scripts\pdf> python .\reduce_gs\reduce_gs.py -h
python reduce_gs.py -h
usage: reduce_gs.py [-h] [-d DPI] [-v VERSION] [-r {o,oui,y,yes,n,non,no}] source destination
Réduction de la taille des fichiers PDF
positional arguments:
source Dossier source
destination Dossier de destination
options:
-h, --help show this help message and exit
-d DPI, --dpi DPI Points par pouce (défaut=150)
-v VERSION, --version VERSION
Version du document PDF (défaut=1.4)
-r {o,oui,y,yes,n,non,no}, --replace {o,oui,y,yes,n,non,no}
Points par pouce (défaut=150)
Pour fonctionner nous devons a minima préciser un dossier source et un dossier destination. Avec le script Python, nous procéderons comme suit :
% python reduce_gs.py ~/Documents/Factures /Users/gilles/Factures
Avec le fichier exécutable, l’utilisation de la commande sera :
(.venv) PS C:\Users\gilles\Documents\Python Scripts\pdf> reduce_gs.exe ~/Documents/Factures /Users/gilles/Factures
Avec le paramètre -r
nous pouvons choisir de ne pas remplacer les fichiers existants dans le dossier destination.
(.venv) PS C:\Users\gilles\Documents\Python Scripts\pdf> python reduce_gs.py "C:\Users\gilles\OneDrive\Documents\Factures\" "C:\Users\gilles\Documents\Factures\" -r non
Pour modifier la densité de points par pouce nous utiliserons le paramètre -d
suivi de la valeur désirée. Rappelons nous que plus la valeur est basse, plus la qualité des images est dégradée. Inversement, il est inutile d’augmenter les DPI au delà de la valeur utilisée par les images sources.
Dans Windows, l’accès a un disque réseau nécessite dans Powershell de se connecter avec des identifiants valides. Pour cela nous devons au préalable lancer la ligne qui suit :
net use \\192.168.1.14\mondisque /user:gilles
Compiler les sources
Les sources de Ghostscript sont disponibles de deux manières : avec une archive TAR compressée ; avec un dépôt distant à dupliquer localement.
Archive
Le code source se télécharge à partir du site de Ghostscript. Il est commun pour toutes les plateformes (Windows, MacOs, Linux). Nous récupérons une archive TAR avec une compression GZIP. Le numéro de version est précisé dans le nom du fichier (ghostscript-10.02.1.tar.gz
pour la version 10.02.1)
% wget https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs10021/ghostscript-10.02.1.tar.gz
% tar xvf ghostscript-10.02.1.tar.gz
% cd ghostscript-10.02.1
%
Dans Linux, nous mettons à jour le système, puis nous installons le compilateur GCC.
% sudo apt update && sudo apt upgrade
% sudo apt install build-essential
Dans MacOs, si nous n’utilisons pas Xcode, nous devons installer les utilisateurs de développement en ligne de commandes. Nous ouvrons le terminal et nous exécutons cette commande.
gilles@MBP-de-Gilles ~ % xcode-select --install
Une fenêtre apparaît et nous cliquons sur le bouton Installer. Nous validons le contrat de licence. L’installation de Command Line Developer Tools se fait automatiquement. Nous pouvons suivre la progression. A la fin, gcc est installé.
Pour vérifier la bonne installation de GCC, nous taperons la commande suivante :
$ gcc -v
gcc version 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04)
gilles@MBP-de-Gilles ~ % gcc -v
Apple clang version 13.1.6 (clang-1316.0.21.2.5)
Target: arm64-apple-darwin21.4.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
Ensuite nous allons exécuter la configuration pour préparer les fichiers makefile
.
% ./configure
Pour afficher l’aide du script de configuration nous exécuterons la commande ./configure --help
dans le terminal.
Pour lancer la compilation de l’exécutable Ghostscript nous lançons la commande ci-après.
% make
Pour supprimer tous les fichiers de compilation nous avons à notre disposition make clean
. A la suite nous allons pouvoir relancer tout le processus de compilation avec une génération de tous les fichiers intermédiaires. Pour exécuter l’exécutable sur le système, nous lancerons la commande make install
à la suite de la compilation. Le fichier exécutable est disponible dans le dossier bin.
Sur MacOS, il peut être utile de connaître la compatibilité de l’exécutable (x86_64 ou Arm64). Nous utilisons l’instruction file
.
% cd bin
gilles@MBP-de-Gilles bin % ls
gs
gilles@MBP-de-Gilles bin % file gs
gs: Mach-O 64-bit executable arm64
Pour lancer la compilation de la librairie partagée Ghostscript, nous exécuterons la commande make so
dans le terminal. Pour nettoyer les fichiers de compilation, nous avons la commande make soclean
. Les librairies compilées sont disponibles dans le dossier sobin
. Sous Linux, nous trouverons des fichiers avec extension so
. Par exemple pour la version 10.02, nous aurons la librairie libgs.so.10.02
, et deux liens symboliques vers la librairie (libgs.so
et libgs.so.10
). Sous MacOs, les librairies auront une extension dylib
. Pour la version 10.02 nous aurons la librairie libgs.dylib.10.02
et deux liens symboliques (libgs.dylib.10.02
ou libgs.dylib.10
). Contrairement à l’exécutable gs (qui intègre la librairie dans l’exécutable), la librairie partagée peut être utilisé avec deux exécutables : gsc
et gsx
. Le premier est identique à gs
. Le deuxième, gsx
, utilise en sortie un périphérique d’affichage s’appuyant sur un widget GTK+.
% gsc -v
GPL Ghostscript 10.02.1 (2023-11-01)
Copyright (C) 2023 Artifex Software, Inc. All rights reserved.
Pour installer localement la librairie partagée nous exécuterons en mode super utilisateur sudo make install-so
dans le terminal.
Dépôt distant
Nous pouvons aussi compiler Ghostscript à partir d’un dépôt distant (Git). Bien entendu il faut au préalable avoir installé Git.
gilles@MBP-de-Gilles % git clone git://git.ghostscript.com/ghostpdl.git
Cloning into 'ghostpdl'...
remote: Enumerating objects: 231573, done.
remote: Counting objects: 100% (8266/8266), done.
remote: Compressing objects: 100% (5125/5125), done.
remote: Total 231573 (delta 4424), reused 4834 (delta 3122), pack-reused 223307
Receiving objects: 100% (231573/231573), 243.30 MiB | 10.57 MiB/s, done.
Resolving deltas: 100% (182471/182471), done.
Nous devons exécuter autogen.sh
pour préparer localement le dépôt. Sous MacOs, nous devons au préalable installer via brew deux outils supplémentaires.
% brew install autoconf automake
Il nous reste à nous placer à la racine du dépôt local, et d’exécuter successivement
gilles@MBP-de-Gilles % cd ghostpdl
gilles@MBP-de-Gilles ghostpdl % ./autogen.sh
gilles@MBP-de-Gilles ghostpdl % ./configure
gilles@MBP-de-Gilles ghostpdl % make
Une fois compilée, le binaire de Ghostscript est placé dans le répertoire bin
.
gilles@MBP-de-Gilles ghostpdl % ./bin/gs
GPL Ghostscript 10.03.0 (2023-09-13)
Copyright (C) 2023 Artifex Software, Inc. All rights reserved.
This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
see the file COPYING for details.
GS>quit
Pour synchroniser localement avec le dépôt distant nous exécuterons à la racine du dépôt local la commande git pull
dans le terminal.
gilles@MBP-de-Gilles ghostpdl % git pull
Pour les utilisateurs de Visual Studio Code, pour référencer la librairie libgs, nous pouvons ajouter à une variable d’environnement
GSAPI_LIB
au fichier de configuration. Cette variable pointera sur le fichier.{ // Utilisez IntelliSense pour en savoir plus sur les attributs possibles. // Pointez pour afficher la description des attributs existants. // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Débogueur Python : Fichier actuel", "type": "debugpy", "request": "launch", "program": "${file}", "console": "integratedTerminal", "env": { "GSAPI_LIB":"/Users/gilles/Downloads/ghostscript-10.02.1/sobin/libgs.dylib.10.02" } } ] }
Windows
Dans le dossier Windows
de l’archive Ghostscript nous pouvons ouvrir dans Microsoft Visual Studio 2022 le fichier solution GhostPDL.sln
. Pour obtenir la librairie nous devons générer la solution complète (Menu Générer -> Générer la solution) ou Ctrl+Maj+B
. Par défaut, la version debug sera générée. Les fichiers résultants se trouveront dans le dossier debugbin
.
Avant d’exécuter le script Python, nous devons définir une variable d’environnement GSAPI_LIB
. Dans Powershell nous procéderons comme cela :
PS > $env:GSAPI_LIB="C:\Users\gilles\Downloads\ghostscript-10.02.1\debugbin\gsdll64.dll"
Pour obtenir la liste des variables d’environnement dans Powershell nous utiliseront la ligne qui suit :
PS > dir env:
Dans l’invite de commandes (cmd) nous utiliserons l’instruction set
:
> set GSAPI_LIBDIR=C:\Users\gilles\Downloads\ghostscript-10.02.1\debugbin\gsdll64.dll
Pour vérifier la présence de la variable nous utilisons sans argument l’instruction set
.