Udostępnianie czcionek w środowisku produkcyjnym
W skrócie
Dział zatytułowany „W skrócie”Twój plik PDF renderuje się poprawnie na laptopie, potem trafia do kontenera i wychodzi jako rząd pustych prostokątów — glif „tofu” — albo z brakującymi akcentami i znakami spoza alfabetu łacińskiego. Przyczyna jest niemal zawsze ta sama: czcionka, którą wybrałeś, nie jest obecna we wdrożonym obrazie.
Natywny silnik NextPDF działający w ramach procesu rozwiązuje czcionki na podstawie
plików czcionek, które rejestr czcionek potrafi odczytać. Nie wykrywa
automatycznie czcionek systemu operacyjnego ani fontconfig — pliki czcionek
zainstalowane w systemie operacyjnym pomagają tylko wtedy, gdy jawnie zarejestrujesz
te pliki albo dodasz zawierający je katalog do ścieżki wyszukiwania FontRegistry.
Kontener zbudowany z odchudzonego obrazu bazowego nie ma czcionek zainstalowanych
przez apt/apk, a nawet gdy je ma, natywny silnik je ignoruje, dopóki nie
wskażesz rejestrowi ich plików. Rozwiązaniem jest dołączenie rzeczywistych plików
czcionek do aplikacji lub obrazu i zarejestrowanie ich w silniku. Rejestr odczytuje
pliki TrueType (.ttf), OpenType (.otf) oraz TrueType Collection (.ttc);
przestarzały Type1 (.pfb) również jest akceptowany, ale rzadko potrzebny w nowych
projektach.
Zanim zaczniesz, potwierdź, że te elementy są na miejscu:
- NextPDF core jest zainstalowany.
- Masz rzeczywiste pliki czcionek, których zamierzasz użyć, i masz prawo je osadzić. Prawa do osadzania są Twoją odpowiedzialnością — zobacz Osadzanie czcionki TrueType i tworzenie jej podzbioru.
- Twoja kompilacja może skopiować te pliki do wdrażanego artefaktu.
To poradnik operacyjny. Kod jest minimalny; praca tkwi w kompilacji i układzie systemu plików. Mechanikę rejestrowania i tworzenia podzbioru pojedynczego kroju na poziomie API znajdziesz w przepisie o osadzaniu i podzbiorze, do którego odnosi się powyższy odnośnik. Ta strona obejmuje umieszczenie plików na maszynie i wskazanie ich silnikowi.
Dlaczego natywny silnik nie znajduje automatycznie czcionek systemu operacyjnego
Dział zatytułowany „Dlaczego natywny silnik nie znajduje automatycznie czcionek systemu operacyjnego”Istnieją dwie odrębne ścieżki renderowania, a kwestia czcionek różni się między nimi.
- Natywny silnik w ramach procesu (domyślny,
Document/writeHtml): silnik nie wywołuje systemu czcionek systemu operacyjnego anifontconfigw celu wykrywania. Rozwiązuje krój przez rejestr czcionek, który odczytuje konkretny plik czcionki, który zarejestrowałeś, albo odnajduje go wewnątrz katalogu skonfigurowanego jako ścieżka wyszukiwania. Zainstalowanie czcionki za pomocąapt-get install fonts-notolub uruchomieniefc-cachesamo w sobie nic nie daje — natywny silnik widzi te pliki tylko wtedy, gdy je zarejestrujesz albo dodasz ich katalog do ścieżki wyszukiwania rejestru. - Mostek Chrome (renderer HTML-do-PDF, który napędza przeglądarkę headless):
ta ścieżka korzysta z czcionek zainstalowanych na hoście za pośrednictwem
normalnego wykrywania czcionek przeglądarki, więc pakiety czcionek
apt/apkifontconfigmają tam znaczenie.
Jeśli czytasz ogólne wytyczne „zainstaluj te pakiety czcionek systemowych w swoim Dockerfile”, dotyczą one mostka Chrome, a nie natywnego silnika opisanego na tej stronie. Dla generowania natywnego dołącz pliki i zarejestruj je.
Krok 1 — Dołącz rzeczywiste pliki czcionek
Dział zatytułowany „Krok 1 — Dołącz rzeczywiste pliki czcionek”Umieść pliki czcionek wewnątrz drzewa aplikacji, aby były wersjonowane i wysyłane z
każdą kompilacją. Konwencjonalną lokalizacją jest katalog resources/fonts/.
your-app/├── resources/│ └── fonts/│ ├── DejaVuSans.ttf│ ├── DejaVuSans-B.ttf│ └── NotoSansCJK-Regular.ttc└── src/Nazwij pliki tak, aby wyszukiwanie katalogowe silnika mogło odnaleźć je po rodzinie
i stylu. Gdy zarejestrujesz katalog (zamiast konkretnego pliku) i później wywołasz
setFont('DejaVuSans', 'B', 12), silnik szuka plików takich jak DejaVuSans-B.ttf,
DejaVuSansB.ttf lub DejaVuSans.ttf w każdym skonfigurowanym katalogu.
Wyszukiwanie katalogowe buduje te kandydujące nazwy z tego samego jednoliterowego
kodu stylu, który przekazujesz do setFont (B dla pogrubienia, I dla
kursywy, BI dla pogrubionej kursywy), a nie z rozpisanego słowa — więc niezawodną
postacią jest Family-<StyleCode>.ttf (na przykład DejaVuSans-B.ttf lub
DejaVuSans-BI.ttf), a nie Family-Bold.ttf. Plik o nazwie DejaVuSans-Bold.ttf
nigdy nie zostanie odnaleziony przez wyszukiwanie katalogowe; aby użyć takiego pliku,
zarejestruj go jawnie za pomocą register() — co parsuje czcionkę i indeksuje ją
pod rodziną i stylem odczytanymi z własnych tablic nazw pliku, dzięki czemu rozpisana
nazwa pliku przestaje mieć znaczenie (zobacz Krok 2).
Krok 2 — Zarejestruj czcionki w silniku
Dział zatytułowany „Krok 2 — Zarejestruj czcionki w silniku”Masz dwa równoważne sposoby uwidocznienia plików. Oba przebiegają przez
NextPDF\Typography\FontRegistry, który implementuje
NextPDF\Contracts\FontRegistryInterface.
Zarejestruj konkretny plik pod aliasem, gdy kontrolujesz dokładny krój:
use NextPDF\Typography\FontRegistry;
$registry = new FontRegistry();$registry->register(__DIR__ . '/../resources/fonts/DejaVuSans.ttf', alias: 'DejaVuSans');register(string $fontFile, string $alias = '', int $fontIndex = 0) akceptuje pliki
.ttf, .otf oraz .ttc, a także przestarzały Type1 .pfb (który ładuje
towarzyszące metryki .afm z tej samej ścieżki); $fontIndex wybiera podczcionkę
wewnątrz TrueType Collection (.ttc). register() parsuje plik i indeksuje krój po
rodzinie i stylu odczytanych z własnych tablic nazw, więc fizyczna nazwa pliku jest
nieistotna po zarejestrowaniu. Opcjonalny $alias to po prostu dodatkowa nazwa
wyszukiwania kroju — nie jest kodem stylu i nie zmienia tego, który styl dostarcza
plik; przekaż go, gdy chcesz wywołać setFont() z nazwą inną niż osadzona nazwa
rodziny czcionki. Zwraca sparsowany FontInfo.
Zarejestruj katalog, gdy chcesz, aby silnik rozwiązywał kroje po nazwie z folderu, który kontrolujesz:
$registry = new FontRegistry('/var/www/app/resources/fonts');// or, equivalently, after construction:$registry->addFontDirectory('/var/www/app/resources/fonts');Konstruktor FontRegistry przyjmuje ten katalog jako swój pierwszy argument, a
addFontDirectory() dodaje kolejne ścieżki wyszukiwania. Goły Document także
udostępnia addFontDirectory() dla przypadku samodzielnego.
Aby użyć rejestru, który samodzielnie wypełniłeś, buduj dokumenty przez
DocumentFactory, który wpina ten dokładnie rejestr do każdego tworzonego przez
siebie dokumentu:
use NextPDF\Core\DocumentFactory;use NextPDF\Graphics\ImageRegistry;
$factory = new DocumentFactory($registry, new ImageRegistry(maxCacheBytes: 0));
$doc = $factory->create();$doc->addPage();$doc->setFont('DejaVuSans', '', 12);$doc->cell(0, 10, 'Réndéred wîth a bundled face — no tofu.', newLine: true);$doc->save('/tmp/out.pdf');Document::createStandalone() buduje własny wewnętrzny rejestr, więc krój, który
zarejestrowałeś w osobnym FontRegistry, jest dla niego niewidoczny. W środowisku
produkcyjnym korzystaj z DocumentFactory (lub fabryki Twojego frameworka), aby w
użyciu był wypełniony rejestr.
Konfiguracja frameworka
Dział zatytułowany „Konfiguracja frameworka”Każda integracja frameworka udostępnia te same dwie koncepcje jako konfigurację,
więc rzadko sięgasz bezpośrednio do rejestru. W pliku nextpdf.php pakietu Laravel
fonts_path (domyślnie NEXTPDF_FONTS_PATH, z odwołaniem awaryjnym do
resource_path('fonts')) jest katalogiem wyszukiwania, a preload_fonts to lista
bezwzględnych ścieżek plików czcionek parsowanych przy starcie procesu roboczego.
Skieruj fonts_path na katalog, który dołączyłeś, a Twoje zarejestrowane kroje
rozwiążą się automatycznie.
Krok 3 — Udostępnij czcionki w obrazie Docker
Dział zatytułowany „Krok 3 — Udostępnij czcionki w obrazie Docker”W kontenerze pliki czcionek muszą być częścią warstwy obrazu, skopiowaną w czasie
kompilacji. Ponieważ kod aplikacji i czcionki wysyłane są razem, gdy dołączysz je
pod resources/fonts/, normalne COPY . . już je przenosi. Jeśli trzymasz czcionki
poza kontekstem kompilacji, skopiuj je jawnie i upewnij się, że ścieżka, którą
rejestrujesz, odpowiada ścieżce wewnątrz obrazu.
# Native engine: NO system font packages are required.# The native engine does not discover OS-installed fonts automatically; install OS# font packages (`apt-get install fonts-*`) only if you also register them or point# the font registry's search directory at their files.FROM php:8.4-cli
WORKDIR /var/www/app
# Bundle the application, including resources/fonts/, into the image.COPY . /var/www/app
# Make the bundled directory the engine's font search path.ENV NEXTPDF_FONTS_PATH=/var/www/app/resources/fonts
CMD ["php", "bin/generate.php"]W niezmiennym systemie plików lub systemie plików tylko do odczytu (kontener
readOnlyRootFilesystem, obraz bezserwerowy lub utwardzony host) pliki czcionek są
odczytywane w czasie generowania i nigdy zapisywane, więc montowanie tylko do
odczytu jest w porządku. Jedynym zapisem, którego silnik może chcieć, jest jego
pamięć podręczna sparsowanych czcionek: albo daj temu katalogowi mały zapisywalny
wolumen, albo rozgrzej i zablokuj rejestr przy starcie (następna sekcja), aby nie
podjęto próby żadnego zapisu ani rejestracji w czasie działania.
Krok 4 — Rozgrzewka i weryfikacja
Dział zatytułowany „Krok 4 — Rozgrzewka i weryfikacja”W długo działającym procesie roboczym sparsuj każdy krój raz przy starcie, a potem zablokuj rejestr, aby nie zachodziła rejestracja per żądanie, a błędna konfiguracja zawodziła głośno, zamiast po cichu wracać do wartości awaryjnej:
$registry = new FontRegistry('/var/www/app/resources/fonts');$registry->warmup([ '/var/www/app/resources/fonts/DejaVuSans.ttf', '/var/www/app/resources/fonts/DejaVuSans-B.ttf',]);$registry->lock();Po lock() register(), addFontDirectory() oraz warmup() zgłaszają wyjątek, co
zamienia pomyłkę „błędna ścieżka w obrazie” w twardy błąd startu, zamiast w stronę z
tofu na produkcji.
Dodaj wdrożeniową kontrolę dymną, która renderuje jedną stronę każdym wymaganym krojem. Poniższa kontrola nagłówka weryfikuje jedynie, że dokument wytworzył wyjście — nie dowodzi, że czcionka została sparsowana, osadzona ani nawet rozwiązana. Krój, którego silnik nie potrafi odnaleźć, może wrócić do standardowej czcionki bazowej (a przy obecnym, nieścisłym zachowaniu profil zgodności może zamiast tego dostarczyć dołączony zamiennik), wciąż emitując prawidłowy, niepusty plik PDF — więc nawet tam, gdzie ta wartość awaryjna ma miejsce, sama ta kontrola nie wychwyci cichej degradacji. Nie polegaj na tym, że wartość awaryjna jest gwarantowana lub cicha na każdej ścieżce; zweryfikuj osadzony program bezpośrednio, jak pokazano poniżej:
$doc = $factory->create();$doc->addPage();$doc->setFont('DejaVuSans', '', 12);$doc->cell(0, 10, 'warmup check', newLine: true);
$pdf = $doc->getPdfData();
// `getPdfData()` would normally throw on a real failure; this header check only// confirms serialization returned PDF bytes, not that any specific font resolved.if (!str_starts_with($pdf, '%PDF')) { throw new RuntimeException('Font warmup smoke check produced no PDF output.');}Aby faktycznie obalić wdrożenie, gdy brakuje kroju, sprawdź wyemitowany plik PDF pod
kątem osadzonego programu czcionki. Zarejestrowany krój, który się rozwiązuje, niesie
własny słownik czcionki z osadzonym programem, więc potwierdzenie jego obecności
wychwytuje przypadek, w którym żądany krój nigdy się nie rozwiązał (cokolwiek silnik
przyjął jako wartość awaryjną), a którego kontrola nagłówka nie wyłapuje. To, który
klucz przechowuje program, zależy od formatu konturów: kontury TrueType (.ttf,
.ttc) używają /FontFile2, kontury CFF/OpenType (.otf z konturami PostScript)
używają /FontFile3, a przestarzały Type1 (.pfb) używa /FontFile.
Jeśli wszystko, czego potrzebujesz, to sygnał niezależny od formatu „jakiś program
czcionki osadzony”, testuj pod kątem /FontFile samego w sobie — ponieważ
/FontFile jest podłańcuchem zarówno /FontFile2, jak i /FontFile3, goła kontrola
podłańcucha pasuje już do każdego typu konturów, a dodanie /FontFile2//FontFile3
jako dodatkowych gałęzi || jest zbędne:
if (!str_contains($pdf, '/FontFile')) { throw new RuntimeException('No embedded font program found — face fell back.');}Goły podłańcuch /FontFile nie potrafi jednak odróżnić typów konturów. Aby je
rozróżnić, dopasuj dokładny token z granicą słowa, tak aby /FontFile nie zadziałał
także na /FontFile2 ani /FontFile3:
$isTrueType = preg_match('~/FontFile2\b~', $pdf) === 1; // TrueType (.ttf/.ttc)$isCffOtf = preg_match('~/FontFile3\b~', $pdf) === 1; // CFF/OpenType (.otf)$isType1 = preg_match('~/FontFile(?![23])\b~', $pdf) === 1; // Type1 (.pfb)
if (!$isTrueType && !$isCffOtf && !$isType1) { throw new RuntimeException('No embedded font program found — face fell back.');}Tak czy inaczej, traktuj to wyłącznie jako zgrubną heurystykę, a nie niezawodną
bramkę wdrożeniową. Surowe wyszukiwanie bajtów w zserializowanym pliku PDF jest
niedokładne z kilku powodów: programy czcionek mogą żyć wewnątrz skompresowanych
strumieni obiektów (gdzie /FontFile* nigdy nie pojawia się jako zwykłe bajty),
aktualizacje przyrostowe mogą dołączać lub zastępować obiekty, nieosadzone lub
standardowe czcionki z grupy standard-14 legalnie nie niosą żadnego programu
czcionki, a różnice serializacji (kolejność obiektów, białe znaki, kodowanie
nazw) mogą przesunąć lub ukryć token. W najlepszym razie potwierdza, że jakiś krój
osadził program — nigdy że rozwiązał się konkretny krój, którego chciałeś.
Dla prawdziwej bramki wdrożeniowej nie polegaj na wyszukiwaniu bajtów. Sparsuj
wyemitowany plik PDF właściwym parserem PDF lub inspektorem obiektów i potwierdź, że
obiekt czcionki dla docelowego kroju niesie osadzony program /FontFile//FontFile2//FontFile3,
albo użyj dostarczonego przez produkt potwierdzenia rozwiązania czcionki, jeśli takie
jest dostępne dla Twojej integracji. Powyższe wyrażenia regularne świadome tokenów są
przydatne do szybkiej lokalnej kontroli poprawności, ale to inspekcja strukturalna
powinna obalić wdrożenie. Struktura osadzania i słownika czcionek jest opisana w
Osadzanie czcionki TrueType i tworzenie jej podzbioru.
Przypadki brzegowe i pułapki
Dział zatytułowany „Przypadki brzegowe i pułapki”createStandalone()ma własny rejestr. Krój zarejestrowany w osobnymFontRegistrynie jest widoczny dla dokumentu samodzielnego. UżyjDocumentFactory(lub fabryki frameworka), aby aktywnym był Twój rejestr.- Pliki stylów muszą istnieć jako pliki. Silnik nie syntezuje pogrubienia ani
kursywy z kroju zwykłego. Jeśli wywołasz
setFont('DejaVuSans', 'B'), wyszukiwanie katalogowe szukaDejaVuSans-B.ttf,DejaVuSansB.ttflubDejaVuSans.ttf(także warianty z małymi literami i.otf) — tworzy kandydata z dosłownego kodu styluB, więc nigdy nie szukaDejaVuSans-Bold.ttf. Plik z rozpisaną nazwą taką jakDejaVuSans-Bold.ttfrozwiązuje się tylko wtedy, gdy zarejestrujesz go jawnie za pomocąregister(), który indeksuje go po rodzinie i stylu odczytanych z własnych tablic nazw pliku niezależnie od nazwy pliku; poleganie na wyszukiwaniu katalogowym w celu jego odnalezienia daje pudło, po którym silnik może wrócić do czcionki bazowej (nie jest to gwarantowana ani zawsze cicha ścieżka) — degradacja, przed którą ostrzega ta strona. - Ścieżki ze strumieniowymi opakowaniami i ścieżki zdalne są odrzucane. Rejestr
odmawia ścieżek zawierających schemat URI lub bajt null. Rejestruj wyłącznie pliki
lokalne; dla czcionek pobieranych w czasie działania użyj
registerFromBinary()z surowymi bajtami. - Zablokowany rejestr jest niezmienny. Po wywołaniu
lock()każde późniejszeregister(),addFontDirectory()lubwarmup()zgłasza wyjątek. Metody wyszukiwania pozostają dostępne. Zarejestruj i rozgrzej wszystko przed zablokowaniem. - Kolekcje CJK są duże. Zarejestruj właściwą podczcionkę pliku
.ttcza pomocą$fontIndexi zaplanuj budżet na większy osadzony podzbiór. Zobacz uwagi o CJK w przepisie o osadzaniu i podzbiorze.
Uwagi dotyczące bezpieczeństwa
Dział zatytułowany „Uwagi dotyczące bezpieczeństwa”- Plik czcionki to niezaufane binarne wejście. Dołączaj wyłącznie czcionki ze źródeł, którym ufasz, i waliduj pochodzenie każdego kroju przyjętego od użytkowników końcowych.
- Zablokowanie rejestru po rozgrzewce usuwa powierzchnię mutacji w czasie działania i sprawia, że pomyłka w ścieżce zawodzi przy starcie, zamiast po cichu degradować wyjście.
- Nie wstawiaj danych wejściowych użytkownika do zarejestrowanej ścieżki pliku. Zarejestruj stały zestaw dołączonych krojów; nie pozwalaj, aby żądanie wybierało dowolną ścieżkę w systemie plików.
Zgodność
Dział zatytułowany „Zgodność”Ten przewodnik nie zgłasza normatywnego roszczenia co do standardów. Każdy pokazany
symbol to zweryfikowana powierzchnia publiczna: NextPDF\Typography\FontRegistry
(register(), addFontDirectory(), warmup(), lock(), argument konstruktora
katalogu), jego kontrakt NextPDF\Contracts\FontRegistryInterface,
NextPDF\Core\DocumentFactory::create() oraz NextPDF\Core\Document::setFont()
/ addFontDirectory(). Klucze Laravel fonts_path oraz preload_fonts to
udokumentowana konfiguracja pakietu nextpdf/laravel. Zachowanie osadzania i znacznika
podzbioru, z cytatami ISO 32000-2, jest udokumentowane w przepisie o osadzaniu i
podzbiorze, do którego odnosi się sekcja Zobacz także.
Zobacz także
Dział zatytułowany „Zobacz także”- Osadzanie czcionki TrueType i tworzenie jej podzbioru: przepis na poziomie API dotyczący rejestrowania jednego kroju i automatycznego podzbioru przy zapisie.
- Renderowanie HTML do strony PDF: natywna ścieżka HTML, która rozwiązuje czcionki przez ten sam rejestr.
- Zwracanie wygenerowanego pliku PDF z kontrolera: wpięcie dokumentu zbudowanego przez fabrykę w odpowiedź frameworka.
- Użycie produkcyjne w Laravel: konfiguracja czcionek frameworka i rozgrzewka przy starcie procesu roboczego.