You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
deceasedName = "Unknown Soul",
causeOfDeath = "Terminal Spookiness",
dateOfDeath = `October 31, ${new Date().getFullYear()}`,
placeOfDeath = "The Haunted Manor",
issuingAuthority = "Ministry of Midnight Frights",
signatureName = "Grim R. Eaper, Chief Mortician"
) {
const certificateWidth = 800;
const certificateHeight = 600;
// --- Font Loading ---
const titleFontFamily = 'Metal Mania';
const decorativeFontFamily = 'Cinzel'; // For labels and important text
const bodyFontFamily = 'Times New Roman'; // Fallback and body text
async function loadGoogleFont(fontFamily, weight = '400') {
if (typeof document === 'undefined' || !document.fonts) {
// console.warn("document.fonts API not available. Skipping custom font loading.");
return false; // Environment without document or document.fonts (e.g., Node.js test)
}
const fontCssFamily = fontFamily.replace(/\s+/g, '+');
const fontCheckString = `12px "${fontFamily}"`; // Generic string to check if font is loaded
try {
if (document.fonts.check(fontCheckString)) {
// Font is already known to the FontFaceSet. Attempt to ensure it's loaded.
await document.fonts.load(fontCheckString);
return true;
}
} catch (e) {
// console.warn(`Error checking font ${fontFamily}: ${e.message}`);
// Proceed to attempt loading via link if check fails or not supported
}
const linkId = `google-font-${fontCssFamily}-${weight}`;
if (document.getElementById(linkId)) {
// Stylesheet already added, try to load font
try {
await document.fonts.load(fontCheckString);
return true;
} catch (e) {
// console.warn(`Error loading already-linked font ${fontFamily}: ${e.message}`);
return false;
}
}
return new Promise((resolve) => {
const link = document.createElement('link');
link.id = linkId;
link.rel = 'stylesheet';
link.href = `https://fonts.googleapis.com/css2?family=${fontCssFamily}:wght@${weight}&display=swap`;
link.onload = async () => {
try {
await document.fonts.load(fontCheckString);
resolve(true);
} catch (e) {
console.warn(`Failed to load font "${fontFamily}" (weight ${weight}) using document.fonts.load. Error: ${e.message}`);
resolve(false);
}
};
link.onerror = () => {
console.warn(`Failed to load stylesheet for font: ${fontFamily} from ${link.href}`);
resolve(false);
};
document.head.appendChild(link);
});
}
// Load necessary fonts
await Promise.all([
loadGoogleFont(titleFontFamily, '400'),
loadGoogleFont(decorativeFontFamily, '400'), // Cinzel has various weights, 400 is common
]);
// --- Canvas Setup ---
const canvas = document.createElement('canvas');
canvas.width = certificateWidth;
canvas.height = certificateHeight;
const ctx = canvas.getContext('2d');
// --- Helper: wrapText ---
function wrapText(context, text, x, y, maxWidth, lineHeight) {
const words = text.split(' ');
let line = '';
let currentTextY = y;
const originalTextAlign = context.textAlign; // Preserve original alignment
context.textAlign = 'left'; // wrapText assumes left alignment for positioning
for (let n = 0; n < words.length; n++) {
const testLine = line + words[n] + ' ';
const metrics = context.measureText(testLine);
const testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
context.fillText(line.trim(), x, currentTextY);
line = words[n] + ' ';
currentTextY += lineHeight;
} else {
line = testLine;
}
}
context.fillText(line.trim(), x, currentTextY);
context.textAlign = originalTextAlign; // Restore original alignment
return currentTextY + lineHeight; // Return Y for the baseline of the next potential line
}
// --- Helper: drawLabelAndValue ---
function drawLabelAndValue(currentCtx, label, value, x, y, labelFont, valueFont, lHeight, valIndent, valMaxWidth) {
currentCtx.textAlign = 'left';
currentCtx.font = labelFont;
currentCtx.fillStyle = 'black';
currentCtx.fillText(label, x, y);
currentCtx.font = valueFont;
const valueX = x + valIndent;
// Ensure valMaxWidth is positive
const effectiveValMaxWidth = Math.max(10, valMaxWidth - (valIndent > 0 ? 0 : Math.abs(valIndent)));
// Use wrapText for the value part. Pass fillStyle as it might change.
const originalFillStyle = currentCtx.fillStyle;
currentCtx.fillStyle = 'black'; // Ensure value is black
const nextY = wrapText(currentCtx, value, valueX, y, effectiveValMaxWidth, lHeight);
currentCtx.fillStyle = originalFillStyle;
return nextY; // Y position for the next element, includes last line height
}
// --- Background and Border ---
ctx.fillStyle = '#FAF0E6'; // Linen / Parchment
ctx.fillRect(0, 0, certificateWidth, certificateHeight);
ctx.strokeStyle = '#5C4033'; // Dark Brown
ctx.lineWidth = 10;
ctx.strokeRect(10, 10, certificateWidth - 20, certificateHeight - 20);
ctx.strokeStyle = '#000000'; // Black
ctx.lineWidth = 2;
ctx.strokeRect(20, 20, certificateWidth - 40, certificateHeight - 40);
// --- Title ---
ctx.fillStyle = '#800000'; // Dark Red
ctx.font = `60px "${titleFontFamily}", "Times New Roman", serif`;
ctx.textAlign = 'center';
ctx.fillText("CERTIFICATE OF DEATH", certificateWidth / 2, 80);
// Decorative line under title
ctx.beginPath();
ctx.moveTo(50, 100);
ctx.lineTo(certificateWidth - 50, 100);
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke();
let currentY = 130; // Starting Y for content below title
// --- Photo Area ---
const photoBoxWidth = 140;
const photoBoxHeight = 170;
const photoMargin = 40;
const photoX = certificateWidth - photoBoxWidth - photoMargin;
const photoY = currentY;
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.strokeRect(photoX -1, photoY -1, photoBoxWidth + 2, photoBoxHeight + 2); // Border for photo
ctx.fillStyle = '#E0E0E0'; // Light gray background for photo box
ctx.fillRect(photoX, photoY, photoBoxWidth, photoBoxHeight);
if (originalImg && originalImg.complete && originalImg.naturalWidth > 0) {
const imgAspect = originalImg.naturalWidth / originalImg.naturalHeight;
const boxAspect = photoBoxWidth / photoBoxHeight;
let dw, dh, dxDraw, dyDraw;
if (imgAspect > boxAspect) { // Image wider than box aspect ratio
dw = photoBoxWidth;
dh = dw / imgAspect;
dxDraw = photoX;
dyDraw = photoY + (photoBoxHeight - dh) / 2;
} else { // Image taller or same aspect ratio
dh = photoBoxHeight;
dw = dh * imgAspect;
dxDraw = photoX + (photoBoxWidth - dw) / 2;
dyDraw = photoY;
}
ctx.drawImage(originalImg, dxDraw, dyDraw, dw, dh);
} else {
ctx.font = `italic 14px "${bodyFontFamily}", serif`;
ctx.fillStyle = '#555555';
ctx.textAlign = 'center';
wrapText(ctx, "Photo of Deceased Not Available", photoX + photoBoxWidth / 2, photoY + photoBoxHeight / 2 - 14, photoBoxWidth - 10, 16);
}
// --- Text Fields Area ---
const textStartX = 40;
// Max width for text fields block (left of photo)
const textBlockMaxWidth = photoX - textStartX - 20; // 20 for margin between text and photo
const fieldLabelFont = `bold 17px "${decorativeFontFamily}", "${bodyFontFamily}", serif`;
const fieldValueFont = `16px "${bodyFontFamily}", serif`;
const fieldLineHeight = 20;
const valueIndentFromLabelStart = 190; // Fixed indent for value from textStartX
const valueMaxWidth = textBlockMaxWidth - valueIndentFromLabelStart;
const fieldVerticalSpacing = 8; // Extra space after each field is drawn
currentY = drawLabelAndValue(ctx, "Full Name of Deceased:", deceasedName, textStartX, currentY, fieldLabelFont, fieldValueFont, fieldLineHeight, valueIndentFromLabelStart, valueMaxWidth) + fieldVerticalSpacing;
currentY = drawLabelAndValue(ctx, "Date of Demise:", dateOfDeath, textStartX, currentY, fieldLabelFont, fieldValueFont, fieldLineHeight, valueIndentFromLabelStart, valueMaxWidth) + fieldVerticalSpacing;
currentY = drawLabelAndValue(ctx, "Cause of Demise:", causeOfDeath, textStartX, currentY, fieldLabelFont, fieldValueFont, fieldLineHeight, valueIndentFromLabelStart, valueMaxWidth) + fieldVerticalSpacing;
currentY = drawLabelAndValue(ctx, "Place of Demise:", placeOfDeath, textStartX, currentY, fieldLabelFont, fieldValueFont, fieldLineHeight, valueIndentFromLabelStart, valueMaxWidth) + fieldVerticalSpacing;
// --- Attestation Text ---
// Ensure currentY is below both the text fields and the photo
currentY = Math.max(currentY, photoY + photoBoxHeight + 20);
currentY += 10; // Margin before attestation paragraph
ctx.font = `italic 15px "${bodyFontFamily}", serif`;
ctx.fillStyle = 'black';
ctx.textAlign = 'left';
const attestationText = `This document solemnly certifies that the aforementioned individual, ${deceasedName}, has officially ceased all vital signs and has departed from the mortal coil. This certificate is issued under the high authority of ${issuingAuthority} and serves as an official record of their transition to the great unknown. Witnessed and recorded on this fateful day.`;
// The attestation spans the full width below the fields/photo.
currentY = wrapText(ctx, attestationText, textStartX, currentY, certificateWidth - (2 * textStartX), 18);
// --- Signature Area ---
const signatureAreaY = certificateHeight - 100;
// If content is too long, it might overlap. For this template, we assume it fits.
// It's better to make sure signatureAreaY is below currentY if currentY grew too much
// currentY = Math.max(currentY + 20, signatureAreaY); // Position signature area dynamically (can push canvas longer) or cap content
currentY = signatureAreaY; // For fixed size canvas, use fixed position
ctx.font = `16px "${bodyFontFamily}", serif`;
ctx.fillStyle = 'black';
ctx.textAlign = 'left';
const signedByText = "Signed and Certified By:";
ctx.fillText(signedByText, textStartX, currentY);
const signatureLineY = currentY + 30;
ctx.beginPath();
ctx.moveTo(textStartX, signatureLineY);
ctx.lineTo(textStartX + 280, signatureLineY);
ctx.lineWidth = 1;
ctx.strokeStyle = 'black';
ctx.stroke();
ctx.font = `italic 16px "${decorativeFontFamily}", "${bodyFontFamily}", serif`;
ctx.fillText(signatureName, textStartX, signatureLineY - 5); // Signature name above the line
ctx.font = `14px "${bodyFontFamily}", serif`;
ctx.fillText(`(${issuingAuthority})`, textStartX, signatureLineY + 20);
// --- Official Seal ---
const sealRadius = 35;
const sealX = certificateWidth - textStartX - sealRadius - 10; // Right align seal
const sealY = signatureLineY - sealRadius / 2;
ctx.beginPath();
ctx.arc(sealX, sealY, sealRadius, 0, Math.PI * 2);
ctx.fillStyle = '#8B0000'; // Dark red for seal
ctx.fill();
ctx.strokeStyle = '#DAA520'; // Gold border for seal
ctx.lineWidth = 2;
ctx.stroke();
// Additional decorative circles for seal
ctx.beginPath();
ctx.arc(sealX, sealY, sealRadius - 5, 0, Math.PI * 2);
ctx.stroke();
ctx.fillStyle = '#DAA520'; // Gold text on seal
ctx.font = `bold 10px "${titleFontFamily}", "Arial", sans-serif`;
ctx.textAlign = 'center';
ctx.fillText("OFFICIAL", sealX, sealY - 5);
ctx.fillText("SEAL OF", sealX, sealY + 5);
ctx.fillText("FRIGHTS", sealX, sealY + 15);
return canvas;
}
Apply Changes