Salta ai contenuti
getnextpdf.com

stabilità: Sperimentale

Impostare testo CJK con codifica cmap-aware

Questa ricetta registra un font TrueType CJK. Codifica poi testo in cinese tradizionale tramite la facade cmap-aware FontInfo::encodeText(). La facade produce un flusso di byte CID Identity-H a due byte. La ricetta segue examples/35-cjk-cmap-demo.php. Prima di farvi affidamento, leggere la nota sull’ambito riportata di seguito.

L’architettura cmap-aware per la codifica del testo viene rilasciata in fasi (ADR-013). La Fase 1 è stata integrata: la facade FontInfo::encodeText() e la strategia di codifica cmap-aware sono collegate e raggiungibili da userland. La Fase 2 è in corso: instrada il renderer e il writer attraverso la facade. Le Fasi 3 e 4 sono in sospeso: l’emissione per font di /ToUnicode, /CIDSystemInfo, /Encoding e /CIDToGIDMap, e il resolver dei font sostitutivi, non sono ancora collegati al writer.

Tenere conto di queste conseguenze:

  • Questa ricetta illustra la facade di codifica — il livello di codifica del testo — non una modalità di scrittura verticale pronta all’uso. Per il percorso di layout HTML/CSS esiste ora un composer per la modalità di scrittura verticale opzionale ed sperimentale (il flag layoutVerticalComposer, che pilota i valori CSS vertical-rl / vertical-lr); vedere Modalità di scrittura verticale. La superficie del documento non dispone ancora di un metodo imperativo setWritingMode né di un setter vertical-rl: la composizione verticale è opzionale e guidata dal CSS.
  • L’esempio di base è, come dichiara esplicitamente la sua intestazione, uno smoke test di integrazione, non una fixture di conformità. La validazione PDF/UA-2 e PDF/A-4 regredirà sull’output prodotto in questo modo finché non saranno integrate le Fasi 3 e 4. Non affermare che l’output prodotto da questo percorso sia conforme. La conformità deve essere stabilita da un checker e, allo stato attuale, non convaliderebbe questo output.
  • L’infrastruttura delle metriche per la scrittura verticale esiste ma è interna. Tale infrastruttura comprende il value object CjkVerticalMetrics e gli emitter /W2 e /DW2. Non è esposta come chiamata userland per la «scrittura verticale» e i suoi dizionari non sono ancora emessi dal writer.
Terminal window
composer require nextpdf/core:^3

Il vincolo fa riferimento al pacchetto nextpdf/core. L’esempio viene eseguito su PHP 8.4. La fixture di test Noto Sans TC inclusa nel pacchetto rende la ricetta autonoma.

ISO 32000-2 modella l’emissione del testo su tre livelli: codepoint Unicode, codice di carattere e ID del glifo. Per un font TrueType CJK, il motore utilizza un font composito Type 0 con codifica Identity-H. Con questa codifica, la stringa visualizzata è composta da coppie di byte che indicizzano il CIDFont (ISO 32000-2, reference_id=).

FontRegistry::register() analizza il font. FontInfo::encodeText($unicodeText) risolve quindi una strategia di codifica tramite FontEncodingStrategyResolver. Per un font TrueType CJK registrato, delega a TrueTypeCmapStrategy. L’EncodedGlyphRun restituito contiene il flusso di byte Identity-H, l’operando di stringa PDF, le larghezze di avanzamento per glifo, i codepoint utilizzati e la mappa GID→Unicode. Il subsetting CJK usa i codepoint utilizzati secondo ADR-008. Un futuro flusso /ToUnicode utilizzerà la mappa GID→Unicode. La modalità selezionata è EncodingMode::TwoByteCid.

Due strutture CIDFont definiscono la scrittura verticale in PDF. La prima è l’array di metriche verticali per glifo /W2 (ISO 32000-2, reference_id=). La seconda è il set di metriche verticali predefinite /DW2 (ISO 32000-2, reference_id=). NextPDF dispone del value object e degli emitter per entrambe, tramite CjkVerticalMetrics::toW2Array(), toW2RangeArray() e toDw2Array(). Sono interni e il writer non li emette ancora. Vedere la nota sull’ambito.

  • FontRegistry::register(string $fontFile, string $alias = '', int $fontIndex = 0): FontInfoNextPDF\Typography\FontRegistry.
  • FontInfo::encodeText(string $unicodeText): EncodedGlyphRunNextPDF\Typography\FontInfo. La facade della Fase 1.
  • EncodedGlyphRunNextPDF\Typography\Encoding\EncodedGlyphRun (byteStream, pdfStringOperand, mode, advanceWidths, toUnicodeMap, usedCodepoints, glyphCount()).
  • EncodingModeNextPDF\Typography\Encoding\EncodingMode (SingleByte, TwoByteCid).
  • CjkVerticalMetricsNextPDF\Typography\CjkVerticalMetrics. Value object interno per le metriche verticali. È documentato per trasparenza, non come percorso di scrittura da userland.

La tabella PHPDoc completa è generata dal codice sorgente.

<?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";

Questo esempio è autonomo ed eseguibile nell’harness. Rispecchia examples/35-cjk-cmap-demo.php. Innanzitutto registrare la fixture Noto Sans TC inclusa. Quindi verificare che la facade cmap-aware sia raggiungibile. Infine eseguire il rendering tramite DocumentFactory, così da utilizzare il registry popolato.

<?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 previsto:

Wrote cjk-vertical-writing.pdf (Phase 1+2 dry-run; not a conformance fixture)
  • Non è una fixture di conformità. Come indica l’intestazione dell’esempio di base, questo output è uno smoke test di integrazione. I controlli PDF/UA-2 e PDF/A-4 regrediscono su questo output finché non saranno integrate le Fasi 3 e 4. Non registrarlo come golden di conformità.
  • Nessuna API imperativa del documento per la modalità di scrittura. Non esiste alcuna chiamata pubblica di Document come setWritingMode() né un setter vertical-rl / vertical-lr. Il composer verticale HTML/CSS sperimentale è separato, opzionale e guidato dal CSS tramite il flag layoutVerticalComposer (vedere la nota sull’ambito). Gli emitter /W2 e /DW2 esistono internamente. Non sono esposti e non sono ancora scritti nel dizionario dei font.
  • Proprietà del registry. Document::createStandalone() crea il proprio registry. Usare DocumentFactory affinché il documento legga il registry popolato con il font CJK.
  • Percorso finale del flusso di byte. Finché la Fase 2 non sarà conclusa, il flusso di contenuto visibile passa ancora attraverso il percorso di testo legacy. La parte oggi comprovata e raggiungibile è la fase di codifica a monte, cioè la ricerca diretta della cmap più il flusso di byte Identity-H.
  • Costo del subsetting CJK. I font CJK di grandi dimensioni vengono sottoposti a subsetting tramite un sottoprocesso isolato. Tale sottoprocesso dispone di un fallback nativo PHP e di un timeout di due secondi (ADR-008).

encodeText() esegue un singolo passaggio di ricerca diretta nella cmap sull’input. È lineare rispetto al numero di codepoint, O(n). Il budget è wall_ms: 2000, peak_mb: 128. Questo budget è il più alto dell’insieme perché i font CJK sono di grandi dimensioni e il loro subsetting rappresenta il costo dominante. ADR-008 isola tale operazione in modo che non possa bloccare il chiamante.

Un file di font CJK è un input binario non attendibile. Il parser rifiuta i percorsi stream-wrapper e i byte null. Il subsetting CJK viene eseguito in un sottoprocesso isolato senza stato ereditato (ADR-008). Convalidare la provenienza dei font forniti dagli utenti finali. Il contenuto di testo CJK viene sottoposto a rendering, non interpretato.

DichiarazioneSpecificaClausolareference_id
Per un font Type 0 Identity-H/Identity-V, la stringa visualizzata è composta da coppie di byte che indicizzano il CIDFont.ISO 32000-2iso32000_2_sec9#x1.x49.p90
L’array W2 fornisce le metriche di scrittura verticale per glifo e si applica solo ai CIDFont utilizzati per la scrittura verticale.ISO 32000-2iso32000_2_sec9#x1.x44.p23
L’array DW2 fornisce le metriche di scrittura verticale predefinite per un CIDFont.ISO 32000-2iso32000_2_sec9#x1.x44.p22

Questa ricetta dimostra che la facade di codifica CJK cmap-aware è raggiungibile da userland (Fase 1). Non rivendica, per il file prodotto, né output di scrittura verticale né conformità PDF/UA-2 / PDF/A-4. L’emissione lato writer di /ToUnicode e delle metriche verticali (Fasi 3 e 4) è in sospeso e, allo stato attuale, un checker non convaliderebbe questo output.

Non applicabile.