Ajouter un filigrane dans PDF avec ghostscript
Si vous avez déjà les outils ghostscript installés sur votre machine, voici un moyen d’ajouter un filigrane sur chaque page d’un PDF.
La démarche est simple, mais la mise en œuvre ne l’est pas tant, surtout quand on ne veut ou peut pas installer des outils en plus. Notamment de grosses suites logicielles, souvent propriétaires. À noter que LibreOffice Draw permet de le faire facilement (via le mode masque, dans le sous-menu affichage), mais j’ai remarqué sur mes documents de test que certains textes étaient décalés et donc dénaturaient, voire rendaient illisibles le document final.
J’ai donc cherché une solution à base de ligne de commande et de ghostscript. C’était long et laborieux, mais pas impossible. Avec toutefois quelques subtilités à prendre en compte.
Sans plus attendre : des solutions.
#!/usr/bin/env bash
readonly WATERMARK_FILE=$(mktemp /tmp/watermark.XXXXXXX.ps)
filename=${1:-}
watermark=${2:-}
cat > "${WATERMARK_FILE}" << HERE
<<
/BeginPage
{
2 eq { pop false }
{
0.65 .setfillconstantalpha
/Helvetica_Bold 50 selectfont
.40 setgray
gsave
130 70 moveto 50 rotate (${watermark}) show
grestore
true
} ifelse
} bind
>> setpagedevice
HERE
gs -dBATCH -dALLOWPSTRANSPARENCY -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile="watermarked_${filename}" "${WATERMARK_FILE}" "${filename}"
Il est assez facile de trouver ce bout de ghostscript un peu partout, à peu de choses près.
Mais avec les versions actuelles, il faut penser à ajouter -dALLOWPSTRANSPARENCY
, sans quoi la génération échouera avec une erreur cryptique.
Dans le script également, j’ai (quasiment) toujours vu /EndPage
, d’où l’envie de beaucoup de monde de jouer avec la transparence,
car ainsi le texte est généré par dessus le texte original.
Or nous voulons le mettre dessous.
La bonne solution pour ça est d’utiliser en fait /BeginPage
.
La transparence devient optionnelle du coup, mais le texte plein est ainsi moins prononcé ce qui ne gêne pas la lecture du document.
Les éléments notables
Pour personnaliser un peu tout ça, vous pouvez jouer avec les paramètres suivants :
selectfont
: le nombre avant indique la taille du texte. Plus la valeur est élevée, plus le texte sera grand.moveto
: les coordonnées de départ du texte. La première valeur correspond aux abscisses, la seconde aux ordonnées. L’origine se situe en bas à gauche du document.rotate
: l’angle du texte par rapport à l’horizontale. En valeur positive, le texte tourne dans le sens contraire des aiguilles d’une montre..setfillconstantalpha
: la transparence. Plus celle valeur est faible, plus le texte sera estompé.setgray
: l’intensité du gris. Plus la valeur est proche de 1, plus il sera clair.
Bonus : un style « contour » pour le texte
cat > "${WATERMARK_FILE}" << HERE
<<
/BeginPage
{
2 eq { pop false }
{
gsave
/Helvetica findfont 48 scalefont setfont
.45 setgray
newpath
130 70 moveto 50 rotate
(${watermark}) false charpath
1 setlinewidth stroke
grestore
true
} ifelse
} bind
>> setpagedevice
HERE
Pour avoir plusieurs lignes
En reprenant les deux exemples précédents, si l’on veut faire plusieurs lignes, il suffit de dupliquer les blocs gsave
et grestore
puis de changer les coordonnées des instructions moveto
de chaque ligne pour éviter qu’elles se superposent.
Style plein :
cat > "${WATERMARK_FILE}" << HERE
<<
/BeginPage
{
2 eq { pop false }
{
/Helvetica_Bold 48 selectfont
0.65 .setfillconstantalpha
0.40 setgray
gsave
100 350 moveto 50 rotate (${line_1}) show
grestore
gsave
100 250 moveto 50 rotate (${line_2}) show
grestore
gsave
100 150 moveto 50 rotate (${line_3}) show
grestore
true
} ifelse
} bind
>> setpagedevice
HERE
Style contour :
cat > "${WATERMARK_FILE}" << HERE
<<
/BeginPage
{
2 eq { pop false }
{
/Helvetica findfont 48 scalefont setfont
.45 setgray
gsave
newpath
100 350 moveto 50 rotate
(${line_1}) false charpath
1 setlinewidth stroke
grestore
gsave
newpath
100 250 moveto 50 rotate
(${line_2}) false charpath
1 setlinewidth stroke
grestore
gsave
newpath
100 150 moveto 50 rotate
(${line_3}) false charpath
1 setlinewidth stroke
grestore
true
} ifelse
} bind
>> setpagedevice
Conclusion
Ça n’est pas optimal (surtout au niveau du code ghostscript), ni forcément bien placé mais ça fait le boulot.
À noter que pour les textes longs, ce n’est pas auto-adaptatif et donnera donc un débordement hors du PDF, d’où mon bricolage avec les lignes multiples.
Sources
Tous en anglais hélas :