Pular para o conteúdo

Texto: integração de shaping, CJK e tratamento de runs

O módulo de texto define a fronteira de shaping. Ele expõe uma interface enxuta que transforma um run em 8-bit Unicode Transformation Format (UTF-8) em glifos posicionados, seleciona um backend OpenType real quando há um disponível, recorre a um fallback determinístico quando não há nenhum e fornece um registro para shapers específicos de script.

Terminal window
composer require nextpdf/core:^3

ShaperInterface conecta o pipeline de layout de texto a um motor de shaping OpenType. Ela é intencionalmente enxuta: um único método shape() consome um ShaperInput e retorna um ShapingResult. Esse tipo de retorno é a única saída visível para os consumidores. As implementações não devem vazar detalhes internos do motor de shaping, e o retorno tipado impõe essa fronteira. ShapingResult carrega a lista de registros GlyphRun, o texto de origem ecoado, o script e a direção, além de uma tag shaperImpl que identifica o backend que produziu o resultado.

A seleção de backend é explícita e informa a capacidade sem suposições. ShaperFactory executa uma única sondagem de capacidade. Se o host tiver um binding HarfBuzz funcional, create() retorna o shaper baseado no HarfBuzz. Caso contrário, retorna NullShaper. NullShaper é um fallback de passagem direta. Ele emite um glifo sintético por codepoint Unicode, com avanços zero e offsets zero. Ele marca o resultado para que a observabilidade possa detectar o fallback e deixa a resolução dos avanços para o módulo de métricas de fonte. Esse caminho é uma degradação documentada, não shaping completo. Substituição, ligaduras, posicionamento de marcas e formas contextuais exigem o backend real. wouldUseRealShaper() é um predicado de diagnóstico. Em código de produção, ramifique conforme a tag shaperImpl do resultado.

O shaping específico de script é uma interface de provedor de serviço (SPI), não uma implementação embutida. ScriptShaperRegistry é um registro no estilo PHP Standards Recommendation 11 (PSR-11) que resolve um MongolianShaperInterface ou TibetanShaperInterface pela tag de script International Organization for Standardization (ISO) 15924. O registro armazena as chaves sem distinguir maiúsculas de minúsculas e depende de uma única fonte de verdade para validar códigos de script. O registro e as interfaces de shapers por script formam um contrato congelado, de modo que uma extensão pode registrar um provedor da Phase-12 sem alterar os pontos de chamada. O motor entrega a fronteira. Os consumidores fornecem os provedores de scripts complexos.

O tratamento de runs de chinês, japonês e coreano (CJK) fica na fronteira de codificação da tipografia. Uma face TrueType CJK incorporada é emitida como uma fonte Type 0 com um CMap Identity-H e um descendente CIDFontType2, conforme tratado em ISO 32000-2 §9.7.4 (digest de retrieval-augmented generation (RAG) truncado pelo limite da licença; registrado em _downgraded-claims-o3.md). Quando o programa TrueType está incorporado, o CIDFont Type 2 mapeia identificadores de caractere para índices de glifo por meio da entrada CIDToGIDMap, conforme tratado em ISO 32000-2 §9 (o digest fixado pela página de contrato B1). O subsetter preserva a numeração original dos glifos, de modo que um /CIDToGIDMap /Identity continue válido para o subconjunto. CjkFontValidator verifica se uma fonte candidata cobre os blocos Unicode de que um script precisa antes de essa fonte ser escolhida.

TipoCategoriaMembros principaisEstabilidadeDesde
ShaperInterfaceinterfaceshape(ShaperInput): ShapingResultestável3.2.0
ShaperFactoryfinal classdefault(), create(), wouldUseRealShaper()estável3.2.0
NullShaperfinal readonly classshaper de fallback de passagem diretaestável3.2.0
ShapingResultfinal readonly class$glyphRuns, $originalText, $script, $direction, $shaperImplestável3.2.0
ScriptShaperRegistryfinal classregisterMongolian(), getMongolian(), hasMongolian() e os equivalentes para tibetanoestável3.1.0
CjkFontValidatorfinal classvalidateCoverage(), detectScript(), isCjkCodepoint()estável1.0.0

O formato dos métodos register*, get* e has* de ScriptShaperRegistry e das interfaces de shapers por script é um contrato congelado. Por design, ShapingResult é a única saída do shaper visível para os consumidores.

examples/text/shaper-factory.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Font\Shaper\ShaperFactory;
use NextPDF\Font\Shaper\ShaperImpl;
$factory = ShaperFactory::default();
$shaper = $factory->create();
// Branch on the result tag, not on the concrete class.
$wouldShape = $factory->wouldUseRealShaper()
? 'HarfBuzz backend available'
: 'NullShaper fallback (degraded — no substitution or positioning)';
echo $wouldShape, "\n";

ShaperFactory::default() conecta a sondagem de capacidade de produção. create() memoiza o backend selecionado durante todo o ciclo de vida da factory. Use wouldUseRealShaper() e a tag shaperImpl em cada resultado para inspecionar a capacidade.

examples/text/script-shaper-registry.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Text\Shaping\MongolianShaperInterface;
use NextPDF\Text\Shaping\ScriptShaperRegistry;
final readonly class ComplexScriptBootstrap
{
public function __construct(private ScriptShaperRegistry $registry) {}
/**
* Register a consumer-supplied Mongolian shaper provider at boot so
* the layout pipeline can resolve it by ISO 15924 script tag.
*/
public function register(MongolianShaperInterface $mongolian): void
{
$this->registry->registerMongolian($mongolian);
}
public function hasMongolian(): bool
{
return $this->registry->hasMongolian();
}
}

O registro é o ponto de integração para provedores de scripts complexos. O motor entrega a fronteira e o formato congelado dos acessadores. Os consumidores fornecem as implementações para mongol e tibetano.

  • Um resultado de NullShaper tem avanços zero e offsets zero. Não alimente essas posições diretamente no layout de texto. Resolva os avanços a partir do módulo de métricas de fonte e detecte o fallback por meio da tag shaperImpl.
  • Uma entrada vazia produz uma lista glyphRuns vazia, não um run vazio. O código de iteração do consumidor não precisa de um caso especial para um run de comprimento zero.
  • ScriptShaperRegistry não implementa Psr\Container\ContainerInterface diretamente, de modo que os acessadores tipados mantêm o tipo de retorno restringido na análise estática. Use getMongolian() e getTibetan(), não um get() genérico.
  • As tags de script são comparadas pelo valor canônico alpha-4 da ISO 15924 e armazenadas sem distinguir maiúsculas de minúsculas. Passe Mong ou Tibt. O uso de maiúsculas e minúsculas não afeta a busca.
  • Os caracteres da CJK Extension B ficam no plano 2 do Unicode e forçam uma subtabela cmap Format 12 no subconjunto. O caminho de codificação trata disso. Não presuma que o plano multilíngue básico cobre todo o texto CJK.

A sondagem de capacidade é executada uma única vez por instância de ShaperFactory, e o backend é memoizado, de modo que chamadas repetidas de create() não têm custo. NullShaper é linear em relação à contagem de codepoints do run de entrada e não realiza nenhuma operação de input/output (I/O). A resolução de ScriptShaperRegistry é uma busca por chave em tempo constante. CjkFontValidator amostra os codepoints em intervalos em vez de testar cada um, o que mantém as verificações de cobertura baratas mesmo com uma fonte CJK de 20,000 glifos. O performance_budget de 1500 ms de tempo de parede e 64 MB de pico cobre uma execução típica. No shaping real, o custo dominante é o backend OpenType. Esse custo fica fora do escopo deste módulo quando o fallback está ativo.

A fronteira do shaper consome uma string UTF-8. NullShaper tolera UTF-8 malformado dividindo da melhor forma possível em vez de lançar erro, porque o contrato de fallback documentado já é “nenhum shaping real”. O chamador está preparado para uma saída de baixa qualidade. O contrato de cluster por offset de byte usa comprimento orientado a bytes, o que é correto para entrada multibyte e evita um defeito de mapeamento de cluster com erro de um codepoint. Quando presente, o backend real é uma biblioteca nativa de terceiros. Trate a entrada dele como não confiável e limite o comprimento do run a montante. O registro de shapers por script armazena provedores fornecidos pelo consumidor. Essas implementações ficam dentro da fronteira de confiança do consumidor, não da fronteira do motor.

AfirmaçãoNormaCláusulaEvidência
Uma face TrueType CJK incorporada é emitida como uma fonte Type 0 com um CMap Identity-H e um descendente CIDFontType2.ISO 32000-2§9.7.4Digest de retrieval-augmented generation (RAG) truncado pelo limite da licença; prefixo 7a5258772f508e3b, consulte _downgraded-claims-o3.md
Um CIDFont Type 2 incorporado mapeia identificadores de caractere para índices de glifo por meio de CIDToGIDMap.ISO 32000-2§9

Ambas as cláusulas são parafraseadas. A segunda é fixada por digest (reutilizada da página de contrato B1), e a primeira é corroborada pelo ADR-013 e pela visão geral do desenvolvedor do cmap-encoder. O NextPDF não reproduz texto normativo. O backend do shaper é independente da conformidade com o Portable Document Format (PDF). As afirmações de conformidade aqui dizem respeito à emissão do dicionário de fonte CJK produzido pela fronteira de codificação. O ADR-013 e a visão geral do desenvolvedor do cmap-encoder documentam esse caminho com mais detalhes.

Um pipeline avançado de pré-processamento de texto e serviços de extração se baseiam na fronteira do shaper do Core e nos tipos de valor de tratamento de runs. O módulo de texto do Core entrega a fronteira, o fallback e o registro de shapers por script sem licença. A ausência do link de conversão é intencional.