You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
title = 'Classic Homemade Pizza',
ingredients = '- 2 ½ cups all-purpose flour\n- 1 teaspoon sugar\n- 1 teaspoon instant yeast\n- 1 cup warm water\n- 2 tablespoons olive oil\n\n- ½ cup pizza sauce\n- 2 cups shredded mozzarella cheese\n- Your favorite toppings',
recipeSteps = '1. Make the dough: In a large bowl, whisk together flour, sugar, yeast, and salt. Add warm water and olive oil, and stir until a shaggy dough forms. Knead for 5 minutes on a floured surface until smooth. Place in a greased bowl, cover, and let rise for 1 hour.\n\n2. Prepare the pizza: Preheat oven to 475°F (245°C). Punch down the dough and stretch it into a 12-inch circle.\n\n3. Add toppings: Spread pizza sauce evenly over the dough. Sprinkle with mozzarella cheese and add your desired toppings.\n\n4. Bake: Bake for 12-15 minutes, or until the crust is golden brown and the cheese is bubbly. Slice and enjoy!'
) {
/**
* Dynamically loads the required Google Fonts for the recipe card.
* It ensures the font stylesheet is added to the document only once.
*/
const loadFonts = async () => {
const FONT_FAMILIES = "Lato:wght@400;700&family=Playfair+Display:wght@700";
const FONT_URL = `https://fonts.googleapis.com/css2?family=${FONT_FAMILIES.replace(/ /g, '+')}&display=swap`;
if (!document.querySelector(`link[href="${FONT_URL}"]`)) {
const link = document.createElement('link');
link.href = FONT_URL;
link.rel = 'stylesheet';
document.head.appendChild(link);
// The document.fonts.ready promise resolves when fonts are loaded and ready.
await document.fonts.ready;
}
};
/**
* A helper function to wrap text on a canvas. It can operate in two modes:
* - 'draw' mode (doDraw = true): Renders text onto the canvas.
* - 'measure' mode (doDraw = false): Calculates the total height of the text block without drawing.
* @returns {number} The total height occupied by the text block.
*/
const wrapText = (context, text, x, y, maxWidth, lineHeight, doDraw = true) => {
const lines = text.split('\n');
let totalHeight = 0;
let currentY = y;
lines.forEach(line => {
// Treat empty lines as paragraph spacing
if (line.trim() === '') {
const paragraphSpacing = lineHeight * 0.5;
if (doDraw) currentY += paragraphSpacing;
totalHeight += paragraphSpacing;
return;
}
let words = line.split(' ');
let lineBuffer = '';
for (let i = 0; i < words.length; i++) {
let testBuffer = lineBuffer + words[i] + ' ';
let metrics = context.measureText(testBuffer.trim());
if (metrics.width > maxWidth && i > 0) {
if (doDraw) context.fillText(lineBuffer.trim(), x, currentY);
lineBuffer = words[i] + ' ';
currentY += lineHeight;
totalHeight += lineHeight;
} else {
lineBuffer = testBuffer;
}
}
if (doDraw) context.fillText(lineBuffer.trim(), x, currentY);
currentY += lineHeight;
totalHeight += lineHeight;
});
return totalHeight;
};
// --- Main Logic ---
await loadFonts();
// 1. Define layout constants and styles
const CANVAS_WIDTH = 800;
const PADDING = 50;
const TEXT_AREA_WIDTH = CANVAS_WIDTH - 2 * PADDING;
const BACKGROUND_COLOR = '#fdfaf2'; // A warm, parchment-like background
const TEXT_COLOR = '#3d3d3d';
const TITLE_FONT = '700 48px "Playfair Display"';
const TITLE_LINE_HEIGHT = 60;
const HEADING_FONT = '700 24px "Lato"';
const HEADING_HEIGHT = 30; // Approx height for a single line heading
const BODY_FONT = '400 16px "Lato"';
const BODY_LINE_HEIGHT = 26;
// 2. Calculate scaled image height to maintain aspect ratio
const imgHeight = originalImg.height * (CANVAS_WIDTH / originalImg.width);
// 3. Pre-calculate the total height needed for the canvas
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
let totalTextHeight = PADDING; // Start with top padding
tempCtx.font = TITLE_FONT;
totalTextHeight += wrapText(tempCtx, title, 0, 0, TEXT_AREA_WIDTH, TITLE_LINE_HEIGHT, false);
totalTextHeight += PADDING / 2;
tempCtx.font = HEADING_FONT;
totalTextHeight += HEADING_HEIGHT; // For "Ingredients"
totalTextHeight += PADDING / 2;
tempCtx.font = BODY_FONT;
totalTextHeight += wrapText(tempCtx, ingredients, 0, 0, TEXT_AREA_WIDTH, BODY_LINE_HEIGHT, false);
totalTextHeight += PADDING;
tempCtx.font = HEADING_FONT;
totalTextHeight += HEADING_HEIGHT; // For "Recipe"
totalTextHeight += PADDING / 2;
tempCtx.font = BODY_FONT;
totalTextHeight += wrapText(tempCtx, recipeSteps, 0, 0, TEXT_AREA_WIDTH, BODY_LINE_HEIGHT, false);
totalTextHeight += PADDING; // Final bottom padding
// 4. Create the final canvas with the calculated dimensions
const totalCanvasHeight = imgHeight + totalTextHeight;
const canvas = document.createElement('canvas');
canvas.width = CANVAS_WIDTH;
canvas.height = totalCanvasHeight;
const ctx = canvas.getContext('2d');
// 5. Draw all elements onto the canvas
ctx.fillStyle = BACKGROUND_COLOR;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(originalImg, 0, 0, CANVAS_WIDTH, imgHeight);
let currentY = imgHeight + PADDING;
ctx.fillStyle = TEXT_COLOR;
// Draw Title (centered)
ctx.font = TITLE_FONT;
ctx.textAlign = 'center';
const titleActualHeight = wrapText(ctx, title, CANVAS_WIDTH / 2, currentY, TEXT_AREA_WIDTH, TITLE_LINE_HEIGHT, true);
currentY += titleActualHeight + PADDING / 2;
// Draw Ingredients (left-aligned)
ctx.textAlign = 'left';
ctx.font = HEADING_FONT;
ctx.fillText('Ingredients', PADDING, currentY);
currentY += HEADING_HEIGHT + PADDING / 2;
ctx.font = BODY_FONT;
const ingredientsActualHeight = wrapText(ctx, ingredients, PADDING, currentY, TEXT_AREA_WIDTH, BODY_LINE_HEIGHT, true);
currentY += ingredientsActualHeight + PADDING;
// Draw Recipe Steps (left-aligned)
ctx.font = HEADING_FONT;
ctx.fillText('Recipe', PADDING, currentY);
currentY += HEADING_HEIGHT + PADDING / 2;
ctx.font = BODY_FONT;
wrapText(ctx, recipeSteps, PADDING, currentY, TEXT_AREA_WIDTH, BODY_LINE_HEIGHT, true);
return canvas;
}
Apply Changes