Ir al contenido
getnextpdf.com

Hacer que los PDF con CJK y árabe se copien y peguen correctamente

Crear un archivo en formato de documento portátil (PDF) con texto en chino, japonés, coreano (CJK) y árabe que se copie y pegue como los caracteres lógicos originales. El motor asigna cada glifo a Unicode mediante un CMap /ToUnicode, canonicaliza el resultado con la forma de normalización de composición de compatibilidad (NFKC) y envuelve las secuencias modeladas o con espaciado entre letras en un /Span que lleva /ActualText. Al registrar las fuentes y escribir el contenido, la extracción se mantiene correcta. Verificar la extracción con pdftotext/Poppler, no con PyMuPDF; veraPDF valida la accesibilidad universal de PDF 2 (PDF/UA-2), no la extracción.

Ventana de terminal
composer require nextpdf/core

Registrar una fuente CJK, como Noto Sans CJK, y una fuente compatible con el árabe cuyo mapa de caracteres cubra el bloque Arabic Presentation Forms-B, como Noto Naskh Arabic. Incrustar únicamente fuentes con licencia para ello.

Los códigos de glifo de un flujo de contenido no son Unicode. Un CMap /ToUnicode vuelve a asignar cada código a Unicode para que un lector pueda extraer el texto (ISO 32000-2 §9.10). El motor canonicaliza esos valores con la forma de normalización de composición de compatibilidad (NFKC), tal como la define Unicode UAX #15. Un ideograma de compatibilidad CJK y una forma de presentación árabe se asignan a su carácter base, de modo que la búsqueda y la copia devuelven texto canónico en lugar de un punto de código de compatibilidad.

Dos casos necesitan algo más que /ToUnicode: el latín con espaciado entre letras y el árabe modelado de derecha a izquierda. El motor los dibuja como glifos espaciados o reordenados, por lo que el orden de los glifos por sí solo no puede recuperar la cadena lógica. Envuelve cada secuencia en contenido marcado /Span con /ActualText, un reemplazo exacto del contenido que encierra (ISO 32000-2 §14.9). Los extractores que respetan /ActualText devuelven la cadena lógica.

SímboloUbicaciónFunción
FontRegistry::register(string $fontFile, string $alias = ''): FontInfoNextPDF\Typography\FontRegistryRegistra los tipos de letra CJK y árabe.
DocumentFactory::create(): DocumentNextPDF\Core\DocumentFactoryConstruye un documento que usa tu registro.
Document::writeHtml(string $html): staticNextPDF\Core\Concerns\HasTextOutputRepresenta el contenido multilingüe.
<?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');
Ventana 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.

Este ejemplo independiente etiqueta el documento, añade un encabezado con espaciado entre letras y escribe en la ruta indicada por el arnés.

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

Ejecutar pdftotext sobre la salida. El encabezado con espaciado entre letras se extrae como CODE WORD sin espacios insertados; la línea CJK se extrae como sus caracteres base y la línea árabe se extrae como la cadena lógica.

  • Verificar la extracción con pdftotext, no con PyMuPDF. El modo de texto sin procesar de PyMuPDF ignora el /ActualText en línea y devuelve glifos visuales, por lo que puede infravalorar la corrección. Poppler (pdftotext) respeta /ActualText; veraPDF valida PDF/UA-2, no la extracción.
  • La extracción necesita /ToUnicode. Registrar e incrustar las fuentes para que el escritor emita el CMap /ToUnicode. Una fuente no estándar que no se incrusta no puede garantizar una asignación a Unicode.
  • /ActualText cubre las secuencias modeladas y con espaciado entre letras. El texto simple, sin modelar y sin espaciado, se extrae correctamente solo con /ToUnicode; el envoltorio /Span preserva las secuencias espaciadas o reordenadas.
  • Las tablas HTML etiquetadas pasan las comprobaciones de PDF/UA-2. La extracción es correcta, y una <table> HTML etiquetada ahora pasa veraPDF --flavour ua2 sin ningún fallo — véase Accesibilidad.

La construcción del CMap /ToUnicode y de los envoltorios /Span escala linealmente con el número de glifos. Esta receta usa como presupuesto wall_ms: 1500, peak_mb: 96.

Validar la longitud de las cadenas multilingües proporcionadas por el usuario para que el tamaño de la salida se mantenga acotado. El constructor de /ToUnicode rechaza las mitades de pares suplentes y los códigos fuera del espacio de códigos, de modo que un mapa malformado no pueda crear un recurso de extracción corrupto. El motor no ejecuta scripts ni obtiene recursos remotos para las fuentes locales.

AfirmaciónEspecificaciónCláusula
Un CMap /ToUnicode asigna códigos de carácter a Unicode para la extracción.ISO 32000-2§9.10
/ActualText es un reemplazo exacto del contenido que encierra.ISO 32000-2§14.9
NFKC es la descomposición de compatibilidad seguida de la composición canónica.Unicode UAX #15§1.2

No aplicable.