Codifique texto CJK com codificação cmap-aware
Visão geral
Seção intitulada “Visão geral”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.
Escopo e status (leia primeiro)
Seção intitulada “Escopo e status (leia primeiro)”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
setWritingModenem settervertical-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
CjkVerticalMetricse os emissores/W2e/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.
Instalação
Seção intitulada “Instalação”composer require nextpdf/core:^3A 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.
Visão conceitual
Seção intitulada “Visão conceitual”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.
Superfície da API
Seção intitulada “Superfície da API”FontRegistry::register(string $fontFile, string $alias = '', int $fontIndex = 0): FontInfo—NextPDF\Typography\FontRegistry.FontInfo::encodeText(string $unicodeText): EncodedGlyphRun—NextPDF\Typography\FontInfo. A fachada da Fase 1.EncodedGlyphRun—NextPDF\Typography\Encoding\EncodedGlyphRun(byteStream,pdfStringOperand,mode,advanceWidths,toUnicodeMap,usedCodepoints,glyphCount()).EncodingMode—NextPDF\Typography\Encoding\EncodingMode(SingleByte,TwoByteCid).CjkVerticalMetrics—NextPDF\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.
Exemplo de código — Início rápido
Seção intitulada “Exemplo de código — Início 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();$font = $registry->register('/path/to/NotoSansTC-Regular.ttf', alias: 'NotoSansTC');
$encoded = $font->encodeText('PDF 2.0 引擎');
assert($encoded->mode === EncodingMode::TwoByteCid); // cmap-aware branch firedecho $encoded->glyphCount() . " glyph run entries\n";Exemplo de código — Produção
Seção intitulada “Exemplo de código — Produção”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)Casos extremos e armadilhas
Seção intitulada “Casos extremos e armadilhas”- 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-rlevertical-lr. Os emissores/W2e/DW2existem 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. UseDocumentFactorypara 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).
Desempenho
Seção intitulada “Desempenho”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.
Notas de segurança
Seção intitulada “Notas de segurança”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.
Conformidade
Seção intitulada “Conformidade”| Declaração | Especificação | Cláusula | reference_id |
|---|---|---|---|
| Para uma fonte Type 0 Identity-H/Identity-V, a string exibida é composta por pares de bytes que indexam o CIDFont. | ISO 32000-2 | iso32000_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-2 | iso32000_2_sec9#x1.x44.p23 | |
| O array DW2 fornece as métricas de escrita vertical padrão para uma CIDFont. | ISO 32000-2 | iso32000_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.
Contexto comercial
Seção intitulada “Contexto comercial”Não aplicável.