💡 Esempio completo funzionante disponibile su GitHub:
python-linux-container-pdf-signing
Introduzione
Lo script funziona localmente. Lo containerizzi su python:3.11-slim e fallisce al import groupdocs.signature. Lo risolvi e fallisce di nuovo alla prima firma. Nessuno dei due errori indica cosa manca realmente.
La firma in container con Python è un flusso di lavoro GroupDocs.Signature che richiede due livelli di provisioning anziché uno: le librerie runtime .NET su cui è costruito il binding e i font con cui ogni firma testuale deve essere renderizzata. Questo tutorial costruisce entrambi, poi lo script che risolve una famiglia di font a runtime invece di hard‑codificarne una, così lo stesso codice funziona nel container e sulla macchina su cui è stato scritto.
Perché entrambi i livelli sono importanti
GroupDocs.Signature per Python è un binding .NET, quindi libicu e una libreria compatibile con OpenSSL 1.1 devono esistere prima che qualsiasi import abbia successo. Questo è il primo livello, ed è ben documentato in Running in Docker.
Il motivo per cui i due livelli vengono confusi è che entrambi falliscono in momenti adiacenti all’import e nessuno degli errori ne indica la causa. Un libssl1.1 mancante genera un errore di loader su un oggetto condiviso; un font mancante genera un errore di firma avvolto in un’eccezione proxy. Nessuno dice “la tua immagine di base è troppo piccola”, che è ciò che entrambi realmente significano.
Il secondo livello sono i font, ed è quello che sorprende le persone. python:3.11-slim non contiene alcun file di font. GroupDocs.Signature non sostituisce una famiglia mancante – specificarne una non installata genera un’eccezione, e nulla viene scritto – e rimuovere il font non è nemmeno una soluzione temporanea, perché la libreria richiede allora il proprio default e fallisce allo stesso modo. Su un’immagine senza font, una firma testuale è semplicemente impossibile.
Prerequisiti
Python 3.11 (la ruota è limitata a CPython 3.14) e groupdocs-signature-net==26.1. Docker se vuoi vedere entrambi i fallimenti di proposito, il che richiede circa dieci minuti.
Installazione
pip install groupdocs-signature-net==26.1
Passo 1 – Costruire il livello .NET
libssl1.1 non è presente in bookworm, quindi proviene da uno snapshot Debian fissato:
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/*
Punti chiave:
- Questo livello rende l’import funzionante; non dice nulla sui font.
- Fissare la data dello snapshot mantiene la build riproducibile quando l’archivio avanza.
Passo 2 – Costruire il livello dei font
Quattro pacchetti, mantenuti come loro proprio livello così da poterli commentare per riprodurre il fallimento:
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 è il risolutore e fornisce fc-list. fonts-dejavu-core è il minimo per latino, greco e cirillico. fonts-liberation copre i documenti che fanno riferimento a Arial o Times New Roman per nome. fonts-noto-cjk copre cinese, giapponese e coreano.
Passo 3 – Chiedere alla libreria quale famiglia può usare
Scansionare /usr/share/fonts per un nome file sembra equivalente ma non lo è: fonts-noto-cjk installa NotoSansCJK-Regular.ttc, il cui nome famiglia è Noto Sans CJK JP. La risposta portabile è una prova – una vera firma in un file temporaneo – con il fallimento convertito in un valore:
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
Osserva attentamente font.size = 10.0. Il binding mappa la dimensione a un float .NET e rifiuta un int con “numeric argument expected, got ‘int’”. Poiché ciò avviene all’interno della prova, ogni famiglia candidata fallisce e l’output appare esattamente come un’immagine senza font. Ho aggiunto tre pacchetti di font a un’immagine che li aveva già tutti prima di notare il problema.
La risoluzione diventa quindi un ciclo:
for candidate in candidates:
if try_family(source_path, candidate) is None:
return candidate
return None
Passo 4 – Firmare ciò che è stato risolto, verificare ciò che è stato firmato
La famiglia latina è obbligatoria, quella CJK opzionale:
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)
Poi verifica, perché il CJK renderizzato come caselle vuote non genera alcun errore:
options = TextVerifyOptions()
options.text = expected_text
options.match_type = gsd.TextMatchType.CONTAINS
options.all_pages = True
result = sign.verify(options)
CONTAINS è deliberato: in modalità valutazione la libreria aggiunge testo di prova alla pagina, e una corrispondenza esatta segnalerebbe un documento perfettamente valido come fallito.
E le documentazioni che dicono che Python ha supporto Linux limitato?
La pagina Running in Docker elenca i pacchetti Python pronti per Linux e omette Signature. Con groupdocs-signature-net==26.1 questo esempio ha firmato e verificato all’interno di python:3.11-slim, CJK incluso, con entrambi i livelli installati. Considera l’elenco obsoleto piuttosto che un blocco, e conferma con la tua versione prima di impegnarti in un deployment.
Applicazioni nel mondo reale
Un servizio di fatturazione che appone una linea di approvazione su PDF generati ha esattamente bisogno di questo: il livello .NET, un font latino e un controllo di risoluzione all’avvio. Il controllo è ciò che trasforma un deployment difettoso in un container che rifiuta di avviarsi, anziché in una coda di fatture che falliscono silenziosamente una alla volta. Un portale documenti che accetta nomi dei clienti in qualsiasi scrittura richiede anche il pacchetto CJK, più il passaggio di verifica, perché è l’unica cosa che separa una casella vuota da un nome firmato.
Dove collocare il controllo di risoluzione
Mettilo doveunque venga eseguito una sola volta per processo: una chiamata a livello di modulo, un gestore di durata di FastAPI, un AppConfig.ready di Django, o le prime righe del main di un worker. Ne escono due valori, la famiglia latina e quella CJK, e entrambi appartengono al log di avvio accanto al conteggio dei font.
Questa collocazione fa più che risparmiare tempo di probing. Sposta il fallimento dalla gestione della richiesta – dove è il problema di un singolo cliente e una traccia di stack che nessuno legge – all’avvio, dove è un deployment che non è partito e qualcuno lo sta già osservando. Un container che termina con “no usable font family, install fonts-dejavu-core” non richiede alcun debugging.
Risoluzione dei problemi comuni
import groupdocs.signature fallisce
Il livello .NET è mancante o il repository snapshot non era raggiungibile durante la build. Questo è il primo livello e non ha nulla a che fare con i font. Controlla il log di build per lo step apt prima di toccare qualsiasi codice di firma, perché un fetch di snapshot fallito non impedisce la costruzione dell’immagine.
Ogni font candidato fallisce, ma fc-list mostra dei font
Controlla che font.size non sia un int prima di aggiungere altri pacchetti.
La firma è presente ma il testo CJK è rappresentato da caselle
fonts-noto-cjk è mancante. La firma è stata scritta con una famiglia che non possiede glifi per quei punti di codice, ed è per questo che esiste il passaggio di verifica: fallisce esattamente in questo caso, dove la firma segnala successo.
Cosa stampano realmente le due immagini
Esegui entrambe e leggi le prime quattro righe. L’immagine senza font riporta font files on disk: 0, entrambe le linee di risoluzione come (none), l’errore deliberato di font mancante, e poi esce con codice 3 mostrando la correzione minima. L’immagine provisionata riporta un conteggio font diverso da zero, DejaVu Sans per il latino e Noto Sans CJK JP per il CJK, due firme applicate e entrambi i testi verificati.
Quella coppia di output è l’artifact da conservare. Incollalo nelle note di deployment e la prossima persona che cambia l’immagine di base avrà un riferimento su come dovrebbe apparire un container sano, senza dover capire fontconfig.
Conclusione
Due livelli e una prova. Installa le dipendenze .NET, installa almeno fontconfig e DejaVu, risolvi la famiglia chiedendo anziché presumere, e verifica l’output prima di dichiarare il lavoro concluso. Non è molto codice, ed è il tipo di cosa che appare ovvia col senno di poi e invisibile in una traccia di errore. Il repository di esempio fornisce entrambi i Dockerfile, così la differenza tra un’immagine funzionante e una rotta è una sola build.