Tipografía: registro de fuentes, subsetting, CMap, codificación, BiDi
De un vistazo
Sección titulada «De un vistazo»El módulo de tipografía convierte un archivo de fuente y una cadena Unicode en los bytes que necesita un flujo de contenido PDF. Se encarga del análisis de fuentes, del registro durante la vida del proceso, del subsetting de glifos, del CMap ToUnicode, de las estrategias de codificación que tienen en cuenta el cmap y del motor bidireccional de Unicode.
Instalación
Sección titulada «Instalación»composer require nextpdf/core:^3Resumen conceptual
Sección titulada «Resumen conceptual»FontRegistry es el almacén de fuentes durante la vida del proceso e implementa FontRegistryInterface. Analiza una sola vez un archivo TrueType, OpenType, TTC o Type 1 (PFB y AFM) y devuelve un FontInfo inmutable. El registro está pensado para workers de larga duración: precargar un conjunto de fuentes al arrancar y llamar a lock(). El registro rechaza toda mutación posterior mientras las búsquedas siguen sirviendo tráfico. Solo conserva datos PHP simples —metadatos analizados y los bytes sin procesar de la fuente—, de modo que un grupo de workers puede compartir una única instancia. registerFromBinary() acepta bytes de fuente sin procesar, que el puente @font-face de HTML usa para una fuente obtenida de un origen remoto o de un URI de datos.
El motor incrusta y aplica subsetting a cada fuente que utiliza. Un programa de fuente incrustado se incluye dentro del PDF, de modo que el documento se renderiza igual en cualquier visor, con independencia de las fuentes instaladas en el sistema —ISO 32000-2 §9. Un subconjunto contiene solo los glifos a los que el documento hace referencia, lo cual es decisivo para contenido CJK o rico en Unicode —ISO 32000-2 §9. FontSubsetter analiza el directorio de tablas original, extrae el cmap, resuelve las dependencias de glifos compuestos como un cierre transitivo y reconstruye las tablas head, hhea, maxp, cmap, loca, glyf y hmtx. Conserva la numeración original de los identificadores de glifo y rellena con ceros los espacios sin usar, de modo que un CIDToGIDMap de tipo /Identity sigue siendo válido. Devuelve la fuente original sin cambios cuando el subconjunto ahorraría menos del diez por ciento, lo que evita trabajo que no se amortiza por sí mismo. CffSubsetter realiza la operación equivalente para las fuentes OpenType que contienen una tabla de contornos en formato Compact Font Format.
La emisión de texto es una traducción en tres etapas: punto de código Unicode, después código de carácter en el flujo de contenido y, por último, identificador de glifo dentro de la fuente. El módulo la modela como un único colaborador explícito. FontInfo::encodeText() es la fachada; FontEncodingStrategyResolver despacha por fuente. Una fuente TrueType u OpenType incrustada que contiene un cmap Unicode se enruta a TrueTypeCmapStrategy, que emite un flujo hexadecimal Identity-H de dos bytes. Esa es la forma que requiere una fuente Type 0 con un CMap Identity-H y un descendiente CIDFontType2 (ISO 32000-2 §9.7.4; el digest del fragmento RAG correspondiente se devolvió truncado por el límite de licencia, registrado en _downgraded-claims-o3.md). Cualquier otra fuente —las fuentes estándar Base 14, Type 1 PFB y AFM— se enruta a Base14EncodingStrategy, que emite una cadena literal WinAnsi de un byte. Ese flujo abarca todo el repertorio de WinAnsiEncoding (página de códigos 1252 de Windows): latín acentuado, el signo del euro y los signos de puntuación tipográfica habituales. Los puntos de código que quedan fuera de él se descartan del flujo de un byte y toman el respaldo de fuente por clúster cuando se ha registrado una fuente que los cubre (ISO 32000-2 Annex D.2). El resolver cubre todo el espacio de valores de FontInfo; no hay ninguna ruta que admita null. ToUnicodeCMapBuilder construye el recurso /ToUnicode que permite a un lector recuperar el Unicode original a partir de una fuente Identity-H. Aplica una fusión voraz de bfrange y un tope de 100 entradas por bloque.
BidiEngine es el servicio de frontera para el algoritmo bidireccional de Unicode (UAX #9, Unicode 16). Cuando el soporte de aislamientos está desactivado, delega en el resolver heredado, de modo que el código llamador existente no se ve afectado. Cuando está activado, ejecuta la canalización que tiene en cuenta los aislamientos: la pila de aislamientos explícitos con una profundidad máxima de 125, las pasadas de tipo débil, las pasadas de tipo neutral —incluida la resolución de corchetes emparejados— y las pasadas de nivel implícito y de reordenación de líneas. La cobertura de glifos CJK de una fuente candidata es un diagnóstico aparte: CjkFontValidator muestrea los bloques Unicode requeridos por cada escritura e informa de un porcentaje de cobertura.
Superficie de la API
Sección titulada «Superficie de la API»| Tipo | Clase | Miembros clave | Estabilidad | Desde |
|---|---|---|---|---|
FontRegistry | final class | register(), registerType1(), registerFromBinary(), registerFromDirectory(), get(), has(), all(), warmup(), lock(), isLocked(), memoryUsage() | estable | 1.7.0 |
FontInfo | final readonly class | $family, $type, $widths, $unicodeMap, $cmapForward, getKey(), encodeText() | estable | 1.0.0 |
FontSubsetter | final class | subset(string, array<int>, int): string | estable | 1.0.0 |
CffSubsetter | final class | Subsetting de contornos OpenType/CFF | estable | 1.0.0 |
FontEncodingStrategyResolver | final class | resolve(FontInfo): FontEncodingStrategy | estable | 2.7.0 |
ToUnicodeCMapBuilder | final class | buildFromRun(), buildFromMap(), encodeUnicodeUtf16Be() | estable | 2.7.0 |
BidiEngine | final class | Resolución consciente de aislamientos UAX #9 | estable | 3.1.0 |
CjkFontValidator | final class | validateCoverage(), detectScript(), isCjkCodepoint() | estable | 1.0.0 |
FontInfo es inmutable: la firma de su constructor y sus propiedades públicas están congeladas. Las estrategias de codificación son funciones puras de (FontInfo, UTF-8 text): con la misma entrada, producen el mismo EncodedGlyphRun en cada llamada.
Ejemplo de código — Inicio rápido
Sección titulada «Ejemplo de código — Inicio rápido»<?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() analiza la fuente una sola vez y devuelve un FontInfo inmutable. encodeText() se enruta a través del resolver y devuelve un EncodedGlyphRun que contiene el flujo de bytes, el operando de cadena PDF, los anchos de avance por glifo y el mapa de GID a Unicode que consume un CMap /ToUnicode.
Ejemplo de código — Producción
Sección titulada «Ejemplo de código — Producción»<?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() y después lock() es la secuencia de arranque del worker. Tras lock(), toda mutación lanza una excepción. Las búsquedas siguen sirviendo tráfico. memoryUsage() devuelve un MemoryReport, de modo que un worker puede controlar la caché de fuentes frente a su presupuesto.
Casos límite y trampas
Sección titulada «Casos límite y trampas»- Un registro bloqueado rechaza
register(),registerFromBinary(),addFontDirectory()ywarmup(). Precargar y bloquear al arrancar; no registrar nunca durante el manejo de peticiones. FontSubsetter::subset()devuelve los bytes originales sin cambios cuando el ahorro sería inferior al diez por ciento, o cuando falta una tabla esencial. Una fuente devuelta igual a la de entrada es la ruta documentada sin ganancia, no un fallo.- El subsetter conserva la numeración original de los identificadores de glifo y rellena con ceros los glifos sin usar. Esto mantiene válido
CIDToGIDMap /Identity; no supongas que los identificadores de glifo se renumeran a un rango contiguo. registerFromBinary()escribe los bytes en un archivo temporal para analizarlos y elimina tanto el archivo de la extensión como el archivo base detempnam()en un bloquefinally. Los datos de fuente no confiables son una superficie de ataque de análisis: deben controlarse antes de que lleguen al analizador (consulta las notas de seguridad).BidiEnginedelega de forma literal en el resolver heredado cuando el soporte de aislamientos está desactivado. Los caracteres de formato de aislamiento se tratan entonces como neutrales a efectos de frontera. Activar el soporte de aislamientos a través de la política de conformidad para obtener el comportamiento completo de UAX #9.CjkFontValidatormuestrea puntos de código a intervalos en lugar de comprobar cada uno, de modo que su cifra de cobertura es una estimación estadísticamente adecuada, no un recuento exhaustivo.
Rendimiento
Sección titulada «Rendimiento»El análisis de fuentes domina el primer uso; el registro lo amortiza a una sola vez por proceso. Tras la precarga, get() y has() son búsquedas en mapa O(1). El coste del subsetting escala con la cantidad de glifos que el documento realmente usa, no con la tabla completa de glifos de la fuente. Por eso el subsetting aporta a la vez una ganancia de tamaño y de velocidad para el contenido CJK: el subsetter maneja fuentes con más de 20,000 glifos mediante búsqueda binaria, búferes preasignados y operaciones de cadena en bloque. La resolución de glifos compuestos está acotada: se limita a 100 iteraciones de cierre para defenderse de referencias circulares entre componentes. El analizador del cmap de formato 12 limita los recuentos de grupos y entradas para acotar la memoria frente a una fuente hostil. El performance_budget de 1500 ms de reloj y 64 MB de pico cubre una precarga de fuentes típica más el renderizado del documento.
Notas de seguridad
Sección titulada «Notas de seguridad»Dos superficies son relevantes para la seguridad. La primera es la entrada de fuentes. register() y registerFromBinary() analizan bytes arbitrarios. registerFromBinary() materializa un archivo temporal. Los envoltorios de flujo y los bytes nulos en las rutas se rechazan en la frontera. Los datos de fuente no confiables deben pasar una política de recursos externos que acote el tamaño del archivo y el recuento de glifos antes de que lleguen al analizador. Los lectores binarios del subsetter comprueban los límites de cada desplazamiento. Los analizadores del cmap limitan los recuentos de grupos, entradas y tablas (numGroups > 31000 y un tope de entradas de 200,000 en el formato 12), de modo que una fuente manipulada no puede provocar una asignación ilimitada. La segunda superficie es la recuperación de texto: ToUnicodeCMapBuilder valida que cada código de carácter esté dentro del espacio de códigos de 16 bits y que cada valor Unicode sea un escalar válido —los semipares sustitutos se rechazan—, de modo que un mapa malformado no puede producir un recurso de extracción corrupto. Tratar cualquier fuente o texto suministrado externamente como no confiable.
Conformidad
Sección titulada «Conformidad»| Afirmación | Estándar | Cláusula | Evidencia |
|---|---|---|---|
| Cada fuente que usa el documento se incrusta, de modo que el documento se renderiza sin depender de las fuentes del sistema. | ISO 32000-2 | §9 | |
| La fuente incrustada se reduce mediante subsetting a los glifos a los que el documento hace referencia. | ISO 32000-2 | §9 | |
Una fuente TrueType CJK incrustada se emite como una fuente Type 0 con un CMap Identity-H y un descendiente CIDFontType2. | ISO 32000-2 | §9.7.4 | Digest RAG truncado por el límite de licencia; prefijo 7a5258772f508e3b, consulta _downgraded-claims-o3.md |
Las dos primeras cláusulas están parafraseadas y fijadas por digest. El digest RAG completo de la tercera cláusula no se devolvió (truncamiento por el límite de licencia); está corroborado por la ADR-013 y el resumen para desarrolladores del codificador del cmap, y se registra como degradado. NextPDF no reproduce texto normativo. La conformidad con PDF/A-4 y PDF/UA-2 para contenido CJK depende del subsetting del lado del escritor y del cableado de /ToUnicode que se controla allí.
Contexto comercial
Sección titulada «Contexto comercial»Un paquete comercial de características OpenType y cadenas premium de respaldo de fuentes se construyen sobre el registro y el punto de unión de codificación del Core. El módulo de tipografía del Core incrusta, aplica subsetting y codifica cada fuente sin necesidad de licencia; el paquete de pago añade una resolución de respaldo curada. La omisión de un enlace de conversión es intencionada: es documentación, no un embudo de ventas.
Véase también
Sección titulada «Véase también»- Fuente: registro TrueType, OpenType y CID — los tipos de valor de fuente, la incrustación y el respaldo.
- Texto: shaping, salto de línea, BiDi — el punto de unión para el manejo de runs y shaping que consume los glifos codificados.
- Contratos / Tipografía — los contratos de
FontRegistryInterfacey del preprocesador de texto. - Motor de renderizado de HTML — el puente
@font-faceque llama aregisterFromBinary().