Please bookmark this page to avoid losing your image tool!

Victorian Medicine Bottle Label Creator Tool

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
// 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;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The Victorian Medicine Bottle Label Creator Tool allows users to design customized vintage-style labels for medicine bottles. By providing options for input such as product name, slogan, ingredients, directions, and manufacturer details, users can create visually appealing labels that reflect an old-world aesthetic. This tool is particularly useful for historical reenactors, craft brewers, or anyone looking to add a nostalgic touch to their product packaging. With template options for fonts, colors, and layout, it offers an easy way to produce professional-quality labels suitable for personal or commercial use.

Leave a Reply

Your email address will not be published. Required fields are marked *