Przejdź do głównej zawartości

Składaj tekst CJK z użyciem kodowania uwzględniającego cmap

Ten przepis rejestruje krój TrueType obsługujący języki chiński, japoński i koreański (CJK), a następnie koduje tekst w języku chińskim tradycyjnym przez fasadę FontInfo::encodeText() uwzględniającą cmap. Fasada zwraca strumień bajtów Identity-H z dwubajtowymi CID. Przepis wzoruje się na examples/35-cjk-cmap-demo.php. Zanim oprzesz na nim pracę, przeczytaj uwagę o zakresie.

Architektura kodowania tekstu uwzględniająca cmap jest wdrażana etapami (ADR-013). Etap 1 jest gotowy: fasada FontInfo::encodeText() oraz strategia kodowania uwzględniająca cmap są podłączone i dostępne z poziomu kodu użytkownika. Etap 2 jest w toku: obejmuje skierowanie renderer i writer przez fasadę. Etapy 3 i 4 są w przygotowaniu: emisja dla poszczególnych czcionek /ToUnicode, /CIDSystemInfo, /Encoding oraz /CIDToGIDMap, a także resolver czcionek zastępczych, nie są jeszcze podłączone do writera.

Planuj z uwzględnieniem tych konsekwencji:

  • Ten przepis demonstruje fasadę kodowania, a nie kompletny tryb pisma pionowego. Powierzchnia dokumentu nie udostępnia dziś publicznego API trybu pisma, więc nie ma wywołania setWritingMode ani settera vertical-rl.
  • Towarzyszący przykład jest, jak wskazuje jego własny nagłówek, integracyjnym testem dymnym, a nie fiksturą zgodności. Walidacja PDF/UA-2 i PDF/A-4 ulegnie regresji dla danych wyjściowych utworzonych w ten sposób, dopóki etapy 3 i 4 nie zostaną wdrożone. Nie deklaruj zgodności danych wyjściowych z tej ścieżki. O zgodności decyduje walidator, który obecnie nie zatwierdzi tych danych wyjściowych.
  • Infrastruktura metryk pisma pionowego istnieje, ale jest wewnętrzna. Obejmuje obiekt wartości CjkVerticalMetrics oraz emitery /W2 i /DW2. NextPDF nie udostępnia jej jako wywołania „pisz pionowo” na poziomie kodu użytkownika, a writer nie emituje jeszcze powiązanych słowników.
Okno terminala
composer require nextpdf/core:^3

To ograniczenie dotyczy pakietu nextpdf/core. Przykład działa na PHP 8.4. Dzięki dołączonej fiksturze testowej Noto Sans TC przepis jest samowystarczalny.

ISO 32000-2 modeluje emisję tekstu w trzech warstwach: punkt kodowy Unicode, kod znaku oraz identyfikator glifu. Dla kroju TrueType CJK silnik używa złożonej czcionki Type 0 z kodowaniem Identity-H. W tym kodowaniu wyświetlany ciąg składa się z par bajtów indeksujących CIDFont (ISO 32000-2).

FontRegistry::register() parsuje krój. FontInfo::encodeText($unicodeText) następnie wybiera strategię kodowania przez FontEncodingStrategyResolver. Dla zarejestrowanego kroju TrueType CJK przekazuje wywołanie do TrueTypeCmapStrategy. Zwracany EncodedGlyphRun zawiera strumień bajtów Identity-H, operand ciągu PDF, szerokości postępu dla poszczególnych glifów, użyte punkty kodowe oraz mapę GID→Unicode. Tworzenie podzbioru CJK używa punktów kodowych zgodnie z ADR-008. Przyszły strumień /ToUnicode użyje mapy GID→Unicode. Wybranym trybem jest EncodingMode::TwoByteCid.

Pismo pionowe w PDF definiują dwie struktury CIDFont. Pierwszą jest tablica metryk pionowych dla poszczególnych glifów /W2 (ISO 32000-2). Drugą są domyślne metryki pionowe /DW2 (ISO 32000-2). NextPDF dostarcza dla nich obiekt wartości oraz emitery przez CjkVerticalMetrics::toW2Array(), toW2RangeArray() oraz toDw2Array(). Te elementy są wewnętrzne, a writer jeszcze ich nie emituje. Zobacz uwagę o zakresie.

  • FontRegistry::register(string $fontFile, string $alias = '', int $fontIndex = 0): FontInfoNextPDF\Typography\FontRegistry.
  • FontInfo::encodeText(string $unicodeText): EncodedGlyphRunNextPDF\Typography\FontInfo. Fasada etapu 1.
  • EncodedGlyphRunNextPDF\Typography\Encoding\EncodedGlyphRun (byteStream, pdfStringOperand, mode, advanceWidths, toUnicodeMap, usedCodepoints, glyphCount()).
  • EncodingModeNextPDF\Typography\Encoding\EncodingMode (SingleByte, TwoByteCid).
  • CjkVerticalMetricsNextPDF\Typography\CjkVerticalMetrics. Wewnętrzny obiekt wartości metryk pionowych. Udokumentowano go dla przejrzystości, nie jako ścieżkę zapisu na poziomie kodu użytkownika.

Pełna tabela PHPDoc jest generowana ze źródła.

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

Ten przykład jest samowystarczalny i można go uruchomić w harnessie. Odzwierciedla examples/35-cjk-cmap-demo.php. Najpierw zarejestruj dołączoną fiksturę Noto Sans TC. Potem potwierdź, że fasada uwzględniająca cmap jest dostępna. Na końcu renderuj przez DocumentFactory, aby dokument korzystał z uzupełnionego rejestru.

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

Oczekiwany STDOUT:

Wrote cjk-vertical-writing.pdf (Phase 1+2 dry-run; not a conformance fixture)
  • To nie jest fikstura zgodności. Jak wskazuje własny nagłówek towarzyszącego przykładu, te dane wyjściowe są integracyjnym testem dymnym. Kontrole PDF/UA-2 i PDF/A-4 ulegają dla nich regresji, dopóki etapy 3 i 4 nie zostaną wdrożone. Nie rejestruj tego przykładu jako wzorca zgodności.
  • Brak API trybu pisma. Żadne publiczne wywołanie nie przełącza dokumentu na pismo pionowe obejmujące vertical-rl i vertical-lr. Emitery /W2 i /DW2 istnieją wewnętrznie. Nie są udostępnione i nie są jeszcze zapisywane do słownika czcionki.
  • Własność rejestru. Document::createStandalone() buduje własny rejestr. Użyj DocumentFactory, aby dokument korzystał z rejestru, który wypełniono krojem CJK.
  • Ścieżka końcowego strumienia bajtów. Dopóki etap 2 nie zostanie zamknięty, widoczny strumień treści nadal przechodzi przez starszą ścieżkę tekstu. Sprawdzoną, dostępną dziś częścią jest wcześniejszy krok kodowania: wyszukiwanie w przód w cmap oraz strumień bajtów Identity-H.
  • Koszt tworzenia podzbioru CJK. Duże kroje CJK tworzą podzbiór przez izolowany podproces. Ten podproces ma mechanizm awaryjny natywny dla PHP oraz dwusekundowy limit czasu (ADR-008).

encodeText() wykonuje pojedyncze przejście wyszukiwania w przód w cmap po danych wejściowych. Jest liniowy względem liczby punktów kodowych, O(n). Budżet wynosi wall_ms: 2000, peak_mb: 128. Ten budżet jest najwyższy w tym zestawie, ponieważ kroje CJK są duże, a tworzenie podzbioru jest dominującym kosztem. ADR-008 izoluje tę operację, aby nie mogła zablokować kodu wywołującego.

Plik czcionki CJK jest niezaufanym wejściem binarnym. Parser odrzuca ścieżki ze stream wrapperami oraz bajtami null. Tworzenie podzbioru CJK działa w izolowanym podprocesie bez dziedziczonego stanu (ADR-008). Sprawdzaj pochodzenie krojów dostarczanych przez użytkownika końcowego. Treść tekstowa CJK jest renderowana, a nie interpretowana.

StwierdzenieSpecyfikacjaKlauzulareference_id
Dla czcionki Type 0 z kodowaniem Identity-H/Identity-V wyświetlany ciąg składa się z par bajtów indeksujących CIDFont.ISO 32000-2iso32000_2_sec9#x1.x49.p90
Tablica W2 podaje metryki pisma pionowego dla poszczególnych glifów i dotyczy wyłącznie czcionek CIDFont używanych do pisma pionowego.ISO 32000-2iso32000_2_sec9#x1.x44.p23
Tablica DW2 podaje domyślne metryki pisma pionowego dla czcionki CIDFont.ISO 32000-2iso32000_2_sec9#x1.x44.p22

Ten przepis pokazuje, że fasada kodowania CJK uwzględniająca cmap jest dostępna z poziomu kodu użytkownika (etap 1). Nie twierdzi, że utworzony plik zawiera dane wyjściowe pisma pionowego ani że jest zgodny z PDF/UA-2 / PDF/A-4. Emisja /ToUnicode oraz metryk pionowych po stronie writera (etapy 3 i 4) jest w przygotowaniu, a walidator obecnie nie zatwierdziłby tych danych wyjściowych.

Nie dotyczy.