Make CJK and Arabic PDFs copy-paste correct
At a glance
Section titled “At a glance”Create a Portable Document Format (PDF) file with Chinese, Japanese, Korean (CJK), and Arabic text that copies and pastes as the original logical characters. The engine maps every glyph to Unicode through a /ToUnicode CMap, canonicalizes the result with Normalization Form Compatibility Composition (NFKC), and wraps shaped or letter-spaced runs in a /Span carrying /ActualText. Register the fonts and write the content; extraction stays correct. Verify extraction with pdftotext/Poppler, not PyMuPDF; veraPDF validates PDF/Universal Accessibility 2 (PDF/UA-2), not extraction.
Install
Section titled “Install”composer require nextpdf/coreRegister a CJK font, such as Noto Sans CJK, and an Arabic-capable font whose character map covers the Arabic Presentation Forms-B block, such as Noto Naskh Arabic. Embed only fonts that you are licensed to embed.
Conceptual overview
Section titled “Conceptual overview”Glyph codes in a content stream are not Unicode. A /ToUnicode CMap maps each code back to Unicode so a reader can extract text (ISO 32000-2 §9.10). The engine canonicalizes those values with Normalization Form Compatibility Composition (NFKC), as defined by Unicode UAX #15. A CJK Compatibility Ideograph and an Arabic presentation form map to their base character, so search and copy return canonical text instead of a compatibility code point.
Two cases need more than /ToUnicode: letter-spaced Latin and shaped right-to-left Arabic. The engine draws them as spaced or reordered glyphs, so glyph order alone cannot recover the logical string. It wraps each run in a /Span marked-content sequence carrying /ActualText, an exact replacement for the enclosed content (ISO 32000-2 §14.9). Extractors that honor /ActualText return the logical string.
API surface
Section titled “API surface”| Symbol | Location | Role |
|---|---|---|
FontRegistry::register(string $fontFile, string $alias = ''): FontInfo | NextPDF\Typography\FontRegistry | Register the CJK and Arabic faces. |
DocumentFactory::create(): Document | NextPDF\Core\DocumentFactory | Build a document that uses your registry. |
Document::writeHtml(string $html): static | NextPDF\Core\Concerns\HasTextOutput | Render multilingual content. |
Code sample — Quick start
Section titled “Code sample — Quick start”<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\DocumentFactory;use NextPDF\Graphics\ImageRegistry;use NextPDF\Typography\FontRegistry;
$fonts = new FontRegistry();$fonts->register(__DIR__ . '/NotoSansCJK-Regular.ttf', alias: 'CJK');$fonts->register(__DIR__ . '/NotoNaskhArabic-Regular.ttf', alias: 'Arabic');
$doc = (new DocumentFactory($fonts, new ImageRegistry(maxCacheBytes: 0)))->create();$doc->addPage();$doc->writeHtml( '<p style="font-family: \'CJK\';">PDF 2.0 引擎 — 量子</p>' . '<p style="direction: rtl; font-family: \'Arabic\';">فاتورة</p>');$doc->save(__DIR__ . '/multilingual.pdf');pdftotext multilingual.pdf - | head# Extracts the logical text: "PDF 2.0 引擎 — 量子" and the logical Arabic "فاتورة",# not compatibility code points or reversed presentation forms.Code sample — Production
Section titled “Code sample — Production”This self-contained example tags the document, adds a letter-spaced heading, and writes to the harness path.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\DocumentFactory;use NextPDF\Graphics\ImageRegistry;use NextPDF\Typography\FontRegistry;
$fonts = new FontRegistry();$fonts->register(__DIR__ . '/NotoSansCJK-Regular.ttf', alias: 'CJK');$fonts->register(__DIR__ . '/NotoNaskhArabic-Regular.ttf', alias: 'Arabic');
$doc = (new DocumentFactory($fonts, new ImageRegistry(maxCacheBytes: 0)))->create();$doc->setTitle('Multilingual extraction');$doc->enableTaggedPdf('en');$doc->addPage();
$html = <<<'HTML'<h1 style="font-family: 'CJK'; letter-spacing: 3px;">CODE WORD</h1><p style="font-family: 'CJK';">中文 · 日本語 · 한국어 · 量子 (compatibility ideograph)</p><p style="direction: rtl; font-family: 'Arabic';">المبلغ الإجمالي 380.00</p>HTML;
$doc->writeHtml($html);
$out = getenv('NEXTPDF_OUT');$doc->save($out !== false ? $out : __DIR__ . '/multilingual-copy-paste-extraction.pdf');
echo "Wrote the multilingual PDF\n";Run pdftotext on the output. The letter-spaced heading extracts as CODE WORD with no inserted spaces, the CJK line extracts as its base characters, and the Arabic line extracts as the logical string.
Edge cases & gotchas
Section titled “Edge cases & gotchas”- Verify extraction with pdftotext, not PyMuPDF. PyMuPDF’s raw text mode ignores inline
/ActualTextand returns visual glyphs, so it can under-report correctness. Poppler (pdftotext) honors/ActualText; veraPDF validates PDF/UA-2, not extraction. - Extraction needs
/ToUnicode. Register and embed fonts so the writer emits the/ToUnicodeCMap. A non-embedded, non-standard font cannot guarantee a Unicode mapping. /ActualTextcovers shaped and letter-spaced runs. For plain, unshaped, unspaced text,/ToUnicodealone extracts correctly; the/Spanwrapper preserves spaced or reordered runs.- Tagged HTML tables pass PDF/UA-2 checks. Extraction is correct, and a tagged HTML
<table>now passesveraPDF --flavour ua2with zero failures — see Accessibility.
Performance
Section titled “Performance”Building the /ToUnicode CMap and /Span wrappers scales linearly with glyph count. This recipe budgets wall_ms: 1500, peak_mb: 96.
Security notes
Section titled “Security notes”Validate the length of user-supplied multilingual strings so output size stays bounded. The /ToUnicode builder rejects surrogate halves and out-of-codespace codes, so a malformed map cannot create a corrupt extraction resource. The engine runs no scripts and fetches no remote resources for local fonts.
Conformance
Section titled “Conformance”| Statement | Spec | Clause |
|---|---|---|
A /ToUnicode CMap maps character codes to Unicode for extraction. | ISO 32000-2 | §9.10 |
/ActualText is an exact replacement for the enclosed content. | ISO 32000-2 | §14.9 |
| NFKC is compatibility decomposition followed by canonical composition. | Unicode UAX #15 | §1.2 |
Commercial context
Section titled “Commercial context”Not applicable.
See also
Section titled “See also”- Extract text content — the foundation for tagged extraction.
- Render right-to-left Arabic HTML — Arabic shaping and right-to-left text.
- Typography —
/ToUnicodeand NFKC canonicalization. - Accessibility —
/ActualTextand tagged-table support.