コンテンツにスキップ
getnextpdf.com

CJK およびアラビア語の PDF をコピー&ペーストできるようにする

中国語・日本語・韓国語(CJK)およびアラビア語のテキストを、元の論理的な文字列としてコピー&ペーストできる Portable Document Format (PDF) ファイルを作成します。エンジンは、/ToUnicode CMap によってすべてのグリフを Unicode へマッピングし、その結果を Normalization Form Compatibility Composition (NFKC) で正規化したうえで、シェーピングされたランやレタースペーシングされたランを、/ActualText を保持する /Span でラップします。フォントを登録してからコンテンツを書き込めば、抽出結果は正しく保たれます。抽出の検証は PyMuPDF ではなく pdftotext/Poppler で行ってください。veraPDF は PDF/Universal Accessibility 2 (PDF/UA-2) を検証するものであり、抽出を検証するものではありません。

Terminal window
composer require nextpdf/core

CJK フォント(Noto Sans CJK など)と、文字マップがアラビア語 Presentation Forms-B ブロックをカバーするアラビア語対応フォント(Noto Naskh Arabic など)を登録します。埋め込みがライセンスで許可されているフォントのみを埋め込んでください。

コンテンツストリーム内のグリフコードは Unicode ではありません。/ToUnicode CMap は、リーダーがテキストを抽出できるよう、各コードを Unicode へマッピングします(ISO 32000-2 §9.10)。エンジンは、Unicode UAX #15 で定義されている Normalization Form Compatibility Composition (NFKC) によって、それらの値を正規化します。CJK 互換漢字とアラビア語の表示形は基底文字へマッピングされるため、検索やコピーでは互換用コードポイントではなく正規化されたテキストが返されます。

/ToUnicode だけでは不十分なケースが 2 つあります。レタースペーシングされたラテン文字と、シェーピングされた右から左へのアラビア語です。エンジンはこれらを、スペースを空けたり並べ替えたりしたグリフとして描画するため、グリフの順序だけでは論理的な文字列を復元できません。エンジンは各ランを、囲まれたコンテンツの正確な置換である /ActualText を保持する /Span マークコンテンツシーケンスでラップします(ISO 32000-2 §14.9)。/ActualText を尊重する抽出ツールは、論理的な文字列を返します。

シンボル場所役割
FontRegistry::register(string $fontFile, string $alias = ''): FontInfoNextPDF\Typography\FontRegistryCJK およびアラビア語の書体の登録
DocumentFactory::create(): DocumentNextPDF\Core\DocumentFactoryレジストリを使用するドキュメントの構築
Document::writeHtml(string $html): staticNextPDF\Core\Concerns\HasTextOutput多言語コンテンツのレンダリング
<?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');
Terminal window
pdftotext multilingual.pdf - | head
# Extracts the logical text: "PDF 2.0 引擎 — 量子" and the logical Arabic "فاتورة",
# not compatibility code points or reversed presentation forms.

この自己完結したサンプルでは、ドキュメントにタグを付け、レタースペーシングされた見出しを追加し、ハーネスのパスへ書き込みます。

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

出力ファイルに対して pdftotext を実行します。レタースペーシングされた見出しはスペースが挿入されずに CODE WORD として抽出され、CJK 行は基底文字として抽出され、アラビア語の行は論理的な文字列として抽出されます。

  • 抽出の検証は PyMuPDF ではなく pdftotext で行ってください。 PyMuPDF の生テキストモードはインラインの /ActualText を無視し、視覚的なグリフを返すため、正しさを過小に報告することがあります。Poppler(pdftotext)は /ActualText を尊重します。veraPDF は PDF/UA-2 を検証するものであり、抽出を検証するものではありません。
  • 抽出には /ToUnicode が必要です。 ライターが /ToUnicode CMap を出力するよう、フォントを登録して埋め込んでください。埋め込まれていない非標準フォントでは、Unicode マッピングを保証できません。
  • /ActualText は、シェーピングされたランとレタースペーシングされたランをカバーします。 プレーンでシェーピングもスペーシングもされていないテキストは、/ToUnicode だけで正しく抽出されます。/Span のラッパーは、スペースが空けられたランや並べ替えられたランを保持します。
  • タグ付き HTML テーブルは PDF/UA-2 チェックを通過します。 抽出は正しく行われ、タグ付き HTML の <table> は、いまや veraPDF --flavour ua2 を失敗ゼロで通過します。アクセシビリティを参照してください。

/ToUnicode CMap と /Span ラッパーの構築は、グリフ数に比例して線形にスケールします。このレシピのバジェットは wall_ms: 1500, peak_mb: 96 です。

ユーザー指定の多言語文字列は長さを検証し、出力サイズを抑えてください。/ToUnicode ビルダーはサロゲートの片割れやコードスペース外のコードを拒否するため、不正なマップから破損した抽出リソースが生成されることはありません。エンジンはスクリプトを実行せず、ローカルフォントについてリモートリソースを取得することもありません。

記述仕様条項
抽出のための、文字コードから Unicode への /ToUnicode CMap によるマッピングISO 32000-2§9.10
囲まれたコンテンツの正確な置換である /ActualTextISO 32000-2§14.9
互換分解の後に正準合成を行う NFKCUnicode UAX #15§1.2

該当なし。