Typografia: rejestr czcionek, tworzenie podzbiorów, CMap, kodowanie, BiDi
W skrócie
Dział zatytułowany „W skrócie”Moduł typografii zamienia plik czcionki i ciąg znaków Unicode na bajty potrzebne w strumieniu treści Portable Document Format (PDF). Obsługuje analizę składni czcionek, rejestr działający przez cały czas życia procesu, tworzenie podzbiorów glifów, generowanie map CMap ToUnicode, strategie kodowania z uwzględnieniem cmap oraz silnik obsługi tekstu dwukierunkowego Unicode.
Instalacja
Dział zatytułowany „Instalacja”composer require nextpdf/core:^3Przegląd koncepcyjny
Dział zatytułowany „Przegląd koncepcyjny”FontRegistry przechowuje czcionki przez cały czas życia procesu i implementuje FontRegistryInterface. Jednokrotnie analizuje plik TrueType, OpenType, TrueType Collection (TTC) lub Type 1 (Printer Font Binary (PFB) oraz Adobe Font Metrics (AFM)) i zwraca niezmienny obiekt FontInfo. Używaj go w długo działających procesach roboczych: rozgrzej zestaw czcionek przy starcie, a następnie wywołaj lock(). Od tej chwili rejestr odrzuca każdą modyfikację, a operacje wyszukiwania nadal obsługują ruch. Przechowuje wyłącznie czyste dane PHP: przeanalizowane metadane oraz surowe bajty czcionki. Pula procesów roboczych może współdzielić jedną instancję. registerFromBinary() przyjmuje surowe bajty czcionki, których most @font-face HyperText Markup Language (HTML) używa w przypadku czcionki pobranej ze źródła zdalnego lub z identyfikatora URI danych.
Silnik osadza każdą używaną czcionkę i tworzy jej podzbiór. Osadzony program czcionki znajduje się wewnątrz pliku PDF, dzięki czemu dokument renderuje się tak samo w każdej przeglądarce i nie zależy od zainstalowanych czcionek systemowych — ISO 32000-2 §9. Podzbiór zawiera wyłącznie glify, do których dokument się odwołuje, co ma znaczenie dla treści chińskich, japońskich i koreańskich (CJK) lub intensywnie korzystających z Unicode — ISO 32000-2 §9. FontSubsetter analizuje oryginalny katalog tablic, wyodrębnia cmap, rozwiązuje zależności glifów złożonych w ramach domknięcia przechodniego i odbudowuje tablice head, hhea, maxp, cmap, loca, glyf i hmtx. Zachowuje oryginalną numerację identyfikatorów glifów i wypełnia zerami nieużywane sloty, dzięki czemu CIDToGIDMap o wartości /Identity pozostaje prawidłowy. Zwraca oryginalną czcionkę bez zmian, gdy podzbiór pozwoliłby zaoszczędzić mniej niż dziesięć procent, aby uniknąć pracy, która się nie zwraca. CffSubsetter wykonuje tę samą operację dla czcionek OpenType, które zawierają tablicę konturów Compact Font Format.
Emisja tekstu obejmuje trzy translacje: punkt kodowy Unicode, kod znaku w strumieniu treści oraz identyfikator glifu wewnątrz czcionki. Moduł jawnie utrzymuje tę ścieżkę. FontInfo::encodeText() jest fasadą; FontEncodingStrategyResolver kieruje wywołania zależnie od czcionki. Osadzona czcionka TrueType lub OpenType z cmap Unicode jest kierowana do TrueTypeCmapStrategy, która emituje dwubajtowy strumień szesnastkowy Identity-H. Jest to postać wymagana przez czcionkę Type 0 z mapą CMap Identity-H i potomkiem CIDFontType2 (ISO 32000-2 §9.7.4; odpowiadający skrót fragmentu retrieval-augmented generation (RAG) zwrócono w postaci obciętej przez limit licencji, co odnotowano w _downgraded-claims-o3.md). Każda inna czcionka — standardowe czcionki Base 14, Type 1 PFB i AFM — jest kierowana do Base14EncodingStrategy, która emituje jednobajtowy literał ciągu znaków WinAnsi. Ten strumień obejmuje pełny repertuar WinAnsiEncoding (strona kodowa Windows 1252) — łacinę z diakrytykami, znak euro oraz typowe znaki interpunkcyjne typograficzne. Punkty kodowe spoza niego są pomijane w strumieniu jednobajtowym i korzystają z mechanizmu zastępczego czcionek dla poszczególnych klastrów, gdy zarejestrowano czcionkę pokrywającą (ISO 32000-2 Annex D.2). Resolver obejmuje całą przestrzeń wartości FontInfo; nie ma ścieżki, która dopuszczałaby wartość null. ToUnicodeCMapBuilder buduje zasób /ToUnicode, który pozwala czytnikowi odzyskać oryginalny Unicode z czcionki Identity-H. Stosuje zachłanne łączenie bfrange oraz limit 100 wpisów na blok.
BidiEngine jest usługą graniczną dla algorytmu dwukierunkowego Unicode, zdefiniowanego w Unicode Standard Annex #9 (UAX #9), Unicode 16. Gdy obsługa izolatów jest wyłączona, deleguje do starszego resolvera, dzięki czemu istniejący wywołujący zachowują dotychczasowe działanie. Gdy obsługa izolatów jest włączona, uruchamia potok świadomy izolatów: stos jawnych izolatów o maksymalnej głębokości 125, przebiegi typów słabych, przebiegi typów neutralnych z rozwiązywaniem sparowanych nawiasów oraz przebiegi poziomu niejawnego i ponownego porządkowania wierszy. Pokrycie glifów CJK w czcionce kandydującej jest osobną diagnostyką: CjkFontValidator próbkuje wymagane bloki Unicode dla każdego systemu pisma i raportuje procentowe pokrycie.
Powierzchnia API
Dział zatytułowany „Powierzchnia API”| Typ | Rodzaj | Kluczowe składowe | Stabilność | Od wersji |
|---|---|---|---|---|
FontRegistry | klasa final | register(), registerType1(), registerFromBinary(), registerFromDirectory(), get(), has(), all(), warmup(), lock(), isLocked(), memoryUsage() | stabilny | 1.7.0 |
FontInfo | klasa final readonly | $family, $type, $widths, $unicodeMap, $cmapForward, getKey(), encodeText() | stabilny | 1.0.0 |
FontSubsetter | klasa final | subset(string, array<int>, int): string | stabilny | 1.0.0 |
CffSubsetter | klasa final | tworzenie podzbiorów konturów OpenType/CFF | stabilny | 1.0.0 |
FontEncodingStrategyResolver | klasa final | resolve(FontInfo): FontEncodingStrategy | stabilny | 2.7.0 |
ToUnicodeCMapBuilder | klasa final | buildFromRun(), buildFromMap(), encodeUnicodeUtf16Be() | stabilny | 2.7.0 |
BidiEngine | klasa final | rozwiązywanie UAX #9 z uwzględnieniem izolatów | stabilny | 3.1.0 |
CjkFontValidator | klasa final | validateCoverage(), detectScript(), isCjkCodepoint() | stabilny | 1.0.0 |
FontInfo jest niezmienny: jego sygnatura konstruktora i właściwości publiczne są zamrożone. Strategie kodowania są czystymi funkcjami (FontInfo, UTF-8 text): dla tego samego wejścia zwracają ten sam EncodedGlyphRun przy każdym wywołaniu.
Przykład kodu — szybki start
Dział zatytułowany „Przykład kodu — szybki start”<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Typography\Encoding\EncodingMode;use NextPDF\Typography\FontRegistry;
$registry = new FontRegistry();$cjkFont = $registry->register('/path/to/NotoSansTC-Regular.ttf', alias: 'NotoSansTC');
$encoded = $cjkFont->encodeText('PDF 2.0 引擎 — 使用 CMap 編碼');
// An embedded CJK TrueType face resolves to the two-byte Identity-H path.assert($encoded->mode === EncodingMode::TwoByteCid);register() analizuje czcionkę jednokrotnie i zwraca niezmienny FontInfo. encodeText() przechodzi przez resolver i zwraca EncodedGlyphRun ze strumieniem bajtów, operandem ciągu znaków PDF, szerokościami posuwu dla poszczególnych glifów oraz mapą identyfikatorów glifów (GID) na Unicode, którą wykorzystuje mapa CMap /ToUnicode.
Przykład kodu — środowisko produkcyjne
Dział zatytułowany „Przykład kodu — środowisko produkcyjne”<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Exception\NextPdfException;use NextPDF\Typography\FontRegistry;use Psr\Log\LoggerInterface;
final readonly class FontBootstrap{ public function __construct( private FontRegistry $registry, private LoggerInterface $logger, ) {}
/** * Warm a font set at worker boot, then lock the registry for the * lifetime of the process. * * @param list<string> $fontFiles Absolute paths to font files. */ public function boot(array $fontFiles): void { try { $this->registry->warmup($fontFiles); $this->registry->lock(); } catch (NextPdfException $e) { $this->logger->error('Font warmup failed', ['error' => $e->getMessage()]);
throw $e; }
$report = $this->registry->memoryUsage(); $this->logger->info('Font cache primed', [ 'fonts' => $report->entryCount, 'bytes' => $report->currentBytes, ]); }}warmup(), po którym następuje lock(), to sekwencja rozruchu procesu roboczego. Po lock() każda próba modyfikacji zgłasza wyjątek, a operacje wyszukiwania nadal obsługują ruch. memoryUsage() zwraca MemoryReport, dzięki czemu proces roboczy może śledzić pamięć podręczną czcionek względem swojego budżetu.
Przypadki brzegowe i pułapki
Dział zatytułowany „Przypadki brzegowe i pułapki”- Gdy rejestr jest zablokowany, odrzuca
register(),registerFromBinary(),addFontDirectory()iwarmup(). Rozgrzej i zablokuj rejestr przy starcie; nigdy nie rejestruj podczas obsługi żądania. FontSubsetter::subset()zwraca oryginalne bajty bez zmian, gdy oszczędność byłaby poniżej dziesięciu procent lub gdy brakuje istotnej tablicy. Zwrócenie czcionki identycznej z wejściem to udokumentowana ścieżka braku zysku, nie błąd.- Mechanizm tworzenia podzbiorów zachowuje oryginalną numerację identyfikatorów glifów i wypełnia zerami nieużywane glify. Dzięki temu
CIDToGIDMap /Identitypozostaje prawidłowy; nie zakładaj, że identyfikatory glifów są przenumerowane na ciągły zakres. registerFromBinary()zapisuje bajty do pliku tymczasowego w celu analizy i usuwa zarówno plik z rozszerzeniem, jak i plik bazowytempnam()w blokufinally. Niezaufane dane czcionki stanowią powierzchnię ataku na analizator; zabezpiecz je, zanim dotrą do analizatora (zobacz Uwagi dotyczące bezpieczeństwa).BidiEnginedeleguje dosłownie do starszego resolvera, gdy obsługa izolatów jest wyłączona. Znaki formatujące izolatów są wówczas traktowane jako neutralne wobec granic. Włącz obsługę izolatów za pomocą polityki zgodności, aby uzyskać pełne zachowanie UAX #9.CjkFontValidatorpróbkuje punkty kodowe co pewien krok, zamiast testować każdy z nich, więc jego wartość pokrycia jest statystycznie wystarczającym szacunkiem, a nie wyczerpującym zliczeniem.
Wydajność
Dział zatytułowany „Wydajność”Analiza czcionki jest dominującym kosztem przy pierwszym użyciu; rejestr amortyzuje ten koszt do jednego razu na proces. Po rozgrzaniu get() i has() to wyszukiwania w mapie o złożoności O(1). Koszt tworzenia podzbiorów skaluje się z liczbą glifów używanych przez dokument, a nie z pełną tablicą glifów czcionki. Dlatego tworzenie podzbiorów zmniejsza rozmiar i poprawia szybkość dla treści CJK: mechanizm obsługuje czcionki z ponad 20,000 glifami za pomocą wyszukiwania binarnego, wstępnie przydzielonych buforów i operacji masowych na ciągach znaków. Rozwiązywanie glifów złożonych jest ograniczone; zatrzymuje się po 100 iteracjach domknięcia, aby chronić przed cyklicznymi odwołaniami do komponentów. Analizator cmap Format 12 limituje liczbę grup i wpisów, aby zmniejszyć zużycie pamięci dla wrogiego wejścia czcionki. performance_budget wynoszący 1500 ms czasu rzeczywistego i 64 MB szczytowego zużycia pamięci pokrywa typowe rozgrzanie czcionek wraz z renderowaniem dokumentu.
Uwagi dotyczące bezpieczeństwa
Dział zatytułowany „Uwagi dotyczące bezpieczeństwa”Pod względem bezpieczeństwa istotne są dwie powierzchnie. Pierwszą jest wejście czcionki. register() i registerFromBinary() analizują dowolne bajty. registerFromBinary() materializuje plik tymczasowy. Warstwa graniczna odrzuca opakowania strumieni i bajty null w ścieżkach. Niezaufane dane czcionki muszą przejść przez politykę zasobów zewnętrznych, która ogranicza rozmiar pliku i liczbę glifów, zanim dotrą do analizatora. Czytniki binarne mechanizmu tworzenia podzbiorów sprawdzają zakres każdego przesunięcia. Analizatory cmap ograniczają liczbę grup, wpisów i tablic (numGroups > 31000 oraz limit wpisów wynoszący 200,000 w Format 12), więc spreparowana czcionka nie może wymusić nieograniczonej alokacji. Drugą powierzchnią jest odzyskiwanie tekstu: ToUnicodeCMapBuilder sprawdza, czy każdy kod znaku mieści się w 16-bitowej przestrzeni kodu i czy każda wartość Unicode jest prawidłową wartością skalarną. Odrzuca połówki par zastępczych, więc nieprawidłowo sformułowana mapa nie może wytworzyć uszkodzonego zasobu do ekstrakcji. Traktuj każdą czcionkę i każdy tekst z zewnątrz jako niezaufane wejście.
Zgodność
Dział zatytułowany „Zgodność”| Twierdzenie | Standard | Klauzula | Dowód |
|---|---|---|---|
| Każda czcionka używana przez dokument jest osadzona, dzięki czemu dokument renderuje się bez polegania na czcionkach systemowych. | ISO 32000-2 | §9 | |
| Osadzona czcionka jest zawężona do glifów, do których dokument się odwołuje. | ISO 32000-2 | §9 | |
Osadzona czcionka CJK TrueType jest emitowana jako czcionka Type 0 z mapą CMap Identity-H i potomkiem CIDFontType2. | ISO 32000-2 | §9.7.4 | Skrót RAG obcięty przez limit licencji; prefiks 7a5258772f508e3b, zobacz _downgraded-claims-o3.md |
Pierwsze dwie klauzule zostały sparafrazowane i powiązane ze skrótem. Pełny skrót RAG trzeciej klauzuli nie został zwrócony (obcięcie z powodu limitu licencji); ADR-013 oraz przegląd dla deweloperów kodera cmap potwierdzają ją, a wpis odnotowano jako obniżony. NextPDF nie reprodukuje tekstu normatywnego. Zgodność PDF/A-4 i PDF/UA-2 dla treści CJK zależy od tworzenia podzbiorów po stronie zapisu oraz podłączenia /ToUnicode, które jest śledzone w tamtym miejscu.
Kontekst komercyjny
Dział zatytułowany „Kontekst komercyjny”Komercyjny pakiet funkcji OpenType i premium łańcuchy zastępcze czcionek opierają się na rejestrze Core i warstwie kodowania. Moduł typografii Core osadza każdą czcionkę, tworzy jej podzbiory i koduje ją bez licencji; płatny pakiet dodaje wyselekcjonowane rozwiązywanie czcionek zastępczych. Pominięcie odsyłacza konwersji jest zamierzone: ta strona jest dokumentacją, a nie ścieżką sprzedaży.
Zobacz także
Dział zatytułowany „Zobacz także”- Font: rejestr TrueType, OpenType i CID — typy wartości czcionek, osadzanie i zastępowanie.
- Text: kształtowanie, podział, BiDi — obsługa przebiegów i kształtowanie, które wykorzystuje zakodowane glify.
- Contracts / Typography — kontrakty
FontRegistryInterfaceoraz preprocesora tekstu. - Silnik renderowania HTML — most
@font-face, który wywołujeregisterFromBinary().