@cognima/banners
Version:
Biblioteca avançada para geração de banners dinâmicos para diversas plataformas e aplicações
1,215 lines (1,047 loc) • 41.9 kB
JavaScript
;
/**
* Módulo de banners para E-commerce
*
* Este módulo fornece funções para criar banners de e-commerce
* com diferentes estilos e opções de personalização.
*
* @module e-commerce-banner
* @author Cognima Team (melhorado)
* @version 2.0.0
*/
const PImage = require('pureimage');
const fs = require('fs');
const path = require('path');
const utils = require('../utils');
const constants = require('./constants');
const effects = require('./effects');
const imageProcessor = require('./image-processor');
const imageFilters = require('./image-filters');
const validator = require('./validator');
/**
* Cria um banner de produto para e-commerce
*
* @async
* @param {Object} options - Opções de configuração
* @param {string} options.productName - Nome do produto
* @param {string} options.productImageURL - URL da imagem do produto
* @param {string} [options.price] - Preço do produto
* @param {string} [options.currency="R$"] - Símbolo da moeda
* @param {string} [options.discountBadge] - Texto do badge de desconto (ex: "20% OFF")
* @param {string} [options.ctaText="COMPRAR AGORA"] - Texto do botão de call-to-action
* @param {string} [options.backgroundColor="#FFFFFF"] - Cor de fundo
* @param {string} [options.accentColor="#FF6B6B"] - Cor de destaque
* @param {string} [options.textColor="#333333"] - Cor do texto
* @param {string} [options.style="standard"] - Estilo do banner ("standard", "minimal", "bold", "gradient")
* @param {number} [options.width=1200] - Largura do banner em pixels
* @param {number} [options.height=628] - Altura do banner em pixels
* @param {string} [options.font="Poppins"] - Nome da fonte
* @param {boolean} [options.showRating=false] - Se deve mostrar classificação por estrelas
* @param {number} [options.rating=5] - Classificação do produto (1-5)
* @param {boolean} [options.showBorder=true] - Se deve mostrar borda
* @param {string} [options.borderColor="#EEEEEE"] - Cor da borda
* @param {number} [options.borderWidth=2] - Largura da borda em pixels
* @param {number} [options.borderRadius=10] - Raio da borda em pixels
* @param {boolean} [options.showShadow=true] - Se deve mostrar sombra
* @param {string} [options.shadowColor="rgba(0,0,0,0.1)"] - Cor da sombra
* @param {Array<string>} [options.tags=[]] - Tags do produto
* @param {string} [options.backgroundImageURL] - URL da imagem de fundo
* @param {number} [options.backgroundOpacity=0.1] - Opacidade da imagem de fundo
* @param {boolean} [options.applyFilter=false] - Se deve aplicar filtro à imagem do produto
* @param {string} [options.filterType="none"] - Tipo de filtro ("none", "grayscale", "sepia", "blur", etc.)
* @returns {Promise<Buffer>} - Buffer da imagem gerada
*/
async function createProductBanner(options) {
// Validação de parâmetros
validator.validateRequiredParams(options, ['productName', 'productImageURL']);
// Valores padrão
const defaults = {
price: '',
currency: 'R$',
discountBadge: '',
ctaText: 'COMPRAR AGORA',
backgroundColor: '#FFFFFF',
accentColor: '#FF6B6B',
textColor: '#333333',
style: 'standard',
width: 1200,
height: 628,
font: 'Poppins',
showRating: false,
rating: 5,
showBorder: true,
borderColor: '#EEEEEE',
borderWidth: 2,
borderRadius: 10,
showShadow: true,
shadowColor: 'rgba(0,0,0,0.1)',
tags: [],
backgroundImageURL: '',
backgroundOpacity: 0.1,
applyFilter: false,
filterType: 'none'
};
// Mescla as opções com os valores padrão
const settings = { ...defaults, ...options };
// Cria a imagem
const img = PImage.make(settings.width, settings.height);
const ctx = img.getContext('2d');
try {
// Aplica o estilo de acordo com a opção selecionada
switch (settings.style) {
case 'minimal':
await drawMinimalStyle(ctx, settings);
break;
case 'bold':
await drawBoldStyle(ctx, settings);
break;
case 'gradient':
await drawGradientStyle(ctx, settings);
break;
case 'standard':
default:
await drawStandardStyle(ctx, settings);
break;
}
// Retorna o buffer da imagem
return await utils.getBufferFromImage(img);
} catch (error) {
console.error('Erro ao criar banner de produto:', error);
throw error;
}
}
/**
* Desenha o estilo padrão do banner de produto
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawStandardStyle(ctx, settings) {
// Desenha o fundo
ctx.fillStyle = settings.backgroundColor;
ctx.fillRect(0, 0, settings.width, settings.height);
// Se tiver imagem de fundo, desenha com opacidade
if (settings.backgroundImageURL) {
const backgroundImage = await utils.loadImage(settings.backgroundImageURL);
ctx.globalAlpha = settings.backgroundOpacity;
ctx.drawImage(backgroundImage, 0, 0, settings.width, settings.height);
ctx.globalAlpha = 1.0;
}
// Carrega a imagem do produto
let productImage = await utils.loadImage(settings.productImageURL);
// Aplica filtro se necessário
if (settings.applyFilter && settings.filterType !== 'none') {
productImage = await imageFilters.applyFilter(
productImage,
settings.filterType
);
}
// Calcula as dimensões e posições
const productWidth = settings.width * 0.4;
const productHeight = settings.height * 0.7;
const productX = settings.width * 0.1;
const productY = (settings.height - productHeight) / 2;
// Desenha a imagem do produto
utils.drawImageProp(ctx, productImage, productX, productY, productWidth, productHeight);
// Desenha o conteúdo de texto
const textX = productX + productWidth + 50;
const textWidth = settings.width - textX - 50;
// Nome do produto
ctx.font = `bold 48px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapText(ctx, settings.productName, textX, settings.height * 0.3, textWidth, 60);
// Preço
if (settings.price) {
ctx.font = `bold 64px ${settings.font}`;
ctx.fillStyle = settings.accentColor;
ctx.fillText(`${settings.currency} ${settings.price}`, textX, settings.height * 0.5);
}
// Badge de desconto
if (settings.discountBadge) {
const badgeWidth = 120;
const badgeHeight = 40;
const badgeX = textX;
const badgeY = settings.height * 0.55;
// Desenha o fundo do badge
ctx.fillStyle = settings.accentColor;
utils.roundRect(ctx, badgeX, badgeY, badgeWidth, badgeHeight, 5, true);
// Desenha o texto do badge
ctx.font = `bold 20px ${settings.font}`;
ctx.fillStyle = '#FFFFFF';
ctx.textAlign = 'center';
ctx.fillText(settings.discountBadge, badgeX + badgeWidth / 2, badgeY + 28);
ctx.textAlign = 'left';
}
// Classificação por estrelas
if (settings.showRating) {
const starSize = 25;
const starSpacing = 5;
const startX = textX;
const startY = settings.height * 0.6;
for (let i = 0; i < 5; i++) {
const starX = startX + i * (starSize + starSpacing);
const filled = i < settings.rating;
// Desenha estrela
ctx.fillStyle = filled ? '#FFD700' : '#DDDDDD';
utils.drawStar(ctx, starX, startY, starSize);
}
}
// Tags
if (settings.tags.length > 0) {
const tagHeight = 30;
const tagSpacing = 10;
const tagY = settings.height * 0.65;
let currentX = textX;
ctx.font = `16px ${settings.font}`;
for (const tag of settings.tags) {
const tagWidth = ctx.measureText(tag).width + 20;
// Desenha o fundo da tag
ctx.fillStyle = utils.adjustColor(settings.accentColor, 0.2);
utils.roundRect(ctx, currentX, tagY, tagWidth, tagHeight, 15, true);
// Desenha o texto da tag
ctx.fillStyle = settings.textColor;
ctx.textAlign = 'center';
ctx.fillText(tag, currentX + tagWidth / 2, tagY + 20);
ctx.textAlign = 'left';
currentX += tagWidth + tagSpacing;
// Quebra para a próxima linha se necessário
if (currentX + tagWidth > textX + textWidth) {
break;
}
}
}
// Botão CTA
const ctaWidth = 250;
const ctaHeight = 60;
const ctaX = textX;
const ctaY = settings.height * 0.75;
// Desenha o fundo do botão
ctx.fillStyle = settings.accentColor;
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 8, true);
// Desenha o texto do botão
ctx.font = `bold 24px ${settings.font}`;
ctx.fillStyle = '#FFFFFF';
ctx.textAlign = 'center';
ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 38);
ctx.textAlign = 'left';
// Desenha borda se necessário
if (settings.showBorder) {
ctx.strokeStyle = settings.borderColor;
ctx.lineWidth = settings.borderWidth;
utils.roundRect(ctx, settings.borderWidth / 2, settings.borderWidth / 2,
settings.width - settings.borderWidth, settings.height - settings.borderWidth,
settings.borderRadius, false, true);
}
}
/**
* Desenha o estilo minimalista do banner de produto
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawMinimalStyle(ctx, settings) {
// Desenha o fundo
ctx.fillStyle = settings.backgroundColor;
ctx.fillRect(0, 0, settings.width, settings.height);
// Carrega a imagem do produto
let productImage = await utils.loadImage(settings.productImageURL);
// Aplica filtro se necessário
if (settings.applyFilter && settings.filterType !== 'none') {
productImage = await imageFilters.applyFilter(
productImage,
settings.filterType
);
}
// Calcula as dimensões e posições
const productWidth = settings.width * 0.35;
const productHeight = settings.height * 0.65;
const productX = settings.width * 0.55;
const productY = (settings.height - productHeight) / 2;
// Desenha a imagem do produto
utils.drawImageProp(ctx, productImage, productX, productY, productWidth, productHeight);
// Desenha o conteúdo de texto
const textX = settings.width * 0.1;
const textWidth = productX - textX - 50;
// Nome do produto
ctx.font = `300 42px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapText(ctx, settings.productName, textX, settings.height * 0.35, textWidth, 50);
// Preço
if (settings.price) {
ctx.font = `bold 54px ${settings.font}`;
ctx.fillStyle = settings.textColor;
ctx.fillText(`${settings.currency} ${settings.price}`, textX, settings.height * 0.55);
}
// Badge de desconto
if (settings.discountBadge) {
ctx.font = `bold 24px ${settings.font}`;
ctx.fillStyle = settings.accentColor;
ctx.fillText(settings.discountBadge, textX, settings.height * 0.65);
}
// Botão CTA
const ctaWidth = 200;
const ctaHeight = 50;
const ctaX = textX;
const ctaY = settings.height * 0.75;
// Desenha a borda do botão
ctx.strokeStyle = settings.accentColor;
ctx.lineWidth = 2;
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 0, false, true);
// Desenha o texto do botão
ctx.font = `bold 20px ${settings.font}`;
ctx.fillStyle = settings.accentColor;
ctx.textAlign = 'center';
ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 33);
ctx.textAlign = 'left';
}
/**
* Desenha o estilo bold do banner de produto
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawBoldStyle(ctx, settings) {
// Desenha o fundo
ctx.fillStyle = settings.accentColor;
ctx.fillRect(0, 0, settings.width, settings.height);
// Carrega a imagem do produto
let productImage = await utils.loadImage(settings.productImageURL);
// Aplica filtro se necessário
if (settings.applyFilter && settings.filterType !== 'none') {
productImage = await imageFilters.applyFilter(
productImage,
settings.filterType
);
}
// Calcula as dimensões e posições
const productWidth = settings.width * 0.45;
const productHeight = settings.height * 0.8;
const productX = settings.width * 0.5;
const productY = (settings.height - productHeight) / 2;
// Desenha a imagem do produto
utils.drawImageProp(ctx, productImage, productX, productY, productWidth, productHeight);
// Desenha o conteúdo de texto
const textX = settings.width * 0.08;
const textWidth = productX - textX - 30;
// Nome do produto
ctx.font = `bold 52px ${settings.font}`;
ctx.fillStyle = '#FFFFFF';
utils.wrapText(ctx, settings.productName.toUpperCase(), textX, settings.height * 0.3, textWidth, 60);
// Preço
if (settings.price) {
ctx.font = `bold 72px ${settings.font}`;
ctx.fillStyle = '#FFFFFF';
ctx.fillText(`${settings.currency} ${settings.price}`, textX, settings.height * 0.5);
}
// Badge de desconto
if (settings.discountBadge) {
const badgeWidth = 140;
const badgeHeight = 50;
const badgeX = textX;
const badgeY = settings.height * 0.55;
// Desenha o fundo do badge
ctx.fillStyle = '#FFFFFF';
utils.roundRect(ctx, badgeX, badgeY, badgeWidth, badgeHeight, 25, true);
// Desenha o texto do badge
ctx.font = `bold 24px ${settings.font}`;
ctx.fillStyle = settings.accentColor;
ctx.textAlign = 'center';
ctx.fillText(settings.discountBadge, badgeX + badgeWidth / 2, badgeY + 35);
ctx.textAlign = 'left';
}
// Botão CTA
const ctaWidth = 250;
const ctaHeight = 70;
const ctaX = textX;
const ctaY = settings.height * 0.7;
// Desenha o fundo do botão
ctx.fillStyle = '#FFFFFF';
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 35, true);
// Desenha o texto do botão
ctx.font = `bold 26px ${settings.font}`;
ctx.fillStyle = settings.accentColor;
ctx.textAlign = 'center';
ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 43);
ctx.textAlign = 'left';
}
/**
* Desenha o estilo gradient do banner de produto
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawGradientStyle(ctx, settings) {
// Cria um gradiente de fundo
const gradient = ctx.createLinearGradient(0, 0, settings.width, settings.height);
gradient.addColorStop(0, settings.accentColor);
gradient.addColorStop(1, utils.adjustColor(settings.accentColor, -0.3));
// Desenha o fundo
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, settings.width, settings.height);
// Adiciona um padrão de pontos ao fundo
effects.addDotPattern(ctx, settings.width, settings.height, 20, 'rgba(255,255,255,0.1)');
// Carrega a imagem do produto
let productImage = await utils.loadImage(settings.productImageURL);
// Aplica filtro se necessário
if (settings.applyFilter && settings.filterType !== 'none') {
productImage = await imageFilters.applyFilter(
productImage,
settings.filterType
);
}
// Calcula as dimensões e posições
const productWidth = settings.width * 0.4;
const productHeight = settings.height * 0.75;
const productX = settings.width * 0.05;
const productY = (settings.height - productHeight) / 2;
// Adiciona sombra à imagem do produto
if (settings.showShadow) {
ctx.shadowColor = settings.shadowColor;
ctx.shadowBlur = 30;
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
}
// Desenha a imagem do produto
utils.drawImageProp(ctx, productImage, productX, productY, productWidth, productHeight);
// Remove a sombra
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
// Desenha o conteúdo de texto
const textX = productX + productWidth + 60;
const textWidth = settings.width - textX - 60;
// Adiciona um retângulo semi-transparente para o texto
ctx.fillStyle = 'rgba(255,255,255,0.15)';
utils.roundRect(ctx, textX - 20, settings.height * 0.2, textWidth + 40, settings.height * 0.6, 20, true);
// Nome do produto
ctx.font = `bold 48px ${settings.font}`;
ctx.fillStyle = '#FFFFFF';
utils.wrapText(ctx, settings.productName, textX, settings.height * 0.3, textWidth, 60);
// Preço
if (settings.price) {
ctx.font = `bold 64px ${settings.font}`;
ctx.fillStyle = '#FFFFFF';
// Adiciona sombra ao texto do preço
ctx.shadowColor = 'rgba(0,0,0,0.3)';
ctx.shadowBlur = 5;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.fillText(`${settings.currency} ${settings.price}`, textX, settings.height * 0.5);
// Remove a sombra
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
}
// Badge de desconto
if (settings.discountBadge) {
const badgeWidth = 140;
const badgeHeight = 50;
const badgeX = textX;
const badgeY = settings.height * 0.55;
// Desenha o fundo do badge
ctx.fillStyle = '#FFFFFF';
utils.roundRect(ctx, badgeX, badgeY, badgeWidth, badgeHeight, 25, true);
// Desenha o texto do badge
ctx.font = `bold 24px ${settings.font}`;
ctx.fillStyle = settings.accentColor;
ctx.textAlign = 'center';
ctx.fillText(settings.discountBadge, badgeX + badgeWidth / 2, badgeY + 35);
ctx.textAlign = 'left';
}
// Botão CTA
const ctaWidth = 250;
const ctaHeight = 70;
const ctaX = textX;
const ctaY = settings.height * 0.7;
// Desenha o fundo do botão com gradiente
const ctaGradient = ctx.createLinearGradient(ctaX, ctaY, ctaX + ctaWidth, ctaY + ctaHeight);
ctaGradient.addColorStop(0, '#FFFFFF');
ctaGradient.addColorStop(1, '#F0F0F0');
ctx.fillStyle = ctaGradient;
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 35, true);
// Desenha o texto do botão
ctx.font = `bold 26px ${settings.font}`;
ctx.fillStyle = settings.accentColor;
ctx.textAlign = 'center';
ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 43);
ctx.textAlign = 'left';
}
/**
* Cria um banner de promoção para e-commerce
*
* @async
* @param {Object} options - Opções de configuração
* @param {string} options.title - Título da promoção
* @param {string} [options.subtitle] - Subtítulo da promoção
* @param {string} [options.backgroundImageURL] - URL da imagem de fundo
* @param {string} [options.discountText] - Texto de desconto (ex: "50% OFF")
* @param {string} [options.ctaText="COMPRAR AGORA"] - Texto do botão de call-to-action
* @param {string} [options.validUntil] - Data de validade da promoção
* @param {string} [options.backgroundColor="#FF6B6B"] - Cor de fundo
* @param {string} [options.textColor="#FFFFFF"] - Cor do texto
* @param {string} [options.accentColor="#FFFFFF"] - Cor de destaque
* @param {string} [options.style="standard"] - Estilo do banner ("standard", "minimal", "bold", "seasonal")
* @param {number} [options.width=1200] - Largura do banner em pixels
* @param {number} [options.height=628] - Altura do banner em pixels
* @param {string} [options.font="Poppins"] - Nome da fonte
* @param {boolean} [options.showOverlay=true] - Se deve mostrar sobreposição na imagem de fundo
* @param {number} [options.overlayOpacity=0.5] - Opacidade da sobreposição
* @param {string} [options.overlayColor="#000000"] - Cor da sobreposição
* @param {string} [options.season] - Temporada para o estilo seasonal ("christmas", "halloween", "summer", "blackfriday")
* @returns {Promise<Buffer>} - Buffer da imagem gerada
*/
async function createPromotionBanner(options) {
// Validação de parâmetros
validator.validateRequiredParams(options, ['title']);
// Valores padrão
const defaults = {
subtitle: '',
backgroundImageURL: '',
discountText: '',
ctaText: 'COMPRAR AGORA',
validUntil: '',
backgroundColor: '#FF6B6B',
textColor: '#FFFFFF',
accentColor: '#FFFFFF',
style: 'standard',
width: 1200,
height: 628,
font: 'Poppins',
showOverlay: true,
overlayOpacity: 0.5,
overlayColor: '#000000',
season: ''
};
// Mescla as opções com os valores padrão
const settings = { ...defaults, ...options };
// Cria a imagem
const img = PImage.make(settings.width, settings.height);
const ctx = img.getContext('2d');
try {
// Aplica o estilo de acordo com a opção selecionada
switch (settings.style) {
case 'minimal':
await drawMinimalPromotionStyle(ctx, settings);
break;
case 'bold':
await drawBoldPromotionStyle(ctx, settings);
break;
case 'seasonal':
await drawSeasonalPromotionStyle(ctx, settings);
break;
case 'standard':
default:
await drawStandardPromotionStyle(ctx, settings);
break;
}
// Retorna o buffer da imagem
return await utils.getBufferFromImage(img);
} catch (error) {
console.error('Erro ao criar banner de promoção:', error);
throw error;
}
}
/**
* Desenha o estilo padrão do banner de promoção
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawStandardPromotionStyle(ctx, settings) {
// Desenha o fundo
ctx.fillStyle = settings.backgroundColor;
ctx.fillRect(0, 0, settings.width, settings.height);
// Se tiver imagem de fundo, desenha com sobreposição
if (settings.backgroundImageURL) {
const backgroundImage = await utils.loadImage(settings.backgroundImageURL);
ctx.drawImage(backgroundImage, 0, 0, settings.width, settings.height);
// Adiciona sobreposição se necessário
if (settings.showOverlay) {
ctx.fillStyle = utils.hexToRgba(settings.overlayColor, settings.overlayOpacity);
ctx.fillRect(0, 0, settings.width, settings.height);
}
}
// Desenha o conteúdo de texto centralizado
ctx.textAlign = 'center';
// Título
ctx.font = `bold 72px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapTextCentered(ctx, settings.title.toUpperCase(), settings.width / 2, settings.height * 0.35, settings.width * 0.8, 80);
// Subtítulo
if (settings.subtitle) {
ctx.font = `300 36px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapTextCentered(ctx, settings.subtitle, settings.width / 2, settings.height * 0.5, settings.width * 0.7, 50);
}
// Texto de desconto
if (settings.discountText) {
const discountSize = 120;
const discountX = settings.width / 2;
const discountY = settings.height * 0.65;
// Desenha um círculo para o desconto
ctx.fillStyle = settings.accentColor;
ctx.beginPath();
ctx.arc(discountX, discountY, discountSize, 0, Math.PI * 2);
ctx.fill();
// Desenha o texto de desconto
ctx.font = `bold 48px ${settings.font}`;
ctx.fillStyle = settings.backgroundColor;
ctx.fillText(settings.discountText, discountX, discountY + 15);
}
// Botão CTA
const ctaWidth = 300;
const ctaHeight = 70;
const ctaX = (settings.width - ctaWidth) / 2;
const ctaY = settings.height * 0.8;
// Desenha o fundo do botão
ctx.fillStyle = settings.accentColor;
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 35, true);
// Desenha o texto do botão
ctx.font = `bold 28px ${settings.font}`;
ctx.fillStyle = settings.backgroundColor;
ctx.fillText(settings.ctaText, settings.width / 2, ctaY + 45);
// Data de validade
if (settings.validUntil) {
ctx.font = `16px ${settings.font}`;
ctx.fillStyle = settings.textColor;
ctx.fillText(settings.validUntil, settings.width / 2, settings.height * 0.95);
}
// Restaura o alinhamento de texto
ctx.textAlign = 'left';
}
/**
* Desenha o estilo minimalista do banner de promoção
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawMinimalPromotionStyle(ctx, settings) {
// Desenha o fundo
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(0, 0, settings.width, settings.height);
// Adiciona uma borda fina
ctx.strokeStyle = settings.backgroundColor;
ctx.lineWidth = 3;
ctx.strokeRect(20, 20, settings.width - 40, settings.height - 40);
// Desenha o conteúdo de texto centralizado
ctx.textAlign = 'center';
// Título
ctx.font = `bold 64px ${settings.font}`;
ctx.fillStyle = settings.backgroundColor;
utils.wrapTextCentered(ctx, settings.title, settings.width / 2, settings.height * 0.35, settings.width * 0.7, 70);
// Subtítulo
if (settings.subtitle) {
ctx.font = `300 32px ${settings.font}`;
ctx.fillStyle = '#333333';
utils.wrapTextCentered(ctx, settings.subtitle, settings.width / 2, settings.height * 0.5, settings.width * 0.6, 40);
}
// Texto de desconto
if (settings.discountText) {
ctx.font = `bold 96px ${settings.font}`;
ctx.fillStyle = settings.backgroundColor;
ctx.fillText(settings.discountText, settings.width / 2, settings.height * 0.7);
}
// Botão CTA
const ctaWidth = 250;
const ctaHeight = 60;
const ctaX = (settings.width - ctaWidth) / 2;
const ctaY = settings.height * 0.8;
// Desenha a borda do botão
ctx.strokeStyle = settings.backgroundColor;
ctx.lineWidth = 2;
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 0, false, true);
// Desenha o texto do botão
ctx.font = `bold 24px ${settings.font}`;
ctx.fillStyle = settings.backgroundColor;
ctx.fillText(settings.ctaText, settings.width / 2, ctaY + 38);
// Data de validade
if (settings.validUntil) {
ctx.font = `16px ${settings.font}`;
ctx.fillStyle = '#333333';
ctx.fillText(settings.validUntil, settings.width / 2, settings.height * 0.95);
}
// Restaura o alinhamento de texto
ctx.textAlign = 'left';
}
/**
* Desenha o estilo bold do banner de promoção
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawBoldPromotionStyle(ctx, settings) {
// Cria um gradiente de fundo
const gradient = ctx.createLinearGradient(0, 0, settings.width, settings.height);
gradient.addColorStop(0, settings.backgroundColor);
gradient.addColorStop(1, utils.adjustColor(settings.backgroundColor, -0.3));
// Desenha o fundo
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, settings.width, settings.height);
// Adiciona elementos decorativos
for (let i = 0; i < 5; i++) {
const size = Math.random() * 200 + 100;
const x = Math.random() * settings.width;
const y = Math.random() * settings.height;
ctx.fillStyle = utils.hexToRgba(settings.accentColor, 0.1);
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fill();
}
// Desenha o conteúdo de texto centralizado
ctx.textAlign = 'center';
// Texto de desconto
if (settings.discountText) {
ctx.font = `bold 120px ${settings.font}`;
ctx.fillStyle = settings.accentColor;
// Adiciona sombra ao texto
ctx.shadowColor = 'rgba(0,0,0,0.3)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.fillText(settings.discountText, settings.width / 2, settings.height * 0.4);
// Remove a sombra
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
}
// Título
ctx.font = `bold 72px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapTextCentered(ctx, settings.title.toUpperCase(), settings.width / 2, settings.height * 0.55, settings.width * 0.8, 80);
// Subtítulo
if (settings.subtitle) {
ctx.font = `300 36px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapTextCentered(ctx, settings.subtitle, settings.width / 2, settings.height * 0.7, settings.width * 0.7, 50);
}
// Botão CTA
const ctaWidth = 350;
const ctaHeight = 80;
const ctaX = (settings.width - ctaWidth) / 2;
const ctaY = settings.height * 0.8;
// Desenha o fundo do botão
ctx.fillStyle = settings.accentColor;
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 40, true);
// Desenha o texto do botão
ctx.font = `bold 32px ${settings.font}`;
ctx.fillStyle = settings.backgroundColor;
ctx.fillText(settings.ctaText, settings.width / 2, ctaY + 50);
// Data de validade
if (settings.validUntil) {
ctx.font = `18px ${settings.font}`;
ctx.fillStyle = settings.textColor;
ctx.fillText(settings.validUntil, settings.width / 2, settings.height * 0.95);
}
// Restaura o alinhamento de texto
ctx.textAlign = 'left';
}
/**
* Desenha o estilo seasonal do banner de promoção
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawSeasonalPromotionStyle(ctx, settings) {
// Configurações específicas para cada temporada
const seasonalSettings = {
christmas: {
backgroundColor: '#D42426',
accentColor: '#0F8A5F',
textColor: '#FFFFFF',
decorations: ['snowflake', 'gift', 'tree']
},
halloween: {
backgroundColor: '#FF6600',
accentColor: '#000000',
textColor: '#FFFFFF',
decorations: ['pumpkin', 'ghost', 'bat']
},
summer: {
backgroundColor: '#00BFFF',
accentColor: '#FFD700',
textColor: '#FFFFFF',
decorations: ['sun', 'palm', 'wave']
},
blackfriday: {
backgroundColor: '#000000',
accentColor: '#FF0000',
textColor: '#FFFFFF',
decorations: ['tag', 'cart', 'discount']
}
};
// Usa as configurações da temporada selecionada ou as configurações padrão
const season = settings.season.toLowerCase();
const currentSeason = seasonalSettings[season] || {
backgroundColor: settings.backgroundColor,
accentColor: settings.accentColor,
textColor: settings.textColor,
decorations: []
};
// Desenha o fundo
ctx.fillStyle = currentSeason.backgroundColor;
ctx.fillRect(0, 0, settings.width, settings.height);
// Se tiver imagem de fundo, desenha com sobreposição
if (settings.backgroundImageURL) {
const backgroundImage = await utils.loadImage(settings.backgroundImageURL);
ctx.drawImage(backgroundImage, 0, 0, settings.width, settings.height);
// Adiciona sobreposição se necessário
if (settings.showOverlay) {
ctx.fillStyle = utils.hexToRgba(currentSeason.backgroundColor, settings.overlayOpacity);
ctx.fillRect(0, 0, settings.width, settings.height);
}
}
// Adiciona decorações sazonais
if (currentSeason.decorations.length > 0) {
await addSeasonalDecorations(ctx, settings, currentSeason.decorations);
}
// Desenha o conteúdo de texto centralizado
ctx.textAlign = 'center';
// Título
ctx.font = `bold 72px ${settings.font}`;
ctx.fillStyle = currentSeason.textColor;
utils.wrapTextCentered(ctx, settings.title.toUpperCase(), settings.width / 2, settings.height * 0.35, settings.width * 0.8, 80);
// Subtítulo
if (settings.subtitle) {
ctx.font = `300 36px ${settings.font}`;
ctx.fillStyle = currentSeason.textColor;
utils.wrapTextCentered(ctx, settings.subtitle, settings.width / 2, settings.height * 0.5, settings.width * 0.7, 50);
}
// Texto de desconto
if (settings.discountText) {
const discountSize = 130;
const discountX = settings.width / 2;
const discountY = settings.height * 0.65;
// Desenha um círculo para o desconto
ctx.fillStyle = currentSeason.accentColor;
ctx.beginPath();
ctx.arc(discountX, discountY, discountSize, 0, Math.PI * 2);
ctx.fill();
// Desenha o texto de desconto
ctx.font = `bold 54px ${settings.font}`;
ctx.fillStyle = currentSeason.textColor;
ctx.fillText(settings.discountText, discountX, discountY + 15);
}
// Botão CTA
const ctaWidth = 300;
const ctaHeight = 70;
const ctaX = (settings.width - ctaWidth) / 2;
const ctaY = settings.height * 0.8;
// Desenha o fundo do botão
ctx.fillStyle = currentSeason.accentColor;
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 35, true);
// Desenha o texto do botão
ctx.font = `bold 28px ${settings.font}`;
ctx.fillStyle = currentSeason.textColor;
ctx.fillText(settings.ctaText, settings.width / 2, ctaY + 45);
// Data de validade
if (settings.validUntil) {
ctx.font = `16px ${settings.font}`;
ctx.fillStyle = currentSeason.textColor;
ctx.fillText(settings.validUntil, settings.width / 2, settings.height * 0.95);
}
// Restaura o alinhamento de texto
ctx.textAlign = 'left';
}
/**
* Adiciona decorações sazonais ao banner
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
* @param {Array<string>} decorations - Lista de decorações a serem adicionadas
*/
async function addSeasonalDecorations(ctx, settings, decorations) {
// Implementação simplificada - em uma versão real, carregaria imagens de decorações
// ou desenharia formas específicas para cada tipo de decoração
for (let i = 0; i < 10; i++) {
const size = Math.random() * 50 + 20;
const x = Math.random() * settings.width;
const y = Math.random() * settings.height;
ctx.fillStyle = utils.hexToRgba('#FFFFFF', 0.2);
// Escolhe uma decoração aleatória da lista
const decoration = decorations[Math.floor(Math.random() * decorations.length)];
switch (decoration) {
case 'snowflake':
drawSnowflake(ctx, x, y, size);
break;
case 'gift':
drawGift(ctx, x, y, size);
break;
case 'tree':
drawTree(ctx, x, y, size);
break;
case 'pumpkin':
drawPumpkin(ctx, x, y, size);
break;
case 'ghost':
drawGhost(ctx, x, y, size);
break;
case 'bat':
drawBat(ctx, x, y, size);
break;
case 'sun':
drawSun(ctx, x, y, size);
break;
case 'palm':
drawPalm(ctx, x, y, size);
break;
case 'wave':
drawWave(ctx, x, y, size);
break;
case 'tag':
drawTag(ctx, x, y, size);
break;
case 'cart':
drawCart(ctx, x, y, size);
break;
case 'discount':
drawDiscount(ctx, x, y, size);
break;
default:
// Desenha um círculo como fallback
ctx.beginPath();
ctx.arc(x, y, size / 2, 0, Math.PI * 2);
ctx.fill();
break;
}
}
}
// Funções para desenhar decorações sazonais
// Estas são implementações simplificadas - em uma versão real, seriam mais detalhadas
function drawSnowflake(ctx, x, y, size) {
ctx.beginPath();
for (let i = 0; i < 6; i++) {
const angle = (Math.PI / 3) * i;
ctx.moveTo(x, y);
ctx.lineTo(x + Math.cos(angle) * size, y + Math.sin(angle) * size);
}
ctx.stroke();
}
function drawGift(ctx, x, y, size) {
ctx.fillRect(x - size / 2, y - size / 2, size, size);
ctx.fillRect(x - size / 4, y - size / 2 - size / 4, size / 2, size * 1.5);
ctx.fillRect(x - size / 2 - size / 4, y - size / 4, size * 1.5, size / 2);
}
function drawTree(ctx, x, y, size) {
ctx.beginPath();
ctx.moveTo(x, y - size);
ctx.lineTo(x + size / 2, y);
ctx.lineTo(x - size / 2, y);
ctx.closePath();
ctx.fill();
ctx.fillRect(x - size / 6, y, size / 3, size / 2);
}
function drawPumpkin(ctx, x, y, size) {
ctx.beginPath();
ctx.arc(x, y, size / 2, 0, Math.PI * 2);
ctx.fill();
ctx.fillRect(x - size / 10, y - size / 2 - size / 5, size / 5, size / 5);
}
function drawGhost(ctx, x, y, size) {
ctx.beginPath();
ctx.arc(x, y - size / 3, size / 2, Math.PI, 0, true);
ctx.lineTo(x + size / 2, y + size / 2);
ctx.lineTo(x + size / 4, y + size / 4);
ctx.lineTo(x, y + size / 2);
ctx.lineTo(x - size / 4, y + size / 4);
ctx.lineTo(x - size / 2, y + size / 2);
ctx.closePath();
ctx.fill();
}
function drawBat(ctx, x, y, size) {
ctx.beginPath();
ctx.arc(x, y, size / 4, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(x - size / 2, y - size / 4, size / 3, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(x + size / 2, y - size / 4, size / 3, 0, Math.PI * 2);
ctx.fill();
}
function drawSun(ctx, x, y, size) {
ctx.beginPath();
ctx.arc(x, y, size / 2, 0, Math.PI * 2);
ctx.fill();
for (let i = 0; i < 8; i++) {
const angle = (Math.PI / 4) * i;
ctx.beginPath();
ctx.moveTo(x + Math.cos(angle) * size / 2, y + Math.sin(angle) * size / 2);
ctx.lineTo(x + Math.cos(angle) * size, y + Math.sin(angle) * size);
ctx.stroke();
}
}
function drawPalm(ctx, x, y, size) {
ctx.fillRect(x - size / 10, y, size / 5, size);
for (let i = 0; i < 5; i++) {
const angle = (Math.PI / 6) + (Math.PI / 12) * i;
ctx.beginPath();
ctx.ellipse(x, y, size / 3, size, angle, 0, Math.PI);
ctx.fill();
}
}
function drawWave(ctx, x, y, size) {
ctx.beginPath();
ctx.moveTo(x - size, y);
for (let i = 0; i < 4; i++) {
const cp1x = x - size + (i * size / 2);
const cp1y = y + ((i % 2 === 0) ? -size / 3 : size / 3);
const cp2x = x - size + (i * size / 2) + size / 4;
const cp2y = y + ((i % 2 === 0) ? size / 3 : -size / 3);
const endX = x - size + ((i + 1) * size / 2);
const endY = y;
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endX, endY);
}
ctx.stroke();
}
function drawTag(ctx, x, y, size) {
ctx.beginPath();
ctx.moveTo(x, y - size / 2);
ctx.lineTo(x + size / 2, y);
ctx.lineTo(x, y + size / 2);
ctx.lineTo(x - size / 2, y);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.arc(x, y, size / 6, 0, Math.PI * 2);
ctx.stroke();
}
function drawCart(ctx, x, y, size) {
ctx.beginPath();
ctx.moveTo(x - size / 2, y);
ctx.lineTo(x + size / 2, y);
ctx.lineTo(x + size / 3, y - size / 2);
ctx.lineTo(x - size / 3, y - size / 2);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.arc(x - size / 4, y + size / 6, size / 6, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(x + size / 4, y + size / 6, size / 6, 0, Math.PI * 2);
ctx.fill();
}
function drawDiscount(ctx, x, y, size) {
ctx.beginPath();
ctx.arc(x, y, size / 2, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x - size / 3, y - size / 3);
ctx.lineTo(x + size / 3, y + size / 3);
ctx.stroke();
}
module.exports = {
createProductBanner,
createPromotionBanner
};