Aller au contenu

Composer du texte CJK avec un encodage tenant compte de la cmap

Ce recipe enregistre une fonte CJK TrueType, puis encode du texte en chinois traditionnel au moyen de la façade FontInfo::encodeText(), qui tient compte de la cmap. La façade produit un flux d’octets Identity-H fondé sur des CID de deux octets. Ce recipe suit examples/35-cjk-cmap-demo.php. Lis la note de portée ci-dessous avant de t’appuyer dessus.

L’architecture d’encodage de texte tenant compte de la cmap est livrée par phases (ADR-013). La phase 1 est livrée : la façade FontInfo::encodeText() et la stratégie d’encodage tenant compte de la cmap sont câblées et accessibles depuis l’espace utilisateur. La phase 2 est en cours : elle fait passer le renderer et le writer par la façade. Les phases 3 et 4 sont en attente : l’émission par fonte de /ToUnicode, /CIDSystemInfo, /Encoding et /CIDToGIDMap, ainsi que le résolveur de fonte de substitution, ne sont pas encore câblés dans le writer.

Organise-toi en tenant compte de ces conséquences :

  • Ce recipe illustre la façade d’encodage, et non un mode d’écriture verticale prêt à l’emploi. La surface du document n’expose aujourd’hui aucune API publique de mode d’écriture, c’est-à-dire aucun setWritingMode et aucun setter vertical-rl.
  • L’exemple sous-jacent est, selon son propre en-tête, un test de fumée d’intégration, pas une fixture de conformité. La validation PDF/UA-2 et PDF/A-4 va régresser pour une sortie produite de cette façon, tant que les phases 3 et 4 n’ont pas été livrées. N’affirme pas que la sortie issue de ce chemin est conforme. La conformité relève du vérificateur, et il ne validera pas encore cette sortie.
  • L’infrastructure de métriques d’écriture verticale existe, mais elle est interne. Elle se compose de l’objet valeur CjkVerticalMetrics et des émetteurs /W2 et /DW2. NextPDF ne l’expose pas comme un appel « écrire verticalement » dans l’espace utilisateur, et le writer n’émet pas encore les dictionnaires correspondants.
Fenêtre de terminal
composer require nextpdf/core:^3

La contrainte correspond au package nextpdf/core. L’exemple s’exécute sous PHP 8.4. Une fixture de test Noto Sans TC fournie permet au recipe de rester autonome.

ISO 32000-2 modélise l’émission de texte en trois couches : le point de code Unicode, le code de caractère et l’identifiant de glyphe. Pour une fonte CJK TrueType, le moteur utilise une fonte Type 0 composite avec l’encodage Identity-H. Avec cet encodage, la chaîne affichée est une suite de paires d’octets qui indexent la CIDFont (ISO 32000-2).

FontRegistry::register() analyse la fonte. FontInfo::encodeText($unicodeText) résout ensuite une stratégie d’encodage via FontEncodingStrategyResolver. Pour une fonte CJK TrueType enregistrée, il aiguille vers TrueTypeCmapStrategy. Le EncodedGlyphRun renvoyé contient le flux d’octets Identity-H, l’opérande de chaîne PDF, les largeurs d’avance par glyphe, les points de code utilisés et la table GID→Unicode. Le sous-ensemblage CJK utilise les points de code utilisés selon l’ADR-008. Un futur flux /ToUnicode utilisera la table GID→Unicode. Le mode sélectionné est EncodingMode::TwoByteCid.

Deux structures de CIDFont définissent l’écriture verticale en PDF. La première est le tableau de métriques verticales par glyphe /W2 (ISO 32000-2). La seconde est l’ensemble de métriques verticales par défaut /DW2 (ISO 32000-2). NextPDF possède l’objet valeur et les émetteurs pour les deux, via CjkVerticalMetrics::toW2Array(), toW2RangeArray() et toDw2Array(). Ces éléments restent internes, et le writer ne les émet pas encore. Voir la note de portée.

  • FontRegistry::register(string $fontFile, string $alias = '', int $fontIndex = 0): FontInfoNextPDF\Typography\FontRegistry.
  • FontInfo::encodeText(string $unicodeText): EncodedGlyphRunNextPDF\Typography\FontInfo. La façade de la phase 1.
  • EncodedGlyphRunNextPDF\Typography\Encoding\EncodedGlyphRun (byteStream, pdfStringOperand, mode, advanceWidths, toUnicodeMap, usedCodepoints, glyphCount()).
  • EncodingModeNextPDF\Typography\Encoding\EncodingMode (SingleByte, TwoByteCid).
  • CjkVerticalMetricsNextPDF\Typography\CjkVerticalMetrics. Objet valeur interne de métriques verticales. Il est documenté par transparence, pas comme un chemin d’écriture dans l’espace utilisateur.

Le tableau PHPDoc complet est généré depuis le code source.

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

Cet exemple est autonome et exécutable par le harnais. Il reflète examples/35-cjk-cmap-demo.php. Enregistre d’abord la fixture Noto Sans TC fournie. Puis confirme que la façade tenant compte de la cmap est accessible. Effectue ensuite le rendu via DocumentFactory pour que le registre prérempli soit bien utilisé.

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

Sortie STDOUT attendue :

Wrote cjk-vertical-writing.pdf (Phase 1+2 dry-run; not a conformance fixture)
  • Pas une fixture de conformité. D’après l’en-tête même de l’exemple sous-jacent, cette sortie est un test de fumée d’intégration. Les contrôles PDF/UA-2 et PDF/A-4 régressent pour elle tant que les phases 3 et 4 n’ont pas été livrées. Ne l’enregistre pas comme golden de conformité.
  • Pas d’API de mode d’écriture. Aucun appel public ne bascule vers l’écriture verticale, ce qui couvrirait vertical-rl et vertical-lr. Les émetteurs /W2 et /DW2 existent en interne. Ils ne sont ni exposés ni encore écrits dans le dictionnaire de fonte.
  • Propriété du registre. Document::createStandalone() construit son propre registre. Utilise DocumentFactory pour que le document utilise le registre que tu as rempli avec la fonte CJK.
  • Chemin final du flux d’octets. Tant que la phase 2 n’est pas terminée, le flux de contenu visible passe encore par le chemin de texte historique. La partie démontrée et accessible aujourd’hui est l’étape d’encodage en amont, c’est-à-dire la recherche directe dans la cmap et le flux d’octets Identity-H.
  • Coût du sous-ensemblage CJK. Les grandes fontes CJK sont sous-ensemblées dans un sous-processus isolé. Ce sous-processus dispose d’un repli natif en PHP et d’un délai d’attente de deux secondes (ADR-008).

encodeText() effectue une seule passe de recherche directe dans la cmap sur l’entrée. Sa complexité est linéaire en nombre de points de code, en O(n). Le budget est wall_ms: 2000, peak_mb: 128. Ce budget est le plus élevé de cet ensemble, car les fontes CJK sont grandes et leur sous-ensemblage est le coût dominant. L’ADR-008 isole ce travail pour qu’il ne puisse pas bloquer l’appelant.

Un fichier de fonte CJK est une entrée binaire non fiable. L’analyseur rejette les chemins utilisant un wrapper de flux et les octets nuls. Le sous-ensemblage CJK s’exécute dans un sous-processus isolé sans état hérité (ADR-008). Valide la provenance des fontes fournies par l’utilisateur final. Le contenu textuel CJK est rendu, pas interprété.

ÉnoncéSpécificationClausereference_id
Pour une fonte Type 0 Identity-H/Identity-V, la chaîne affichée est une suite de paires d’octets qui indexent la CIDFont.ISO 32000-2iso32000_2_sec9#x1.x49.p90
Le tableau W2 donne les métriques d’écriture verticale par glyphe et ne s’applique qu’aux CIDFonts utilisées pour l’écriture verticale.ISO 32000-2iso32000_2_sec9#x1.x44.p23
Le tableau DW2 donne les métriques d’écriture verticale par défaut d’une CIDFont.ISO 32000-2iso32000_2_sec9#x1.x44.p22

Ce recipe montre que la façade d’encodage CJK tenant compte de la cmap est accessible depuis l’espace utilisateur (phase 1). Il ne prétend pas que le fichier produit fournit une sortie en écriture verticale ni une conformité PDF/UA-2 / PDF/A-4. L’émission par le writer de /ToUnicode et des métriques verticales (phases 3 et 4) est en attente, et un vérificateur ne validerait pas cette sortie aujourd’hui.

Sans objet.