Assinatura visível
Carimbo no PDF com texto, imagem e posição definidos por você.
Por padrão a assinatura é invisível: está no arquivo e aparece no painel de assinaturas do leitor de PDF. Com a assinatura visível, o PDF ganha um carimbo na página, com o texto, a imagem e a posição que o seu sistema definir. A API só desenha o que for pedido, dentro do campo da assinatura (não é possível acrescentar o carimbo depois de assinar).
O pedido vai no prepare, porque o certificado já é conhecido e permite preencher o nome e o CPF do signatário:
// pontos = 1/72 de polegada; a origem é o canto INFERIOR esquerdo da página (A4 em pé = 595 x 842)
$visual = ShieldSign\Client::visualRepresentation(
page: 'last', // 1, 2, ... ou 'last'
x: 40, y: 40, width: 260, height: 80,
text: "Assinado digitalmente por {{signer}}\nCPF {{cpf}}\nData: {{date}}",
imagePath: '/var/app/assets/logo-carimbo.png', // PNG ou JPEG, até 2 MB; vai ao fundo do retângulo
fontSize: 9, imageOpacity: 0.35, border: true,
);
$p = $api->prepare($token, $certificadoDerBase64, visual: $visual); // o mesmo carimbo em todos os documentos
$p = $api->prepare($token, $certificadoDerBase64, visualPerDocument: [$v0, null, $v2]); // lote: um por documento; null = invisível
const resultado = await client.signPades({
pdf, signer, signaturePolicyId: "pades-icpbrasil-ad-rb", securityContextId: "icp-brasil",
visualRepresentation: {
page: "last",
rect: { x: 40, y: 40, width: 260, height: 80 },
text: "Assinado digitalmente por {{signer}}\nCPF {{cpf}}\nData: {{date}}",
image: bytesDoPng, // opcional: PNG ou JPEG, até 2 MB
fontSize: 9, border: true,
},
});
Campos#
| Campo | Regra |
|---|---|
page |
1, 2… ou "last". Página inexistente: 400. |
retângulo (x, y, width, height; em JavaScript, dentro de rect) |
Mínimo de 20 x 10. Precisa caber na página: a mensagem de erro informa o tamanho da página e o que foi pedido. |
text |
Até 2000 caracteres. Aceita os marcadores abaixo. Quebra de linha automática; se não couber na altura, 400 dizendo quantas linhas faltam. null usa o texto padrão; "" mostra só a imagem. |
image |
PNG ou JPEG, até 2 MB e 12 megapixels. Validada de verdade (arquivo corrompido: 400). No cliente PHP: imagePath ou imageBytes; em JavaScript: image. Aceita data URL. |
fontSize |
De 5 a 48 (padrão 9). A fonte é embutida (DejaVu Sans): acentos e símbolos latinos funcionam. |
fontColor |
#RRGGBB ou #RGB (padrão preto). |
textAlign, textVerticalAlign |
left/center/right (padrão left) e top/middle/bottom (padrão middle). |
includeSigningTime |
false omite a linha "Data: …" do texto padrão. Com text próprio, simplesmente não escreva {{date}}. |
autoFontSize |
true: o texto que não cabe reduz o corpo (até 5 pt) em vez de dar 400. |
imageOpacity, border |
Opacidade da imagem de fundo e borda do retângulo. |
imagePlacement |
Onde a imagem fica (veja abaixo). |
Marcadores do texto#
| Marcador | Valor |
|---|---|
{{signer}} |
Nome do titular (sem o :CPF do nome do certificado). |
{{date}} |
Data e hora em UTC, do momento do preparo. |
{{issuer}} |
AC emissora. |
{{cpf}}, {{cnpj}} |
Do certificado; vazios se ele não tiver. |
{{company}} |
Razão social do e-CNPJ; vazio nos demais certificados. |
Qualquer outro {{...}} é recusado.
Imagem alinhada em um canto, com o texto ao lado#
$visual = Client::visualRepresentation(1, 40, 700, 300, 90,
"Assinado digitalmente por\n{{signer}}\nCPF {{cpf}}\nData: {{date}}", imageBytes: $seloPng, fontSize: 9,
extra: ['imagePlacement' => ['horizontal' => 'right', 'vertical' => 'top', 'width' => 48, 'height' => 48, 'margin' => 3, 'textFlow' => 'beside']]);
imagePlacement |
Efeito |
|---|---|
horizontal (left/center/right) e vertical (top/middle/bottom) |
Canto ou centro. Padrão: centro. |
width e height (pontos) ou scale (0 a 1) |
Tamanho máximo, mantendo a proporção. width e height mandam sobre scale. |
margin |
Pontos entre a imagem e a borda do retângulo. |
textFlow |
overlay (padrão): o texto fica por cima da imagem. beside: o texto usa só o espaço que sobra ao lado (exige left ou right). |
Sem imagePlacement, a imagem fica centralizada, no tamanho máximo, atrás do texto.
Por que o texto "não cabe" e como evitar#
A fonte é a DejaVu Sans, mais larga que a Helvetica ou a Arial. A largura média de um caractere, em múltiplos do corpo, é de cerca de 0,52 para minúsculas, 0,58 para dígitos e 0,64 para MAIÚSCULAS (os nomes nos certificados ICP-Brasil vêm em maiúsculas). A entrelinha é 1,25 vezes o corpo.
Caracteres por linha ≈ (largura − 6) / (corpo × fator). Com corpo 9 e 243 pt de largura, cabem cerca de 49 minúsculas ou 40 MAIÚSCULAS por linha. Para resolver: aumente o retângulo, reduza o fontSize, encurte o texto, ou use autoFontSize: true (o servidor reduz o corpo até caber; se nem 5 pt couber, responde 400 com a conta).
Lotes e páginas de tamanhos diferentes#
Calcule a posição por documento (o seu servidor sabe o tamanho de cada PDF) e use visualPerDocument. Em JavaScript, é documents[i].visualRepresentation. Com páginas rotacionadas (/Rotate), o retângulo continua sendo dado no espaço do PDF sem rotação, e o carimbo sai de pé.
Erros#
Todas as validações do carimbo respondem 400 invalid_visual_representation, e a sessão continua aberta: corrija e chame o prepare de novo. Em lote, a mensagem começa com documento N:, em que N é o índice do documento a partir de 0.
Com vários signatários, cada um envia o seu prepare com a sua posição: veja Vários signatários.