Pular para o conteúdo
getnextpdf.com

Garanta cópia e colagem corretas em PDFs com CJK e árabe

Crie um arquivo Portable Document Format (PDF) com texto em chinês, japonês, coreano (CJK) e árabe que seja copiado e colado como os caracteres lógicos originais. O motor mapeia cada glifo para Unicode por meio de um CMap /ToUnicode, canonicaliza o resultado com Normalization Form Compatibility Composition (NFKC) e envolve as sequências moldadas ou com espaçamento entre letras em um /Span que carrega /ActualText. Registre as fontes e escreva o conteúdo; a extração continuará correta. Verifique a extração com pdftotext/Poppler, não com PyMuPDF; o veraPDF valida PDF/Universal Accessibility 2 (PDF/UA-2), não a extração.

Terminal window
composer require nextpdf/core

Registre uma fonte CJK, como a Noto Sans CJK, e uma fonte com suporte a árabe cujo mapa de caracteres cubra o bloco Arabic Presentation Forms-B, como a Noto Naskh Arabic. Incorpore apenas fontes que você tenha licença para incorporar.

Os códigos de glifo em um stream de conteúdo não são Unicode. Um CMap /ToUnicode mapeia cada código de volta para Unicode para que um leitor consiga extrair o texto (ISO 32000-2 §9.10). O motor canonicaliza esses valores com Normalization Form Compatibility Composition (NFKC), conforme definido pelo Unicode UAX #15. Tanto um ideograma de compatibilidade CJK quanto uma forma de apresentação árabe são mapeados para seu caractere base; assim, a busca e a cópia retornam o texto canônico em vez de um ponto de código de compatibilidade.

Dois casos precisam de mais do que /ToUnicode: latim com espaçamento entre letras e árabe moldado da direita para a esquerda. O motor os desenha como glifos espaçados ou reordenados; por isso, só a ordem dos glifos não basta para recuperar a string lógica. Ele envolve cada uma dessas sequências em conteúdo marcado /Span que carrega /ActualText, uma substituição exata para o conteúdo contido (ISO 32000-2 §14.9). Os extratores que honram /ActualText retornam a string lógica.

SímboloLocalizaçãoFunção
FontRegistry::register(string $fontFile, string $alias = ''): FontInfoNextPDF\Typography\FontRegistryRegistra as faces CJK e árabe.
DocumentFactory::create(): DocumentNextPDF\Core\DocumentFactoryConstrói um documento que usa o seu registro.
Document::writeHtml(string $html): staticNextPDF\Core\Concerns\HasTextOutputRenderiza conteúdo multilíngue.
<?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.

Este exemplo autossuficiente marca o documento, adiciona um título com espaçamento entre letras e grava no caminho usado pelo harness.

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

Execute pdftotext sobre a saída. O título com espaçamento entre letras é extraído como CODE WORD sem espaços inseridos, a linha CJK é extraída como os respectivos caracteres base e a linha árabe é extraída como a string lógica.

  • Verifique a extração com pdftotext, não com PyMuPDF. O modo de texto bruto do PyMuPDF ignora o /ActualText inline e retorna os glifos visuais, então ele pode indicar uma falha onde não há uma. O Poppler (pdftotext) honra /ActualText; o veraPDF valida PDF/UA-2, não a extração.
  • A extração precisa de /ToUnicode. Registre e incorpore as fontes para que o escritor emita o CMap /ToUnicode. Uma fonte não incorporada e não padrão não consegue garantir um mapeamento Unicode.
  • /ActualText cobre sequências moldadas e com espaçamento entre letras. Para texto simples, não moldado e sem espaçamento, /ToUnicode sozinho é extraído corretamente; o invólucro /Span preserva as sequências espaçadas ou reordenadas.
  • As tabelas HTML marcadas passam nas verificações de PDF/UA-2. A extração é correta, e uma <table> HTML marcada agora passa no veraPDF --flavour ua2 com zero falhas — consulte Acessibilidade.

Construir o CMap /ToUnicode e os invólucros /Span escala linearmente com a contagem de glifos. Esta receita tem orçamento de wall_ms: 1500, peak_mb: 96.

Valide o comprimento das strings multilíngues fornecidas pelo usuário para que o tamanho da saída permaneça limitado. O construtor de /ToUnicode rejeita metades de substitutos (surrogate halves) e códigos fora do espaço de código; assim, um mapa malformado não consegue criar um recurso de extração corrompido. O motor não executa scripts e não busca recursos remotos para fontes locais.

DeclaraçãoEspecificaçãoCláusula
Um CMap /ToUnicode mapeia códigos de caractere para Unicode na extração.ISO 32000-2§9.10
/ActualText é uma substituição exata para o conteúdo contido.ISO 32000-2§14.9
NFKC é a decomposição de compatibilidade seguida da composição canônica.Unicode UAX #15§1.2

Não aplicável.