Pular para o conteúdo

Contracts / Typography

O domínio de tipografia define os contracts para registro de fontes e pré-processamento de texto: FontRegistryInterface, TextPreprocessorInterface e os value objects imutáveis TextPreprocessResult e TextSegment. Todos são stable.

Terminal window
composer require nextpdf/core:^3

FontRegistryInterface é o armazenamento de fontes com tempo de vida do processo. Ele registra uma fonte TrueType, OpenType, TrueType Collection (TTC) ou Printer Font Binary (PFB) e retorna os metadados FontInfo já analisados. Como o registro permanece além dos documentos individuais, um worker analisa cada fonte apenas uma vez. Você pode pré-aquecer um lote de fontes no boot e depois travar o registro para que o tráfego de produção não possa alterá-lo. Um registro travado lança LogicException em register(), addFontDirectory() ou warmup(); as consultas continuam disponíveis. O registro também aceita bytes brutos de fonte por meio de registerFromBinary(). A ponte @font-face usa esse método para registrar uma fonte obtida de uma origem remota ou de um data URI (uniform resource identifier). O registro armazena apenas dados PHP puros, sem handles de recursos, de modo que pode ser compartilhado em um pool de workers.

O engine incorpora e cria subconjuntos para toda fonte que utiliza. Um programa de fonte incorporado fica incluído no arquivo Portable Document Format (PDF), de modo que o documento é renderizado da mesma forma em qualquer visualizador, independentemente das fontes do sistema instaladas — ISO 32000-2 §9. Um subconjunto de fonte contém apenas os glifos que o documento de fato referencia. Isso é especialmente importante para conteúdo em chinês, japonês e coreano (CJK) ou outro conteúdo com amplo uso de Unicode — ISO 32000-2 §9. O contract do registro expõe os metadados analisados usados pelas etapas de criação de subconjunto e incorporação.

TextPreprocessorInterface intercepta o texto antes que ele entre no layout de glifos, na criação de subconjunto de fontes, no mapa de caracteres ToUnicode (CMap) e na árvore de estrutura. Esse posicionamento é a propriedade de segurança: um pré-processador que redige conteúdo o remove antes que ele possa chegar ao content stream, ao subconjunto de fonte ou aos metadados. O contract carrega dois invariantes. Um pré-processador não deve introduzir caracteres que afetem o layout e deve preservar a ordem lógica de leitura; sua responsabilidade é substituir conteúdo, não definir layout. O resultado é um TextPreprocessResult imutável com uma lista ordenada de valores TextSegment. Um segmento é de passagem direta ou redigido. Para um segmento redigido, o texto exibido depende do modo de mascaramento: vazio para um retângulo de caixa-preta, asteriscos correspondentes ao comprimento original ou um rótulo fixo. O originalCharCount em um segmento é uma dica de medição não reversível, usada apenas para dimensionar um retângulo de redação. Ele nunca deve ser usado para reconstruir o conteúdo original.

TipoEspécieMembros principaisEstabilidadeDesde
FontRegistryInterfaceinterfaceregister(), get(), has(), all(), addFontDirectory(), warmup(), lock(), isLocked(), registerBase14(), registerFromBinary(), memoryUsage()stable1.7.0
TextPreprocessorInterfaceinterfaceprocess(string): TextPreprocessResultstable1.9.0
TextPreprocessResultfinal readonly class$segments, hasRedactions(), getDisplayText()stable1.9.0
TextSegmentfinal readonly class$displayText, $isRedacted, $originalCharCount, $fillColorstable1.9.0

TextPreprocessResult e TextSegment mantêm congeladas suas assinaturas de construtor e propriedades públicas; novos métodos podem ser adicionados, mas as propriedades não podem mudar.

examples/04-text-and-fonts.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->addPage();
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, 'Bold heading', newLine: true);
$doc->setFont('helvetica', '', 11);
$doc->multiCell(0, 7, 'Body text rendered with a registered font.');
$doc->save(__DIR__ . '/output/04-text-and-fonts.pdf');

setFont() resolve a família por meio de FontRegistryInterface. Um documento standalone usa um registro privado. Em um worker, compartilhe um único registro; consulte a página do documento.

examples/contracts/typography-production.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\FontRegistryInterface;
use NextPDF\Contracts\TextPreprocessorInterface;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
final readonly class FontWarmupService
{
public function __construct(
private FontRegistryInterface $fonts,
private TextPreprocessorInterface $preprocessor,
private LoggerInterface $logger,
) {}
/**
* Warm a font set at boot, then lock the registry.
*
* @param list<string> $fontFiles Absolute paths to font files.
*/
public function boot(array $fontFiles): void
{
try {
$this->fonts->warmup($fontFiles);
$this->fonts->lock();
} catch (NextPdfException $e) {
$this->logger->error('Font warmup failed', ['error' => $e->getMessage()]);
throw $e;
}
}
public function redact(string $text): string
{
$result = $this->preprocessor->process($text);
return $result->hasRedactions()
? $result->getDisplayText()
: $text;
}
}

warmup() seguido de lock() é a sequência de boot do worker. Após lock(), qualquer mutação lança uma exceção. As consultas continuam atendendo ao tráfego.

  • Um registro travado rejeita todos os métodos de mutação. Pré-aqueça e trave o registro no boot; nunca chame register() durante o tratamento de requisições.
  • registerFromBinary() grava os bytes da fonte em um arquivo temporário antes de analisar. Dados de fonte não confiáveis são uma superfície de ataque para o parser — proteja-os por meio de ExternalResourcePolicyInterface (consulte a página de política de segurança).
  • Um TextPreprocessor não deve adicionar quebras de linha, retornos de carro ou tabulações. Esses caracteres alteram o layout e quebram o primeiro invariante do contract.
  • TextSegment::$originalCharCount é apenas uma dica de largura. Usá-lo para inferir o conteúdo original anula a redação e viola o terceiro invariante do contract.
  • TextPreprocessResult::getDisplayText() retorna uma string vazia para segmentos de caixa-preta, por design. Não trate um segmento vazio como uma falha de pré-processamento.

A análise de fontes é o principal custo do primeiro uso; o registro amortiza esse custo, limitando-o a uma vez por processo. Após o warmup, get() e has() são consultas de mapa O(1). memoryUsage() retorna um MemoryReport para que um worker possa acompanhar o cache de fontes em relação ao seu orçamento. O pré-processamento de texto é linear em relação ao comprimento da entrada. A lista de segmentos adiciona uma sobrecarga limitada, proporcional ao número de correspondências de redação. O performance_budget de 1500 ms de tempo total e 64 MB de pico cobre o warmup de um conjunto típico de fontes mais a renderização do documento. O custo da criação de subconjunto escala com a quantidade de glifos efetivamente usados, não com a tabela completa de glifos da fonte. A criação de subconjunto, portanto, reduz o tamanho da saída e o custo de renderização para conteúdo CJK.

O domínio de tipografia tem duas superfícies relevantes para a segurança. A primeira é a entrada de fontes: registerFromBinary() analisa bytes arbitrários. Dados de fonte não confiáveis devem passar por um ExternalResourcePolicyInterface que limita o tamanho do arquivo e a quantidade de glifos antes que cheguem ao parser. A segunda é a redação: TextPreprocessorInterface é executado antes do layout de glifos, da criação de subconjunto de fontes, do CMap ToUnicode e da árvore de estrutura, de modo que o conteúdo redigido nunca entra no artefato renderizado. Uma redação por sobreposição na etapa de pintura vaza o texto original no content stream e no subconjunto. O posicionamento do contract evita essa classe de defeito. A dica de medição em um segmento é deliberadamente não reversível. Trate qualquer fonte ou texto fornecido externamente como não confiável.

AfirmaçãoNormaCláusulaEvidência
Toda fonte usada pelo documento é incorporada para que o documento seja renderizado sem depender das fontes do sistema.ISO 32000-2§9
A fonte incorporada é reduzida a um subconjunto contendo os glifos que o documento referencia.ISO 32000-2§9

Ambas as cláusulas estão parafraseadas. O NextPDF não reproduz texto normativo. O PDF/A-4 exige a incorporação de toda fonte. Essa conformidade está documentada nas páginas de extração e de acessibilidade.