💡 Exemple complet fonctionnel disponible sur GitHub:
sign-pdf-in-linux-container-fonts-dotnet

L’ancienne méthode était douloureuse

Le service signe les factures. Il fonctionne sur un ordinateur portable avec trois cents polices installées, passe la révision, et est conteneurisé un vendredi. Le lundi, la première tâche du cluster se termine avec un code de sortie non nul et le message Sign document error: Font Arial was not found, et quelqu’un passe la matinée à lire les traces de pile avant que quiconque ne pense à demander quelles polices une image mcr.microsoft.com/dotnet/runtime:8.0 contient réellement.

La réponse est aucune. Zéro fichier de police, mesuré sur l’image dans laquelle s’exécute l’exemple de cet article.

Il est utile de savoir comment les autres environnements d’exécution se comparent, car l’échec apparaît différemment selon chacun. eclipse-temurin:17-jre inclut 8 fichiers DejaVu et node:18-bookworm en inclut 6, tous deux pour AWT, ce qui explique pourquoi les images JVM et Node signent le texte latin sans problème et ne tombent en erreur que lorsqu’une chaîne japonaise ou chinoise apparaît. python:3.11-slim ne fournit aucune police, comme l’image d’exécution .NET, donc il échoue dès la première signature. Personne n’obtient les caractères CJK gratuitement sur aucune d’elles.

L’approvisionnement en polices du conteneur est l’étape qui permet la signature de texte dans une image Linux avec GroupDocs.Signature pour .NET. C’est important car la bibliothèque ne remplace pas une famille manquante : nommer une police qui n’est pas installée déclenche une erreur et n’écrit aucun document. Cet article place l’image sans police à côté de celle corrigée, montre ce qui a changé, et couvre la résolution à l’exécution qui maintient le même code fonctionnant sur une machine de développeur.

Il existe une meilleure façon

Deux choses doivent être vraies. L’image doit contenir au moins une police, et le code doit cesser de supposer laquelle.

La première correspond à une couche Dockerfile. La seconde est une étape de résolution : au lieu de coder en dur Arial, demandez à la bibliothèque laquelle des plusieurs familles candidates elle peut réellement utiliser, et conservez la première qui fonctionne. Le résultat s’exécute inchangé dans un conteneur slim, sous Windows et en CI, car il ne fait jamais d’affirmation sur l’environnement qu’il n’a pas vérifié.

Une chose qui ne fonctionne pas, et il vaut la peine de le dire clairement car c’est la première chose que les gens essaient : laisser la police non définie. Sans SignatureFont, GroupDocs.Signature demande son propre défaut, Times New Roman, que l’image sans police ne possède pas non plus. L’appel échoue de la même manière.

La nouvelle méthode : deux images, une différence

Étape 1 - Examiner ce que l’image contient

Avant de signer quoi que ce soit, répertoriez les fichiers de police. Le nombre transforme une exception vague en diagnostic, car zéro police et un nom de famille incorrect nécessitent des correctifs différents :

string[] roots =
{
    "/usr/share/fonts",
    "/usr/local/share/fonts",
    Path.Combine(home, ".fonts"),
    Path.Combine(home, ".local/share/fonts"),
    Environment.GetFolderPath(Environment.SpecialFolder.Fonts),
    "/System/Library/Fonts",
    "/Library/Fonts",
};

Notez ce qui est absent : System.Drawing. System.Drawing.Common est uniquement disponible sous Windows à partir de .NET 7 et lève une exception sous Linux, ainsi le code de police construit dessus échoue dans le conteneur pour une raison secondaire, non liée.

Étape 2 - Ajouter la couche de polices

Quatre paquets, un RUN, et l’échec disparaît :

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 pour le débogage. fonts-dejavu-core constitue le minimum pour le latin, le grec et le cyrillique. fonts-liberation fournit des substituts métriquement compatibles pour Arial, Times New Roman et Courier New, qui sont les polices réellement référencées par les documents créés sous Windows. fonts-noto-cjk couvre le chinois, le japonais et le coréen.

Étape 3 - Résoudre une famille au lieu d’en nommer une

La façon portable de choisir une police consiste à tenter une signature jetable pour chaque candidat et à conserver la première qui ne lève pas d’exception :

foreach (string candidate in candidates)
{
    if (TryFamily(sourcePath, candidate).Ok)
    {
        return candidate;
    }
}

return null;

La détection par nom de fichier est le raccourci tentant mais il est erroné. Le paquet Debian fonts-noto-cjk installe NotoSansCJK-Regular.ttc, dont le nom de famille est Noto Sans CJK JP. Une correspondance de nom de fichier manque les polices présentes et indique des familles qui ne seront pas résolues lorsqu’elles sont passées à SignatureFont.

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

Une famille latine résolue est requise ; une famille CJK résolue est optionnelle et son absence entraîne simplement un saut, pas un plantage :

var options = new List<SignOptions>
{
    BuildTextOptions(LatinText, latinFamily, top: 50),
};

if (cjkFamily is not null)
{
    options.Add(BuildTextOptions(CjkText, cjkFamily, top: 120));
}

SignResult result = signature.Sign(outputPath, options);

Puis lisez le fichier à nouveau, car le CJK sans police CJK peut s’afficher sous forme de cases vides sans lever aucune exception :

var options = new TextSearchOptions { AllPages = true };
List<TextSignature> found = signature.Search<TextSignature>(options);

Comparaison côte à côte : avant vs. après

Dockerfile.nofonts Dockerfile
Fichiers de police dans l’image 0 DejaVu, Liberation, Noto CJK
Signature de texte latin échoue, sortie 3 écrite et récupérée lors de la lecture
Signature de texte CJK échoue écrite et récupérée
Erreur affichée Font <name> was not found aucune
Différence de code aucune - même binaire aucune - même binaire

La dernière ligne est le point essentiel. Rien dans l’application n’a changé entre les deux exécutions. Le dépôt d’exemple fournit les deux fichiers afin que la comparaison nécessite deux commandes docker build plutôt que de faire confiance. Conservez également la variante sans police dans le dépôt : c’est le moyen le plus rapide de reproduire l’échec lorsqu’une personne change d’image de base six mois plus tard et que les signatures cessent discrètement d’apparaître.

Pourquoi ne pas simplement installer toutes les polices ?

Parce que la taille de l’image est une contrainte réelle et que les quatre paquets ci‑dessus couvrent déjà les polices les plus utilisées par les documents. fonts-dejavu-core seul suffit pour la signature latine, grecque et cyrillique ; Liberation est important lorsque les documents font référence aux familles Windows par leur nom ; Noto CJK est celui qui est réellement volumineux et ne justifie son poids que si vous signez du texte d’Asie de l’Est. Installez ce dont vos documents ont besoin, puis vérifiez avec une lecture en retour.

Exemple réel : le travailleur de signature par lots

Un travailleur de file d’attente signe quelques milliers de PDF chaque nuit. Avec la résolution au démarrage, il consigne une ligne indiquant les familles qu’il utilisera, et si rien ne se résout il s’arrête avant de toucher la file d’attente plutôt que d’échouer message par message. Cette vérification au démarrage transforme un problème de police d’une série de jobs échoués en un conteneur qui refuse de démarrer avec une raison d’une ligne.

Le coût du sondage est suffisamment faible pour être ignoré au démarrage et trop important pour être répété par document. Chaque sondage est une vraie signature écrite dans un fichier temporaire, ainsi la liste latine coûte jusqu’à quatre d’entre elles et la liste CJK jusqu’à huit, le tout sur un PDF d’une page. Résolvez une fois, mettez en cache les deux noms de famille, et le chemin par document est exactement celui d’avant : construisez les options, appelez Sign, lisez le nombre de résultats.

J’ai perdu un après‑midi avec la version qui devinait. Elle a parcouru le répertoire des polices, trouvé NotoSansCJK-Regular.ttc, indiqué le CJK comme disponible, puis échoué sur chaque nom de famille que j’ai dérivé de ce nom de fichier. Le sondage avec une vraie signature était à la fois plus simple et correct.

Quoi d’autre pose problème dans un conteneur ?

Encore une chose, et elle n’est pas liée aux polices : InvariantGlobalization=true. C’est un conseil standard pour retirer ICU d’une image .NET, et avec GroupDocs.Signature cela fait que le tout premier new Signature(...) lève une CultureNotFoundException : ... en-US est un identifiant de culture invalide, parce que SignatureSettings crée un CultureInfo("en-US"). Gardez la globalisation activée et laissez ICU rester dans l’image. La page des exigences système est l’endroit où vérifier la prise en charge de la plateforme avant de s’engager sur une image de base.

Conclusion

Un service de signature qui fonctionne localement et échoue dans Docker manque presque toujours de polices, et la solution consiste en une couche de quatre paquets plus un code qui résout une famille plutôt que d’en supposer une. Construisez les deux images à partir de l’exemple, exécutez-les côte à côte, et lisez les lignes [fonts] : tout l’argument tient dans cette unique comparaison.

Ressources supplémentaires