Pular para o conteúdo

Codifique texto CJK com codificação cmap-aware

Esta receita registra uma face TrueType chinesa, japonesa e coreana (CJK) e, em seguida, codifica texto em chinês tradicional por meio da fachada cmap-aware FontInfo::encodeText(). A fachada retorna um fluxo de bytes (stream) Identity-H com CIDs de dois bytes. A receita segue examples/35-cjk-cmap-demo.php. Leia a nota de escopo antes de depender desse caminho.

A arquitetura de codificação de texto cmap-aware está sendo entregue em fases (ADR-013). A Fase 1 chegou: a fachada FontInfo::encodeText() e a estratégia de codificação cmap-aware estão conectadas e acessíveis pelo código de usuário. A Fase 2 está em andamento: ela roteia o renderizador e o gravador pela fachada. As Fases 3 e 4 estão pendentes: a emissão por fonte de /ToUnicode, /CIDSystemInfo, /Encoding e /CIDToGIDMap, e o resolvedor de fonte substituta, ainda não estão integrados ao gravador.

Planeje considerando estas consequências:

  • Esta receita demonstra a fachada de codificação, não um modo completo de escrita vertical. A superfície atual do documento não possui API pública de modo de escrita, portanto não há chamada setWritingMode nem setter vertical-rl.
  • O exemplo-base é, segundo seu próprio cabeçalho, um teste de fumaça de integração, não um fixture de conformidade. A validação PDF/UA-2 e PDF/A-4 vai regredir na saída produzida dessa forma até a chegada das Fases 3 e 4. Não afirme que a saída deste caminho está em conformidade. Quem decide a conformidade é o verificador, e ele ainda não aprovará esta saída.
  • A infraestrutura de métricas de escrita vertical existe, mas é interna. Ela inclui o objeto de valor CjkVerticalMetrics e os emissores /W2 e /DW2. O NextPDF não a expõe como uma chamada de código de usuário para “escrever verticalmente”, e o gravador ainda não emite seus dicionários.
Terminal window
composer require nextpdf/core:^3

A restrição acima corresponde ao pacote nextpdf/core. O exemplo é executado em PHP 8.4. O fixture de teste Noto Sans TC incluído mantém esta receita autocontida.

A ISO 32000-2 modela a emissão de texto em três camadas: codepoint Unicode, código de caractere e ID de glifo. Para uma face TrueType CJK, o engine usa uma fonte Type 0 composta com codificação Identity-H. Com essa codificação, a string exibida usa pares de bytes que indexam a CIDFont (ISO 32000-2).

FontRegistry::register() analisa a face. FontInfo::encodeText($unicodeText) então resolve uma estratégia de codificação por meio de FontEncodingStrategyResolver. Para uma face TrueType CJK registrada, o despacho segue para TrueTypeCmapStrategy. O EncodedGlyphRun retornado carrega o fluxo de bytes (stream) Identity-H, o operando de string PDF, as larguras de avanço por glifo, os codepoints usados e o mapa GID→Unicode. O subsetting CJK usa os codepoints conforme o ADR-008. Um futuro fluxo (stream) /ToUnicode usará o mapa GID→Unicode. O modo selecionado é EncodingMode::TwoByteCid.

Duas estruturas de CIDFont definem a escrita vertical em PDF. A primeira é o array de métricas verticais por glifo /W2 (ISO 32000-2). A segunda é a métrica vertical padrão /DW2 (ISO 32000-2). O NextPDF fornece o objeto de valor e os emissores para ambas por meio de CjkVerticalMetrics::toW2Array(), toW2RangeArray() e toDw2Array(). Essas estruturas são internas, e o gravador ainda não as emite. Consulte a nota de escopo.

  • FontRegistry::register(string $fontFile, string $alias = '', int $fontIndex = 0): FontInfoNextPDF\Typography\FontRegistry.
  • FontInfo::encodeText(string $unicodeText): EncodedGlyphRunNextPDF\Typography\FontInfo. A fachada da 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 verticais, documentado por transparência, não como um caminho de escrita disponível ao código de usuário.

A tabela completa do PHPDoc é gerada a partir do código-fonte.

<?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 exemplo é autocontido e executável pelo harness. Ele espelha examples/35-cjk-cmap-demo.php. Primeiro, registre o fixture Noto Sans TC incluído. Em seguida, confirme que a fachada cmap-aware está acessível. Depois, renderize por meio de DocumentFactory para que o documento use o registro que você populou.

<?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)
  • Não é um fixture de conformidade. Segundo o próprio cabeçalho do exemplo-base, esta saída é um teste de fumaça de integração. As verificações PDF/UA-2 e PDF/A-4 regridem nela até a chegada das Fases 3 e 4. Não a registre como um golden de conformidade.
  • Sem API de modo de escrita. Nenhuma chamada pública alterna para escrita vertical, o que cobriria vertical-rl e vertical-lr. Os emissores /W2 e /DW2 existem internamente. Eles não são expostos e ainda não são gravados no dicionário de fonte.
  • Propriedade do registro. Document::createStandalone() constrói seu próprio registro. Use DocumentFactory para que o documento leia o registro que você populou com a face CJK.
  • Caminho final do fluxo de bytes. Até que a Fase 2 seja concluída, o fluxo de conteúdo visível ainda passa pelo caminho de texto legado. A parte comprovada e acessível hoje é a etapa de codificação a montante: a busca direta no cmap mais o fluxo de bytes (stream) Identity-H.
  • Custo do subsetting CJK. Faces CJK grandes passam pelo subsetting por meio de um subprocesso isolado. Esse subprocesso tem um fallback PHP nativo e um timeout de dois segundos (ADR-008).

encodeText() faz uma única passagem de busca direta no cmap pela entrada. É linear em relação à contagem de codepoints, O(n). O orçamento é wall_ms: 2000, peak_mb: 128. Este orçamento é o mais alto deste conjunto porque as faces CJK são grandes, e o subsetting é o custo dominante. O ADR-008 isola esse trabalho para impedir que ele bloqueie o chamador.

Um arquivo de fonte CJK é entrada binária não confiável. O parser rejeita caminhos de stream-wrapper e bytes nulos. O subsetting CJK é executado em um subprocesso isolado sem herdar estado (ADR-008). Valide a procedência de faces fornecidas pelo usuário final. O conteúdo de texto CJK é renderizado, não interpretado.

DeclaraçãoEspecificaçãoCláusulareference_id
Para uma fonte Type 0 Identity-H/Identity-V, a string exibida é composta por pares de bytes que indexam o CIDFont.ISO 32000-2iso32000_2_sec9#x1.x49.p90
O array W2 fornece métricas de escrita vertical por glifo e aplica-se apenas a CIDFonts usadas para escrita vertical.ISO 32000-2iso32000_2_sec9#x1.x44.p23
O array DW2 fornece as métricas de escrita vertical padrão para uma CIDFont.ISO 32000-2iso32000_2_sec9#x1.x44.p22

Esta receita mostra que a fachada de codificação CJK cmap-aware está acessível pelo código de usuário (Fase 1). Ela não afirma saída de escrita vertical nem conformidade PDF/UA-2 / PDF/A-4 para o arquivo produzido. A emissão de /ToUnicode e de métricas verticais no gravador (Fases 3 e 4) está pendente, e um verificador não aprovaria esta saída hoje.

Não aplicável.