You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg,
pageTitle = "Explorer's Journal",
dateLabelText = "Date:",
locationLabelText = "Location:",
notesHeadingText = "Observations & Notes:"
) {
// --- Font Family Constant ---
const FONT_FAMILY = "Caveat"; // A nice handwritten-style font
// --- Helper Function to Load Google Font ---
async function loadGoogleFont(fontName, weights = ['400', '700']) {
const fontId = `google-font-${fontName.replace(/\s+/g, '-')}`;
if (document.getElementById(fontId)) {
// Link tag exists, implies font loading was already initiated.
// We just need to check/wait for its readiness.
return document.fonts.load(`1em "${fontName}"`)
.then(() => {
// console.log(`Font "${fontName}" confirmed ready (pre-existing request).`);
return true;
})
.catch(() => {
// console.warn(`Font "${fontName}" (pre-existing request) not ready or failed to load.`);
return false; // Signal that font is not available
});
}
return new Promise((resolve) => { // Simplified to always resolve, returning true/false
const link = document.createElement('link');
link.id = fontId;
link.rel = 'stylesheet';
let fontQuery = fontName.replace(/\s+/g, '+');
if (weights && weights.length > 0) {
fontQuery += `:wght@${weights.join(';')}`;
}
link.href = `https://fonts.googleapis.com/css2?family=${fontQuery}&display=swap`;
link.onload = async () => {
try {
await document.fonts.load(`1em "${fontName}"`); // Ensure font is usable
// console.log(`Font "${fontName}" loaded and ready.`);
resolve(true);
} catch (e) {
// console.error(`Error confirming font "${fontName}" readiness after stylesheet load:`, e);
resolve(false); // Font might not be the one requested, but proceed with fallback
}
};
link.onerror = () => {
// console.error(`Failed to load Google Font stylesheet for "${fontName}".`);
resolve(false); // Critical failure to load stylesheet, proceed with fallback
};
document.head.appendChild(link);
});
}
// --- Load The Chosen Font ---
// We don't strictly need to await the result if we're okay with a flash of unstyled text (FOUT),
// but for canvas rendering, it's crucial the font is ready before drawing text.
await loadGoogleFont(FONT_FAMILY, ['400', '700']); // Load regular and bold weights
// --- Canvas Setup ---
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// --- Page & Style Constants ---
const PAGE_WIDTH = 800;
const PAGE_HEIGHT = 1100;
const MARGIN = 50;
const CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;
const BACKGROUND_COLOR = '#FDF5E6'; // Old Lace (Paper color)
const TEXT_COLOR = '#4A3B31'; // Dark brown (Ink color)
const LINE_COLOR = '#D2B48C'; // Tan (Lighter lines for ruling)
const IMAGE_BORDER_COLOR = '#5D4037'; // Darker brown for image border
const FONT_TITLE_SIZE = 36;
const FONT_HEADING_SIZE = 24;
const FONT_TEXT_SIZE = 20;
const TEXT_LINE_SPACING = FONT_TEXT_SIZE * 1.6; // Spacing for handwritten notes lines
const FONT_STRING = (size, weight = "normal") => `${weight} ${size}px "${FONT_FAMILY}", cursive, sans-serif`;
canvas.width = PAGE_WIDTH;
canvas.height = PAGE_HEIGHT;
// --- 1. Draw Background ---
ctx.fillStyle = BACKGROUND_COLOR;
ctx.fillRect(0, 0, PAGE_WIDTH, PAGE_HEIGHT);
let currentY = MARGIN;
// --- 2. Draw Page Title ---
ctx.fillStyle = TEXT_COLOR;
ctx.font = FONT_STRING(FONT_TITLE_SIZE, "bold");
ctx.textAlign = 'center';
// Adjust Y for better visual centering of text typical in Caviat font
ctx.fillText(pageTitle, PAGE_WIDTH / 2, currentY + FONT_TITLE_SIZE * 0.8);
currentY += FONT_TITLE_SIZE + 30;
// --- 3. Date and Location Fields ---
ctx.font = FONT_STRING(FONT_TEXT_SIZE);
ctx.textAlign = 'left';
ctx.fillStyle = TEXT_COLOR;
const fieldTextBaselineY = currentY + FONT_TEXT_SIZE * 0.8; // Adjusted for Caviat
const fieldLineYOffset = FONT_TEXT_SIZE * 0.2 + fieldTextBaselineY; // Line below baseline
const labelToLineSpacing = 10; // Space between text label and start of line
// Date Field
const dateLabelWidth = ctx.measureText(dateLabelText).width;
ctx.fillText(dateLabelText, MARGIN, fieldTextBaselineY);
ctx.beginPath();
ctx.moveTo(MARGIN + dateLabelWidth + labelToLineSpacing, fieldLineYOffset);
ctx.lineTo(MARGIN + CONTENT_WIDTH / 2 - 20, fieldLineYOffset);
ctx.strokeStyle = LINE_COLOR;
ctx.lineWidth = 1.5;
ctx.stroke();
// Location Field
const locationLabelWidth = ctx.measureText(locationLabelText).width;
ctx.fillText(locationLabelText, MARGIN + CONTENT_WIDTH / 2, fieldTextBaselineY);
ctx.beginPath();
ctx.moveTo(MARGIN + CONTENT_WIDTH / 2 + locationLabelWidth + labelToLineSpacing, fieldLineYOffset);
ctx.lineTo(MARGIN + CONTENT_WIDTH, fieldLineYOffset);
ctx.stroke();
currentY += FONT_TEXT_SIZE + 30;
// --- 4. Draw Image Area ---
const imageContainerX = MARGIN;
const imageContainerY = currentY;
const imageContainerWidth = CONTENT_WIDTH;
const imageContainerHeight = PAGE_HEIGHT * 0.33; // Max height for the image display area
// Calculate image draw dimensions to fit and maintain aspect ratio
let drawW = originalImg.width;
let drawH = originalImg.height;
const imgAspect = originalImg.width / originalImg.height;
const containerAspect = imageContainerWidth / imageContainerHeight;
if (imgAspect > containerAspect) { // Image is wider than container space
drawW = imageContainerWidth;
drawH = drawW / imgAspect;
} else { // Image is taller than container space (or perfectly matches aspect)
drawH = imageContainerHeight;
drawW = drawH * imgAspect;
}
// Center image within its allocated container space
const imgActualX = imageContainerX + (imageContainerWidth - drawW) / 2;
const imgActualY = imageContainerY + (imageContainerHeight - drawH) / 2;
// "Pasted Paper" effect for the image
const paperPadding = 8;
ctx.fillStyle = '#FEFBF3'; // Slightly off-white, like photo paper
ctx.shadowColor = 'rgba(0,0,0,0.25)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
// Draw the "paper" behind the image
ctx.fillRect(
imgActualX - paperPadding / 2,
imgActualY - paperPadding / 2,
drawW + paperPadding,
drawH + paperPadding
);
// Reset shadow for subsequent drawings
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
// Draw the actual image
ctx.drawImage(originalImg, imgActualX, imgActualY, drawW, drawH);
// Add a thin border line around the image
ctx.strokeStyle = IMAGE_BORDER_COLOR;
ctx.lineWidth = 1;
ctx.strokeRect(imgActualX, imgActualY, drawW, drawH);
currentY += imageContainerHeight + 35; // Space after image area
// --- 5. "Observations & Notes" Heading ---
// Ensure there's space for heading and at least two lines of notes
if (currentY < PAGE_HEIGHT - MARGIN - (FONT_HEADING_SIZE + TEXT_LINE_SPACING * 2)) {
ctx.font = FONT_STRING(FONT_HEADING_SIZE, "bold");
ctx.textAlign = 'left';
ctx.fillStyle = TEXT_COLOR;
ctx.fillText(notesHeadingText, MARGIN, currentY + FONT_HEADING_SIZE * 0.8);
currentY += FONT_HEADING_SIZE + 20; // Space after heading
}
// --- 6. Journal Lines for Writing ---
ctx.strokeStyle = LINE_COLOR;
ctx.lineWidth = 1;
// Stop drawing lines if too close to the bottom margin
const bottomLimit = PAGE_HEIGHT - MARGIN - TEXT_LINE_SPACING / 2;
while (currentY < bottomLimit) {
ctx.beginPath();
ctx.moveTo(MARGIN, currentY);
ctx.lineTo(MARGIN + CONTENT_WIDTH, currentY);
ctx.stroke();
currentY += TEXT_LINE_SPACING;
}
return canvas;
}
Apply Changes