Aller au contenu
getnextpdf.com

Produire des PDF CJK et arabes fiables au copier-coller

Crée un fichier PDF (Portable Document Format) dont le texte chinois, japonais, coréen (CJK) et arabe se copie-colle sous forme de caractères logiques d’origine. Le moteur associe chaque glyphe à l’Unicode via une CMap /ToUnicode, canonicalise le résultat avec la forme de normalisation de composition de compatibilité (NFKC), et enveloppe les séquences mises en forme ou espacées par lettres dans un /Span doté d’un /ActualText. Enregistre les polices et écris le contenu ; l’extraction reste correcte. Vérifie l’extraction avec pdftotext/Poppler, pas avec PyMuPDF ; veraPDF valide PDF/Universal Accessibility 2 (PDF/UA-2), pas l’extraction.

Fenêtre de terminal
composer require nextpdf/core

Enregistre une police CJK, par exemple Noto Sans CJK, ainsi qu’une police compatible avec l’arabe dont la table de caractères couvre le bloc Arabic Presentation Forms-B, par exemple Noto Naskh Arabic. N’incorpore que les polices que tu es autorisé à incorporer.

Les codes de glyphes d’un flux de contenu ne sont pas de l’Unicode. Une CMap /ToUnicode associe chaque code à l’Unicode, afin qu’un lecteur puisse extraire le texte (ISO 32000-2 §9.10). Le moteur canonicalise ces valeurs avec la forme de normalisation de composition de compatibilité (NFKC), telle que définie par Unicode UAX #15. Un idéogramme de compatibilité CJK et une forme de présentation arabe sont ramenés à leur caractère de base, si bien que la recherche et la copie renvoient le texte canonique au lieu d’un point de code de compatibilité.

Deux cas nécessitent plus que /ToUnicode : le latin espacé par lettres et l’arabe de droite à gauche mis en forme. Le moteur les dessine comme des glyphes espacés ou réordonnés ; l’ordre des glyphes seul ne suffit donc pas à retrouver la chaîne logique. Il enveloppe chaque séquence dans une séquence de contenu marqué /Span porteuse d’un /ActualText, un remplacement exact du contenu englobé (ISO 32000-2 §14.9). Les outils d’extraction qui respectent /ActualText renvoient la chaîne logique.

SymboleEmplacementRôle
FontRegistry::register(string $fontFile, string $alias = ''): FontInfoNextPDF\Typography\FontRegistryEnregistre les polices CJK et arabe.
DocumentFactory::create(): DocumentNextPDF\Core\DocumentFactoryConstruit un document qui utilise ton registre.
Document::writeHtml(string $html): staticNextPDF\Core\Concerns\HasTextOutputRend le contenu multilingue.
<?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');
Fenêtre de terminal
pdftotext multilingual.pdf - | head
# Extracts the logical text: "PDF 2.0 引擎 — 量子" and the logical Arabic "فاتورة",
# not compatibility code points or reversed presentation forms.

Cet exemple autonome balise le document, ajoute un titre espacé par lettres et écrit la sortie vers le chemin fourni par le harnais.

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

Exécute pdftotext sur le fichier de sortie. Le titre espacé par lettres s’extrait en CODE WORD sans espaces insérés, la ligne CJK s’extrait avec ses caractères de base, et la ligne arabe s’extrait sous forme de chaîne logique.

  • Vérifie l’extraction avec pdftotext, pas avec PyMuPDF. Le mode texte brut de PyMuPDF ignore /ActualText en ligne et renvoie les glyphes visuels, ce qui peut sous-évaluer l’exactitude. Poppler (pdftotext) respecte /ActualText ; veraPDF valide PDF/UA-2, pas l’extraction.
  • L’extraction a besoin de /ToUnicode. Enregistre et incorpore les polices pour que le moteur d’écriture émette la CMap /ToUnicode. Une police non incorporée et non standard ne permet pas de garantir une correspondance Unicode.
  • /ActualText couvre les séquences mises en forme et espacées par lettres. Pour du texte simple, non mis en forme et non espacé, l’extraction fonctionne correctement avec /ToUnicode seul ; l’enveloppe /Span préserve les séquences espacées ou réordonnées.
  • Les tableaux HTML balisés passent les contrôles PDF/UA-2. L’extraction est correcte, et un <table> HTML balisé passe désormais veraPDF --flavour ua2 sans aucun échec ; consulte Accessibilité.

La construction de la CMap /ToUnicode et des enveloppes /Span évolue linéairement avec le nombre de glyphes. Le budget de cette recette est wall_ms: 1500, peak_mb: 96.

Valide la longueur des chaînes multilingues fournies par l’utilisateur afin de garder la taille de sortie bornée. Le constructeur de /ToUnicode rejette les demi-surrogates et les codes hors de l’espace de codes ; une correspondance malformée ne peut donc pas créer une ressource d’extraction corrompue. Le moteur n’exécute aucun script et ne récupère aucune ressource distante pour les polices locales.

ÉnoncéSpécificationClause
Une CMap /ToUnicode associe les codes de caractères à l’Unicode pour l’extraction.ISO 32000-2§9.10
/ActualText est un remplacement exact du contenu englobé.ISO 32000-2§14.9
NFKC est une décomposition de compatibilité suivie d’une composition canonique.Unicode UAX #15§1.2

Sans objet.