💡 Pełny działający przykład dostępny na GitHubie:
sign-pdf-in-linux-container-fonts-dotnet
Stara metoda była bolesna
Usługa podpisuje faktury. Działa na laptopie z trzystoma zainstalowanymi czcionkami, przechodzi przegląd i w piątek zostaje spakowana do kontenera. W poniedziałek pierwszy proces w klastrze kończy się niezerowym kodem wyjścia z komunikatem Sign document error: Font Arial was not found, a ktoś spędza rano na czytaniu stack trace, zanim ktokolwiek pomyśli zapytać, jakie czcionki naprawdę znajdują się w obrazie mcr.microsoft.com/dotnet/runtime:8.0.
Odpowiedź brzmi: żadnych. Zero plików czcionek, zmierzone w obrazie, w którym uruchamiany jest przykład z tego artykułu.
Warto wiedzieć, jak wypadają inne środowiska uruchomieniowe, ponieważ awaria wygląda inaczej w każdym z nich. eclipse-temurin:17-jre zawiera 8 plików DejaVu, a node:18-bookworm – 6, oba dla AWT, co tłumaczy, dlaczego obrazy JVM i Node cicho podpisują tekst łaciński i zawodzą dopiero, gdy pojawi się ciąg japoński lub chiński. python:3.11-slim nie zawiera żadnych czcionek, podobnie jak obraz .NET runtime, więc zawodzi przy pierwszym podpisie. Nikt nie dostaje czcionek CJK „za darmo” w żadnym z tych obrazów.
Dostarczanie czcionek do kontenera to krok, który umożliwia podpisywanie tekstu w obrazie Linux z użyciem GroupDocs.Signature dla .NET. Ma to znaczenie, ponieważ biblioteka nie podmienia brakującej rodziny: podanie nazwy czcionki, której nie ma, powoduje błąd i nie zapisuje dokumentu. Ten artykuł zestawia obraz bez czcionek z obrazem naprawionym, pokazuje, co się zmieniło, i opisuje rozwiązywanie w czasie uruchomienia, które pozwala używać tego samego kodu na maszynie deweloperskiej.
Istnieje lepszy sposób
Muszą być spełnione dwa warunki. Obraz musi zawierać przynajmniej jedną czcionkę, a kod musi przestać zakładać, że to właśnie ona jest dostępna.
Pierwszy warunek realizuje warstwa w Dockerfile. Drugi to krok rozwiązywania: zamiast na stałe kodować Arial, zapytaj bibliotekę, której z kilku kandydatów rodziny faktycznie może użyć, i zachowaj pierwszą działającą. Wynik działa niezmieniony w cienkim kontenerze, w Windows i w CI, ponieważ nigdy nie zakłada nic o środowisku, którego nie sprawdził.
Jedna rzecz, która nie działa, i warto to jasno zaznaczyć, bo jest pierwszą rzeczą, którą ludzie próbują: pozostawienie czcionki nieustawionej. Bez SignatureFont GroupDocs.Signature domyślnie żąda Times New Roman, której również brakuje w obrazie bez czcionek. Wywołanie kończy się identycznym błędem.
Nowy sposób: dwa obrazy, jedna różnica
Krok 1 – sprawdź, co ma obraz
Zanim coś podpiszesz, wypisz pliki czcionek. Liczba zamienia niejasny wyjątek w diagnozę, ponieważ zero czcionek i błędna nazwa rodziny wymagają różnych poprawek:
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",
};
Zauważ, co brakuje: System.Drawing. System.Drawing.Common jest dostępny tylko w Windows od .NET 7 i rzuca wyjątek w Linux, więc kod czcionek oparty na tej bibliotece nie działa w kontenerze z innego powodu.
Krok 2 – dodaj warstwę czcionek
Cztery pakiety, jedno polecenie RUN i problem znika:
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 to resolver i udostępnia fc-list do debugowania. fonts-dejavu-core to minimalny zestaw dla łacińskiego, greckiego i cyrylicy. fonts-liberation dostarcza metrically‑compatible zamienniki dla Arial, Times New Roman i Courier New, czyli tego, na co faktycznie odwołują się dokumenty tworzone w Windows. fonts-noto-cjk obejmuje chiński, japoński i koreański.
Krok 3 – rozwiąż rodzinę zamiast podawać nazwę
Przenośny sposób wyboru czcionki to próba tymczasowego podpisu dla każdego kandydata i zachowanie pierwszego, który nie rzuci wyjątku:
foreach (string candidate in candidates)
{
if (TryFamily(sourcePath, candidate).Ok)
{
return candidate;
}
}
return null;
Wykrywanie po nazwie pliku to kusząca, ale błędna, skrócona metoda. Pakiet fonts-noto-cjk w Debianie instaluje NotoSansCJK-Regular.ttc, którego nazwa rodziny to Noto Sans CJK JP. Dopasowanie po nazwie pliku pomija czcionki, które są obecne, i zgłasza rodziny, które nie zostaną rozpoznane przez SignatureFont.
Krok 4 – podpisz to, co zostało rozpoznane, i zweryfikuj wynik
Rozpoznana rodzina łacińska jest wymagana; rozpoznana rodzina CJK jest opcjonalna – jej brak oznacza pominięcie, a nie awarię:
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);
Następnie odczytaj plik, ponieważ CJK bez czcionki CJK może wyświetlać się jako puste kwadraty, nie generując żadnego błędu:
var options = new TextSearchOptions { AllPages = true };
List<TextSignature> found = signature.Search<TextSignature>(options);
Rzut oka: przed i po
Dockerfile.nofonts |
Dockerfile |
|
|---|---|---|
| Pliki czcionek w obrazie | 0 | DejaVu, Liberation, Noto CJK |
| Podpis tekstu łacińskiego | niepowodzenie, exit 3 | zapisany i odczytany przy weryfikacji |
| Podpis tekstu CJK | niepowodzenie | zapisany i odczytany |
| Wyświetlony błąd | Font <name> was not found |
brak |
| Różnica w kodzie | brak – ten sam binarny | brak – ten sam binarny |
Ostatni wiersz jest sednem. Nic w aplikacji nie zmieniło się między dwoma uruchomieniami. Repozytorium przykładu zawiera oba pliki, więc porównanie wymaga dwóch poleceń docker build zamiast domysłów. Zachowaj także wariant bez czcionek w repozytorium: to najszybszy sposób odtworzenia awarii, gdy ktoś zmieni bazowy obraz po sześciu miesiącach i podpisy przestaną się pojawiać.
Dlaczego nie zainstalować każdej czcionki?
Ponieważ rozmiar obrazu jest realnym ograniczeniem, a cztery wymienione pakiety już pokrywają najczęstsze scenariusze. fonts-dejavu-core wystarcza do podpisów łacińskich, greckich i cyrylicy; Liberation jest potrzebny, gdy dokumenty odwołują się do rodzin Windows po nazwie; Noto CJK jest jedynym naprawdę dużym pakietem i płaci się za niego tylko wtedy, gdy podpisuje się tekst wschodnioazjatycki. Zainstaluj to, czego potrzebują Twoje dokumenty, a potem zweryfikuj przy odczycie.
Przykład z życia: pracownik wsadowego podpisywania
Pracownik kolejki podpisuje kilka tysięcy PDF‑ów nocą. Dzięki rozwiązywaniu przy starcie, loguje jedną linię z nazwami rodzin, które zamierza używać, i jeśli nic nie zostanie rozpoznane, kończy działanie przed dotknięciem kolejki, zamiast zawodzić przy każdym dokumencie. Ten kontrolny test przy starcie zamienia problem czcionek z ciągu nieudanych zadań w kontener, który odmawia uruchomienia z jednozdaniowym powodem.
Koszt sondowania jest na tyle mały, że można go zignorować przy starcie, a jednocześnie na tyle duży, by nie powtarzać go przy każdym dokumencie. Każde sondowanie to prawdziwy podpis zapisany do pliku tymczasowego, więc lista łacińska kosztuje do czterech takich operacji, a lista CJK do ośmiu, wszystko na jednopaginowym PDF. Rozwiąż raz, zapamiętaj dwie nazwy rodzin i ścieżka per dokument pozostaje taka sama jak wcześniej: buduj opcje, wywołaj Sign, odczytaj liczbę wyników.
Straciłem po południu na wersji, która zgadywała. Przeszukiwała katalog czcionek, znalazła NotoSansCJK-Regular.ttc, zgłosiła CJK jako dostępne i potem zawiodła przy każdej nazwie rodziny wyprowadzonej z tej nazwy pliku. Sondowanie rzeczywistym podpisem było zarówno prostsze, jak i poprawne.
Co jeszcze gryzie w kontenerze?
Jeszcze jedna rzecz, niezwiązana z czcionkami: InvariantGlobalization=true. To standardowa rada przy usuwaniu ICU z obrazu .NET, a w połączeniu z GroupDocs.Signature powoduje, że pierwsze new Signature(...) rzuca CultureNotFoundException: ... en-US is an invalid culture identifier, ponieważ SignatureSettings tworzy CultureInfo("en-US"). Pozostaw globalizację włączoną i pozwól ICU pozostać w obrazie. Strona system requirements to miejsce, gdzie sprawdzić wsparcie platformy przed wyborem obrazu bazowego.
Wnioski
Usługa podpisująca, która działa lokalnie, a w Dockerze nie, prawie zawsze brak jej czcionek, a naprawa polega na warstwie czterech pakietów oraz kodzie, który rozwiązuje rodzinę zamiast zakładać jedną. Zbuduj oba obrazy z przykładu, uruchom je obok siebie i przeczytaj linie [fonts]: cała argumentacja mieści się w tym jednym porównaniu.