Provisione fontes em produção
Visão geral
Seção intitulada “Visão geral”Seu PDF renderiza corretamente no seu laptop, depois vai para um contêiner e sai como uma fileira de caixas vazias — o glifo “tofu” — ou com acentos e caracteres não latinos ausentes. A causa é quase sempre a mesma: a fonte que você selecionou não está presente na imagem implantada.
O engine NextPDF nativo, executado no mesmo processo, resolve fontes a partir de
arquivos de fonte que o registro de fontes consegue ler. Ele não descobre
fontes do sistema operacional ou do fontconfig automaticamente — arquivos de
fonte instalados pelo sistema operacional só ajudam se você registrar
explicitamente esses arquivos ou adicionar o diretório que os contém ao caminho de
busca do FontRegistry. Um contêiner construído a partir de uma imagem base enxuta
não tem fontes instaladas por apt/apk, e mesmo quando tem, o engine nativo as
ignora a menos que você aponte o registro para os arquivos delas. A correção é
empacotar os arquivos de fonte reais dentro da sua aplicação ou imagem e registrá-los
no engine. O registro lê arquivos TrueType (.ttf), OpenType (.otf) e
TrueType Collection (.ttc); o Type1 legado (.pfb) também é aceito, mas raramente
é necessário em trabalhos novos.
Antes de começar, confirme que estas peças estão no lugar:
- O NextPDF core está instalado.
- Você tem os arquivos de fonte reais que pretende usar, e você está licenciado para incorporá-los. Os direitos de incorporação são responsabilidade sua — consulte Incorpore e faça subset de uma fonte TrueType.
- Seu build consegue copiar esses arquivos para o artefato implantado.
Este é um how-to de operações. O código é mínimo; o trabalho está no build e no layout do sistema de arquivos. Para a mecânica em nível de API de registrar e fazer subset de um único face, leia a receita de incorporar-e-fazer-subset linkada acima. Esta página cobre como colocar os arquivos na máquina e apontar o engine para eles.
Por que o engine nativo não encontra fontes do sistema operacional automaticamente
Seção intitulada “Por que o engine nativo não encontra fontes do sistema operacional automaticamente”Existem dois caminhos de renderização distintos, e a história das fontes difere entre eles.
- Engine nativo no mesmo processo (o padrão,
Document/writeHtml): o engine não chama o sistema de fontes do sistema operacional nem ofontconfigpara descoberta. Ele resolve um face por meio do registro de fontes, que lê um arquivo de fonte específico que você registrou ou encontra um dentro de um diretório que você configurou como caminho de busca. Instalar uma fonte comapt-get install fonts-notoou rodarfc-cachenão faz nada por si só — o engine nativo enxerga esses arquivos apenas se você os registrar ou adicionar o diretório deles ao caminho de busca do registro. - Ponte do Chrome (o renderizador HTML-para-PDF que aciona um navegador
headless): esse caminho de fato usa as fontes instaladas do host por meio da
descoberta de fontes normal do navegador, então pacotes de fonte
apt/apke ofontconfigimportam ali.
Se você ler orientações genéricas do tipo “instale estes pacotes de fonte do sistema no seu Dockerfile”, elas se aplicam à ponte do Chrome, não ao engine nativo coberto nesta página. Para geração nativa, empacote os arquivos e registre-os.
Passo 1 — Empacote os arquivos de fonte reais
Seção intitulada “Passo 1 — Empacote os arquivos de fonte reais”Coloque os arquivos de fonte dentro da árvore da sua aplicação para que sejam
versionados e enviados com cada build. Um local convencional é um diretório
resources/fonts/.
your-app/├── resources/│ └── fonts/│ ├── DejaVuSans.ttf│ ├── DejaVuSans-B.ttf│ └── NotoSansCJK-Regular.ttc└── src/Nomeie os arquivos para que a busca de diretório do engine consiga encontrá-los por
família e estilo. Quando você registra um diretório (em vez de um arquivo
específico) e depois chama setFont('DejaVuSans', 'B', 12), o engine procura
arquivos como DejaVuSans-B.ttf, DejaVuSansB.ttf ou DejaVuSans.ttf em cada
diretório configurado. A busca de diretório constrói esses nomes candidatos a partir
do mesmo código de estilo de uma letra que você passa para setFont (B para
negrito, I para itálico, BI para negrito-itálico), não de uma palavra por
extenso — então a forma confiável é
Family-<StyleCode>.ttf (por exemplo DejaVuSans-B.ttf ou DejaVuSans-BI.ttf),
não Family-Bold.ttf. Um arquivo chamado DejaVuSans-Bold.ttf nunca é
encontrado pela busca de diretório; para usar tal arquivo, registre-o
explicitamente com register() —
que analisa a fonte e a indexa pela família e estilo lidos das próprias tabelas de
nome do arquivo, então o nome de arquivo por extenso deixa de importar (veja o
Passo 2).
Passo 2 — Registre as fontes no engine
Seção intitulada “Passo 2 — Registre as fontes no engine”Você tem duas formas equivalentes de tornar os arquivos visíveis. Ambas passam por
NextPDF\Typography\FontRegistry, que implementa
NextPDF\Contracts\FontRegistryInterface.
Registre um arquivo específico sob um alias quando você controla o face exato:
use NextPDF\Typography\FontRegistry;
$registry = new FontRegistry();$registry->register(__DIR__ . '/../resources/fonts/DejaVuSans.ttf', alias: 'DejaVuSans');register(string $fontFile, string $alias = '', int $fontIndex = 0) aceita
arquivos .ttf, .otf e .ttc, além do Type1 .pfb legado (que carrega suas
métricas .afm companheiras do mesmo caminho); $fontIndex seleciona uma sub-fonte
dentro de um TrueType Collection (.ttc). O register() analisa o arquivo e indexa
o face pela família e estilo lidos das próprias tabelas de nome, então o nome de
arquivo físico é irrelevante depois de registrado. O $alias opcional é apenas um
nome de lookup extra para o face — ele não é um código de estilo e não muda qual
estilo o arquivo fornece; passe-o quando você quiser chamar setFont() com um nome
diferente do nome de família embutido na fonte. Ele retorna o FontInfo analisado.
Registre um diretório quando você quer que o engine resolva faces por nome a partir de uma pasta que você controla:
$registry = new FontRegistry('/var/www/app/resources/fonts');// or, equivalently, after construction:$registry->addFontDirectory('/var/www/app/resources/fonts');O construtor do FontRegistry recebe esse diretório como seu primeiro argumento, e
addFontDirectory() adiciona mais caminhos de busca. Um Document simples também
expõe addFontDirectory() para o caso standalone.
Para usar um registro que você mesmo populou, construa documentos por meio do
DocumentFactory, que conecta exatamente esse registro a cada documento que ele
cria:
use NextPDF\Core\DocumentFactory;use NextPDF\Graphics\ImageRegistry;
$factory = new DocumentFactory($registry, new ImageRegistry(maxCacheBytes: 0));
$doc = $factory->create();$doc->addPage();$doc->setFont('DejaVuSans', '', 12);$doc->cell(0, 10, 'Réndéred wîth a bundled face — no tofu.', newLine: true);$doc->save('/tmp/out.pdf');O Document::createStandalone() constrói seu próprio registro interno, então um
face que você registrou em um FontRegistry separado é invisível para ele. Em
produção, passe pelo DocumentFactory (ou pela factory do seu framework) para que o
registro populado seja o que está em uso.
Configuração de framework
Seção intitulada “Configuração de framework”Cada integração de framework expõe os mesmos dois conceitos como configuração,
então você raramente toca no registro diretamente. No nextpdf.php do pacote
Laravel, fonts_path (padrão NEXTPDF_FONTS_PATH, com fallback para
resource_path('fonts')) é o diretório de busca, e preload_fonts é uma lista de
caminhos absolutos de arquivos de fonte analisados no boot do worker. Aponte
fonts_path para o diretório que você empacotou e os seus faces registrados
resolvem automaticamente.
Passo 3 — Provisione fontes em uma imagem Docker
Seção intitulada “Passo 3 — Provisione fontes em uma imagem Docker”Em um contêiner, os arquivos de fonte precisam fazer parte da camada da imagem,
copiados no momento do build. Como o código da aplicação e as fontes são enviados
juntos quando você os empacota sob resources/fonts/, um COPY . . normal já os
carrega. Se você mantém as fontes fora do contexto de build, copie-as
explicitamente e garanta que o caminho que você registra corresponda ao caminho
dentro da imagem.
# Native engine: NO system font packages are required.# The native engine does not discover OS-installed fonts automatically; install OS# font packages (`apt-get install fonts-*`) only if you also register them or point# the font registry's search directory at their files.FROM php:8.4-cli
WORKDIR /var/www/app
# Bundle the application, including resources/fonts/, into the image.COPY . /var/www/app
# Make the bundled directory the engine's font search path.ENV NEXTPDF_FONTS_PATH=/var/www/app/resources/fonts
CMD ["php", "bin/generate.php"]Em um sistema de arquivos imutável ou somente leitura (um contêiner
readOnlyRootFilesystem, uma imagem serverless ou um host endurecido), os arquivos
de fonte são lidos no momento da geração e nunca são escritos, então um mount
somente leitura é adequado. A única escrita que o engine pode querer é o seu cache
de fontes analisadas: ou dê a esse diretório um pequeno volume gravável, ou aqueça e
trave o registro no boot (próxima seção) para que nenhuma escrita ou registro em
runtime seja tentado.
Passo 4 — Aqueça e verifique
Seção intitulada “Passo 4 — Aqueça e verifique”Em um worker de longa duração, analise cada face uma vez no boot, depois trave o registro para que nenhum registro por requisição aconteça e uma má configuração falhe ruidosamente em vez de cair silenciosamente para um fallback:
$registry = new FontRegistry('/var/www/app/resources/fonts');$registry->warmup([ '/var/www/app/resources/fonts/DejaVuSans.ttf', '/var/www/app/resources/fonts/DejaVuSans-B.ttf',]);$registry->lock();Depois de lock(), register(), addFontDirectory() e warmup() lançam, o que
transforma um erro de “caminho errado na imagem” em uma falha de boot dura, em vez
de uma página tofu em produção.
Adicione uma verificação de smoke de implantação que renderiza uma página com cada face exigido. A verificação de cabeçalho abaixo apenas confere que o documento produziu saída — ela não prova que a fonte foi analisada, incorporada nem mesmo resolvida. Um face que o engine não consegue encontrar pode cair para uma fonte base padrão (e, sob o comportamento não estrito atual, um perfil de conformidade pode em vez disso fornecer um substituto empacotado) e ainda assim emitir um PDF válido e não vazio — então mesmo onde esse fallback acontece, esta verificação sozinha não captura a degradação silenciosa. Não conte com o fallback ser garantido ou silencioso em todos os caminhos; verifique o programa incorporado diretamente, como mostrado abaixo:
$doc = $factory->create();$doc->addPage();$doc->setFont('DejaVuSans', '', 12);$doc->cell(0, 10, 'warmup check', newLine: true);
$pdf = $doc->getPdfData();
// `getPdfData()` would normally throw on a real failure; this header check only// confirms serialization returned PDF bytes, not that any specific font resolved.if (!str_starts_with($pdf, '%PDF')) { throw new RuntimeException('Font warmup smoke check produced no PDF output.');}Para realmente reprovar a implantação quando um face está ausente, verifique o PDF
emitido em busca do programa de fonte incorporado. Um face registrado que resolve
carrega seu próprio dicionário de fonte com um programa incorporado, então afirmar a
presença dele captura o caso em que o face solicitado nunca resolveu (seja qual for
o fallback do engine) que a verificação de cabeçalho deixa passar. Qual chave
contém o programa depende do formato de outline: outlines TrueType (.ttf, .ttc)
usam /FontFile2, outlines CFF/OpenType (.otf com outlines PostScript) usam
/FontFile3, e o Type1 legado (.pfb) usa /FontFile.
Se tudo o que você precisa é de um sinal agnóstico de formato de “algum programa de
fonte incorporado”, teste por /FontFile sozinho — porque /FontFile é uma
substring tanto de /FontFile2 quanto de /FontFile3, uma verificação simples de
substring já casa com todos os tipos de outline, e adicionar /FontFile2//FontFile3
como ramos || extras é redundante:
if (!str_contains($pdf, '/FontFile')) { throw new RuntimeException('No embedded font program found — face fell back.');}Uma substring simples de /FontFile, porém, não consegue distinguir os tipos de
outline. Para diferenciá-los, faça o match no token exato com um limite de palavra
para que /FontFile não dispare também em /FontFile2 ou /FontFile3:
$isTrueType = preg_match('~/FontFile2\b~', $pdf) === 1; // TrueType (.ttf/.ttc)$isCffOtf = preg_match('~/FontFile3\b~', $pdf) === 1; // CFF/OpenType (.otf)$isType1 = preg_match('~/FontFile(?![23])\b~', $pdf) === 1; // Type1 (.pfb)
if (!$isTrueType && !$isCffOtf && !$isType1) { throw new RuntimeException('No embedded font program found — face fell back.');}De qualquer forma, trate isto como uma heurística grosseira apenas, não como um
gate de implantação confiável. Uma busca bruta de bytes sobre o PDF serializado é
imprecisa por vários motivos: programas de fonte podem viver dentro de object
streams comprimidos (onde /FontFile* nunca aparece como bytes simples),
incremental updates podem acrescentar ou substituir objetos, fontes não
incorporadas ou standard-14 legitimamente não carregam nenhum programa de fonte, e
diferenças de serialização (ordenação de objetos, espaço em branco, codificação
de nomes) podem mover ou ocultar o token. Na melhor das hipóteses, ela confirma que
algum face incorporou um programa — nunca que o face específico que você queria
resolveu.
Para um gate de implantação de verdade, não confie na busca de bytes. Analise o PDF
emitido com um analisador de PDF apropriado ou um inspetor de objetos e afirme que o
objeto de fonte do seu face alvo carrega um programa /FontFile//FontFile2//FontFile3
incorporado, ou use uma asserção de resolução de fonte fornecida pelo produto, se
houver uma disponível para a sua integração. As regexes com reconhecimento de token
acima são úteis para uma verificação rápida de sanidade local, mas uma inspeção
estrutural é o que deve reprovar a implantação. A estrutura de incorporação e de
dicionário de fonte é descrita em
Incorpore e faça subset de uma fonte TrueType.
Casos extremos e pegadinhas
Seção intitulada “Casos extremos e pegadinhas”createStandalone()tem seu próprio registro. Um face registrado em umFontRegistryseparado não é visível para um documento standalone. UseDocumentFactory(ou a factory do framework) para que o seu registro seja o ativo.- Arquivos de estilo precisam existir como arquivos. O engine não sintetiza
negrito ou itálico a partir de um face regular. Se você chama
setFont('DejaVuSans', 'B'), a busca de diretório procuraDejaVuSans-B.ttf,DejaVuSansB.ttfouDejaVuSans.ttf(variantes em minúsculas e.otftambém) — ela forma o candidato a partir do código de estilo literalB, então nunca procuraDejaVuSans-Bold.ttf. Um arquivo com um nome por extenso comoDejaVuSans-Bold.ttfsó resolve quando você o registra explicitamente comregister(), que o indexa pela família e estilo lidos das próprias tabelas de nome do arquivo, independentemente do nome de arquivo; depender da busca de diretório para encontrá-lo produz um miss, após o qual o engine pode cair para uma fonte base (não um caminho garantido ou sempre silencioso) — a degradação sobre a qual esta página alerta. - Caminhos de stream-wrapper e remotos são rejeitados. O registro recusa
caminhos que contêm um esquema de URI ou um byte nulo. Registre apenas arquivos
locais; para fontes buscadas em runtime use
registerFromBinary()com os bytes brutos. - Registro travado é imutável. Depois de chamar
lock(), qualquerregister(),addFontDirectory()ouwarmup()posterior lança. Os métodos de lookup continuam disponíveis. Registre e aqueça tudo antes de travar. - Coleções CJK são grandes. Registre a sub-fonte certa de um
.ttccom$fontIndex, e reserve orçamento para um subset incorporado maior. Consulte as notas de CJK na receita de incorporar-e-fazer-subset.
Notas de segurança
Seção intitulada “Notas de segurança”- Um arquivo de fonte é entrada binária não confiável. Empacote apenas fontes de fontes em que você confia, e valide a proveniência de qualquer face aceito de usuários finais.
- Travar o registro após o warmup remove uma superfície de mutação em runtime e faz um erro de caminho falhar no boot em vez de degradar a saída silenciosamente.
- Não interpole entrada do usuário em um caminho de arquivo registrado. Registre um conjunto fixo de faces empacotados; não deixe uma requisição escolher um caminho arbitrário do sistema de arquivos.
Conformidade
Seção intitulada “Conformidade”Este guia não faz nenhuma afirmação normativa de padrões. Cada símbolo mostrado é
superfície pública verificada: NextPDF\Typography\FontRegistry (register(),
addFontDirectory(), warmup(), lock(), o argumento de diretório do
construtor), seu contrato NextPDF\Contracts\FontRegistryInterface,
NextPDF\Core\DocumentFactory::create() e NextPDF\Core\Document::setFont()
/ addFontDirectory(). As chaves fonts_path e preload_fonts do Laravel são a
configuração documentada do pacote nextpdf/laravel. O comportamento de
incorporação e de tag de subset, com suas citações da ISO 32000-2, está documentado
na receita de incorporar-e-fazer-subset linkada em Veja também.
Veja também
Seção intitulada “Veja também”- Incorpore e faça subset de uma fonte TrueType: a receita em nível de API para registrar um face e o subset automático no save.
- Renderize HTML para uma página PDF: o caminho HTML nativo, que resolve fontes pelo mesmo registro.
- Retorne um PDF gerado a partir de um controller: conecte um documento construído por factory a uma resposta de framework.
- Uso em produção com Laravel: a configuração de fontes do framework e o warmup no boot do worker.