Tipografia: registro dei font, subsetting, CMap, codifica, BiDi
In sintesi
Sezione intitolata “In sintesi”Il modulo di tipografia trasforma un file di font e una stringa Unicode nei byte richiesti da un flusso di contenuto PDF. Gestisce il parsing dei font, un registro valido per l’intera durata del processo, il subsetting dei glifi, la CMap ToUnicode, le strategie di codifica basate su cmap e il motore bidirezionale Unicode.
Installazione
Sezione intitolata “Installazione”composer require nextpdf/core:^3Panoramica concettuale
Sezione intitolata “Panoramica concettuale”FontRegistry è l’archivio dei font valido per l’intera durata del processo e implementa FontRegistryInterface. Analizza una volta sola un file TrueType, OpenType, TTC o Type 1 (PFB e AFM) e restituisce un FontInfo immutabile. Il registro è progettato per worker di lunga durata: eseguire il warmup di un set di font all’avvio e chiamare lock(). Il registro rifiuta qualsiasi mutazione successiva, mentre le ricerche continuano a gestire il traffico. Contiene esclusivamente dati PHP puri — metadati analizzati e byte grezzi del font — quindi un pool di worker può condividere una sola istanza. registerFromBinary() accetta i byte grezzi del font, usati dal bridge HTML @font-face quando recupera un font da un’origine remota o da un URI di dati.
Il motore incorpora ogni font usato e ne applica il subsetting. Un programma di font incorporato è incluso nel PDF, in modo che il documento sia renderizzato allo stesso modo in qualsiasi visualizzatore, indipendentemente dai font di sistema installati — ISO 32000-2 §9. Un subset contiene solo i glifi a cui il documento fa riferimento, aspetto decisivo per i contenuti CJK o ricchi di Unicode — ISO 32000-2 §9. FontSubsetter analizza la directory delle tabelle originale, estrae la cmap, risolve le dipendenze dei glifi compositi come chiusura transitiva e ricostruisce le tabelle head, hhea, maxp, cmap, loca, glyf e hmtx. Mantiene la numerazione originale degli identificatori dei glifi e riempie di zeri gli slot inutilizzati, in modo che una CIDToGIDMap di tipo /Identity rimanga valida. Restituisce il font originale invariato quando il subset farebbe risparmiare meno del dieci percento, evitando un’elaborazione che non si ripaga. CffSubsetter esegue lo stesso lavoro per i font OpenType che contengono una tabella di contorni in formato Compact Font Format.
L’emissione del testo è una traduzione in tre fasi: punto di codice Unicode, poi codice di carattere nel flusso di contenuto, infine identificatore di glifo all’interno del font. Il modulo modella l’intero passaggio come un unico collaboratore esplicito. FontInfo::encodeText() è la facade; FontEncodingStrategyResolver effettua il dispatch per ciascun font. Un font TrueType o OpenType incorporato che contiene una cmap Unicode viene instradato a TrueTypeCmapStrategy, che emette un flusso esadecimale Identity-H a due byte. Questa è la forma richiesta da un font Type 0 con una CMap Identity-H e un discendente CIDFontType2 (ISO 32000-2 §9.7.4; il digest del chunk RAG corrispondente è stato restituito troncato dal limite di licenza, registrato in _downgraded-claims-o3.md). Ogni altro font — font standard Base 14, Type 1 PFB e AFM — viene instradato a Base14EncodingStrategy, che emette una stringa letterale WinAnsi a un byte. Tale flusso copre l’intero repertorio WinAnsiEncoding (Windows code page 1252) — latino accentato, il segno dell’euro e la punteggiatura tipografica comune. I punti di codice esterni a esso vengono eliminati dal flusso a un byte e ricorrono al fallback dei font per cluster quando è registrato un font che li copre (ISO 32000-2 Annex D.2). Il resolver copre in modo totale lo spazio dei valori di FontInfo; non esiste alcun percorso nullable. ToUnicodeCMapBuilder costruisce la risorsa /ToUnicode che consente a un lettore di recuperare l’Unicode originale da un font Identity-H. Applica un’aggregazione greedy dei bfrange e un limite di blocco di 100 voci.
BidiEngine è il servizio di confine per l’algoritmo bidirezionale Unicode (UAX #9, Unicode 16). Con il supporto degli isolate disattivato, delega al resolver legacy in modo che i chiamanti esistenti non subiscano modifiche. Con il supporto attivato, esegue la pipeline che riconosce gli isolate: lo stack degli isolate espliciti con una profondità massima di 125, le fasi per i tipi deboli, le fasi per i tipi neutri inclusa la risoluzione delle parentesi accoppiate e le fasi di livello implicito e di riordino delle righe. La copertura dei glifi CJK per un font candidato è una diagnostica separata: CjkFontValidator campiona i blocchi Unicode richiesti per ciascuno script e riporta una percentuale di copertura.
Superficie API
Sezione intitolata “Superficie API”| Tipo | Genere | Membri principali | Stabilità | Da |
|---|---|---|---|---|
FontRegistry | classe final | register(), registerType1(), registerFromBinary(), registerFromDirectory(), get(), has(), all(), warmup(), lock(), isLocked(), memoryUsage() | stable | 1.7.0 |
FontInfo | classe final readonly | $family, $type, $widths, $unicodeMap, $cmapForward, getKey(), encodeText() | stable | 1.0.0 |
FontSubsetter | classe final | subset(string, array<int>, int): string | stable | 1.0.0 |
CffSubsetter | classe final | Subsetting dei contorni OpenType/CFF | stable | 1.0.0 |
FontEncodingStrategyResolver | classe final | resolve(FontInfo): FontEncodingStrategy | stable | 2.7.0 |
ToUnicodeCMapBuilder | classe final | buildFromRun(), buildFromMap(), encodeUnicodeUtf16Be() | stable | 2.7.0 |
BidiEngine | classe final | Risoluzione UAX #9 con riconoscimento degli isolate | stable | 3.1.0 |
CjkFontValidator | classe final | validateCoverage(), detectScript(), isCjkCodepoint() | stable | 1.0.0 |
FontInfo è immutabile: la firma del costruttore e le proprietà pubbliche sono congelate. Le strategie di codifica sono funzioni pure di (FontInfo, UTF-8 text) — a parità di input, restituiscono lo stesso EncodedGlyphRun a ogni chiamata.
Esempio di codice — Avvio rapido
Sezione intitolata “Esempio di codice — Avvio rapido”<?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() analizza il font una volta sola e restituisce un FontInfo immutabile. encodeText() instrada attraverso il resolver e restituisce un EncodedGlyphRun che contiene il flusso di byte, l’operando stringa PDF, le larghezze di avanzamento per glifo e la mappa GID-Unicode utilizzata da una CMap /ToUnicode.
Esempio di codice — Produzione
Sezione intitolata “Esempio di codice — Produzione”<?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() seguito da lock() è la sequenza di avvio del worker. Dopo lock(), ogni mutazione genera un’eccezione. Le ricerche continuano a gestire il traffico. memoryUsage() restituisce un MemoryReport, in modo che un worker possa monitorare la cache dei font rispetto al proprio budget.
Casi limite e insidie
Sezione intitolata “Casi limite e insidie”- Un registro bloccato rifiuta
register(),registerFromBinary(),addFontDirectory()ewarmup(). Eseguire il warmup e bloccare all’avvio; non registrare mai durante la gestione delle richieste. FontSubsetter::subset()restituisce i byte originali invariati quando il risparmio sarebbe inferiore al dieci percento o quando manca una tabella essenziale. Un font restituito uguale all’input è il caso documentato di assenza di guadagno, non un errore.- Il subsetter mantiene la numerazione originale degli identificatori dei glifi e riempie di zeri i glifi inutilizzati. In questo modo
CIDToGIDMap /Identityrimane valida; non presumere che gli identificatori dei glifi vengano rinumerati in un intervallo contiguo. registerFromBinary()scrive i byte in un file temporaneo per analizzarli ed elimina sia il file con estensione sia il file base ditempnam()in un bloccofinally. I dati di font non attendibili sono una superficie d’attacco durante il parsing: filtrarli prima che raggiungano il parser (vedere le note sulla sicurezza).BidiEnginedelega testualmente al resolver legacy quando il supporto degli isolate è disattivato. I caratteri di formattazione degli isolate vengono quindi trattati come neutri rispetto al confine. Attivare il supporto degli isolate tramite il criterio di conformità per ottenere il comportamento completo di UAX #9.CjkFontValidatorcampiona i punti di codice a intervalli regolari anziché verificarli tutti, quindi la sua percentuale di copertura è una stima statisticamente adeguata, non un conteggio esaustivo.
Prestazioni
Sezione intitolata “Prestazioni”Il parsing del font domina il primo utilizzo; il registro ne ammortizza il costo a una volta sola per processo. Dopo il warmup, get() e has() sono ricerche su mappa O(1). Il costo del subsetting cresce con il numero di glifi effettivamente utilizzati dal documento, non con la tabella completa dei glifi del font. Per questo il subsetting è vantaggioso sia in termini di dimensioni sia di velocità per i contenuti CJK: il subsetter gestisce font con oltre 20,000 glifi tramite ricerca binaria, buffer preallocati e operazioni su stringhe in blocco. La risoluzione dei glifi compositi è limitata — si arresta a 100 iterazioni di chiusura per difendersi dai riferimenti circolari tra componenti. Il parser cmap Format 12 limita il numero di gruppi e di voci per contenere l’uso di memoria in presenza di un font ostile. Il performance_budget di 1500 ms di tempo reale e 64 MB di picco copre un tipico warmup dei font più il rendering del documento.
Note sulla sicurezza
Sezione intitolata “Note sulla sicurezza”Le superfici rilevanti per la sicurezza sono due. La prima è l’input dei font. register() e registerFromBinary() analizzano byte arbitrari. registerFromBinary() materializza un file temporaneo. I wrapper di flusso e i byte null nei percorsi vengono rifiutati al confine. I dati di font non attendibili devono superare un criterio per le risorse esterne che limiti la dimensione del file e il numero di glifi prima che raggiungano il parser. I lettori binari del subsetter verificano i limiti di ogni offset. I parser cmap limitano il numero di gruppi, voci e tabelle (numGroups > 31000 e un limite di voci di 200,000 nel Format 12) in modo che un font malevolo non possa innescare un’allocazione illimitata. La seconda superficie è il recupero del testo: ToUnicodeCMapBuilder verifica che ogni codice di carattere sia all’interno dello spazio di codici a 16 bit e che ogni valore Unicode sia uno scalare valido — le metà surrogate vengono rifiutate — in modo che una mappa malformata non possa produrre una risorsa di estrazione corrotta. Considerare non attendibile qualsiasi font o testo fornito dall’esterno.
Conformità
Sezione intitolata “Conformità”| Asserzione | Standard | Clausola | Prove |
|---|---|---|---|
| Ogni font utilizzato dal documento è incorporato, in modo che il documento sia renderizzato senza dipendere dai font di sistema. | ISO 32000-2 | §9 | |
| Il font incorporato è ridotto tramite subsetting ai glifi a cui il documento fa riferimento. | ISO 32000-2 | §9 | |
Un font TrueType CJK incorporato viene emesso come font Type 0 con una CMap Identity-H e un discendente CIDFontType2. | ISO 32000-2 | §9.7.4 | Digest RAG troncato dal limite di licenza; prefisso 7a5258772f508e3b, vedere _downgraded-claims-o3.md |
Le prime due clausole sono parafrasate e ancorate al digest. Il digest RAG completo della terza clausola non è stato restituito (troncamento per limite di licenza); è corroborato dall’ADR-013 e dalla panoramica per sviluppatori del codificatore cmap e registrato come declassato. NextPDF non riproduce il testo normativo. La conformità PDF/A-4 e PDF/UA-2 per i contenuti CJK dipende dal subsetting lato writer e dal cablaggio /ToUnicode tracciati in quella sede.
Contesto commerciale
Sezione intitolata “Contesto commerciale”Un feature pack OpenType commerciale e catene premium di fallback dei font si basano sul registro Core e sullo snodo di codifica. Il modulo di tipografia Core incorpora, applica il subsetting e codifica ogni font senza licenza; il pack a pagamento aggiunge una risoluzione di fallback curata. L’omissione di un collegamento di conversione è intenzionale — questa è documentazione, non un percorso di vendita.
Vedere anche
Sezione intitolata “Vedere anche”- Font: registro TrueType, OpenType e CID — i tipi di valore dei font, l’incorporamento e il fallback.
- Testo: shaping, interruzione, BiDi — lo snodo di gestione delle run e di shaping che consuma i glifi codificati.
- Contratti / Tipografia — il contratto
FontRegistryInterfacee quello del preprocessore di testo. - Motore di rendering HTML — il bridge
@font-faceche chiamaregisterFromBinary().