💡 Exemple complet fonctionnel disponible sur GitHub :
python-linux-container-pdf-signing

Introduction

Le script fonctionne localement. Vous le conteneurisez sur python:3.11-slim, et il échoue à import groupdocs.signature. Vous corrigez cela, et il échoue de nouveau à la première signature. Aucun des deux messages d’erreur ne mentionne ce qui manque réellement.

La signature de conteneur avec Python est un flux de travail GroupDocs.Signature qui nécessite deux couches d’approvisionnement plutôt qu’une : les bibliothèques d’exécution .NET sur lesquelles le binding est construit, et les polices que chaque signature texte doit rendre. Ce tutoriel construit les deux, puis le script qui résout une famille de polices à l’exécution au lieu d’en coder une en dur, de sorte que le même code fonctionne dans le conteneur et sur la machine où vous l’avez écrit.

Pourquoi les deux couches sont importantes

GroupDocs.Signature pour Python est un binding .NET, donc libicu et une bibliothèque compatible OpenSSL 1.1 doivent exister avant que tout import ne réussisse. C’est la couche 1, et elle est bien documentée dans Running in Docker.

La raison pour laquelle les deux couches sont confondues est que les deux échouent à des moments proches de l’import et qu’aucune des erreurs ne nomme sa cause. Un libssl1.1 manquant vous donne une erreur de chargeur concernant un objet partagé ; une police manquante vous donne une erreur de signature enveloppée dans une exception proxy. Aucun ne dit « votre image de base est trop petite », ce qui est en fait ce que signifient les deux.

La couche 2, ce sont les polices, et c’est celle qui surprend les gens. python:3.11-slim ne contient aucun fichier de police. GroupDocs.Signature ne substitue pas une famille manquante — nommer une famille qui n’est pas installée lève une exception, et rien n’est écrit — et nettoyer la police n’est pas non plus une solution de contournement, car la bibliothèque demande alors sa propre police par défaut et échoue de la même façon. Sur une image sans police, une signature texte est simplement impossible.

Conditions préalables

Python 3.11 (la roue ci‑dessous ne supporte pas CPython 3.14) et groupdocs-signature-net==26.1. Docker si vous voulez voir les deux échecs volontairement, ce qui vaut bien dix minutes.

Installation

pip install groupdocs-signature-net==26.1

Étape 1 - Construire la couche .NET

libssl1.1 n’est pas présent dans bookworm, il provient donc d’un instantané Debian épinglé :

ENV SNAPSHOT_DATE=20220328T000000Z
RUN echo "deb [trusted=yes] http://snapshot.debian.org/archive/debian/${SNAPSHOT_DATE} bullseye main" \
        > /etc/apt/sources.list.d/debian-archive.list \
    && apt-get -o Acquire::Check-Valid-Until=false update \
    && apt-get install -y --no-install-recommends \
        libicu67 \
        libssl1.1 \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

Points clés :

  • Cette couche ne fait que permettre l’import ; elle ne dit rien des polices.
  • Épingler la date de l’instantané rend la construction reproductible lorsque les archives évoluent.

Étape 2 - Construire la couche de polices

Quatre paquets, conservés comme leur propre couche afin de pouvoir les commenter pour reproduire l’échec :

RUN apt-get update && apt-get install -y --no-install-recommends \
        fontconfig \
        fonts-dejavu-core \
        fonts-liberation \
        fonts-noto-cjk \
    && fc-cache -f \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

fontconfig est le résolveur et vous fournit fc-list. fonts-dejavu-core constitue le minimum latin, grec et cyrillique. fonts-liberation couvre les documents qui font référence à Arial ou Times New Roman par leur nom. fonts-noto-cjk couvre le chinois, le japonais et le coréen.

Étape 3 - Demander à la bibliothèque quelle famille elle peut utiliser

Parcourir /usr/share/fonts à la recherche d’un nom de fichier semble équivalent, mais ne l’est pas : fonts-noto-cjk installe NotoSansCJK-Regular.ttc, dont le nom de famille est Noto Sans CJK JP. La réponse portable est un test — une vraie signature dans un fichier temporaire — avec la défaillance convertie en valeur :

with signature.Signature(source_path) as sign:
    options = TextSignOptions()
    options.text = "probe"
    options.left = 10
    options.top = 10
    options.width = 60
    options.height = 20
    font = SignatureFont()
    font.family_name = family_name
    font.size = 10.0
    options.font = font
    sign.sign(scratch, [options])
return None

Observez bien font.size = 10.0. Le binding mappe la taille sur un float .NET et rejette un int avec l’erreur : numeric argument expected, got ‘int’. Parce que cela se produit à l’intérieur du test, chaque famille candidate échoue et la sortie ressemble exactement à une image sans police. J’ai ajouté trois paquets de polices à une image qui les contenait déjà avant de remarquer le problème littéral.

La résolution devient alors une boucle :

for candidate in candidates:
    if try_family(source_path, candidate) is None:
        return candidate
return None

Étape 4 - Signer ce qui a été résolu, vérifier ce que vous avez signé

La famille latine est requise, celle CJK est optionnelle :

with signature.Signature(source_path) as sign:
    options = [build_text_options(LATIN_TEXT, latin_family, 50)]
    if cjk_family:
        options.append(build_text_options(CJK_TEXT, cjk_family, 120))
    result = sign.sign(output_path, options)
    return len(result.succeeded)

Puis vérifiez, car le CJK rendu sous forme de cases vides ne soulève aucune exception :

options = TextVerifyOptions()
options.text = expected_text
options.match_type = gsd.TextMatchType.CONTAINS
options.all_pages = True
result = sign.verify(options)

CONTAINS est intentionnel : en mode évaluation la bibliothèque ajoute du texte d’essai à la page, et une correspondance exacte signalerait à tort un document parfaitement correct comme échoué.

Que dire des documents affirmant que Python a un support Linux limité ?

La page Running in Docker répertorie les paquets Python prêts pour Linux et omet Signature. Avec groupdocs-signature-net==26.1 cet exemple a signé et vérifié à l’intérieur de python:3.11-slim, CJK inclus, avec les deux couches installées. Considérez la liste comme obsolète plutôt que comme un obstacle, et validez avec votre propre version avant de vous engager dans un déploiement.

Applications réelles

Un service de facturation qui appose une ligne d’approbation sur des PDF générés a exactement besoin de cela : la couche .NET, une police latine, et une vérification de résolution au démarrage. Cette vérification transforme un mauvais déploiement en un conteneur qui refuse de démarrer, plutôt qu’en une file d’attente de factures qui échouent silencieusement une à une. Un portail de documents qui accepte des noms clients dans n’importe quel script a également besoin du paquet CJK, plus l’étape de vérification, car c’est le seul garde‑fou entre une boîte vide rendue et un nom signé.

Où placer la vérification de résolution

Mettez‑la où que le code s’exécute une fois par processus : un appel au niveau du module, un gestionnaire de durée de vie FastAPI, un AppConfig.ready Django, ou les premières lignes du point d’entrée d’un worker. Deux valeurs en résultent, la famille latine et la famille CJK, et les deux doivent apparaître dans le journal de démarrage à côté du nombre de polices détectées.

Ce placement fait plus que gagner du temps de test. Il déplace l’échec du traitement d’une requête (problème d’un seul client et trace de pile que personne ne lit) au démarrage, où il apparaît comme un déploiement qui ne s’est pas lancé et que quelqu’un surveille déjà. Un conteneur qui s’arrête avec « no usable font family, install fonts-dejavu-core » ne nécessite aucune étape de débogage supplémentaire.

Dépannage des problèmes courants

import groupdocs.signature échoue
La couche .NET est manquante ou le dépôt d’instantané était inaccessible pendant la construction. C’est la couche 1, et cela n’a rien à voir avec les polices. Vérifiez le journal de construction pour l’étape apt avant de toucher au code de signature, car un échec de récupération d’instantané n’arrête pas la création de l’image.

Chaque police candidate échoue, mais fc-list montre des polices
Vérifiez que font.size n’est pas un int avant d’ajouter d’autres paquets.

La signature apparaît mais le texte CJK est en boîtes
fonts-noto-cjk est absent. La signature a été écrite avec une famille qui ne possède aucun glyphe pour ces points de code, d’où l’existence de l’étape de vérification : elle échoue exactement dans ce cas, alors que la signature avait indiqué le succès.

Ce que les deux images affichent réellement

Exécutez les deux et lisez les quatre premières lignes. L’image sans police indique font files on disk: 0, les deux lignes de résolution comme (none), l’erreur volontaire de police manquante, puis sort avec le code 3 après avoir affiché la correction minimale. L’image provisionnée indique un nombre de polices non nul, DejaVu Sans pour le latin et Noto Sans CJK JP pour le CJK, deux signatures appliquées, et les deux textes vérifiés.

Cette paire de sorties constitue l’artéfact à conserver. Collez‑la dans vos notes de déploiement et la prochaine personne qui modifiera l’image de base disposera d’une référence de ce à quoi ressemble un conteneur sain, sans avoir besoin de comprendre fontconfig.

Conclusion

Deux couches et un test. Installez les dépendances .NET, installez au moins fontconfig et DejaVu, résolvez la famille en la demandant plutôt qu’en l’assumant, et vérifiez la sortie avant de déclarer le travail terminé. Ce n’est pas beaucoup de code, et tout cela relève du « obvious in hindsight and invisible in a traceback ». Le dépôt d’exemple fournit les deux Dockerfiles, de sorte que la différence entre une image fonctionnelle et une image cassée ne tient qu’à un seul build.

Ressources supplémentaires