💡 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í.

Další zdroje