You can edit the below JavaScript code to customize the image tool.
Apply Changes
// Helper function to load Google Fonts and ensure they are ready for canvas
async function _victorianLabelCreator_loadGoogleFonts(fontSpecs) {
// fontSpecs: array of objects like [{ name: "Font Name", weights: [400, 700] }]
const uniqueFontRequests = [];
const seenFontNames = new Set();
for (const spec of fontSpecs) {
if (spec.name && !seenFontNames.has(spec.name)) {
seenFontNames.add(spec.name);
uniqueFontRequests.push({
name: spec.name,
weights: spec.weights && spec.weights.length > 0 ? spec.weights : [400] // Default to 400 if no weights specified
});
}
}
if (uniqueFontRequests.length === 0) return Promise.resolve();
const fontFamiliesParams = uniqueFontRequests.map(spec => {
let param = `family=${spec.name.replace(/\s+/g, '+')}`;
// Google Fonts API expects weights like wght@400;700 for variable fonts or specific weights
// For non-variable, it's family=FontName:wght@400;700 or family=FontName:ital,wght@0,400;0,700;1,400
// Using the :wght@ syntax is generally robust.
param += `:wght@${spec.weights.sort((a,b) => a-b).join(';')}`;
return param;
});
const googleFontsUrl = `https://fonts.googleapis.com/css2?${fontFamiliesParams.join('&')}&display=swap`;
const stylesheetId = `google-fonts-stylesheet-${googleFontsUrl.replace(/[^a-zA-Z0-9]/g, '')}`;
if (!document.getElementById(stylesheetId)) {
const link = document.createElement('link');
link.id = stylesheetId;
link.rel = 'stylesheet';
link.href = googleFontsUrl;
document.head.appendChild(link);
await new Promise((resolve, reject) => {
link.onload = resolve;
link.onerror = (err) => {
console.error(`Failed to load Google Fonts stylesheet: ${googleFontsUrl}`, err);
// Potentially resolve anyway, canvas might use fallback fonts
resolve(); // Or reject(err) if fonts are critical
};
});
}
const fontLoadPromises = [];
for (const spec of uniqueFontRequests) {
for (const weight of spec.weights) {
fontLoadPromises.push(
document.fonts.load(`${weight} 12px "${spec.name}"`)
.catch(e => console.warn(`Canvas readiness check for font "${spec.name}" (weight ${weight}) failed:`, e))
);
}
}
try {
await Promise.all(fontLoadPromises);
} catch (e) {
console.warn("Some fonts might not be fully ready for canvas drawing after stylesheet load:", e);
}
// Small delay sometimes helps browsers with canvas font rendering immediately after load
await new Promise(resolve => setTimeout(resolve, 100));
}
// Helper function to draw wrapped text, handling explicit newlines
function _victorianLabelCreator_wrapAndDrawText(ctx, text, x, y, maxWidth, lineHeight, fontFamily, color, fontSize, textAlign = 'center', fontWeight = 'normal', fontStyle = 'normal') {
ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px "${fontFamily}"`;
ctx.fillStyle = color;
ctx.textAlign = textAlign;
ctx.textBaseline = 'top';
let currentDrawY = y;
const logicalLines = text.split('\n');
for (const singleLogicalLine of logicalLines) {
if (singleLogicalLine.trim() === '' && logicalLines.length > 1) { // Handle empty lines if they are intentional separators
currentDrawY += lineHeight;
continue;
}
const words = singleLogicalLine.split(' ');
let currentPhysicalLineBuffer = '';
for (let i = 0; i < words.length; i++) {
const word = words[i];
let testLine;
if (currentPhysicalLineBuffer === '') {
testLine = word;
} else {
testLine = currentPhysicalLineBuffer + ' ' + word;
}
const metrics = ctx.measureText(testLine);
if (metrics.width > maxWidth && currentPhysicalLineBuffer !== '') {
ctx.fillText(currentPhysicalLineBuffer, x, currentDrawY);
currentPhysicalLineBuffer = word;
currentDrawY += lineHeight;
} else {
currentPhysicalLineBuffer = testLine;
}
}
if (currentPhysicalLineBuffer !== '') {
ctx.fillText(currentPhysicalLineBuffer, x, currentDrawY);
}
currentDrawY += lineHeight;
}
return currentDrawY;
}
async function processImage(
originalImg,
productName = "Dr. Quincy's Miraculous Elixir",
slogan = "Cures Gout, Rheumatism & Nervous Debility",
ingredientsIntro = "CONTAINS:",
ingredientsList = "Pure Grain Alcohol (40%), Extract of Serpent's Root, Tincture of Poppy, Aqua Pura Dilutis.",
directionsIntro = "DIRECTIONS:",
directionsText = "One Teaspoonful taken thrice daily, post cibum. For Adults Only.",
manufacturer = "Prepared Exclusively By: The Quincy Apothecary Co.",
location = "London, Paris & New York",
labelWidth = 320,
labelHeight = 500,
backgroundColor = "#FDF5E6",
textColor = "#4A3B31",
borderColor = "#4A3B31",
borderThickness = 4,
innerBorderPadding = 6,
primaryFontName = "IM Fell English SC",
secondaryFontName = "Parisienne",
ornamentText = "❧ Estd. MDCCCXXXVIII ❧",
imgScale = 0.35,
imgPositionYOffset = 0,
mainPadding = 20
) {
await _victorianLabelCreator_loadGoogleFonts([
{ name: primaryFontName, weights: [400, 700] },
{ name: secondaryFontName, weights: [400] }
]);
const canvas = document.createElement('canvas');
canvas.width = labelWidth;
canvas.height = labelHeight;
const ctx = canvas.getContext('2d');
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, labelWidth, labelHeight);
ctx.strokeStyle = borderColor;
ctx.lineWidth = borderThickness;
ctx.strokeRect(borderThickness / 2, borderThickness / 2, labelWidth - borderThickness, labelHeight - borderThickness);
const innerBorderX = borderThickness + innerBorderPadding;
const innerBorderY = borderThickness + innerBorderPadding;
const innerBorderWidth = labelWidth - 2 * innerBorderX; // Width is labelWidth MINUS left and right border+padding combos
const innerBorderHeight = labelHeight - 2 * innerBorderY; // Height is labelHeight MINUS top and bottom border+padding combos
ctx.lineWidth = Math.max(1, Math.floor(borderThickness / 3));
ctx.strokeRect(innerBorderX, innerBorderY, innerBorderWidth, innerBorderHeight);
let currentY = mainPadding;
const contentCenterX = labelWidth / 2;
const contentAreaWidth = labelWidth - 2 * mainPadding;
if (manufacturer) {
currentY = _victorianLabelCreator_wrapAndDrawText(ctx, manufacturer, contentCenterX, currentY, contentAreaWidth, 15, primaryFontName, textColor, 12, 'center', 'normal');
currentY += 8;
}
ctx.fillStyle = borderColor;
const topDecoLineY = currentY + 2;
if (manufacturer) { // Only draw deco line if there was text above it
ctx.fillRect(contentCenterX - contentAreaWidth * 0.2, topDecoLineY, contentAreaWidth * 0.4, 1);
currentY = topDecoLineY + 1 + 8;
}
if (productName) {
currentY = _victorianLabelCreator_wrapAndDrawText(ctx, productName, contentCenterX, currentY, contentAreaWidth, 36, primaryFontName, textColor, 32, 'center', '700');
currentY += 8;
}
if (slogan) {
currentY = _victorianLabelCreator_wrapAndDrawText(ctx, slogan, contentCenterX, currentY, contentAreaWidth * 0.95, 24, secondaryFontName, textColor, 22, 'center', 'normal');
currentY += 12;
}
if (originalImg && originalImg.complete && originalImg.naturalWidth > 0 && imgScale > 0) {
const imgMaxContainerHeight = labelHeight * 0.20;
const imgMaxContainerWidth = contentAreaWidth * 0.7;
let scaledImgW = originalImg.naturalWidth * imgScale;
let scaledImgH = originalImg.naturalHeight * imgScale;
let fitScale = 1;
if (scaledImgW > imgMaxContainerWidth) {
fitScale = Math.min(fitScale, imgMaxContainerWidth / scaledImgW);
}
if (scaledImgH > imgMaxContainerHeight) {
fitScale = Math.min(fitScale, imgMaxContainerHeight / scaledImgH);
}
const finalImgW = scaledImgW * fitScale;
const finalImgH = scaledImgH * fitScale;
// Ensure there's reasonable space left below the image before footer area
const estimatedFooterHeight = 60; // Approximate space for location & ornament text + bottom padding
if (finalImgH > 0 && (currentY + finalImgH + 20) < (labelHeight - estimatedFooterHeight) ) {
const imgX = (labelWidth - finalImgW) / 2;
const imgDrawY = currentY + imgPositionYOffset;
ctx.drawImage(originalImg, imgX, imgDrawY, finalImgW, finalImgH);
currentY = imgDrawY + finalImgH + 12;
}
}
ctx.fillStyle = borderColor;
const midDecoLineY = currentY + 5;
ctx.fillRect(contentCenterX - contentAreaWidth * 0.3, midDecoLineY, contentAreaWidth * 0.6, 1);
currentY = midDecoLineY + 1 + 10;
if (ingredientsIntro) {
currentY = _victorianLabelCreator_wrapAndDrawText(ctx, ingredientsIntro.toUpperCase(), contentCenterX, currentY, contentAreaWidth, 18, primaryFontName, textColor, 14, 'center', '700');
currentY += 3;
}
if (ingredientsList) {
const formattedIngredients = ingredientsList.replace(/,\s*/g, ",\n");
currentY = _victorianLabelCreator_wrapAndDrawText(ctx, formattedIngredients, contentCenterX, currentY, contentAreaWidth * 0.9, 16, primaryFontName, textColor, 11, 'center', 'normal');
currentY += 10;
}
if (directionsIntro) {
currentY = _victorianLabelCreator_wrapAndDrawText(ctx, directionsIntro.toUpperCase(), contentCenterX, currentY, contentAreaWidth, 18, primaryFontName, textColor, 14, 'center', '700');
currentY += 3;
}
if (directionsText) {
currentY = _victorianLabelCreator_wrapAndDrawText(ctx, directionsText, contentCenterX, currentY, contentAreaWidth * 0.9, 16, primaryFontName, textColor, 11, 'center', 'normal');
currentY += 15;
}
let footerCurrentY = labelHeight - mainPadding + innerBorderPadding; // Start slightly higher to account for inner border visual space
if (ornamentText) {
footerCurrentY -= 13;
_victorianLabelCreator_wrapAndDrawText(ctx, ornamentText, contentCenterX, footerCurrentY, contentAreaWidth, 13, primaryFontName, textColor, 10, 'center', 'normal');
}
if (location) {
footerCurrentY -= 5;
footerCurrentY -= 14;
_victorianLabelCreator_wrapAndDrawText(ctx, location, contentCenterX, footerCurrentY, contentAreaWidth, 14, primaryFontName, textColor, 11, 'center', 'normal');
}
return canvas;
}
Apply Changes