💡 Kompletní funkční příklad dostupný na GitHubu:
sign-pdf-in-linux-container-fonts-dotnet
Starý způsob byl bolestivý
Služba podepisuje faktury. Běží na notebooku se třemi sta nainstalovanými fonty, projde revizí a v pátek se zabalí do kontejneru. V pondělí první úloha v clusteru skončí s nenulovým kódem a chybou Sign document error: Font Arial was not found a někdo stráví dopoledne čtením stack trace, než si někdo uvědomí, jaké fonty skutečně obsahuje obraz mcr.microsoft.com/dotnet/runtime:8.0.
Odpověď je žádná. Nula souborů fontů, měřeno na obrazu, ve kterém běží ukázka z tohoto článku.
Stojí za to vědět, jak se ostatní runtime porovnávají, protože selhání vypadá na každém jinak. eclipse-temurin:17-jre obsahuje 8 souborů DejaVu a node:18-bookworm obsahuje 6, oba pro AWT, což je důvod, proč JVM a Node obrazy tiše podepisují latinský text a selžou až při příchodu japonského nebo čínského řetězce. python:3.11-slim neobsahuje žádný, stejně jako .NET runtime obraz, takže selže už při první podpisové operaci. Nikdo nedostane CJK zdarma na žádném z nich.
Poskytování fontů v kontejneru je krok, který umožní podepisování textu v Linuxovém obrazu s GroupDocs.Signature pro .NET. Má to význam, protože knihovna nenahrazuje chybějící rodinu: pojmenování fontu, který není nainstalován, vyvolá chybu a žádný dokument se neuloží. Tento článek postaví obraz bez fontů vedle opraveného, ukáže, co se změnilo, a popíše runtime rozlišení, které udržuje stejný kód funkční na vývojovém stroji.
Existuje lepší způsob
Musí být pravda dvě věci. Obraz potřebuje alespoň jeden font a kód musí přestat předpokládat, který to je.
První je vrstva v Dockerfile. Druhá je krok rozlišení: místo pevného kódování Arial se zeptáme knihovny, kterou z několika kandidátních rodin může skutečně použít, a zachováme první, která funguje. Výsledek běží beze změny v tenkém kontejneru, na Windows i v CI, protože nikdy neasertuje nic o prostředí, které nezkontroloval.
Jedna věc, která nefunguje, a stojí za to ji jasně říct, protože je to první věc, kterou lidé zkouší: nechat font nenastavený. Bez SignatureFont GroupDocs.Signature požaduje svůj výchozí Times New Roman, který také chybí v obrazu bez fontů. Volání selže identicky.
Nový způsob: dva obrazy, jeden rozdíl
Krok 1 – Podívejte se, co obraz obsahuje
Před jakýmkoli podepisováním si vypište soubory fontů. Počet promění vágní výjimku v diagnostiku, protože nula fontů a špatný název rodiny vyžadují různé opravy:
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",
};
Všimněte si, co chybí: System.Drawing. System.Drawing.Common je od .NET 7 pouze pro Windows a na Linuxu vyvolá výjimku, takže kód postavený na něm selže v kontejneru z jiného, nesouvisejícího důvodu.
Krok 2 – Přidejte vrstvu fontů
Čtyři balíčky, jeden RUN a selhání zmizí:
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 je resolver a poskytuje vám fc-list pro ladění. fonts-dejavu-core jsou minimální latinské, řecké a cyrilické fonty. fonts-liberation dodává metricky kompatibilní náhrady za Arial, Times New Roman a Courier New, což jsou fonty, na které se ve skutečnosti odkazují dokumenty vytvořené ve Windows. fonts-noto-cjk pokrývá čínštinu, japonštinu a korejštinu.
Krok 3 – Vyřešte rodinu místo pojmenování jedné
Přenosný způsob, jak vybrat font, je pokusit se o jednorázový podpis pro každého kandidáta a zachovat první, který nevyhodí výjimku:
foreach (string candidate in candidates)
{
if (TryFamily(sourcePath, candidate).Ok)
{
return candidate;
}
}
return null;
Detekce podle názvu souboru je lákavá zkratka, ale je špatná. Debianový balíček fonts-noto-cjk instaluje NotoSansCJK-Regular.ttc, jehož název rodiny je Noto Sans CJK JP. Shoda podle názvu souboru mineme fonty, které jsou přítomny, a tvrdí rodiny, které se při předání do SignatureFont nevyřeší.
Krok 4 – Podepište to, co bylo vyřešeno, ověřte, co jste podepsali
Vyřešená latinská rodina je povinná; vyřešená CJK rodina je volitelná a její absence znamená přeskočení, ne pád:
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);
Pak soubor znovu načtěte, protože CJK bez CJK fontu může vykreslovat prázdné rámečky, aniž by vyvolalo jakoukoli chybu:
var options = new TextSearchOptions { AllPages = true };
List<TextSignature> found = signature.Search<TextSignature>(options);
Vedle sebe: před a po
Dockerfile.nofonts |
Dockerfile |
|
|---|---|---|
| Soubory fontů v obrazu | 0 | DejaVu, Liberation, Noto CJK |
| Podpis latinského textu | selže, exit 3 | zapsáno a obnoveno při čtení zpět |
| Podpis CJK textu | selže | zapsáno a obnoveno |
| Zobrazená chyba | Font <name> was not found |
žádná |
| Rozdíl v kódu | žádný – stejný binární soubor | žádný – stejný binární soubor |
Poslední řádek je podstatný. V aplikaci se mezi dvěma běhy nic nezměnilo. Ukázkové úložiště obsahuje oba soubory, takže srovnání vyžaduje dva příkazy docker build místo pouhého důvěřování. Pošlete také variantu bez fontů do úložiště: je to nejrychlejší způsob, jak reprodukovat selhání, když někdo po šesti měsících přepne základní obrazy a podpisy tiše přestanou být generovány.
Proč neinstalovat všechny fonty?
Protože velikost obrazu je reálné omezení a výše uvedené čtyři balíčky již pokrývají skripty, které používá většina dokumentů. fonts-dejavu-core samotné stačí pro latinské, řecké a cyrilické podpisy; Liberation je důležitá, když dokumenty odkazují na Windows rodiny podle názvu; Noto CJK je ta, která je skutečně velká a platí si jen tehdy, když podepisujete východoasijský text. Nainstalujte to, co vaše dokumenty potřebují, a pak ověřte pomocí čtení zpět.
Reálný příklad: pracovník dávkového podepisování
Fronta pracovníků podepisuje během noci několik tisíc PDF. S rozlišením při startu zapisuje jeden řádek s názvy rodin, které použije, a pokud se nic nevyřeší, ukončí se před tím, než se dotkne fronty, místo aby selhal po jednotlivých zprávách. Tato kontrola při startu převádí problém s fonty ze série selhávajících úloh na kontejner, který se odmítne spustit s jednorázovým důvodem.
Náklady na prohledávání jsou dost malé na to, aby se ignorovaly při startu, a příliš velké na to, aby se opakovaly pro každý dokument. Každé prohledání je skutečný podpis zapsaný do dočasného souboru, takže latinská sada stojí až čtyři takové podpisy a CJK sada až osm, vše proti jedné stránce PDF. Vyřešte jednou, uložte do cache dva názvy rodin a cesta pro jednotlivé dokumenty je přesně stejná jako předtím: sestavte možnosti, zavolejte Sign, přečtěte počet výsledků.
Ztratil jsem odpoledne na verzi, která hádala. Prohledala adresář fontů, našla NotoSansCJK-Regular.ttc, nahlásila CJK jako dostupné a pak selhala u každého názvu rodiny, který jsem odvodil od tohoto souboru. Prohledávání pomocí skutečného podpisu bylo jak jednodušší, tak správné.
Co dalšího škodí v kontejneru?
Jedna věc, která nesouvisí s fonty: InvariantGlobalization=true. Je to standardní doporučení pro odstranění ICU z .NET obrazu a s GroupDocs.Signature způsobí, že první new Signature(...) vyhodí CultureNotFoundException: ... en-US is an invalid culture identifier, protože SignatureSettings vytváří CultureInfo("en-US"). Nechte globalizaci zapnutou a nechte ICU v obrazu. Stránka system requirements je místo, kde zkontrolovat podporu platformy před tím, než se zavážete k základnímu obrazu.
Závěr
Podepisovací služba, která funguje lokálně a selže v Dockeru, téměř vždy postrádá fonty, a oprava spočívá ve čtyřbalíčkové vrstvě plus kódu, který řeší rodinu místo předpokládání jedné. Sestavte oba obrazy ze vzorku, spusťte je vedle sebe a přečtěte řádky [fonts]: celý argument se vejde do tohoto jednoho srovnání.