Add FirmaEC signature style and bump to 1.2.17
This commit is contained in:
@@ -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,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'];
|
||||
|
||||
Reference in New Issue
Block a user