Add FirmaEC signature style and bump to 1.2.17

This commit is contained in:
2026-05-21 22:07:22 -05:00
parent 5d7d610322
commit 8bc244855c
12 changed files with 199 additions and 16 deletions
+79 -4
View File
@@ -33,10 +33,30 @@ function truncate(text, font, size, maxWidth) {
}
function getSignatureWidth(style, signerName = '') {
if (style !== 'qr') return 200;
const normalizedName = String(signerName).trim();
const calculatedWidth = 78 + (normalizedName.length * 4.9);
return Math.max(170, Math.min(270, Math.round(calculatedWidth)));
if (style === 'qr' || style === 'firmaec') {
const normalizedName = String(signerName).trim();
const calculatedWidth = 78 + (normalizedName.length * 4.9);
return Math.max(170, Math.min(270, Math.round(calculatedWidth)));
}
return 200;
}
function wrapToLines(text, font, size, maxWidth, maxLines) {
const words = text.split(' ');
const lines = [];
let current = '';
for (const word of words) {
if (lines.length >= maxLines) break;
const test = current ? `${current} ${word}` : word;
if (font.widthOfTextAtSize(test, size) <= maxWidth) {
current = test;
} else {
if (current) lines.push(current);
current = truncate(word, font, size, maxWidth);
}
}
if (current && lines.length < maxLines) lines.push(current);
return lines;
}
function buildLocalizedSignatureTimestamp({ locale, timeZone } = {}) {
@@ -240,6 +260,60 @@ async function drawStyleImage(pdfDoc, page, sx, sy, sw, sh, { signerName, reason
if (location) page.drawText(truncate(location, f2, 6, textW), { x: textX, y: sy + sh - 59, size: 6, font: f2, color: C.lightGray });
}
// ── Style: FIRMAEC (QR izquierda + texto monoespaciado estilo FirmaEC Ecuador) ─
async function drawStyleFirmaEC(pdfDoc, page, sx, sy, sw, sh, { signerName, verificationUrl, qrPosition, qrBackground, qrBorder }) {
const fBold = await pdfDoc.embedFont(StandardFonts.CourierBold);
const fReg = await pdfDoc.embedFont(StandardFonts.Courier);
const useSoftBackground = qrBackground === 'soft';
const useBorder = qrBorder === 'line';
const qrOnRight = qrPosition === 'right';
const qrContent = verificationUrl || [
'FIRMA DIGITAL VERIFICADA',
`Firmante: ${signerName}`,
].join('\n');
const qrPng = await QRCode.toBuffer(qrContent, {
type: 'png',
width: 120,
margin: 1,
color: { dark: '#000000', light: '#ffffff' },
});
const qrImage = await pdfDoc.embedPng(qrPng);
const qrSize = Math.min(sh - 10, 50);
// Background
const bgColor = useSoftBackground ? C.bgBlue : C.white;
page.drawRectangle({ x: sx, y: sy, width: sw, height: sh, color: bgColor });
const borderColor = useBorder ? rgb(0.15, 0.15, 0.15) : rgb(0.70, 0.70, 0.72);
const borderWidth = useBorder ? 0.85 : 0.5;
page.drawRectangle({ x: sx, y: sy, width: sw, height: sh, borderColor, borderWidth });
// QR image
const qrX = qrOnRight ? sx + sw - qrSize - 4 : sx + 4;
const qrY = sy + (sh - qrSize) / 2;
page.drawImage(qrImage, { x: qrX, y: qrY, width: qrSize, height: qrSize });
// Vertical divider
const divX = qrOnRight ? qrX - 4 : sx + qrSize + 8;
page.drawLine({ start: { x: divX, y: sy + 6 }, end: { x: divX, y: sy + sh - 6 }, thickness: 0.5, color: rgb(0.55, 0.55, 0.58) });
// Text column
const textX = qrOnRight ? sx + 6 : divX + 5;
const textW = sw - qrSize - 18;
page.drawText('Validar unicamente en FirmaEC.', { x: textX, y: sy + sh - 12, size: 6, font: fReg, color: rgb(0.12, 0.12, 0.14) });
page.drawText('Firmado electronicamente por:', { x: textX, y: sy + sh - 21, size: 6, font: fReg, color: rgb(0.12, 0.12, 0.14) });
// Name — bold, uppercase, word-wrapped up to 2 lines
const nameUpper = signerName.toUpperCase();
const nameSize = 8.5;
const nameLines = wrapToLines(nameUpper, fBold, nameSize, textW, 2);
nameLines.forEach((line, i) => {
page.drawText(line, { x: textX, y: sy + sh - 34 - (i * 13), size: nameSize, font: fBold, color: rgb(0.05, 0.05, 0.07) });
});
}
// ── Main sign function ─────────────────────────────────────────────────────────
const signPdf = async ({
pdfPath, p12Buffer, p12Password,
@@ -300,6 +374,7 @@ const signPdf = async ({
if (style === 'qr') await drawStyleQR(pdfDoc, tp, sx, sy, sw, sh, info);
else if (style === 'professional') await drawStyleProfessional(pdfDoc, tp, sx, sy, sw, sh, info);
else if (style === 'image') await drawStyleImage(pdfDoc, tp, sx, sy, sw, sh, info);
else if (style === 'firmaec') await drawStyleFirmaEC(pdfDoc, tp, sx, sy, sw, sh, info);
else await drawStyleClassic(pdfDoc, tp, sx, sy, sw, sh, info);
// ── Save page with visual stamp ───────────────────────────────────
+1 -1
View File
@@ -1,6 +1,6 @@
const usersRepository = require('./users.repository');
const ALLOWED_SIGNATURE_STYLES = ['classic', 'qr', 'professional'];
const ALLOWED_SIGNATURE_STYLES = ['classic', 'qr', 'professional', 'firmaec'];
const ALLOWED_QR_POSITIONS = ['left', 'right'];
const ALLOWED_QR_BACKGROUNDS = ['transparent', 'soft'];
const ALLOWED_QR_BORDERS = ['none', 'line'];