Conceitos centrais
O modelo de dados
type Schema = TextSchema | TableSchema | ImageSchema | SectionSchema | ChartSchema | KpiSchema;
type Template = {
page: PageSize; // { width, height } em mm
headerHeight?: number; // faixas estáticas (mm) repetidas em toda página gerada
footerHeight?: number;
marginLeft?: number;
marginRight?: number;
backgroundImage?: string; // PNG data URI, fundo tipo letterhead
schemas: Schema[];
// Opcional — ver "Templates com várias páginas" abaixo. Quando
// presente e não-vazio, é a fonte da verdade (os campos flat acima
// são ignorados); ausente/vazio cai nos campos flat como única
// página implícita, então todo Template de antes desse campo existir
// continua funcionando sem mudança.
pages?: TemplatePage[];
};
// Mesmo formato do Template, menos o próprio `pages` — um design de
// página independente (tamanho/cabeçalho/rodapé/fundo/schemas próprios).
type TemplatePage = {
id: string;
name?: string;
page: PageSize;
headerHeight?: number;
footerHeight?: number;
marginLeft?: number;
marginRight?: number;
backgroundImage?: string;
schemas: Schema[];
};
type Binding =
| { schemaName: string; type: "scalar"; path: string }
| { schemaName: string; type: "array"; path: string; columns: TableColumn[] }
| { schemaName: string; type: "keyvalue"; paths: string[] }
| { schemaName: string; type: "template"; template: string }
| { schemaName: string; type: "section"; path: string }
| { schemaName: string; type: "chart"; path: string; labelColumn: string; valueColumn: string; filters?: ChartFilterGroup[] };
Todo Schema compartilha BaseSchema (id, name, x/y/width/
height em mm, locked?, sectionId?) mais os campos específicos de
cada tipo — veja a Referência da API pública pra forma
completa e atual.
Unidade de medida: mm em todo o modelo de dados — fácil de
raciocinar, uma folha A4 é simplesmente 210×297mm. Convertido pra
px só pra renderizar no canvas do editor, e pra pt só na hora
de desenhar o PDF de verdade via pdf-lib.
Campos do editor
Texto, tabela, imagem, seção repetida (mestre-detalhe), gráfico (pizza/barra) e cartão indicador (KPI) — todos arrastam/ redimensionam livre (react-rnd), com grade de 5mm travando posição/tamanho por padrão — segure Shift durante o arrasto pra soltar da grade.
- Duplo clique liga edição inline (texto/tabela viram input/textarea direto em cima do campo; imagem abre o seletor de arquivo).
Delete/Backspaceremove todos os campos selecionados;Ctrl/Cmd+C/Vcopia/cola (desativado enquanto digita num input, pra nunca comer digitação normal).- Seleção múltipla:
Ctrl/Cmd+clique, ou arraste uma caixa de seleção sobre o canvas vazio. Arrastar qualquer campo selecionado move o grupo inteiro.
O painel lateral
Uma única fileira de abas, sem aninhamento — Campos (todo campo colocado, clique seleciona, travar/enviar-pra-trás/trazer-pra-frente/ remover), Página (tamanho, orientação, cabeçalho/rodapé/margem, fundo), sempre disponíveis; mais Dados/Estilo/Filtro — só presentes enquanto um campo está selecionado, e só as que fazem sentido pro tipo dele (sem Estilo pra imagem/seção; Filtro só pra gráfico).
As abas são reordenáveis por arraste e fixáveis (esconde com "×",
reaparece com "+") — ordem e abas escondidas persistem no
localStorage, uma preferência de UI separada do Template/
Binding[] salvo.
Edição em bloco na seleção múltipla — selecionar vários campos do MESMO tipo juntos (todos texto, todos KPI, todos gráfico) libera editar em bloco as configurações de Estilo compartilhadas (cor, tamanho de fonte, paleta…) em todos de uma vez, a partir de um painel só; o conteúdo de Dados de cada item fica travado pra evitar sobrescrever o conteúdo próprio de campos diferentes por acidente, exceto pelo punhado de configurações que são genuinamente compartilhadas (ex: o formato de número de um KPI, ou o separador de milhar/"agrupar em Outros" de um gráfico).
Avisos de configuração incompleta
Uma seção/gráfico sem vínculo com o JSON, ou um filtro de gráfico com coluna escolhida mas sem valor preenchido, ganha um ícone de alerta ⚠ amarelo na lista de Campos (e na aba certa) — apontando direto pro que precisa ser corrigido.
Tamanho de página, orientação e fundo
O <Designer> mostra um seletor de tamanho (A4/A3/A5/Carta/Ofício) e
orientação (retrato/paisagem) na aba "Página" —
applyOrientation/orientationOf/matchPreset/PAGE_SIZE_PRESETS
(todos exportados) fazem a mesma conta pra um seletor próprio.
Template.backgroundImage coloca uma imagem (ou a primeira página de
um PDF enviado, rasterizada uma vez) atrás de tudo, tanto no editor
quanto no PDF final.
Templates com várias páginas
Template.pages (opcional) deixa UM Template guardar vários designs de
página diferentes — tamanho, cabeçalho/rodapé, fundo, schemas
próprios — que o generatePdf desenha num PDF só, em sequência, com
{pageNumber}/{pageCount} continuando de um design pro outro (se o
design 1 termina na página física 2, o design 2 começa na página 3, não
reinicia na 1). data/Binding[] são compartilhados entre TODAS as
entradas de pages — nome de schema precisa ficar único no template
inteiro, não só dentro de uma página. O exemplo report-builder constrói
uma UI de abas de página em cima disso: cada aba edita uma
TemplatePage, enquanto as fontes de dados JSON continuam
compartilhadas/globais entre todas as abas.
Fontes customizadas
Passe fontBytes (um TTF/OTF de verdade) pra generatePdf(..., { fontBytes }) pra acentuação/Unicode completos via fontkit. Sem
isso, cai no Helvetica padrão do pdf-lib (cobre a maioria dos
acentos latinos, não tudo). .woff/.woff2 também são aceitos —
normalizeFontBytes(bytes) detecta e descomprime pro TTF/OTF de
verdade que o pdf-lib precisa, automaticamente.