Fornire i font in produzione
In sintesi
Sezione intitolata “In sintesi”Il proprio PDF viene reso correttamente sul portatile, poi viene distribuito in un container e ne esce come una fila di riquadri vuoti — il glifo “tofu” — oppure con accenti e caratteri non latini mancanti. La causa è quasi sempre la stessa: il font selezionato non è presente nell’immagine distribuita.
Il motore nativo, in-process, di NextPDF risolve i font a partire da file di
font che il registro dei font è in grado di leggere. Non individua
automaticamente i font del sistema operativo o di fontconfig — i file di font
installati a livello di sistema operativo sono utili solo se si registrano
esplicitamente tali file o si aggiunge la directory che li contiene al percorso di
ricerca del FontRegistry. Un container costruito a partire da un’immagine di
base ridotta non ha font installati con apt/apk, e anche quando li ha, il
motore nativo li ignora a meno che non si indirizzi il registro verso i loro file.
La soluzione è includere i file di font effettivi all’interno della propria
applicazione o immagine e registrarli con il motore. Il registro legge i file
TrueType (.ttf), OpenType (.otf) e TrueType Collection (.ttc); è accettato
anche il Type1 legacy (.pfb), ma raramente serve per lavori nuovi.
Prima di iniziare, confermare che questi elementi siano a posto:
- NextPDF core è installato.
- Si dispone dei file di font effettivi che si intende usare e si è autorizzati a incorporarli. I diritti di incorporamento sono una responsabilità di chi li usa — si veda Incorporare e creare il subset di un font TrueType.
- La propria build è in grado di copiare tali file nell’artefatto distribuito.
Questa è una guida pratica di tipo operativo. Il codice è minimo; il lavoro sta nella build e nel layout del filesystem. Per i meccanismi a livello di API della registrazione e della creazione del subset di un singolo carattere, leggere la ricetta di incorporamento e subset linkata sopra. Questa pagina tratta di come portare i file sulla macchina e indirizzare il motore verso di essi.
Perché il motore nativo non trova automaticamente i font del sistema operativo
Sezione intitolata “Perché il motore nativo non trova automaticamente i font del sistema operativo”Esistono due percorsi di rendering distinti, e la questione dei font differisce tra i due.
- Motore nativo in-process (l’impostazione predefinita,
Document/writeHtml): il motore non chiama il sistema dei font del sistema operativo néfontconfigper l’individuazione. Risolve un carattere attraverso il registro dei font, che legge uno specifico file di font che è stato registrato oppure ne trova uno all’interno di una directory configurata come percorso di ricerca. Installare un font conapt-get install fonts-notoo eseguirefc-cachenon produce alcun effetto di per sé — il motore nativo vede quei file solo se li si registra o se ne aggiunge la directory al percorso di ricerca del registro. - Bridge Chrome (il renderer HTML-to-PDF che pilota un browser headless):
questo percorso usa i font installati sull’host attraverso la normale
individuazione dei font del browser, perciò i pacchetti di font
apt/apkefontconfigcontano lì.
Se si legge una generica indicazione del tipo “installa questi pacchetti di font di sistema nel tuo Dockerfile”, essa si applica al bridge Chrome, non al motore nativo trattato in questa pagina. Per la generazione nativa, includere i file e registrarli.
Passaggio 1 — Includere i file di font effettivi
Sezione intitolata “Passaggio 1 — Includere i file di font effettivi”Collocare i file di font all’interno dell’albero della propria applicazione in
modo che siano versionati e vengano distribuiti con ogni build. Una posizione
convenzionale è una directory resources/fonts/.
your-app/├── resources/│ └── fonts/│ ├── DejaVuSans.ttf│ ├── DejaVuSans-B.ttf│ └── NotoSansCJK-Regular.ttc└── src/Nominare i file in modo che la ricerca per directory del motore possa trovarli per
famiglia e stile. Quando si registra una directory (anziché un file specifico) e
in seguito si chiama setFont('DejaVuSans', 'B', 12), il motore cerca file come
DejaVuSans-B.ttf, DejaVuSansB.ttf o DejaVuSans.ttf in ciascuna directory
configurata. La ricerca per directory costruisce questi nomi candidati a partire
dallo stesso codice di stile a una sola lettera che si passa a setFont (B
per grassetto, I per corsivo, BI per grassetto-corsivo), non da una parola
scritta per esteso — perciò la forma affidabile è Family-<StyleCode>.ttf (ad
esempio DejaVuSans-B.ttf o DejaVuSans-BI.ttf), non Family-Bold.ttf. Un
file chiamato DejaVuSans-Bold.ttf non viene mai trovato dalla ricerca per
directory; per usare un file simile, registrarlo esplicitamente con register() —
che analizza il font e lo indicizza per la famiglia e lo stile letti dalle tabelle
dei nomi del file stesso, così che il nome di file scritto per esteso non conti più
(si veda il Passaggio 2).
Passaggio 2 — Registrare i font con il motore
Sezione intitolata “Passaggio 2 — Registrare i font con il motore”Esistono due modi equivalenti per rendere visibili i file. Entrambi passano
attraverso NextPDF\Typography\FontRegistry, che implementa
NextPDF\Contracts\FontRegistryInterface.
Registrare un file specifico sotto un alias quando si controlla il carattere esatto:
use NextPDF\Typography\FontRegistry;
$registry = new FontRegistry();$registry->register(__DIR__ . '/../resources/fonts/DejaVuSans.ttf', alias: 'DejaVuSans');register(string $fontFile, string $alias = '', int $fontIndex = 0) accetta file
.ttf, .otf e .ttc, oltre al Type1 legacy .pfb (che carica le sue metriche
.afm compagne dallo stesso percorso); $fontIndex seleziona un sotto-font
all’interno di una TrueType Collection (.ttc). register() analizza il file e
indicizza il carattere per la famiglia e lo stile letti dalle sue tabelle dei nomi,
così che il nome fisico del file sia irrilevante una volta registrato. L’$alias
facoltativo è semplicemente un nome di ricerca aggiuntivo per il carattere — non è
un codice di stile e non cambia quale stile fornisce il file; passarlo quando si
desidera chiamare setFont() con un nome diverso dal nome di famiglia incorporato
nel font. Restituisce il FontInfo analizzato.
Registrare una directory quando si desidera che il motore risolva i caratteri per nome a partire da una cartella che si controlla:
$registry = new FontRegistry('/var/www/app/resources/fonts');// or, equivalently, after construction:$registry->addFontDirectory('/var/www/app/resources/fonts');Il costruttore di FontRegistry accetta quella directory come primo argomento, e
addFontDirectory() aggiunge ulteriori percorsi di ricerca. Anche un Document
nudo espone addFontDirectory() per il caso standalone.
Per usare un registro popolato autonomamente, costruire i documenti tramite
DocumentFactory, che collega esattamente quel registro a ogni documento che crea:
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() costruisce il proprio registro interno, perciò un
carattere registrato su un FontRegistry separato è invisibile per esso. In
produzione, passare per DocumentFactory (o per la factory del proprio framework)
in modo che il registro popolato sia quello effettivamente in uso.
Configurazione del framework
Sezione intitolata “Configurazione del framework”Ogni integrazione di framework espone gli stessi due concetti come configurazione,
così che raramente si tocchi direttamente il registro. Nel nextpdf.php del
pacchetto Laravel, fonts_path (predefinito NEXTPDF_FONTS_PATH, con ripiego su
resource_path('fonts')) è la directory di ricerca, e preload_fonts è un elenco
di percorsi assoluti di file di font analizzati all’avvio del worker. Indirizzare
fonts_path verso la directory che si è inclusa e i caratteri registrati si
risolvono automaticamente.
Passaggio 3 — Fornire i font in un’immagine Docker
Sezione intitolata “Passaggio 3 — Fornire i font in un’immagine Docker”In un container, i file di font devono far parte del layer dell’immagine, copiati
in fase di build. Poiché il codice dell’applicazione e i font vengono distribuiti
insieme quando li si include sotto resources/fonts/, un normale COPY . . li
porta già con sé. Se si tengono i font fuori dal contesto di build, copiarli
esplicitamente e assicurarsi che il percorso che si registra corrisponda al
percorso all’interno dell’immagine.
# 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"]Su un filesystem immutabile o di sola lettura (un container con
readOnlyRootFilesystem, un’immagine serverless o un host irrobustito), i file di
font vengono letti al momento della generazione e non vengono mai scritti, perciò
un mount di sola lettura va bene. L’unica scrittura che il motore potrebbe
desiderare è la sua cache dei font analizzati: o si assegna a quella directory un
piccolo volume scrivibile, oppure si scalda e si blocca il registro all’avvio
(sezione successiva) in modo che non venga tentata alcuna scrittura o registrazione
a runtime.
Passaggio 4 — Scaldare e verificare
Sezione intitolata “Passaggio 4 — Scaldare e verificare”In un worker a lunga durata, analizzare ogni carattere una volta all’avvio, poi bloccare il registro in modo che non avvenga alcuna registrazione per richiesta e una configurazione errata fallisca in modo rumoroso anziché ripiegare silenziosamente:
$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();Dopo lock(), register(), addFontDirectory() e warmup() sollevano
un’eccezione, il che trasforma un errore di “percorso sbagliato nell’immagine” in
un fallimento di avvio duro anziché in una pagina con tofu in produzione.
Aggiungere un controllo smoke di deployment che renda una pagina con ciascun carattere richiesto. Il controllo dell’intestazione qui sotto verifica soltanto che il documento abbia prodotto output — non dimostra che il font sia stato analizzato, incorporato o nemmeno risolto. Un carattere che il motore non riesce a trovare può ripiegare su un font di base standard (e, con il comportamento non-strict attuale, un profilo di conformità potrebbe invece fornire un sostituto incluso) pur continuando a emettere un PDF valido e non vuoto — perciò, anche dove quel ripiego avviene, questo controllo da solo non coglie il degrado silenzioso. Non fare affidamento sul fatto che il ripiego sia garantito o silenzioso su ogni percorso; verificare direttamente il programma incorporato, come mostrato qui sotto:
$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.');}Per far effettivamente fallire il deploy quando un carattere è mancante,
controllare nel PDF emesso la presenza del programma di font incorporato. Un
carattere registrato che si risolve porta con sé il proprio dizionario di font con
un programma incorporato, perciò asserirne la presenza coglie il caso in cui il
carattere richiesto non si è mai risolto (qualunque sia stato il ripiego del
motore) che il controllo dell’intestazione manca. Quale chiave contenga il
programma dipende dal formato dei contorni: i contorni TrueType (.ttf, .ttc)
usano /FontFile2, i contorni CFF/OpenType (.otf con contorni PostScript) usano
/FontFile3, e il Type1 legacy (.pfb) usa /FontFile.
Se tutto ciò che serve è un segnale “un qualche programma di font incorporato”
indipendente dal formato, verificare la presenza di /FontFile da solo —
poiché /FontFile è una sottostringa sia di /FontFile2 sia di /FontFile3, una
semplice verifica di sottostringa corrisponde già a ogni tipo di contorno, e
aggiungere /FontFile2//FontFile3 come rami || aggiuntivi è ridondante:
if (!str_contains($pdf, '/FontFile')) { throw new RuntimeException('No embedded font program found — face fell back.');}Una semplice sottostringa /FontFile, tuttavia, non è in grado di distinguere i
tipi di contorno. Per distinguerli, abbinare il token esatto con un confine di
parola in modo che /FontFile non scatti anche su /FontFile2 o /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.');}In ogni caso, trattare questo come una mera euristica grossolana, non come un
gate di deploy affidabile. Una ricerca grezza di byte sul PDF serializzato è
inaccurata per diversi motivi: i programmi di font possono risiedere all’interno di
stream di oggetti compressi (dove /FontFile* non compare mai come byte in
chiaro), gli aggiornamenti incrementali possono aggiungere o soppiantare
oggetti, i font non incorporati o standard-14 legittimamente non portano alcun
programma di font, e le differenze di serializzazione (ordine degli oggetti,
spaziatura, codifica dei nomi) possono spostare o nascondere il token. Nel migliore
dei casi conferma che un qualche carattere ha incorporato un programma — mai che
si sia risolto il carattere specifico desiderato.
Per un vero gate di deploy, non fare affidamento sulla ricerca di byte. Analizzare
il PDF emesso con un vero parser di PDF o un inspector di oggetti e asserire che
l’oggetto font per il carattere di destinazione porti un programma incorporato
/FontFile//FontFile2//FontFile3, oppure usare un’asserzione di risoluzione
dei font fornita dal prodotto se disponibile per la propria integrazione. Le regex
consapevoli dei token mostrate sopra sono utili per un rapido controllo di
integrità locale, ma è un’ispezione strutturale ciò che dovrebbe far fallire il
deploy. L’incorporamento e la struttura del dizionario di font sono descritti in
Incorporare e creare il subset di un font TrueType.
Casi limite e insidie
Sezione intitolata “Casi limite e insidie”createStandalone()ha il proprio registro. Un carattere registrato su unFontRegistryseparato non è visibile a un documento standalone. UsareDocumentFactory(o la factory del framework) in modo che il proprio registro sia quello attivo.- I file di stile devono esistere come file. Il motore non sintetizza il
grassetto o il corsivo a partire da un carattere regolare. Se si chiama
setFont('DejaVuSans', 'B'), la ricerca per directory cercaDejaVuSans-B.ttf,DejaVuSansB.ttfoDejaVuSans.ttf(anche le varianti minuscole e.otf) — forma il candidato a partire dal codice di stile letteraleB, perciò non cerca maiDejaVuSans-Bold.ttf. Un file con un nome scritto per esteso comeDejaVuSans-Bold.ttfsi risolve solo quando lo si registra esplicitamente conregister(), che lo indicizza per la famiglia e lo stile letti dalle tabelle dei nomi del file stesso a prescindere dal nome di file; affidarsi alla ricerca per directory per trovarlo produce un mancato riscontro, dopo il quale il motore potrebbe ripiegare su un font di base (non un percorso garantito né sempre silenzioso) — il degrado di cui questa pagina avverte. - I percorsi con stream-wrapper e remoti vengono rifiutati. Il registro rifiuta
i percorsi contenenti uno schema URI o un byte nullo. Registrare solo file
locali; per i font recuperati a runtime usare
registerFromBinary()con i byte non elaborati. - Un registro bloccato è immutabile. Una volta chiamato
lock(), qualsiasiregister(),addFontDirectory()owarmup()successivo solleva un’eccezione. I metodi di ricerca restano disponibili. Registrare e scaldare ogni cosa prima di bloccare. - Le collection CJK sono grandi. Registrare il sotto-font corretto di un
.ttccon$fontIndexe prevedere un budget per un subset incorporato più grande. Si vedano le note CJK nella ricetta di incorporamento e subset.
Note sulla sicurezza
Sezione intitolata “Note sulla sicurezza”- Un file di font è input binario non attendibile. Includere solo font provenienti da fonti attendibili e convalidare la provenienza di qualsiasi carattere accettato da utenti finali.
- Bloccare il registro dopo lo scaldamento rimuove una superficie di mutazione a runtime e fa sì che un errore di percorso fallisca all’avvio anziché degradare silenziosamente l’output.
- Non interpolare input dell’utente in un percorso di file registrato. Registrare un insieme fisso di caratteri inclusi; non lasciare che una richiesta scelga un percorso di filesystem arbitrario.
Conformità
Sezione intitolata “Conformità”Questa guida non avanza alcuna affermazione normativa di conformità a standard.
Ogni simbolo mostrato è superficie pubblica verificata:
NextPDF\Typography\FontRegistry (register(), addFontDirectory(), warmup(),
lock(), l’argomento del costruttore per la directory), il suo contratto
NextPDF\Contracts\FontRegistryInterface,
NextPDF\Core\DocumentFactory::create() e NextPDF\Core\Document::setFont() /
addFontDirectory(). Le chiavi Laravel fonts_path e preload_fonts sono la
configurazione documentata del pacchetto nextpdf/laravel. Il comportamento di
incorporamento e del tag di subset, con le sue citazioni ISO 32000-2, è documentato
nella ricetta di incorporamento e subset linkata sotto Vedere anche.
Vedere anche
Sezione intitolata “Vedere anche”- Incorporare e creare il subset di un font TrueType: la ricetta a livello di API per registrare un carattere e per il subset automatico al salvataggio.
- Rendere HTML in una pagina PDF: il percorso HTML nativo, che risolve i font attraverso lo stesso registro.
- Restituire un PDF generato da un controller: collegare un documento costruito da una factory a una risposta di framework.
- Uso in produzione con Laravel: la configurazione dei font del framework e lo scaldamento all’avvio del worker.