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 :

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 :