Ir al contenido

Contratos / Tipografía

El dominio de tipografía incluye el contrato del registro de fuentes y los contratos de preprocesamiento de texto: FontRegistryInterface, TextPreprocessorInterface y los objetos de valor inmutables TextPreprocessResult y TextSegment. Todos son stable.

Ventana de terminal
composer require nextpdf/core:^3

FontRegistryInterface es el almacén de fuentes con vida durante todo el proceso. Registra una fuente TrueType, OpenType, TTC o PFB y devuelve los metadatos FontInfo ya analizados. El registro pervive más allá de los documentos individuales, de modo que un worker analiza cada fuente una sola vez. Permite precargar un lote de fuentes durante el arranque y luego bloquearse para que el tráfico de producción no pueda modificarlo. Un registro bloqueado lanza LogicException en register(), addFontDirectory() o warmup(), mientras que las consultas siguen disponibles. El registro también acepta una fuente a partir de bytes en bruto mediante registerFromBinary(). El puente de @font-face usa este método para registrar una fuente obtenida desde un origen remoto o un URI de datos. El registro contiene solo datos PHP puros (sin handles de recursos), por lo que se puede compartir de forma segura entre un grupo de workers.

El motor incrusta cada fuente que utiliza y crea un subconjunto de ella. Un programa de fuente incrustado se incluye dentro del PDF, de modo que el documento se representa igual en cualquier visor, con independencia de las fuentes del sistema instaladas — ISO 32000-2 §9. Un subconjunto de fuente solo incluye los glifos que el documento referencia realmente, lo que resulta clave para contenido CJK o con abundante Unicode — ISO 32000-2 §9. El contrato del registro expone los metadatos analizados que consumen las etapas de creación de subconjuntos e incrustación.

TextPreprocessorInterface intercepta el texto antes de que entre en la composición de glifos, el subconjunto de fuentes, el CMap ToUnicode y el árbol de estructura. Esta ubicación es la propiedad de seguridad clave: un preprocesador que aplica redacción al contenido lo elimina antes de que pueda llegar al flujo de contenido, al subconjunto de fuente o a los metadatos. El contrato impone dos invariantes. Un preprocesador no debe introducir caracteres que afecten al diseño y debe preservar el orden lógico de lectura; su responsabilidad es la sustitución de contenido, no el diseño. El resultado es un TextPreprocessResult inmutable que contiene una lista ordenada de valores TextSegment. Un segmento es de paso directo o de redacción. Para un segmento redactado, el texto que se muestra depende del modo de enmascaramiento: vacío para un rectángulo de caja negra, asteriscos que igualan la longitud del original o una etiqueta fija. El originalCharCount de un segmento es una pista de medición no reversible que solo se usa para dimensionar un rectángulo de redacción. Nunca debe usarse para reconstruir el contenido original.

TipoClaseMiembros claveEstabilidadDesde
FontRegistryInterfaceinterfaceregister(), get(), has(), all(), addFontDirectory(), warmup(), lock(), isLocked(), registerBase14(), registerFromBinary(), memoryUsage()stable1.7.0
TextPreprocessorInterfaceinterfaceprocess(string): TextPreprocessResultstable1.9.0
TextPreprocessResultfinal readonly class$segments, hasRedactions(), getDisplayText()stable1.9.0
TextSegmentfinal readonly class$displayText, $isRedacted, $originalCharCount, $fillColorstable1.9.0

TextPreprocessResult y TextSegment fijan la firma de su constructor y sus propiedades públicas; se pueden añadir métodos nuevos, pero las propiedades no pueden cambiar.

examples/04-text-and-fonts.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->addPage();
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, 'Bold heading', newLine: true);
$doc->setFont('helvetica', '', 11);
$doc->multiCell(0, 7, 'Body text rendered with a registered font.');
$doc->save(__DIR__ . '/output/04-text-and-fonts.pdf');

setFont() resuelve la familia a través de FontRegistryInterface. El documento independiente usa un registro privado. Un worker comparte un registro (consulta la página del documento).

examples/contracts/typography-production.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\FontRegistryInterface;
use NextPDF\Contracts\TextPreprocessorInterface;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
final readonly class FontWarmupService
{
public function __construct(
private FontRegistryInterface $fonts,
private TextPreprocessorInterface $preprocessor,
private LoggerInterface $logger,
) {}
/**
* Warm a font set at boot, then lock the registry.
*
* @param list<string> $fontFiles Absolute paths to font files.
*/
public function boot(array $fontFiles): void
{
try {
$this->fonts->warmup($fontFiles);
$this->fonts->lock();
} catch (NextPdfException $e) {
$this->logger->error('Font warmup failed', ['error' => $e->getMessage()]);
throw $e;
}
}
public function redact(string $text): string
{
$result = $this->preprocessor->process($text);
return $result->hasRedactions()
? $result->getDisplayText()
: $text;
}
}

La secuencia de arranque del worker es warmup() y luego lock(). Después de lock(), cualquier mutación lanza una excepción. Las consultas siguen atendiendo el tráfico.

  • Un registro bloqueado rechaza todos los métodos de mutación. Precargar y bloquear en el arranque; nunca llamar a register() durante el manejo de la solicitud.
  • registerFromBinary() escribe los bytes de la fuente en un archivo temporal para analizarlos. Los datos de fuente no confiables son una superficie de ataque de análisis sintáctico: deben controlarse mediante ExternalResourcePolicyInterface (consultar la página de política de seguridad).
  • Un TextPreprocessor no debe añadir saltos de línea, retornos de carro ni tabulaciones. Hacerlo cambia el diseño y rompe la primera invariante del contrato.
  • TextSegment::$originalCharCount es solo una pista de ancho. Usarla para inferir el contenido original anula la redacción y viola la tercera invariante del contrato.
  • TextPreprocessResult::getDisplayText() devuelve una cadena vacía para los segmentos de caja negra por diseño. No tratar un segmento vacío como un fallo de preprocesamiento.

El análisis de fuentes es el costo dominante del primer uso; el registro lo amortiza hasta una sola ejecución por proceso. Tras la precarga, get() y has() son búsquedas O(1) en el mapa. memoryUsage() devuelve un MemoryReport para que un worker pueda controlar la caché de fuentes respecto de su presupuesto. El preprocesamiento de texto es lineal en función de la longitud de la entrada. La lista de segmentos añade una sobrecarga acotada, proporcional al número de coincidencias de redacción. El performance_budget de 1500 ms de tiempo total y 64 MB de pico cubre la precarga de un conjunto de fuentes típico más la representación del documento. El costo del subconjunto escala con el número de glifos que realmente se usan, no con la tabla completa de glifos de la fuente. Por lo tanto, el subconjunto reduce el tamaño de salida y el costo de representación del contenido CJK.

El dominio de tipografía tiene dos superficies relevantes para la seguridad. La primera es la entrada de fuentes: registerFromBinary() analiza bytes arbitrarios. Los datos de fuente no confiables deben pasar por un ExternalResourcePolicyInterface que acote el tamaño del archivo y el número de glifos antes de que lleguen al analizador. La segunda es la redacción: TextPreprocessorInterface se ubica antes de la composición de glifos, el subconjunto de fuentes, el CMap ToUnicode y el árbol de estructura, precisamente para que el contenido redactado nunca entre en el artefacto representado. Una redacción implementada como una superposición en el momento del pintado filtra el texto original en el flujo de contenido y en el subconjunto. La ubicación del contrato evita esa clase de defecto. La pista de medición de un segmento es deliberadamente no reversible. Debe tratarse como no confiable cualquier fuente o texto proporcionado externamente.

AfirmaciónNormaCláusulaEvidencia
Cada fuente que usa el documento está incrustada, de modo que el documento se representa sin depender de las fuentes del sistema.ISO 32000-2§9
La fuente incrustada se reduce a un subconjunto de los glifos que el documento referencia.ISO 32000-2§9

Ambas cláusulas se presentan parafraseadas. NextPDF no reproduce el texto normativo. PDF/A-4 exige la incrustación de cada fuente. Esa conformidad se documenta en las páginas de extracción y accesibilidad.