Pular para o conteúdo principal

O que pode e o que não pode derrubar uma geração

O generatePdf traça uma linha: problema no dado, ou em conteúdo mal formado, degrada — o campo renderiza vazio, um caractere é trocado, o PDF sai. Problema estrutural, ou em que conteúdo sumiria em silêncio de um documento que alguém assina, falha alto. Um relatório de 200 páginas não pode morrer porque uma linha tinha um \n.

Degrada (o PDF sai):

SituaçãoO que acontece
Caractere de controle no dado (\n, \t, NUL, C0/C1)Vira espaço. Fonte nenhuma tem glifo pra esses, então é a única renderização possível
Expressão {...} inválidaAquele token renderiza vazio; o editor marca o campo
Path que não resolveVazio
data null, array, string ou númeroIgnorado; campos renderizam vazio
Array vinculado que é número, itens não-objeto, célula com valor objetoIgnorado ou serializado
Cor inválida ("banana", "#zzz")Cai no default
fontSize 0, negativo ou NaN0/negativo valem; NaN cai em 10
Largura de campo 0 ou negativaVale (nada é desenhado)
Vínculo de imagem que resolve pra algo que não é data URICampo fica vazio
Repetição de seção mais alta que uma página inteiraÉ colocada mesmo assim, transbordando aquela página; a repetição seguinte começa numa nova

Falha alto (e por quê):

Cada uma destas é uma classe tipada com um code literal, exportada dos dois entries (json-pdf-designer e json-pdf-designer/server). A coluna abaixo é o que você casa — nunca error.message, que é uma string de diagnóstico em inglês e pode ser reescrita numa release de patch.

SituaçãoClasse (code)blamePor que não degradar
Caractere sem glifo na fonte padrão (emoji/CJK sem fontBytes)UnsupportedGlyphError (unsupportedGlyph)dataEle tem glifo numa fonte completa — descartá-lo removeria conteúdo, em silêncio, de um documento que alguém assina. Passe fontBytes, ou tire o caractere do dado
Tamanho de página ausente, que não é objeto, ou que não são dois números finitos > 0InvalidPageSizeError (invalidPageSize)templateEstrutural: não há default sensato pra adivinhar. Conferido em TODAS as páginas antes de renderizar qualquer uma, então uma página 7 torta não espera seis boas
Imagem corrompida num campoImageUnreadableError (imageUnreadable)templateQuem montou escolheu aquele arquivo; descartar calado esconde o erro
Imagem corrompida como fundo da páginaBackgroundImageUnreadableError (backgroundImageUnreadable)templateIdem, e papel timbrado faltando não é óbvio no resultado
Formato de imagem que não é PNG nem JPEGUnsupportedImageFormatError (unsupportedImageFormat)templateReenvie pelo editor, que converte
Uma imagem acima de 15MB (decodificada)ImageTooLargeError (imageTooLarge)templateProtege quem está gerando — template pode vir de fonte não confiável
Mais de 200 imagens distintas num documentoTooManyImagesError (tooManyImages)templateIdem. Imagens idênticas são deduplicadas por conteúdo antes, então isso conta as de verdade
Upload acima de 20MB, ou que o navegador não consegue lerImageUploadTooLargeError / ImageUploadUnreadableErrordataPego no upload, antes de poder chegar num template
Documento acima de maxPages (default DEFAULT_MAX_PAGES, 5000)PageLimitError (pageLimit)dataRelatório truncado que parece completo é pior que relatório nenhum. Filtre o dado, divida em vários PDFs, ou aumente o maxPages
Uma passada de paginação que não consome nadaPaginationStalledError (paginationStalled)packageBug de aritmética do pacote — girar até um contador estourar escondia isso
.woff2 passado onde o runtime não consegue descomprimirWoff2SupportMissingError / FontDecompressFailedError / FontDecompressTimeoutErrorconfigProblema de ambiente, não de template
Template que não é objeto, ou version que não é númeroTemplateNotAnObjectError / TemplateVersionInvalidErrortemplateNão há de onde migrar
Template.version mais novo que este buildTemplateVersionTooNewError (templateVersionTooNew)templateFormato mais novo pode carregar campo que este build ignoraria calado
Falta um passo de migração entre duas versõesTemplateMigrationMissingError (templateMigrationMissing)packageLacuna nossa, não sua
fontBytes inválido (Unknown font format)não é nosso — quem lança é o pdf-lib—Erro de quem chamou, não do template nem do dado. Veja o caso null abaixo

Como tratar​

Três linhas, e a ordem importa:

import { describePdfError, dictFor, PageLimitError } from "json-pdf-designer/server";

try {
const bytes = await generatePdf(template, data, bindings, { fontBytes });
} catch (err) {
// 1. Um caso específico que você quer tratar à parte — por classe, não por mensagem.
if (err instanceof PageLimitError) return dividirETentarDeNovo(err.maxPages);

// 2. Todo o resto: uma chamada transforma o erro em texto que uma pessoa lê.
const problem = describePdfError(err, dictFor("pt-BR"));
// → { code, blame, title, action?, field?, detail } | null
}

O describePdfError é exaustivo, então não existe ramo "não reconheci este" dentro dele. Ele devolve null pra erro que não é do pacote — a linha do Unknown font format acima é exatamente esse caso — em vez de inventar um título pra uma falha que ele não entende. Guarde a sua cópia pros seus erros, e caia num genérico só quando as duas coisas vierem vazias.

Se você quer o estreitamento sem a cópia — log, métrica, um status HTTP e nada mostrado pra ninguém — o isPdfError(err) estreita unknown pra AnyPdfError, o que já te dá code e blame sem dicionário nenhum envolvido.

Nunca guarde o texto descrito em estado

title e action saem no idioma do dict que você passou. Guarde os dois, e eles congelam naquele idioma — então quem troca o idioma da interface vê aquele pedaço da tela ficar atrás, sem nada falhar e sem nada no log. Guarde o erro cru e descreva na renderização.

- catch (err) { setErro(describePdfError(err, dictFor(locale))); }
+ catch (err) { setCaixaDeErro({ err }); }
+ // …e no corpo do render, pra rederivar a cada troca de idioma:
+ const problem = caixaDeErro && describePdfError(caixaDeErro.err, dictFor(locale));

A caixa ({ err }) não é enfeite: null não distingue "sem erro" de "um erro que é null", e setErro(err) com um erro que por acaso seja função seria lido pelo React como updater.

Num servidor​

O blame existe pra esta decisão não precisar ser tomada duas vezes:

blameO que significaStatus razoável
dataO payload não consegue produzir este documento400, ou 413 no caso do pageLimit
templateO template salvo está errado422
configFalta algo no ambiente (uma fonte, um codec)500, e acorde alguém
packageBug nosso500, e por favor abra um issue

Nada acima precisa de React: as classes, o isPdfError, o describePdfError e o dictFor saem todos do json-pdf-designer/server.

Uma ressalva sobre o erro de glifo. Ele só dispara com a fonte padrão (Helvetica/WinAnsi). Com uma fonte embutida via fontBytes, um caractere que a fonte não cobre é desenhado com o glifo .notdef dela — normalmente um branco ou um quadrado — sem erro nenhum. Isso é comportamento do fontkit, não uma escolha deste pacote, e significa que uma fonte customizada troca uma falha alta por um branco silencioso. Se glifo ausente não pode passar despercebido, confira o dado antes de gerar.