import type { CertificateDesign, CertificateRenderData, CertificateSignatory } from '../types';
export function escapeHtml(input: unknown): string {
return String(input ?? '').replace(/[&<>"']/g, (char) => {
switch (char) {
case '&':
return '&';
case '<':
return '<';
case '>':
return '>';
case '"':
return '"';
default:
return ''';
}
});
}
export function getYear(value: string | undefined | null): string {
const match = String(value ?? '').match(/\b(19|20|21)\d{2}\b/);
if (match) return match[0];
return String(new Date().getFullYear());
}
export function shadeColor(hex: string, percent: number): string {
const normalized = hex.startsWith('#') ? hex.slice(1) : hex;
if (normalized.length !== 6) return hex;
const numeric = parseInt(normalized, 16);
if (Number.isNaN(numeric)) return hex;
const offset = Math.round((percent / 100) * 255);
const clamp = (value: number) => Math.max(0, Math.min(255, value));
const red = clamp(((numeric >> 16) & 0xff) + offset);
const green = clamp(((numeric >> 8) & 0xff) + offset);
const blue = clamp((numeric & 0xff) + offset);
const next = (red << 16) | (green << 8) | blue;
return '#' + next.toString(16).padStart(6, '0');
}
export interface TemplateRenderArgs {
design: CertificateDesign;
data: CertificateRenderData;
}
export interface TemplateRenderOutput {
body: string;
styles: string;
}
export type TemplateRenderer = (args: TemplateRenderArgs) => TemplateRenderOutput;
export interface SignatoryBlockOptions {
wrapperClass?: string;
nameClass: string;
roleClass: string;
roleFirst?: boolean;
}
export function renderSignatoryBlock(sig: CertificateSignatory, options: SignatoryBlockOptions): string {
if (sig.enabled === false) return '';
const sigImg = sig.signatureUrl ? `` : '';
const nameEl = `