@cognima/banners
Version:
Biblioteca avançada para geração de banners dinâmicos para diversas plataformas e aplicações
1,090 lines (925 loc) • 38.1 kB
JavaScript
"use strict";
/**
* Módulo de banners para Marketing
*
* Este módulo fornece funções para criar banners de marketing
* com diferentes estilos e opções de personalização.
*
* @module marketing-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 para anúncio de marketing
*
* @async
* @param {Object} options - Opções de configuração
* @param {string} options.headline - Título principal do anúncio
* @param {string} [options.subheadline] - Subtítulo do anúncio
* @param {string} [options.imageURL] - URL da imagem principal
* @param {string} [options.logoURL] - URL do logo da empresa
* @param {string} [options.ctaText="SAIBA MAIS"] - Texto do botão de call-to-action
* @param {string} [options.ctaURL] - URL para o botão de call-to-action
* @param {string} [options.backgroundColor="#FFFFFF"] - Cor de fundo
* @param {string} [options.textColor="#333333"] - Cor do texto
* @param {string} [options.accentColor="#FF6B6B"] - Cor de destaque
* @param {string} [options.style="standard"] - Estilo do banner ("standard", "minimal", "bold", "corporate")
* @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.showLogo=true] - Se deve mostrar o logo
* @param {number} [options.logoSize=80] - Tamanho do logo em pixels
* @param {string} [options.logoPosition="top-left"] - Posição do logo ("top-left", "top-right", "bottom-left", "bottom-right")
* @param {boolean} [options.showBorder=false] - Se deve mostrar borda
* @param {string} [options.borderColor="#EEEEEE"] - Cor da borda
* @param {number} [options.borderWidth=2] - Largura da borda em pixels
* @param {Array<string>} [options.tags=[]] - Tags do anúncio
* @param {boolean} [options.showOverlay=false] - Se deve mostrar sobreposição na imagem
* @param {number} [options.overlayOpacity=0.3] - Opacidade da sobreposição
* @param {string} [options.overlayColor="#000000"] - Cor da sobreposição
* @param {boolean} [options.applyFilter=false] - Se deve aplicar filtro à imagem
* @param {string} [options.filterType="none"] - Tipo de filtro ("none", "grayscale", "sepia", "blur", etc.)
* @returns {Promise<Buffer>} - Buffer da imagem gerada
*/
async function createAdvertisementBanner(options) {
// Validação de parâmetros
validator.validateRequiredParams(options, ['headline']);
// Valores padrão
const defaults = {
subheadline: '',
imageURL: '',
logoURL: '',
ctaText: 'SAIBA MAIS',
ctaURL: '',
backgroundColor: '#FFFFFF',
textColor: '#333333',
accentColor: '#FF6B6B',
style: 'standard',
width: 1200,
height: 628,
font: 'Poppins',
showLogo: true,
logoSize: 80,
logoPosition: 'top-left',
showBorder: false,
borderColor: '#EEEEEE',
borderWidth: 2,
tags: [],
showOverlay: false,
overlayOpacity: 0.3,
overlayColor: '#000000',
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 drawMinimalAdStyle(ctx, settings);
break;
case 'bold':
await drawBoldAdStyle(ctx, settings);
break;
case 'corporate':
await drawCorporateAdStyle(ctx, settings);
break;
case 'standard':
default:
await drawStandardAdStyle(ctx, settings);
break;
}
// Retorna o buffer da imagem
return await utils.getBufferFromImage(img);
} catch (error) {
console.error('Erro ao criar banner de anúncio:', error);
throw error;
}
}
/**
* Desenha o estilo padrão do banner de anúncio
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawStandardAdStyle(ctx, settings) {
// Desenha o fundo
ctx.fillStyle = settings.backgroundColor;
ctx.fillRect(0, 0, settings.width, settings.height);
// Se tiver imagem, desenha com sobreposição se necessário
if (settings.imageURL) {
const image = await utils.loadImage(settings.imageURL);
// Aplica filtro se necessário
let processedImage = image;
if (settings.applyFilter && settings.filterType !== 'none') {
processedImage = await imageFilters.applyFilter(
image,
settings.filterType
);
}
// Desenha a imagem
utils.drawImageProp(ctx, processedImage, 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 logo se necessário
if (settings.showLogo && settings.logoURL) {
await drawLogo(ctx, settings);
}
// Calcula as posições para o texto
const textX = settings.width * 0.1;
const textWidth = settings.width * 0.8;
// Título principal
ctx.font = `bold 64px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapText(ctx, settings.headline, textX, settings.height * 0.4, textWidth, 70);
// Subtítulo
if (settings.subheadline) {
ctx.font = `300 32px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapText(ctx, settings.subheadline, textX, settings.height * 0.6, textWidth, 40);
}
// Botão CTA
const ctaWidth = 200;
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';
// Tags
if (settings.tags.length > 0) {
const tagHeight = 30;
const tagSpacing = 10;
const tagY = settings.height * 0.85;
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;
}
}
}
// 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,
0, false, true);
}
}
/**
* Desenha o estilo minimalista do banner de anúncio
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawMinimalAdStyle(ctx, settings) {
// Desenha o fundo
ctx.fillStyle = settings.backgroundColor;
ctx.fillRect(0, 0, settings.width, settings.height);
// Desenha o logo se necessário
if (settings.showLogo && settings.logoURL) {
await drawLogo(ctx, settings);
}
// Calcula as posições para o texto
const textX = settings.width * 0.1;
const textWidth = settings.width * 0.8;
// Título principal
ctx.font = `300 60px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapText(ctx, settings.headline, textX, settings.height * 0.4, textWidth, 70);
// Linha de destaque
const lineY = settings.height * 0.5;
const lineWidth = settings.width * 0.2;
ctx.strokeStyle = settings.accentColor;
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(textX, lineY);
ctx.lineTo(textX + lineWidth, lineY);
ctx.stroke();
// Subtítulo
if (settings.subheadline) {
ctx.font = `300 28px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapText(ctx, settings.subheadline, textX, settings.height * 0.6, textWidth, 40);
}
// Botão CTA
const ctaWidth = 180;
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';
// Se tiver imagem, desenha na metade direita
if (settings.imageURL) {
const image = await utils.loadImage(settings.imageURL);
// Aplica filtro se necessário
let processedImage = image;
if (settings.applyFilter && settings.filterType !== 'none') {
processedImage = await imageFilters.applyFilter(
image,
settings.filterType
);
}
// Desenha a imagem na metade direita
const imageX = settings.width * 0.55;
const imageWidth = settings.width * 0.4;
const imageHeight = settings.height * 0.7;
const imageY = (settings.height - imageHeight) / 2;
utils.drawImageProp(ctx, processedImage, imageX, imageY, imageWidth, imageHeight);
}
}
/**
* Desenha o estilo bold do banner de anúncio
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawBoldAdStyle(ctx, settings) {
// Desenha o fundo com cor de destaque
ctx.fillStyle = settings.accentColor;
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('#FFFFFF', 0.1);
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fill();
}
// Se tiver imagem, desenha com sobreposição
if (settings.imageURL) {
const image = await utils.loadImage(settings.imageURL);
// Aplica filtro se necessário
let processedImage = image;
if (settings.applyFilter && settings.filterType !== 'none') {
processedImage = await imageFilters.applyFilter(
image,
settings.filterType
);
}
// Desenha a imagem na metade direita
const imageX = settings.width * 0.5;
const imageWidth = settings.width * 0.5;
const imageHeight = settings.height;
utils.drawImageProp(ctx, processedImage, imageX, 0, imageWidth, imageHeight);
// Adiciona sobreposição gradiente
const gradient = ctx.createLinearGradient(imageX, 0, imageX + imageWidth / 2, 0);
gradient.addColorStop(0, settings.accentColor);
gradient.addColorStop(1, 'transparent');
ctx.fillStyle = gradient;
ctx.fillRect(imageX, 0, imageWidth / 2, settings.height);
}
// Desenha o logo se necessário
if (settings.showLogo && settings.logoURL) {
await drawLogo(ctx, settings);
}
// Calcula as posições para o texto
const textX = settings.width * 0.08;
const textWidth = settings.width * 0.4;
// Título principal
ctx.font = `bold 72px ${settings.font}`;
ctx.fillStyle = '#FFFFFF';
utils.wrapText(ctx, settings.headline.toUpperCase(), textX, settings.height * 0.4, textWidth, 80);
// Subtítulo
if (settings.subheadline) {
ctx.font = `300 32px ${settings.font}`;
ctx.fillStyle = '#FFFFFF';
utils.wrapText(ctx, settings.subheadline, textX, settings.height * 0.65, textWidth, 40);
}
// Botão CTA
const ctaWidth = 220;
const ctaHeight = 70;
const ctaX = textX;
const ctaY = settings.height * 0.8;
// 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 corporativo do banner de anúncio
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawCorporateAdStyle(ctx, settings) {
// Desenha o fundo
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(0, 0, settings.width, settings.height);
// Adiciona uma barra lateral com a cor de destaque
const sidebarWidth = settings.width * 0.05;
ctx.fillStyle = settings.accentColor;
ctx.fillRect(0, 0, sidebarWidth, settings.height);
// Se tiver imagem, desenha com sobreposição se necessário
if (settings.imageURL) {
const image = await utils.loadImage(settings.imageURL);
// Aplica filtro se necessário
let processedImage = image;
if (settings.applyFilter && settings.filterType !== 'none') {
processedImage = await imageFilters.applyFilter(
image,
settings.filterType
);
}
// Desenha a imagem como fundo com baixa opacidade
ctx.globalAlpha = 0.1;
utils.drawImageProp(ctx, processedImage, 0, 0, settings.width, settings.height);
ctx.globalAlpha = 1.0;
}
// Desenha o logo se necessário
if (settings.showLogo && settings.logoURL) {
// Força a posição do logo para o canto superior direito
const originalPosition = settings.logoPosition;
settings.logoPosition = 'top-right';
await drawLogo(ctx, settings);
settings.logoPosition = originalPosition;
}
// Calcula as posições para o texto
const textX = sidebarWidth + settings.width * 0.05;
const textWidth = settings.width * 0.7;
// Título principal
ctx.font = `bold 54px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapText(ctx, settings.headline, textX, settings.height * 0.4, textWidth, 60);
// Linha de destaque
const lineY = settings.height * 0.5;
const lineWidth = settings.width * 0.2;
ctx.strokeStyle = settings.accentColor;
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(textX, lineY);
ctx.lineTo(textX + lineWidth, lineY);
ctx.stroke();
// Subtítulo
if (settings.subheadline) {
ctx.font = `300 28px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapText(ctx, settings.subheadline, textX, settings.height * 0.6, textWidth, 40);
}
// Botão CTA
const ctaWidth = 220;
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, 0, 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,
0, false, true);
}
}
/**
* Desenha o logo na posição especificada
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawLogo(ctx, settings) {
if (!settings.logoURL) return;
try {
const logo = await utils.loadImage(settings.logoURL);
const padding = 20;
let x, y;
// Determina a posição do logo
switch (settings.logoPosition) {
case 'top-right':
x = settings.width - settings.logoSize - padding;
y = padding;
break;
case 'bottom-left':
x = padding;
y = settings.height - settings.logoSize - padding;
break;
case 'bottom-right':
x = settings.width - settings.logoSize - padding;
y = settings.height - settings.logoSize - padding;
break;
case 'top-left':
default:
x = padding;
y = padding;
break;
}
// Desenha o logo
utils.drawImageProp(ctx, logo, x, y, settings.logoSize, settings.logoSize);
} catch (error) {
console.error('Erro ao carregar o logo:', error);
}
}
/**
* Cria um banner para landing page
*
* @async
* @param {Object} options - Opções de configuração
* @param {string} options.headline - Título principal da landing page
* @param {string} [options.subheadline] - Subtítulo da landing page
* @param {string} [options.imageURL] - URL da imagem principal
* @param {string} [options.ctaText="COMECE AGORA"] - Texto do botão de call-to-action
* @param {string} [options.ctaURL] - URL para o botão de call-to-action
* @param {string} [options.backgroundColor="#FFFFFF"] - Cor de fundo
* @param {string} [options.textColor="#333333"] - Cor do texto
* @param {string} [options.accentColor="#4A90E2"] - Cor de destaque
* @param {string} [options.style="standard"] - Estilo do banner ("standard", "hero", "split", "video")
* @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.showLogo=true] - Se deve mostrar o logo
* @param {string} [options.logoURL] - URL do logo da empresa
* @param {Array<string>} [options.features=[]] - Lista de recursos/benefícios
* @param {boolean} [options.showOverlay=true] - Se deve mostrar sobreposição na imagem
* @param {number} [options.overlayOpacity=0.5] - Opacidade da sobreposição
* @param {string} [options.overlayColor="#000000"] - Cor da sobreposição
* @param {string} [options.backgroundPattern="none"] - Padrão de fundo ("none", "dots", "lines", "grid")
* @returns {Promise<Buffer>} - Buffer da imagem gerada
*/
async function createLandingPageBanner(options) {
// Validação de parâmetros
validator.validateRequiredParams(options, ['headline']);
// Valores padrão
const defaults = {
subheadline: '',
imageURL: '',
ctaText: 'COMECE AGORA',
ctaURL: '',
backgroundColor: '#FFFFFF',
textColor: '#333333',
accentColor: '#4A90E2',
style: 'standard',
width: 1200,
height: 628,
font: 'Poppins',
showLogo: true,
logoURL: '',
features: [],
showOverlay: true,
overlayOpacity: 0.5,
overlayColor: '#000000',
backgroundPattern: '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 'hero':
await drawHeroLandingStyle(ctx, settings);
break;
case 'split':
await drawSplitLandingStyle(ctx, settings);
break;
case 'video':
await drawVideoLandingStyle(ctx, settings);
break;
case 'standard':
default:
await drawStandardLandingStyle(ctx, settings);
break;
}
// Retorna o buffer da imagem
return await utils.getBufferFromImage(img);
} catch (error) {
console.error('Erro ao criar banner de landing page:', error);
throw error;
}
}
/**
* Desenha o estilo padrão do banner de landing page
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawStandardLandingStyle(ctx, settings) {
// Desenha o fundo
ctx.fillStyle = settings.backgroundColor;
ctx.fillRect(0, 0, settings.width, settings.height);
// Adiciona padrão de fundo se necessário
if (settings.backgroundPattern !== 'none') {
drawBackgroundPattern(ctx, settings);
}
// Desenha o logo se necessário
if (settings.showLogo && settings.logoURL) {
const logo = await utils.loadImage(settings.logoURL);
const logoSize = 60;
const padding = 20;
utils.drawImageProp(ctx, logo, padding, padding, logoSize, logoSize);
}
// Calcula as posições para o texto
const textX = settings.width * 0.1;
const textWidth = settings.width * 0.8;
// Título principal
ctx.font = `bold 64px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapText(ctx, settings.headline, textX, settings.height * 0.35, textWidth, 70);
// Subtítulo
if (settings.subheadline) {
ctx.font = `300 32px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapText(ctx, settings.subheadline, textX, settings.height * 0.5, textWidth, 40);
}
// Features/Benefícios
if (settings.features.length > 0) {
const featureY = settings.height * 0.6;
const featureSpacing = 40;
ctx.font = `300 24px ${settings.font}`;
ctx.fillStyle = settings.textColor;
for (let i = 0; i < Math.min(settings.features.length, 3); i++) {
const y = featureY + i * featureSpacing;
// Desenha um ícone de check
ctx.fillStyle = settings.accentColor;
ctx.beginPath();
ctx.arc(textX + 10, y - 8, 10, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#FFFFFF';
ctx.font = `bold 16px ${settings.font}`;
ctx.fillText('✓', textX + 5, y - 3);
// Desenha o texto do recurso
ctx.fillStyle = settings.textColor;
ctx.font = `300 24px ${settings.font}`;
ctx.fillText(settings.features[i], textX + 30, y);
}
}
// Botão CTA
const ctaWidth = 250;
const ctaHeight = 60;
const ctaX = textX;
const ctaY = settings.height * 0.8;
// Desenha o fundo do botão
ctx.fillStyle = settings.accentColor;
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 30, 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';
// Se tiver imagem, desenha na metade direita
if (settings.imageURL) {
const image = await utils.loadImage(settings.imageURL);
// Desenha a imagem na metade direita
const imageX = settings.width * 0.6;
const imageWidth = settings.width * 0.35;
const imageHeight = settings.height * 0.7;
const imageY = (settings.height - imageHeight) / 2;
utils.drawImageProp(ctx, image, imageX, imageY, imageWidth, imageHeight);
}
}
/**
* Desenha o estilo hero do banner de landing page
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawHeroLandingStyle(ctx, settings) {
// Se tiver imagem, desenha como fundo
if (settings.imageURL) {
const image = await utils.loadImage(settings.imageURL);
utils.drawImageProp(ctx, image, 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);
}
} else {
// Desenha o fundo com gradiente
const gradient = ctx.createLinearGradient(0, 0, settings.width, settings.height);
gradient.addColorStop(0, settings.accentColor);
gradient.addColorStop(1, utils.adjustColor(settings.accentColor, -0.3));
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, settings.width, settings.height);
}
// Desenha o logo se necessário
if (settings.showLogo && settings.logoURL) {
const logo = await utils.loadImage(settings.logoURL);
const logoSize = 60;
const padding = 20;
utils.drawImageProp(ctx, logo, padding, padding, logoSize, logoSize);
}
// Desenha o conteúdo de texto centralizado
ctx.textAlign = 'center';
// Título principal
ctx.font = `bold 72px ${settings.font}`;
ctx.fillStyle = '#FFFFFF';
utils.wrapTextCentered(ctx, settings.headline, settings.width / 2, settings.height * 0.4, settings.width * 0.8, 80);
// Subtítulo
if (settings.subheadline) {
ctx.font = `300 36px ${settings.font}`;
ctx.fillStyle = '#FFFFFF';
utils.wrapTextCentered(ctx, settings.subheadline, settings.width / 2, settings.height * 0.6, settings.width * 0.7, 50);
}
// Botão CTA
const ctaWidth = 300;
const ctaHeight = 70;
const ctaX = (settings.width - ctaWidth) / 2;
const ctaY = settings.height * 0.75;
// 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 28px ${settings.font}`;
ctx.fillStyle = settings.accentColor;
ctx.fillText(settings.ctaText, settings.width / 2, ctaY + 45);
// Restaura o alinhamento de texto
ctx.textAlign = 'left';
}
/**
* Desenha o estilo split do banner de landing page
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawSplitLandingStyle(ctx, settings) {
// Desenha o fundo dividido
ctx.fillStyle = settings.backgroundColor;
ctx.fillRect(0, 0, settings.width / 2, settings.height);
ctx.fillStyle = settings.accentColor;
ctx.fillRect(settings.width / 2, 0, settings.width / 2, settings.height);
// Adiciona padrão de fundo se necessário
if (settings.backgroundPattern !== 'none') {
// Padrão apenas na metade esquerda
const originalPattern = settings.backgroundPattern;
const originalWidth = settings.width;
settings.width = settings.width / 2;
drawBackgroundPattern(ctx, settings);
settings.backgroundPattern = originalPattern;
settings.width = originalWidth;
}
// Desenha o logo se necessário
if (settings.showLogo && settings.logoURL) {
const logo = await utils.loadImage(settings.logoURL);
const logoSize = 60;
const padding = 20;
utils.drawImageProp(ctx, logo, padding, padding, logoSize, logoSize);
}
// Calcula as posições para o texto na metade esquerda
const textX = settings.width * 0.05;
const textWidth = settings.width * 0.4;
// Título principal
ctx.font = `bold 54px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapText(ctx, settings.headline, textX, settings.height * 0.35, textWidth, 60);
// Subtítulo
if (settings.subheadline) {
ctx.font = `300 28px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapText(ctx, settings.subheadline, textX, settings.height * 0.5, textWidth, 40);
}
// Features/Benefícios
if (settings.features.length > 0) {
const featureY = settings.height * 0.6;
const featureSpacing = 40;
ctx.font = `300 22px ${settings.font}`;
ctx.fillStyle = settings.textColor;
for (let i = 0; i < Math.min(settings.features.length, 3); i++) {
const y = featureY + i * featureSpacing;
// Desenha um ícone de check
ctx.fillStyle = settings.accentColor;
ctx.beginPath();
ctx.arc(textX + 10, y - 8, 10, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#FFFFFF';
ctx.font = `bold 16px ${settings.font}`;
ctx.fillText('✓', textX + 5, y - 3);
// Desenha o texto do recurso
ctx.fillStyle = settings.textColor;
ctx.font = `300 22px ${settings.font}`;
ctx.fillText(settings.features[i], textX + 30, y);
}
}
// Botão CTA
const ctaWidth = 220;
const ctaHeight = 60;
const ctaX = textX;
const ctaY = settings.height * 0.8;
// Desenha o fundo do botão
ctx.fillStyle = settings.accentColor;
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 30, 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';
// Se tiver imagem, desenha na metade direita
if (settings.imageURL) {
const image = await utils.loadImage(settings.imageURL);
// Desenha a imagem na metade direita
const imageX = settings.width * 0.55;
const imageWidth = settings.width * 0.4;
const imageHeight = settings.height * 0.7;
const imageY = (settings.height - imageHeight) / 2;
utils.drawImageProp(ctx, image, imageX, imageY, imageWidth, imageHeight);
}
}
/**
* Desenha o estilo video do banner de landing page
*
* @async
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
async function drawVideoLandingStyle(ctx, settings) {
// Desenha o fundo
ctx.fillStyle = settings.backgroundColor;
ctx.fillRect(0, 0, settings.width, settings.height);
// Se tiver imagem, desenha como thumbnail de vídeo
if (settings.imageURL) {
const image = await utils.loadImage(settings.imageURL);
// Desenha a imagem como thumbnail de vídeo
const videoWidth = settings.width * 0.9;
const videoHeight = settings.height * 0.5;
const videoX = (settings.width - videoWidth) / 2;
const videoY = settings.height * 0.1;
utils.drawImageProp(ctx, image, videoX, videoY, videoWidth, videoHeight);
// Adiciona sobreposição se necessário
if (settings.showOverlay) {
ctx.fillStyle = utils.hexToRgba(settings.overlayColor, settings.overlayOpacity / 2);
ctx.fillRect(videoX, videoY, videoWidth, videoHeight);
}
// Adiciona botão de play
const playSize = 80;
const playX = videoX + videoWidth / 2 - playSize / 2;
const playY = videoY + videoHeight / 2 - playSize / 2;
// Círculo de fundo
ctx.fillStyle = utils.hexToRgba('#000000', 0.7);
ctx.beginPath();
ctx.arc(playX + playSize / 2, playY + playSize / 2, playSize / 2, 0, Math.PI * 2);
ctx.fill();
// Triângulo de play
ctx.fillStyle = '#FFFFFF';
ctx.beginPath();
ctx.moveTo(playX + playSize * 0.35, playY + playSize * 0.25);
ctx.lineTo(playX + playSize * 0.35, playY + playSize * 0.75);
ctx.lineTo(playX + playSize * 0.75, playY + playSize * 0.5);
ctx.closePath();
ctx.fill();
}
// Desenha o logo se necessário
if (settings.showLogo && settings.logoURL) {
const logo = await utils.loadImage(settings.logoURL);
const logoSize = 60;
const padding = 20;
utils.drawImageProp(ctx, logo, padding, padding, logoSize, logoSize);
}
// Desenha o conteúdo de texto centralizado
ctx.textAlign = 'center';
// Título principal
ctx.font = `bold 54px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapTextCentered(ctx, settings.headline, settings.width / 2, settings.height * 0.7, settings.width * 0.8, 60);
// Subtítulo
if (settings.subheadline) {
ctx.font = `300 28px ${settings.font}`;
ctx.fillStyle = settings.textColor;
utils.wrapTextCentered(ctx, settings.subheadline, settings.width / 2, settings.height * 0.8, settings.width * 0.7, 40);
}
// Botão CTA
const ctaWidth = 250;
const ctaHeight = 60;
const ctaX = (settings.width - ctaWidth) / 2;
const ctaY = settings.height * 0.88;
// Desenha o fundo do botão
ctx.fillStyle = settings.accentColor;
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 30, true);
// Desenha o texto do botão
ctx.font = `bold 24px ${settings.font}`;
ctx.fillStyle = '#FFFFFF';
ctx.fillText(settings.ctaText, settings.width / 2, ctaY + 38);
// Restaura o alinhamento de texto
ctx.textAlign = 'left';
}
/**
* Desenha um padrão de fundo
*
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
* @param {Object} settings - Configurações do banner
*/
function drawBackgroundPattern(ctx, settings) {
const patternColor = utils.hexToRgba(settings.textColor, 0.05);
ctx.fillStyle = patternColor;
switch (settings.backgroundPattern) {
case 'dots':
// Desenha um padrão de pontos
const dotSpacing = 30;
const dotSize = 3;
for (let x = dotSpacing; x < settings.width; x += dotSpacing) {
for (let y = dotSpacing; y < settings.height; y += dotSpacing) {
ctx.beginPath();
ctx.arc(x, y, dotSize, 0, Math.PI * 2);
ctx.fill();
}
}
break;
case 'lines':
// Desenha um padrão de linhas
const lineSpacing = 40;
ctx.lineWidth = 1;
ctx.strokeStyle = patternColor;
for (let y = lineSpacing; y < settings.height; y += lineSpacing) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(settings.width, y);
ctx.stroke();
}
break;
case 'grid':
// Desenha um padrão de grade
const gridSpacing = 40;
ctx.lineWidth = 1;
ctx.strokeStyle = patternColor;
// Linhas horizontais
for (let y = gridSpacing; y < settings.height; y += gridSpacing) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(settings.width, y);
ctx.stroke();
}
// Linhas verticais
for (let x = gridSpacing; x < settings.width; x += gridSpacing) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, settings.height);
ctx.stroke();
}
break;
default:
// Nenhum padrão
break;
}
}
module.exports = {
createAdvertisementBanner,
createLandingPageBanner
};