You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, articleTitle = "Изображение", language = "ru", mockText = "") {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set standard canvas dimensions for the Wikipedia page template
const width = 1000;
const height = 800;
canvas.width = width;
canvas.height = height;
const isRu = language.toLowerCase() === 'ru';
// Theme Colors (Wikipedia Vector Theme)
const bgPage = '#f6f6f6';
const bgContent = '#ffffff';
const textMain = '#202122';
const textMuted = '#54595d';
const linkColor = '#0645ad';
const borderItem = '#a2a9b1';
const bgInfobox = '#f8f9fa';
// 1. Fill Page Background (Sidebar and top bar area)
ctx.fillStyle = bgPage;
ctx.fillRect(0, 0, width, height);
// 2. Main Content Area Box
ctx.fillStyle = bgContent;
ctx.fillRect(160, 50, width - 160, height - 50);
ctx.strokeStyle = borderItem;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(160, height);
ctx.lineTo(160, 50);
ctx.lineTo(width, 50);
ctx.stroke();
// 3. Top UI (User Navigation Links)
ctx.fillStyle = linkColor;
ctx.font = '12px sans-serif';
ctx.textAlign = 'right';
ctx.fillText(isRu ? 'Войти' : 'Log in', width - 20, 20);
ctx.fillText(isRu ? 'Создать учётную запись' : 'Create account', width - 70, 20);
ctx.fillText(isRu ? 'Вклад' : 'Contributions', width - 230, 20);
ctx.fillText(isRu ? 'Обсуждение' : 'Talk', width - 280, 20);
ctx.textAlign = 'left';
// 4. Logo Area
// Draw mock globe
ctx.beginPath();
ctx.arc(80, 45, 25, 0, Math.PI * 2);
ctx.fillStyle = '#eaecf0';
ctx.fill();
ctx.strokeStyle = borderItem;
ctx.stroke();
// Draw text logo
ctx.fillStyle = '#000000';
ctx.font = 'normal 18px Georgia, "Times New Roman", serif';
ctx.textAlign = 'center';
ctx.fillText(isRu ? 'ВИКИПЕДИЯ' : 'WIKIPEDIA', 80, 95);
ctx.font = '10px sans-serif';
ctx.fillText(isRu ? 'Свободная энциклопедия' : 'The Free Encyclopedia', 80, 110);
ctx.textAlign = 'left';
// 5. Sidebar Navigation
ctx.fillStyle = linkColor;
ctx.font = '12px sans-serif';
const sidebarLinks = isRu ?
['Заглавная страница', 'Рубрикация', 'Указатель А — Я', 'Избранные статьи', 'Случайная статья', 'Текущие события', 'Сообщить об ошибке'] :
['Main page', 'Contents', 'Current events', 'Random article', 'About Wikipedia', 'Contact us', 'Donate'];
sidebarLinks.forEach((text, i) => {
ctx.fillText(text, 20, 160 + i * 22);
});
// 6. Navigation Tabs
const drawTab = (x, y, w, h, text, isActive, isBold) => {
ctx.fillStyle = isActive ? bgContent : '#eaecf0';
ctx.fillRect(x, y, w, h);
ctx.strokeStyle = borderItem;
ctx.strokeRect(x, y, w, h);
if (isActive) {
// Overlap bottom border with content area
ctx.fillStyle = bgContent;
ctx.fillRect(x + 1, y + h - 1, w - 2, 2);
}
ctx.fillStyle = isActive ? textMain : linkColor;
ctx.font = `${isBold ? 'bold ' : ''}13px sans-serif`;
ctx.textAlign = 'center';
ctx.fillText(text, x + w / 2, y + 17);
ctx.textAlign = 'left';
};
drawTab(170, 24, 70, 26, isRu ? 'Статья' : 'Article', true, true);
drawTab(240, 24, 90, 26, isRu ? 'Обсуждение' : 'Talk', false, false);
drawTab(650, 24, 70, 26, isRu ? 'Читать' : 'Read', true, true);
drawTab(720, 24, 70, 26, isRu ? 'Править' : 'Edit', false, false);
drawTab(790, 24, 120, 26, isRu ? 'История' : 'View history', false, false);
// 7. Search Bar
ctx.fillStyle = bgContent;
ctx.fillRect(width - 200, 20, 180, 24);
ctx.strokeStyle = borderItem;
ctx.strokeRect(width - 200, 20, 180, 24);
ctx.fillStyle = textMuted;
ctx.font = 'italic 12px sans-serif';
ctx.fillText(isRu ? 'Искать в Википедии' : 'Search Wikipedia', width - 190, 36);
// 8. Article Title & Line separator
const titleX = 190;
let titleY = 100;
ctx.fillStyle = textMain;
ctx.font = 'normal 32px Georgia, "Times New Roman", Times, serif';
ctx.fillText(articleTitle, titleX, titleY);
ctx.beginPath();
ctx.moveTo(titleX, titleY + 15);
ctx.lineTo(width - 30, titleY + 15);
ctx.strokeStyle = borderItem;
ctx.stroke();
// 9. Process and Calculate Image Size
const infoW = 320;
const infoX = width - infoW - 30; // Float Right
const infoY = titleY + 30;
const maxImgW = infoW - 20;
const maxImgH = 320;
const imgW = originalImg.naturalWidth || originalImg.width || 1;
const imgH = originalImg.naturalHeight || originalImg.height || 1;
// Scale image to fit inside infobox constraints
let scale = Math.min(maxImgW / imgW, maxImgH / imgH);
if (scale > 1) scale = 1;
const drawW = imgW * scale;
const drawH = imgH * scale;
const rowData = isRu ?
[['Описание', 'Загруженный пользователем медиафайл'], ['Формат', 'Изображение'], ['Дата создания', new Date().toLocaleDateString('ru-RU')], ['Автор', 'Участник Википедии'], ['Лицензия', 'Общественное достояние']] :
[['Description', 'User uploaded media file'], ['Format', 'Digital Image'], ['Date created', new Date().toLocaleDateString('en-US')], ['Author', 'Wikipedia Contributor'], ['License', 'Public Domain']];
const infoboxH = 40 + drawH + 30 + (rowData.length * 28) + 10;
// 10. Draw Infobox Base
ctx.fillStyle = bgInfobox;
ctx.fillRect(infoX, infoY, infoW, infoboxH);
ctx.strokeStyle = '#c8ccd1';
ctx.strokeRect(infoX, infoY, infoW, infoboxH);
// Infobox Header
ctx.fillStyle = '#eaecf0';
ctx.fillRect(infoX, infoY, infoW, 30);
ctx.fillStyle = textMain;
ctx.font = 'bold 13px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(isRu ? 'Информация о файле / Изображение' : 'File Information / Image Details', infoX + infoW / 2, infoY + 20);
// 11. Draw Image inside Infobox
ctx.drawImage(originalImg, infoX + (infoW - drawW) / 2, infoY + 40, drawW, drawH);
// Image Caption
ctx.fillStyle = textMuted;
ctx.font = '12px sans-serif';
ctx.fillText(isRu ? 'Оригинальный размер миниатюры' : 'Thumbnail presentation of the file', infoX + infoW / 2, infoY + 40 + drawH + 18);
ctx.textAlign = 'left';
// 12. Draw Infobox Rows
let rowY = infoY + 40 + drawH + 40;
rowData.forEach(([key, val]) => {
ctx.beginPath();
ctx.moveTo(infoX + 10, rowY - 18);
ctx.lineTo(infoX + infoW - 10, rowY - 18);
ctx.strokeStyle = '#c8ccd1';
ctx.stroke();
ctx.fillStyle = textMain;
ctx.font = 'bold 12px sans-serif';
ctx.fillText(key, infoX + 10, rowY);
ctx.font = '12px sans-serif';
ctx.fillText(val, infoX + 110, rowY);
rowY += 28;
});
// 13. Main Article Text (Wrapped)
let textX = 190;
let textY = infoY + 10;
const maxTextW = infoX - textX - 30; // Make bounds right before the infobox
// Disambiguation snippet
ctx.fillStyle = textMuted;
ctx.font = 'italic 13px sans-serif';
ctx.fillText(isRu ? 'У этого термина существуют и другие значения, см. Изображение (значения).' : 'For other uses, see Image (disambiguation).', textX, textY);
textY += 30;
// Body text
ctx.fillStyle = textMain;
ctx.font = '14px sans-serif';
const defaultTextRu = "На этой странице представлено цифровое изображение, загруженное конечным пользователем. Визуальное оформление страницы сгенерировано динамически и стилизовано под стандарты типичной энциклопедической статьи.\n\nВикипедия — это свободная энциклопедия, которую может редактировать каждый. Шаблон разработан исключительно для графического отображения медиафайлов с применением Canvas API, формируя аутентичный визуальный интерфейс векторной оболочки.\n\nПоддерживается выравнивание текста, отрисовка таблиц с метаданными и корректное масштабирование.";
const defaultTextEn = "This page presents a digital image uploaded by an end-user. The visual layout of the page is dynamically generated and styled symmetrically to typical encyclopedia article properties.\n\nWikipedia is a free online encyclopedia, created and edited by volunteers around the world. This template is designed exclusively for graphic displaying of media with the Canvas API, providing an authentic representation of the active document interface.\n\nText alignment, metadata table rendering, and responsive dimensional scaling are actively supported.";
const finalMockText = mockText.trim() === "" ? (isRu ? defaultTextRu : defaultTextEn) : mockText;
const wrapText = (text, x, y, maxWidth, lineHeight) => {
const paragraphs = text.split('\n');
let currentY = y;
paragraphs.forEach(p => {
if (p.trim() === '') {
currentY += lineHeight * 0.5;
return;
}
let words = p.split(' ');
let line = '';
words.forEach(word => {
let testLine = line + word + ' ';
let metrics = ctx.measureText(testLine);
if (metrics.width > maxWidth && line !== '') {
ctx.fillText(line, x, currentY);
line = word + ' ';
currentY += lineHeight;
} else {
line = testLine;
}
});
ctx.fillText(line, x, currentY);
currentY += lineHeight + 8;
});
return currentY;
};
textY = wrapText(finalMockText, textX, textY, maxTextW, 20);
// 14. Fake Table of Contents / Sub-header
textY += 15;
ctx.fillStyle = textMain;
ctx.font = 'normal 22px Georgia, serif';
ctx.fillText(isRu ? 'Содержание' : 'Contents', textX, textY);
ctx.beginPath();
ctx.moveTo(textX, textY + 10);
ctx.lineTo(textX + maxTextW, textY + 10);
ctx.strokeStyle = borderItem;
ctx.stroke();
ctx.fillStyle = linkColor;
ctx.font = '14px sans-serif';
ctx.fillText(isRu ? '1. Описание файла' : '1. File Description', textX, textY + 40);
ctx.fillText(isRu ? '2. История изменений' : '2. Revision History', textX, textY + 65);
ctx.fillText(isRu ? '3. Лицензирование' : '3. Licensing', textX, textY + 90);
return canvas;
}
Apply Changes