Aprovisionar fuentes en producción
En resumen
Sección titulada «En resumen»Tu PDF se representa correctamente en tu portátil, luego se despliega en un contenedor y sale como una fila de cuadros vacíos (el glifo «tofu») o con acentos y caracteres no latinos ausentes. La causa es casi siempre la misma: la fuente que seleccionaste no está presente en la imagen desplegada.
El motor nativo, en proceso, de NextPDF resuelve las fuentes a partir de
archivos de fuente que el registro de fuentes puede leer. No descubre
automáticamente las fuentes del sistema operativo ni de fontconfig: los archivos
de fuente instalados por el sistema operativo solo ayudan si registras
explícitamente esos archivos o añades su directorio contenedor a la ruta de
búsqueda de FontRegistry. Un contenedor construido a partir de una imagen base
mínima no tiene fuentes instaladas por apt/apk, e incluso cuando las tiene, el
motor nativo las ignora a menos que apuntes el registro a sus archivos. La
solución es empaquetar los archivos de fuente reales dentro de tu aplicación o
imagen y registrarlos en el motor. El registro lee archivos TrueType (.ttf),
OpenType (.otf) y TrueType Collection (.ttc); también se acepta el Type1 antiguo
(.pfb), pero rara vez es necesario para trabajo nuevo.
Antes de empezar, confirma que estas piezas están en su sitio:
- El Core de NextPDF está instalado.
- Tienes los archivos de fuente reales que pretendes usar y tienes licencia para incrustarlos. Los derechos de incrustación son tu responsabilidad; consulta Incrustar y subconjuntar una fuente TrueType.
- Tu compilación puede copiar esos archivos en el artefacto desplegado.
Esta es una guía práctica de operaciones. El código es mínimo; el trabajo está en la compilación y en la disposición del sistema de archivos. Para la mecánica a nivel de API de registrar y subconjuntar una sola tipografía, lee la receta de incrustación y subconjuntado enlazada arriba. Esta página cubre cómo llevar los archivos al servidor y apuntar el motor hacia ellos.
Por qué el motor nativo no encuentra automáticamente las fuentes del sistema operativo
Sección titulada «Por qué el motor nativo no encuentra automáticamente las fuentes del sistema operativo»Hay dos rutas de representación distintas, y la historia de las fuentes difiere entre ambas.
- Motor nativo en proceso (el predeterminado,
Document/writeHtml): el motor no llama al sistema de fuentes del sistema operativo ni afontconfigpara el descubrimiento. Resuelve una tipografía a través del registro de fuentes, que lee un archivo de fuente específico que registraste o encuentra uno dentro de un directorio que configuraste como ruta de búsqueda. Instalar una fuente conapt-get install fonts-notoo ejecutarfc-cacheno hace nada por sí mismo: el motor nativo ve esos archivos solo si los registras o añades su directorio a la ruta de búsqueda del registro. - Puente Chrome (el representador de HTML a PDF que controla un navegador
headless): esta ruta sí usa las fuentes instaladas del host mediante el
descubrimiento de fuentes normal del navegador, así que ahí sí importan los
paquetes de fuentes de
apt/apkyfontconfig.
Si lees orientaciones genéricas del tipo «instala estos paquetes de fuentes del sistema en tu Dockerfile», se aplican al puente Chrome, no al motor nativo que cubre esta página. Para la generación nativa, empaqueta los archivos y regístralos.
Paso 1 — Empaquetar los archivos de fuente reales
Sección titulada «Paso 1 — Empaquetar los archivos de fuente reales»Coloca los archivos de fuente dentro del árbol de tu aplicación para que estén
versionados y se desplieguen con cada compilación. Una ubicación convencional es
un directorio resources/fonts/.
your-app/├── resources/│ └── fonts/│ ├── DejaVuSans.ttf│ ├── DejaVuSans-B.ttf│ └── NotoSansCJK-Regular.ttc└── src/Nombra los archivos de modo que la búsqueda por directorio del motor pueda
encontrarlos por familia y estilo. Cuando registras un directorio (en lugar de
un archivo específico) y luego llamas a setFont('DejaVuSans', 'B', 12), el motor
busca archivos como DejaVuSans-B.ttf, DejaVuSansB.ttf o DejaVuSans.ttf en
cada directorio configurado. La búsqueda por directorio construye esos nombres
candidatos a partir del mismo código de estilo de una sola letra que pasas a
setFont (B para negrita, I para cursiva, BI para negrita-cursiva), no de
una palabra completa, de modo que la forma fiable es Family-<StyleCode>.ttf
(por ejemplo DejaVuSans-B.ttf o DejaVuSans-BI.ttf), no Family-Bold.ttf.
Un archivo llamado DejaVuSans-Bold.ttf nunca lo encuentra la búsqueda por
directorio; para usar tal archivo, regístralo explícitamente con register(), que
analiza la fuente y la indexa por la familia y el estilo leídos de las propias
tablas de nombres del archivo, de modo que el nombre de archivo completo deja de
importar (consulta el Paso 2).
Paso 2 — Registrar las fuentes en el motor
Sección titulada «Paso 2 — Registrar las fuentes en el motor»Tienes dos formas equivalentes de hacer visibles los archivos. Ambas pasan por
NextPDF\Typography\FontRegistry, que implementa
NextPDF\Contracts\FontRegistryInterface.
Registra un archivo específico bajo un alias cuando controlas la tipografía exacta:
use NextPDF\Typography\FontRegistry;
$registry = new FontRegistry();$registry->register(__DIR__ . '/../resources/fonts/DejaVuSans.ttf', alias: 'DejaVuSans');register(string $fontFile, string $alias = '', int $fontIndex = 0) acepta
archivos .ttf, .otf y .ttc, además del Type1 antiguo .pfb (que carga sus
métricas .afm complementarias de la misma ruta); $fontIndex selecciona una
subfuente dentro de un TrueType Collection (.ttc). register() analiza el
archivo e indexa la tipografía por la familia y el estilo leídos de sus propias
tablas de nombres, de modo que el nombre de archivo físico es irrelevante una vez
registrado. El $alias opcional es solo un nombre de búsqueda adicional para la
tipografía: no es un código de estilo y no cambia qué estilo proporciona el
archivo; pásalo cuando quieras llamar a setFont() con un nombre distinto del
nombre de familia incrustado de la fuente. Devuelve el FontInfo analizado.
Registra un directorio cuando quieres que el motor resuelva tipografías por nombre desde una carpeta que controlas:
$registry = new FontRegistry('/var/www/app/resources/fonts');// or, equivalently, after construction:$registry->addFontDirectory('/var/www/app/resources/fonts');El constructor de FontRegistry toma ese directorio como su primer argumento, y
addFontDirectory() añade más rutas de búsqueda. Un Document simple también
expone addFontDirectory() para el caso autónomo.
Para usar un registro que poblaste tú mismo, construye los documentos a través de
DocumentFactory, que conecta ese registro exacto en cada documento que 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() construye su propio registro interno, de modo que
una tipografía que registraste en un FontRegistry separado es invisible para él.
En producción, pasa por DocumentFactory (o la factoría de tu framework) para que
el registro poblado sea el que está en uso.
Configuración del framework
Sección titulada «Configuración del framework»Cada integración de framework expone los mismos dos conceptos como configuración,
así que rara vez tocas el registro directamente. En el nextpdf.php del paquete
de Laravel, fonts_path (predeterminado NEXTPDF_FONTS_PATH, con
resource_path('fonts') como reserva) es el directorio de búsqueda, y
preload_fonts es una lista de rutas absolutas de archivos de fuente que se
analizan al arrancar el worker. Apunta fonts_path al directorio que empaquetaste
y tus tipografías registradas se resuelven automáticamente.
Paso 3 — Aprovisionar fuentes en una imagen de Docker
Sección titulada «Paso 3 — Aprovisionar fuentes en una imagen de Docker»En un contenedor, los archivos de fuente deben formar parte de la capa de imagen,
copiados en tiempo de compilación. Como el código de la aplicación y las fuentes
se despliegan juntos cuando los empaquetas bajo resources/fonts/, un COPY . .
normal ya los lleva. Si mantienes las fuentes fuera del contexto de compilación,
cópialas explícitamente y asegúrate de que la ruta que registras coincide con la
ruta dentro de la imagen.
# 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"]En un sistema de archivos inmutable o de solo lectura (un contenedor con
readOnlyRootFilesystem, una imagen serverless o un host endurecido), los
archivos de fuente se leen en tiempo de generación y nunca se escriben, de modo
que un montaje de solo lectura es correcto. La única escritura que el motor podría
querer es su caché de fuentes analizadas: o bien das a ese directorio un pequeño
volumen escribible, o calientas y bloqueas el registro al arrancar (siguiente
sección) para que no se intente ninguna escritura ni registro en tiempo de
ejecución.
Paso 4 — Calentar y verificar
Sección titulada «Paso 4 — Calentar y verificar»En un worker de larga duración, analiza cada tipografía una vez al arrancar, luego bloquea el registro para que no se produzca ningún registro por solicitud y una mala configuración falle de forma ruidosa en lugar de recurrir silenciosamente a una alternativa:
$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();Tras lock(), register(), addFontDirectory() y warmup() lanzan, lo que
convierte un error de «ruta equivocada en la imagen» en un fallo de arranque
contundente en lugar de una página con tofu en producción.
Añade una comprobación de humo del despliegue que represente una página con cada tipografía requerida. La comprobación de cabecera de abajo solo verifica que el documento produjo salida: no demuestra que la fuente se analizara, se incrustara ni siquiera se resolviera. Una tipografía que el motor no puede encontrar puede recurrir a una fuente base estándar (y, bajo el comportamiento no estricto actual, un perfil de conformidad puede en su lugar proporcionar un sustituto incluido) sin dejar de emitir un PDF válido y no vacío, de modo que, incluso donde ocurre esa reserva, esta comprobación por sí sola no detectará la degradación silenciosa. No confíes en que la reserva esté garantizada ni sea silenciosa en todas las rutas; verifica directamente el programa incrustado, como se muestra abajo:
$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.');}Para que el despliegue falle de verdad cuando falta una tipografía, comprueba en
el PDF emitido el programa de fuente incrustado. Una tipografía registrada que se
resuelve lleva su propio diccionario de fuente con un programa incrustado, así que
afirmar su presencia detecta el caso en que la tipografía solicitada nunca se
resolvió (sea cual sea la alternativa a la que recurrió el motor) que la
comprobación de cabecera no detecta. La clave que contiene el programa depende del
formato de contornos: los contornos TrueType (.ttf, .ttc) usan /FontFile2,
los contornos CFF/OpenType (.otf con contornos PostScript) usan /FontFile3, y
el Type1 antiguo (.pfb) usa /FontFile.
Si todo lo que necesitas es una señal de «algún programa de fuente incrustado»
agnóstica del formato, comprueba /FontFile a secas: como /FontFile es una
subcadena tanto de /FontFile2 como de /FontFile3, una comprobación de subcadena
simple ya coincide con todos los tipos de contorno, y añadir /FontFile2//FontFile3
como ramas || adicionales es redundante:
if (!str_contains($pdf, '/FontFile')) { throw new RuntimeException('No embedded font program found — face fell back.');}Sin embargo, una subcadena /FontFile a secas no puede distinguir los tipos de
contorno. Para diferenciarlos, busca el token exacto con un límite de palabra para
que /FontFile no se dispare también con /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.');}En cualquier caso, trata esto como una heurística aproximada únicamente, no
como una compuerta de despliegue fiable. Una búsqueda de bytes en bruto sobre el
PDF serializado es inexacta por varias razones: los programas de fuente pueden
residir dentro de flujos de objetos comprimidos (donde /FontFile* nunca
aparece como bytes en claro), las actualizaciones incrementales pueden anexar
o sustituir objetos, las fuentes no incrustadas o de los 14 estándar no
llevan legítimamente ningún programa de fuente, y las diferencias de
serialización (orden de objetos, espacios en blanco, codificación de nombres)
pueden mover u ocultar el token. En el mejor de los casos confirma que alguna
tipografía incrustó un programa, nunca que la tipografía específica que querías
se resolvió.
Para una compuerta de despliegue real, no confíes en la búsqueda de bytes. Analiza
el PDF emitido con un analizador de PDF o un inspector de objetos adecuado y
afirma que el objeto de fuente de tu tipografía objetivo lleva un programa
incrustado /FontFile//FontFile2//FontFile3, o usa una aserción de resolución
de fuentes proporcionada por el producto si está disponible para tu integración.
Las expresiones regulares conscientes del token de arriba son útiles para una
comprobación rápida de coherencia local, pero una inspección estructural es lo que
debería hacer fallar el despliegue. La incrustación y la estructura del diccionario
de fuente se describen en
Incrustar y subconjuntar una fuente TrueType.
Casos límite y trampas
Sección titulada «Casos límite y trampas»createStandalone()tiene su propio registro. Una tipografía registrada en unFontRegistryseparado no es visible para un documento autónomo. UsaDocumentFactory(o la factoría del framework) para que tu registro sea el activo.- Los archivos de estilo deben existir como archivos. El motor no sintetiza
negrita ni cursiva a partir de una tipografía regular. Si llamas a
setFont('DejaVuSans', 'B'), la búsqueda por directorio buscaDejaVuSans-B.ttf,DejaVuSansB.ttfoDejaVuSans.ttf(también las variantes en minúscula y.otf): forma el candidato a partir del código de estilo literalB, así que nunca buscaDejaVuSans-Bold.ttf. Un archivo con un nombre completo comoDejaVuSans-Bold.ttfsolo se resuelve cuando lo registras explícitamente conregister(), que lo indexa por la familia y el estilo leídos de las propias tablas de nombres del archivo independientemente del nombre de archivo; confiar en la búsqueda por directorio para encontrarlo produce un fallo, tras el cual el motor puede recurrir a una fuente base (no una ruta garantizada ni siempre silenciosa): la degradación de la que advierte esta página. - Las rutas de envoltorio de flujo y remotas se rechazan. El registro rechaza
rutas que contengan un esquema de URI o un byte nulo. Registra solo archivos
locales; para fuentes obtenidas en tiempo de ejecución usa
registerFromBinary()con los bytes en bruto. - Un registro bloqueado es inmutable. Una vez que llamas a
lock(), cualquierregister(),addFontDirectory()owarmup()posterior lanza. Los métodos de búsqueda siguen disponibles. Registra y calienta todo antes de bloquear. - Las colecciones CJK son grandes. Registra la subfuente correcta de un
.ttccon$fontIndexy presupuesta un subconjunto incrustado mayor. Consulta las notas sobre CJK en la receta de incrustación y subconjuntado.
Notas de seguridad
Sección titulada «Notas de seguridad»- Un archivo de fuente es entrada binaria no confiable. Empaqueta solo fuentes de fuentes de procedencia que confíes, y valida la procedencia de cualquier tipografía aceptada de usuarios finales.
- Bloquear el registro tras el calentamiento elimina una superficie de mutación en tiempo de ejecución y hace que un error de ruta falle en el arranque en lugar de degradar silenciosamente la salida.
- No interpoles entrada de usuario en una ruta de archivo registrada. Registra un conjunto fijo de tipografías empaquetadas; no dejes que una solicitud elija una ruta arbitraria del sistema de archivos.
Conformidad
Sección titulada «Conformidad»Esta guía no hace ninguna afirmación normativa de estándares. Cada símbolo
mostrado es superficie pública verificada: NextPDF\Typography\FontRegistry
(register(), addFontDirectory(), warmup(), lock(), el argumento de
directorio del constructor), su contrato
NextPDF\Contracts\FontRegistryInterface,
NextPDF\Core\DocumentFactory::create() y NextPDF\Core\Document::setFont() /
addFontDirectory(). Las claves fonts_path y preload_fonts de Laravel son la
configuración documentada del paquete nextpdf/laravel. El comportamiento de
incrustación y de etiqueta de subconjunto, con sus citas de ISO 32000-2, está
documentado en la receta de incrustación y subconjuntado enlazada en Véase también.
Véase también
Sección titulada «Véase también»- Incrustar y subconjuntar una fuente TrueType: la receta a nivel de API para registrar una tipografía y el subconjunto automático al guardar.
- Representar HTML en una página PDF: la ruta de HTML nativa, que resuelve las fuentes a través del mismo registro.
- Devolver un PDF generado desde un controlador: conecta un documento construido por factoría en una respuesta de framework.
- Uso en producción con Laravel: la configuración de fuentes del framework y el calentamiento en el arranque del worker.