UNPKG

@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
"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 };