Ir al contenido

Componer texto CJK con codificación basada en cmap

Esta receta registra una tipografía CJK TrueType y después codifica texto en chino tradicional mediante la fachada basada en cmap FontInfo::encodeText(). La fachada produce un flujo de bytes Identity-H con CID de dos bytes. La receta sigue examples/35-cjk-cmap-demo.php. Conviene leer la nota de alcance que aparece más abajo antes de basarse en ella.

La arquitectura de codificación de texto basada en cmap se entrega por fases (ADR-013). La fase 1 ya está disponible: la fachada FontInfo::encodeText() y la estrategia de codificación basada en cmap están integradas y son accesibles desde el espacio de usuario. La fase 2 está en curso: consiste en enrutar el renderer y el escritor a través de la fachada. Las fases 3 y 4 están pendientes: la emisión, por tipografía, de /ToUnicode, /CIDSystemInfo, /Encoding y /CIDToGIDMap, y el resolvedor de tipografías sustitutas, todavía no están integrados en el escritor.

Conviene planificar teniendo en cuenta estas consecuencias:

  • Esta receta demuestra la fachada de codificación, no un modo de escritura vertical listo para usar. La superficie actual del documento no tiene una API pública de modo de escritura, es decir, no hay setWritingMode ni setter vertical-rl.
  • El ejemplo base es, según su propio encabezado, una prueba de humo de integración, no un fixture de conformidad. La validación de PDF/UA-2 y PDF/A-4 fallará con la salida producida de esta forma hasta que se entreguen las fases 3 y 4. No afirmar que la salida de esta ruta sea conforme. La conformidad la decide un validador, que todavía no aprobará esta salida.
  • La infraestructura de métricas de escritura vertical existe, pero es interna. Se compone del objeto de valor CjkVerticalMetrics y de los emisores /W2 y /DW2. NextPDF no la expone como una llamada de espacio de usuario para «escribir en vertical», y el escritor todavía no emite esos diccionarios.
Ventana de terminal
composer require nextpdf/core:^3

La restricción corresponde al paquete nextpdf/core. El ejemplo se ejecuta en PHP 8.4. El fixture de prueba Noto Sans TC incluido mantiene la receta autocontenida.

ISO 32000-2 modela la emisión de texto en tres capas: punto de código Unicode, código de carácter e identificador de glifo. Para una tipografía CJK TrueType, el motor usa una tipografía compuesta Type 0 con codificación Identity-H. Con esta codificación, la cadena mostrada es una serie de pares de bytes que indexan el CIDFont (ISO 32000-2).

FontRegistry::register() analiza la tipografía. Después, FontInfo::encodeText($unicodeText) resuelve una estrategia de codificación a través de FontEncodingStrategyResolver. Para una tipografía CJK TrueType registrada, despacha a TrueTypeCmapStrategy. El EncodedGlyphRun devuelto contiene el flujo de bytes Identity-H, el operando de cadena PDF, los anchos de avance por glifo, los puntos de código usados y el mapa GID→Unicode. La creación de subconjuntos CJK consume los puntos de código usados según ADR-008. Un futuro flujo /ToUnicode usará el mapa GID→Unicode. El modo seleccionado es EncodingMode::TwoByteCid.

Dos estructuras de CIDFont definen la escritura vertical en PDF. La primera es el arreglo de métricas verticales por glifo /W2 (ISO 32000-2). La segunda son las métricas verticales predeterminadas /DW2 (ISO 32000-2). NextPDF tiene el objeto de valor y los emisores para ambas, a través de CjkVerticalMetrics::toW2Array(), toW2RangeArray() y toDw2Array(). Son internos y el escritor todavía no los emite. Consultar la nota de alcance.

  • FontRegistry::register(string $fontFile, string $alias = '', int $fontIndex = 0): FontInfoNextPDF\Typography\FontRegistry.
  • FontInfo::encodeText(string $unicodeText): EncodedGlyphRunNextPDF\Typography\FontInfo. La fachada de la fase 1.
  • EncodedGlyphRunNextPDF\Typography\Encoding\EncodedGlyphRun (byteStream, pdfStringOperand, mode, advanceWidths, toUnicodeMap, usedCodepoints, glyphCount()).
  • EncodingModeNextPDF\Typography\Encoding\EncodingMode (SingleByte, TwoByteCid).
  • CjkVerticalMetricsNextPDF\Typography\CjkVerticalMetrics. Objeto de valor interno de métricas verticales. Se documenta por transparencia, no como una ruta de escritura del espacio de usuario.

La tabla completa de PHPDoc se genera a partir del código fuente.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Typography\Encoding\EncodingMode;
use NextPDF\Typography\FontRegistry;
$registry = new FontRegistry();
$font = $registry->register('/path/to/NotoSansTC-Regular.ttf', alias: 'NotoSansTC');
$encoded = $font->encodeText('PDF 2.0 引擎');
assert($encoded->mode === EncodingMode::TwoByteCid); // cmap-aware branch fired
echo $encoded->glyphCount() . " glyph run entries\n";

Este ejemplo es autocontenido y puede ejecutarse con el arnés. Sigue examples/35-cjk-cmap-demo.php. Primero registra el fixture Noto Sans TC incluido. A continuación confirma que la fachada basada en cmap es accesible. Luego renderiza a través de DocumentFactory para usar el registro poblado.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\DocumentFactory;
use NextPDF\Graphics\ImageRegistry;
use NextPDF\Typography\Encoding\EncodingMode;
use NextPDF\Typography\FontRegistry;
$cjkFontPath = dirname(__DIR__, 2)
. '/fonts/test-fixtures/Noto Sans TC/NotoSansTC-Regular.ttf';
if (!is_file($cjkFontPath)) {
fwrite(STDERR, "Missing CJK font fixture: {$cjkFontPath}\n");
exit(1);
}
$fontRegistry = new FontRegistry();
$cjkFont = $fontRegistry->register($cjkFontPath, alias: 'NotoSansTC');
// Phase 1 facade: prove the cmap-aware path is reachable from userland.
$cjkSample = 'PDF 2.0 引擎 — 使用 CMap 編碼';
$encoded = $cjkFont->encodeText($cjkSample);
if ($encoded->mode !== EncodingMode::TwoByteCid) {
fwrite(STDERR, "Expected TwoByteCid (TrueTypeCmapStrategy branch)\n");
exit(2);
}
$imageRegistry = new ImageRegistry(maxCacheBytes: 0);
$documentFactory = new DocumentFactory($fontRegistry, $imageRegistry);
$doc = $documentFactory->create();
$doc->setTitle('NextPDF CJK CMap-Aware Encoding Demo');
$doc->setLanguage('zh-Hant');
$doc->addPage();
$doc->setFont('helvetica', 'B', 16);
$doc->cell(0, 12, 'CJK cmap-aware encoding (Phase 1 facade)', newLine: true);
$doc->setFont('helvetica', '', 10);
$doc->cell(0, 6, 'Mode: ' . $encoded->mode->name . ' (Identity-H, 2-byte CIDs)', newLine: true);
$doc->cell(0, 6, 'Glyphs: ' . $encoded->glyphCount() . ' run entries', newLine: true);
$doc->cell(0, 6, 'Bytes: ' . strlen($encoded->byteStream) . ' encoded bytes', newLine: true);
$doc->ln(4);
$doc->setFont('NotoSansTC', '', 18);
$doc->cell(0, 12, $cjkSample, newLine: true);
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/cjk-vertical-writing.pdf');
echo "Wrote cjk-vertical-writing.pdf (Phase 1+2 dry-run; not a conformance fixture)\n";

STDOUT esperado:

Wrote cjk-vertical-writing.pdf (Phase 1+2 dry-run; not a conformance fixture)
  • No es un fixture de conformidad. Según el encabezado del propio ejemplo base, esta salida es una prueba de humo de integración. Las comprobaciones de PDF/UA-2 y PDF/A-4 fallan con ella hasta que se entreguen las fases 3 y 4. No debe registrarse como un golden de conformidad.
  • No hay API de modo de escritura. Ninguna llamada pública activa la escritura vertical, lo que incluiría vertical-rl y vertical-lr. Los emisores /W2 y /DW2 existen internamente. No están expuestos y todavía no se escriben en el diccionario de la tipografía.
  • Propiedad del registro. Document::createStandalone() construye su propio registro. Usar DocumentFactory para que el documento lea el registro que se pobló con la tipografía CJK.
  • Ruta del flujo de bytes final. Hasta que finalice la fase 2, el flujo de contenido visible todavía pasa por la ruta de texto heredada. La parte probada y accesible actualmente es el paso previo de codificación: la búsqueda directa en el cmap más el flujo de bytes Identity-H.
  • Costo de la creación de subconjuntos CJK. Las tipografías CJK grandes se crean como subconjuntos a través de un subproceso aislado. Ese subproceso dispone de un mecanismo alternativo nativo de PHP y un tiempo de espera de dos segundos (ADR-008).

encodeText() realiza una única pasada de búsqueda directa en el cmap sobre la entrada. Es lineal respecto al número de puntos de código, O(n). El presupuesto es wall_ms: 2000, peak_mb: 128. Este presupuesto es el más alto de este conjunto porque las tipografías CJK son grandes, y la creación de sus subconjuntos es el costo dominante. ADR-008 aísla ese trabajo para que no pueda bloquear al código llamador.

Un archivo de tipografía CJK es una entrada binaria no confiable. El analizador rechaza rutas con envoltorios de flujo y los bytes nulos. La creación de subconjuntos CJK se ejecuta en un subproceso aislado sin estado heredado (ADR-008). Validar la procedencia de la tipografía en las tipografías suministradas por el usuario final. El contenido de texto CJK se renderiza, no se interpreta.

DeclaraciónEspecificaciónCláusulareference_id
Para una tipografía Type 0 Identity-H/Identity-V, la cadena mostrada son pares de bytes que indexan el CIDFont.ISO 32000-2iso32000_2_sec9#x1.x49.p90
El arreglo W2 proporciona las métricas de escritura vertical por glifo y solo se aplica a los CIDFonts usados para escritura vertical.ISO 32000-2iso32000_2_sec9#x1.x44.p23
El arreglo DW2 proporciona las métricas de escritura vertical predeterminadas de un CIDFont.ISO 32000-2iso32000_2_sec9#x1.x44.p22

Esta receta muestra que la fachada de codificación CJK basada en cmap es accesible desde el espacio de usuario (fase 1). No afirma que el archivo producido tenga salida de escritura vertical ni conformidad con PDF/UA-2 / PDF/A-4. La emisión en el lado del escritor de /ToUnicode y de las métricas verticales (fases 3 y 4) está pendiente, y actualmente un validador no aprobaría esta salida.

No aplica.