UNPKG

@cognima/banners

Version:

Biblioteca avançada para geração de banners dinâmicos para diversas plataformas e aplicações

1,488 lines (1,322 loc) 52.5 kB
"use strict"; /** * Módulo de banners para Comunidade e Engajamento * * Este módulo fornece funções para criar banners para comunidades online, * como anúncios, eventos, citações e enquetes. * * @module community-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 evento * * @async * @param {Object} options - Opções de configuração * @param {string} options.title - Título do evento * @param {string} [options.date] - Data do evento * @param {string} [options.time] - Horário do evento * @param {string} [options.location] - Local do evento * @param {string} [options.description] - Descrição do evento * @param {string} [options.imageURL] - URL da imagem do evento * @param {string} [options.ctaText="PARTICIPAR"] - 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="#4A90E2"] - 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", "modern", "themed") * @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 * @param {number} [options.overlayOpacity=0.5] - Opacidade da sobreposição * @param {string} [options.overlayColor="#000000"] - Cor da sobreposição * @param {string} [options.theme="default"] - Tema para o estilo themed ("conference", "webinar", "meetup", "party") * @returns {Promise<Buffer>} - Buffer da imagem gerada */ async function createEventBanner(options) { // Valores padrão const defaults = { date: "", time: "", location: "", description: "", imageURL: "", ctaText: "PARTICIPAR", ctaURL: "", backgroundColor: "#4A90E2", textColor: "#FFFFFF", accentColor: "#FFFFFF", style: "standard", width: 1200, height: 628, font: "Poppins", showOverlay: true, overlayOpacity: 0.5, overlayColor: "#000000", theme: "default", }; // 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 drawMinimalEventStyle(ctx, settings); break; case "modern": await drawModernEventStyle(ctx, settings); break; case "themed": await drawThemedEventStyle(ctx, settings); break; case "standard": default: await drawStandardEventStyle(ctx, settings); break; } // Retorna o buffer da imagem return await utils.encodeToBuffer(img); } catch (error) { console.error("Erro ao criar banner de evento:", error); throw error; } } /** * Desenha o estilo padrão do banner de evento * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawStandardEventStyle(ctx, settings) { // Desenha o fundo ctx.fillStyle = settings.backgroundColor; ctx.fillRect(0, 0, settings.width, settings.height); // Se tiver imagem, desenha com sobreposição if (settings.imageURL) { const image = await utils.loadImage(settings.imageURL); utils.drawImageProp(ctx, image, 0, 0, settings.width, settings.height); 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 const textX = settings.width * 0.1; const textWidth = settings.width * 0.8; // Título ctx.font = `bold 64px ${settings.font}`; ctx.fillStyle = settings.textColor; utils.wrapText(ctx, settings.title, textX, settings.height * 0.3, textWidth, 70); // Data e Hora let dateTimeString = ""; if (settings.date) dateTimeString += settings.date; if (settings.time) dateTimeString += (settings.date ? " | " : "") + settings.time; if (dateTimeString) { ctx.font = `300 32px ${settings.font}`; ctx.fillStyle = settings.textColor; ctx.fillText(dateTimeString, textX, settings.height * 0.45); } // Local if (settings.location) { ctx.font = `300 28px ${settings.font}`; ctx.fillStyle = settings.textColor; ctx.fillText(settings.location, textX, settings.height * 0.55); } // Descrição if (settings.description) { ctx.font = `300 24px ${settings.font}`; ctx.fillStyle = settings.textColor; utils.wrapText( ctx, settings.description, textX, settings.height * 0.65, textWidth, 30 ); } // Botão CTA if (settings.ctaText) { const ctaWidth = 200; const ctaHeight = 60; const ctaX = textX; const ctaY = settings.height * 0.8; ctx.fillStyle = settings.accentColor; utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 8, true); ctx.font = `bold 24px ${settings.font}`; ctx.fillStyle = settings.backgroundColor; // Cor do texto do botão ctx.textAlign = "center"; ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 38); ctx.textAlign = "left"; } } /** * Desenha o estilo minimalista do banner de evento * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawMinimalEventStyle(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 const textX = settings.width * 0.1; const textWidth = settings.width * 0.8; // Título ctx.font = `bold 54px ${settings.font}`; ctx.fillStyle = settings.backgroundColor; utils.wrapText(ctx, settings.title, textX, settings.height * 0.3, textWidth, 60); // Data e Hora let dateTimeString = ""; if (settings.date) dateTimeString += settings.date; if (settings.time) dateTimeString += (settings.date ? " | " : "") + settings.time; if (dateTimeString) { ctx.font = `300 28px ${settings.font}`; ctx.fillStyle = "#333333"; ctx.fillText(dateTimeString, textX, settings.height * 0.45); } // Local if (settings.location) { ctx.font = `300 24px ${settings.font}`; ctx.fillStyle = "#333333"; ctx.fillText(settings.location, textX, settings.height * 0.55); } // Botão CTA if (settings.ctaText) { const ctaWidth = 180; const ctaHeight = 50; const ctaX = textX; const ctaY = settings.height * 0.7; ctx.strokeStyle = settings.backgroundColor; ctx.lineWidth = 2; utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 0, false, true); ctx.font = `bold 20px ${settings.font}`; ctx.fillStyle = settings.backgroundColor; ctx.textAlign = "center"; ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 33); ctx.textAlign = "left"; } } /** * Desenha o estilo moderno do banner de evento * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawModernEventStyle(ctx, settings) { // Desenha o fundo com gradiente const gradient = ctx.createLinearGradient(0, 0, settings.width, settings.height); gradient.addColorStop(0, settings.backgroundColor); gradient.addColorStop(1, utils.adjustColor(settings.backgroundColor, -0.3)); ctx.fillStyle = gradient; ctx.fillRect(0, 0, settings.width, settings.height); // Adiciona padrão de pontos effects.addDotPattern(ctx, settings.width, settings.height, 20, "rgba(255,255,255,0.1)"); // Se tiver imagem, desenha na metade direita if (settings.imageURL) { const image = await utils.loadImage(settings.imageURL); const imageX = settings.width * 0.55; const imageWidth = settings.width * 0.4; const imageHeight = settings.height * 0.7; const imageY = (settings.height - imageHeight) / 2; // Adiciona sombra à imagem ctx.shadowColor = "rgba(0,0,0,0.2)"; ctx.shadowBlur = 20; ctx.shadowOffsetX = 5; ctx.shadowOffsetY = 5; utils.drawImageProp(ctx, image, imageX, imageY, imageWidth, imageHeight); // Remove a sombra ctx.shadowColor = "transparent"; ctx.shadowBlur = 0; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; } // Desenha o conteúdo de texto na metade esquerda const textX = settings.width * 0.08; const textWidth = settings.width * 0.4; // Título ctx.font = `bold 60px ${settings.font}`; ctx.fillStyle = settings.textColor; utils.wrapText(ctx, settings.title, textX, settings.height * 0.3, textWidth, 70); // Data e Hora let dateTimeString = ""; if (settings.date) dateTimeString += settings.date; if (settings.time) dateTimeString += (settings.date ? " | " : "") + settings.time; if (dateTimeString) { ctx.font = `300 30px ${settings.font}`; ctx.fillStyle = settings.textColor; ctx.fillText(dateTimeString, textX, settings.height * 0.5); } // Local if (settings.location) { ctx.font = `300 26px ${settings.font}`; ctx.fillStyle = settings.textColor; ctx.fillText(settings.location, textX, settings.height * 0.6); } // Botão CTA if (settings.ctaText) { const ctaWidth = 220; const ctaHeight = 60; const ctaX = textX; const ctaY = settings.height * 0.75; ctx.fillStyle = settings.accentColor; utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 30, true); ctx.font = `bold 24px ${settings.font}`; ctx.fillStyle = settings.backgroundColor; // Cor do texto do botão ctx.textAlign = "center"; ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 38); ctx.textAlign = "left"; } } /** * Desenha o estilo temático do banner de evento * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawThemedEventStyle(ctx, settings) { // Configurações específicas para cada tema const themes = { conference: { backgroundColor: "#1E3A5F", accentColor: "#FFC107", textColor: "#FFFFFF", font: "Roboto", decorations: ["geometric-shapes", "lines"], }, webinar: { backgroundColor: "#F5F5F5", accentColor: "#007BFF", textColor: "#333333", font: "Lato", decorations: ["laptop-icon", "speech-bubble"], }, meetup: { backgroundColor: "#6C63FF", accentColor: "#FFFFFF", textColor: "#FFFFFF", font: "Montserrat", decorations: ["map-pin", "calendar-icon"], }, party: { backgroundColor: "#FF4081", accentColor: "#FFD700", textColor: "#FFFFFF", font: "Pacifico", decorations: ["confetti", "balloons"], }, default: { backgroundColor: settings.backgroundColor, accentColor: settings.accentColor, textColor: settings.textColor, font: settings.font, decorations: [], }, }; const currentTheme = themes[settings.theme.toLowerCase()] || themes.default; // Aplica as configurações do tema settings.backgroundColor = currentTheme.backgroundColor; settings.accentColor = currentTheme.accentColor; settings.textColor = currentTheme.textColor; settings.font = currentTheme.font; // Desenha o fundo ctx.fillStyle = settings.backgroundColor; ctx.fillRect(0, 0, settings.width, settings.height); // Adiciona decorações temáticas if (currentTheme.decorations.length > 0) { await addThemedDecorations(ctx, settings, currentTheme.decorations); } // Se tiver imagem, desenha com sobreposição if (settings.imageURL) { const image = await utils.loadImage(settings.imageURL); utils.drawImageProp(ctx, image, 0, 0, settings.width, settings.height); 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 const textX = settings.width * 0.1; const textWidth = settings.width * 0.8; // Título ctx.font = `bold 64px ${settings.font}`; ctx.fillStyle = settings.textColor; utils.wrapText(ctx, settings.title, textX, settings.height * 0.3, textWidth, 70); // Data e Hora let dateTimeString = ""; if (settings.date) dateTimeString += settings.date; if (settings.time) dateTimeString += (settings.date ? " | " : "") + settings.time; if (dateTimeString) { ctx.font = `300 32px ${settings.font}`; ctx.fillStyle = settings.textColor; ctx.fillText(dateTimeString, textX, settings.height * 0.45); } // Local if (settings.location) { ctx.font = `300 28px ${settings.font}`; ctx.fillStyle = settings.textColor; ctx.fillText(settings.location, textX, settings.height * 0.55); } // Botão CTA if (settings.ctaText) { const ctaWidth = 200; const ctaHeight = 60; const ctaX = textX; const ctaY = settings.height * 0.7; ctx.fillStyle = settings.accentColor; utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 8, true); ctx.font = `bold 24px ${settings.font}`; ctx.fillStyle = settings.backgroundColor; // Cor do texto do botão ctx.textAlign = "center"; ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 38); ctx.textAlign = "left"; } } /** * Adiciona decorações temáticas 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 addThemedDecorations(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 < 5; i++) { const size = Math.random() * 80 + 40; const x = Math.random() * settings.width; const y = Math.random() * settings.height; ctx.fillStyle = utils.hexToRgba(settings.accentColor, 0.3); // Escolhe uma decoração aleatória da lista const decoration = decorations[Math.floor(Math.random() * decorations.length)]; switch (decoration) { case "geometric-shapes": drawGeometricShape(ctx, x, y, size); break; case "lines": drawRandomLines(ctx, x, y, size); break; case "laptop-icon": // Implementar desenho de ícone de laptop break; case "speech-bubble": // Implementar desenho de balão de fala break; case "map-pin": // Implementar desenho de pino de mapa break; case "calendar-icon": // Implementar desenho de ícone de calendário break; case "confetti": drawConfetti(ctx, x, y, size); break; case "balloons": drawBalloons(ctx, x, y, size); break; default: ctx.beginPath(); ctx.arc(x, y, size / 2, 0, Math.PI * 2); ctx.fill(); break; } } } // Funções auxiliares para desenhar decorações temáticas function drawGeometricShape(ctx, x, y, size) { const shapes = ["circle", "square", "triangle"]; const shape = shapes[Math.floor(Math.random() * shapes.length)]; ctx.beginPath(); switch (shape) { case "circle": ctx.arc(x, y, size / 2, 0, Math.PI * 2); break; case "square": ctx.rect(x - size / 2, y - size / 2, size, size); break; case "triangle": ctx.moveTo(x, y - size / 2); ctx.lineTo(x + size / 2, y + size / 2); ctx.lineTo(x - size / 2, y + size / 2); ctx.closePath(); break; } ctx.fill(); } function drawRandomLines(ctx, x, y, size) { ctx.beginPath(); for (let i = 0; i < 5; i++) { ctx.moveTo(x + (Math.random() - 0.5) * size, y + (Math.random() - 0.5) * size); ctx.lineTo(x + (Math.random() - 0.5) * size, y + (Math.random() - 0.5) * size); } ctx.stroke(); } function drawConfetti(ctx, x, y, size) { for (let i = 0; i < 10; i++) { const confettiX = x + (Math.random() - 0.5) * size; const confettiY = y + (Math.random() - 0.5) * size; const confettiSize = Math.random() * 10 + 5; ctx.fillStyle = utils.getRandomColor(); ctx.fillRect(confettiX, confettiY, confettiSize, confettiSize); } } function drawBalloons(ctx, x, y, size) { for (let i = 0; i < 3; i++) { const balloonX = x + (Math.random() - 0.5) * size; const balloonY = y + (Math.random() - 0.5) * size; const balloonRadius = Math.random() * 20 + 10; ctx.fillStyle = utils.getRandomColor(); ctx.beginPath(); ctx.arc(balloonX, balloonY, balloonRadius, 0, Math.PI * 2); ctx.fill(); // Desenha a corda do balão ctx.beginPath(); ctx.moveTo(balloonX, balloonY + balloonRadius); ctx.lineTo(balloonX, balloonY + balloonRadius + 30); ctx.stroke(); } } /** * Cria um banner para anúncio * * @async * @param {Object} options - Opções de configuração * @param {string} options.title - Título do anúncio * @param {string} [options.message] - Mensagem do anúncio * @param {string} [options.imageURL] - URL da imagem de fundo * @param {string} [options.backgroundColor="#FFC107"] - Cor de fundo * @param {string} [options.textColor="#333333"] - Cor do texto * @param {string} [options.accentColor="#333333"] - Cor de destaque * @param {string} [options.style="standard"] - Estilo do banner ("standard", "minimal", "bold", "urgent") * @param {number} [options.width=1200] - Largura do banner em pixels * @param {number} [options.height=400] - Altura do banner em pixels * @param {string} [options.font="Poppins"] - Nome da fonte * @param {boolean} [options.showIcon=true] - Se deve mostrar um ícone * @param {string} [options.iconType="megaphone"] - Tipo de ícone ("megaphone", "info", "warning", "star") * @returns {Promise<Buffer>} - Buffer da imagem gerada */ async function createAnnouncementBanner(options) { // Validação de parâmetros validator.validateRequiredParams(options, ["title"]); // Valores padrão const defaults = { message: "", imageURL: "", backgroundColor: "#FFC107", textColor: "#333333", accentColor: "#333333", style: "standard", width: 1200, height: 400, font: "Poppins", showIcon: true, iconType: "megaphone", }; // 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 drawMinimalAnnouncementStyle(ctx, settings); break; case "bold": await drawBoldAnnouncementStyle(ctx, settings); break; case "urgent": await drawUrgentAnnouncementStyle(ctx, settings); break; case "standard": default: await drawStandardAnnouncementStyle(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 drawStandardAnnouncementStyle(ctx, settings) { // Desenha o fundo ctx.fillStyle = settings.backgroundColor; ctx.fillRect(0, 0, settings.width, settings.height); // Se tiver imagem de fundo, desenha if (settings.imageURL) { const image = await utils.loadImage(settings.imageURL); utils.drawImageProp(ctx, image, 0, 0, settings.width, settings.height); } // Desenha o ícone se necessário let iconX = settings.width * 0.1; if (settings.showIcon) { await drawAnnouncementIcon(ctx, settings, iconX, settings.height / 2); iconX += 80; // Ajusta a posição do texto após o ícone } // Desenha o conteúdo de texto const textWidth = settings.width * 0.8 - (settings.showIcon ? 80 : 0); // Título ctx.font = `bold 48px ${settings.font}`; ctx.fillStyle = settings.textColor; utils.wrapText( ctx, settings.title, iconX, settings.height * 0.4, textWidth, 50 ); // Mensagem if (settings.message) { ctx.font = `300 28px ${settings.font}`; ctx.fillStyle = settings.textColor; utils.wrapText( ctx, settings.message, iconX, settings.height * 0.6, textWidth, 35 ); } } /** * 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 drawMinimalAnnouncementStyle(ctx, settings) { // Desenha o fundo ctx.fillStyle = "#F5F5F5"; ctx.fillRect(0, 0, settings.width, settings.height); // Adiciona uma linha de destaque ctx.fillStyle = settings.backgroundColor; ctx.fillRect(0, 0, settings.width, 10); // Desenha o ícone se necessário let iconX = settings.width * 0.05; if (settings.showIcon) { await drawAnnouncementIcon(ctx, settings, iconX, settings.height / 2); iconX += 60; } // Desenha o conteúdo de texto const textWidth = settings.width * 0.85 - (settings.showIcon ? 60 : 0); // Título ctx.font = `bold 42px ${settings.font}`; ctx.fillStyle = settings.textColor; utils.wrapText( ctx, settings.title, iconX, settings.height * 0.4, textWidth, 45 ); // Mensagem if (settings.message) { ctx.font = `300 24px ${settings.font}`; ctx.fillStyle = "#555555"; utils.wrapText( ctx, settings.message, iconX, settings.height * 0.6, textWidth, 30 ); } } /** * 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 drawBoldAnnouncementStyle(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 < 3; i++) { const size = Math.random() * 150 + 80; const x = Math.random() * settings.width; const y = Math.random() * settings.height; ctx.fillStyle = utils.hexToRgba(settings.backgroundColor, 0.2); ctx.beginPath(); ctx.arc(x, y, size, 0, Math.PI * 2); ctx.fill(); } // Desenha o ícone se necessário let iconX = settings.width * 0.1; if (settings.showIcon) { // Altera a cor do ícone para combinar com o fundo const originalAccentColor = settings.accentColor; settings.accentColor = settings.backgroundColor; await drawAnnouncementIcon(ctx, settings, iconX, settings.height / 2, 60); settings.accentColor = originalAccentColor; iconX += 100; } // Desenha o conteúdo de texto const textWidth = settings.width * 0.8 - (settings.showIcon ? 100 : 0); // Título ctx.font = `bold 54px ${settings.font}`; ctx.fillStyle = settings.backgroundColor; utils.wrapText( ctx, settings.title.toUpperCase(), iconX, settings.height * 0.4, textWidth, 60 ); // Mensagem if (settings.message) { ctx.font = `300 30px ${settings.font}`; ctx.fillStyle = settings.backgroundColor; utils.wrapText( ctx, settings.message, iconX, settings.height * 0.6, textWidth, 38 ); } } /** * Desenha o estilo urgente do banner de anúncio * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawUrgentAnnouncementStyle(ctx, settings) { // Desenha o fundo com gradiente de alerta const gradient = ctx.createLinearGradient(0, 0, settings.width, settings.height); gradient.addColorStop(0, "#FF416C"); // Rosa avermelhado gradient.addColorStop(1, "#FF4B2B"); // Laranja avermelhado ctx.fillStyle = gradient; ctx.fillRect(0, 0, settings.width, settings.height); // Adiciona um padrão de listras diagonais effects.addDiagonalStripePattern(ctx, settings.width, settings.height, 20, "rgba(255,255,255,0.1)"); // Desenha o ícone se necessário (ícone de aviso) let iconX = settings.width * 0.05; if (settings.showIcon) { const originalIconType = settings.iconType; settings.iconType = "warning"; // Força o ícone de aviso await drawAnnouncementIcon(ctx, settings, iconX, settings.height / 2, 60, "#FFFFFF"); settings.iconType = originalIconType; iconX += 100; } // Desenha o conteúdo de texto const textWidth = settings.width * 0.85 - (settings.showIcon ? 100 : 0); // Título ctx.font = `bold 52px ${settings.font}`; ctx.fillStyle = "#FFFFFF"; utils.wrapText( ctx, settings.title.toUpperCase(), iconX, settings.height * 0.4, textWidth, 55 ); // Mensagem if (settings.message) { ctx.font = `300 28px ${settings.font}`; ctx.fillStyle = "#FFFFFF"; utils.wrapText( ctx, settings.message, iconX, settings.height * 0.6, textWidth, 35 ); } } /** * Desenha o ícone do banner de anúncio * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner * @param {number} x - Posição X do ícone * @param {number} y - Posição Y do ícone * @param {number} [size=50] - Tamanho do ícone * @param {string} [color] - Cor do ícone (opcional) */ async function drawAnnouncementIcon(ctx, settings, x, y, size = 50, color) { const iconColor = color || settings.accentColor; ctx.fillStyle = iconColor; ctx.strokeStyle = iconColor; ctx.lineWidth = 3; // Centraliza o ícone verticalmente y -= size / 2; switch (settings.iconType) { case "megaphone": // Desenha um megafone (simplificado) ctx.beginPath(); ctx.moveTo(x, y + size * 0.3); ctx.lineTo(x + size * 0.4, y); ctx.lineTo(x + size, y + size * 0.2); ctx.lineTo(x + size, y + size * 0.8); ctx.lineTo(x + size * 0.4, y + size); ctx.lineTo(x, y + size * 0.7); ctx.closePath(); ctx.fill(); break; case "info": // Desenha um círculo com "i" ctx.beginPath(); ctx.arc(x + size / 2, y + size / 2, size / 2, 0, Math.PI * 2); ctx.stroke(); ctx.font = `bold ${size * 0.6}px ${settings.font}`; ctx.textAlign = "center"; ctx.fillText("i", x + size / 2, y + size * 0.7); ctx.textAlign = "left"; break; case "warning": // Desenha um triângulo com "!" ctx.beginPath(); ctx.moveTo(x + size / 2, y); ctx.lineTo(x + size, y + size); ctx.lineTo(x, y + size); ctx.closePath(); ctx.stroke(); ctx.font = `bold ${size * 0.6}px ${settings.font}`; ctx.textAlign = "center"; ctx.fillText("!", x + size / 2, y + size * 0.75); ctx.textAlign = "left"; break; case "star": // Desenha uma estrela utils.drawStar(ctx, x, y, size, 5, 0.5, iconColor); break; default: // Ícone padrão (círculo) ctx.beginPath(); ctx.arc(x + size / 2, y + size / 2, size / 2, 0, Math.PI * 2); ctx.fill(); break; } } /** * Cria um banner para citação * * @async * @param {Object} options - Opções de configuração * @param {string} options.quote - Texto da citação * @param {string} [options.author] - Autor da citação * @param {string} [options.imageURL] - URL da imagem de fundo * @param {string} [options.backgroundColor="#333333"] - Cor de fundo * @param {string} [options.textColor="#FFFFFF"] - Cor do texto * @param {string} [options.accentColor="#FFC107"] - Cor de destaque (para aspas) * @param {string} [options.style="standard"] - Estilo do banner ("standard", "minimal", "modern", "image-focus") * @param {number} [options.width=1200] - Largura do banner em pixels * @param {number} [options.height=628] - Altura do banner em pixels * @param {string} [options.font="Merriweather"] - Nome da fonte (ideal para citações) * @param {boolean} [options.showOverlay=true] - Se deve mostrar sobreposição na imagem * @param {number} [options.overlayOpacity=0.6] - Opacidade da sobreposição * @param {string} [options.overlayColor="#000000"] - Cor da sobreposição * @returns {Promise<Buffer>} - Buffer da imagem gerada */ async function createQuoteBanner(options) { // Validação de parâmetros validator.validateRequiredParams(options, ["quote"]); // Valores padrão const defaults = { author: "", imageURL: "", backgroundColor: "#333333", textColor: "#FFFFFF", accentColor: "#FFC107", style: "standard", width: 1200, height: 628, font: "Merriweather", showOverlay: true, overlayOpacity: 0.6, overlayColor: "#000000", }; // 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 drawMinimalQuoteStyle(ctx, settings); break; case "modern": await drawModernQuoteStyle(ctx, settings); break; case "image-focus": await drawImageFocusQuoteStyle(ctx, settings); break; case "standard": default: await drawStandardQuoteStyle(ctx, settings); break; } // Retorna o buffer da imagem return await utils.getBufferFromImage(img); } catch (error) { console.error("Erro ao criar banner de citação:", error); throw error; } } /** * Desenha o estilo padrão do banner de citação * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawStandardQuoteStyle(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.imageURL) { const image = await utils.loadImage(settings.imageURL); utils.drawImageProp(ctx, image, 0, 0, settings.width, settings.height); 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"; // Aspas de abertura ctx.font = `bold 120px ${settings.font}`; ctx.fillStyle = settings.accentColor; ctx.fillText("“", settings.width / 2, settings.height * 0.3); // Citação ctx.font = `italic 48px ${settings.font}`; ctx.fillStyle = settings.textColor; utils.wrapTextCentered( ctx, settings.quote, settings.width / 2, settings.height * 0.5, settings.width * 0.7, 60 ); // Autor if (settings.author) { ctx.font = `300 28px ${settings.font}`; ctx.fillStyle = settings.textColor; ctx.fillText(`— ${settings.author}`, settings.width / 2, settings.height * 0.75); } // Restaura o alinhamento de texto ctx.textAlign = "left"; } /** * Desenha o estilo minimalista do banner de citação * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawMinimalQuoteStyle(ctx, settings) { // Desenha o fundo ctx.fillStyle = "#FFFFFF"; ctx.fillRect(0, 0, settings.width, settings.height); // Desenha o conteúdo de texto const textX = settings.width * 0.1; const textWidth = settings.width * 0.8; // Aspas de abertura (menores e sutis) ctx.font = `bold 60px ${settings.font}`; ctx.fillStyle = settings.accentColor; ctx.fillText("“", textX, settings.height * 0.3); // Citação ctx.font = `italic 42px ${settings.font}`; ctx.fillStyle = "#333333"; utils.wrapText( ctx, settings.quote, textX + 30, // Pequeno deslocamento após aspas settings.height * 0.4, textWidth - 30, 50 ); // Autor if (settings.author) { ctx.font = `300 24px ${settings.font}`; ctx.fillStyle = "#555555"; ctx.textAlign = "right"; ctx.fillText(`— ${settings.author}`, textX + textWidth, settings.height * 0.75); ctx.textAlign = "left"; } } /** * Desenha o estilo moderno do banner de citação * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawModernQuoteStyle(ctx, settings) { // Desenha o fundo com gradiente const gradient = ctx.createLinearGradient(0, 0, settings.width, settings.height); gradient.addColorStop(0, settings.backgroundColor); gradient.addColorStop(1, utils.adjustColor(settings.backgroundColor, -0.2)); ctx.fillStyle = gradient; ctx.fillRect(0, 0, settings.width, settings.height); // Adiciona um padrão de linhas sutis effects.addHorizontalLinePattern(ctx, settings.width, settings.height, 50, "rgba(255,255,255,0.05)"); // Desenha o conteúdo de texto const textX = settings.width * 0.1; const textWidth = settings.width * 0.8; // Citação (grande e centralizada) ctx.font = `bold 72px ${settings.font}`; ctx.fillStyle = settings.textColor; ctx.textAlign = "center"; utils.wrapTextCentered( ctx, `“${settings.quote}”`, settings.width / 2, settings.height * 0.5, textWidth, 80 ); // Autor (abaixo e menor) if (settings.author) { ctx.font = `300 32px ${settings.font}`; ctx.fillStyle = utils.hexToRgba(settings.textColor, 0.8); ctx.fillText(`— ${settings.author}`, settings.width / 2, settings.height * 0.8); } // Restaura o alinhamento de texto ctx.textAlign = "left"; } /** * Desenha o estilo image-focus do banner de citação * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawImageFocusQuoteStyle(ctx, settings) { // Se tiver imagem de fundo, desenha com sobreposição forte if (settings.imageURL) { const image = await utils.loadImage(settings.imageURL); utils.drawImageProp(ctx, image, 0, 0, settings.width, settings.height); // Sobreposição mais escura para destacar o texto ctx.fillStyle = utils.hexToRgba(settings.overlayColor, Math.max(0.7, settings.overlayOpacity)); ctx.fillRect(0, 0, settings.width, settings.height); } else { // Fallback para fundo sólido se não houver imagem ctx.fillStyle = settings.backgroundColor; ctx.fillRect(0, 0, settings.width, settings.height); } // Desenha o conteúdo de texto centralizado ctx.textAlign = "center"; // Citação ctx.font = `italic 54px ${settings.font}`; ctx.fillStyle = "#FFFFFF"; // Texto sempre branco para contraste utils.wrapTextCentered( ctx, `“${settings.quote}”`, settings.width / 2, settings.height * 0.5, settings.width * 0.7, 65 ); // Autor if (settings.author) { ctx.font = `300 30px ${settings.font}`; ctx.fillStyle = "#DDDDDD"; ctx.fillText(`— ${settings.author}`, settings.width / 2, settings.height * 0.75); } // Restaura o alinhamento de texto ctx.textAlign = "left"; } /** * Cria um banner para enquete * * @async * @param {Object} options - Opções de configuração * @param {string} options.question - Pergunta da enquete * @param {Array<string>} options.pollOptions - Opções da enquete (máximo 5) * @param {string} [options.imageURL] - URL da imagem de fundo * @param {string} [options.backgroundColor="#F0F2F5"] - Cor de fundo * @param {string} [options.textColor="#333333"] - Cor do texto * @param {string} [options.accentColor="#007BFF"] - Cor de destaque (para opções) * @param {string} [options.style="standard"] - Estilo do banner ("standard", "minimal", "modern") * @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=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.showResults=false] - Se deve mostrar resultados (simulado) * @param {Array<number>} [options.results=[]] - Resultados percentuais para cada opção * @returns {Promise<Buffer>} - Buffer da imagem gerada */ async function createPollBanner(options) { // Validação de parâmetros validator.validateRequiredParams(options, ["question", "pollOptions"]); if (options.pollOptions.length < 2 || options.pollOptions.length > 5) { throw new Error("A enquete deve ter entre 2 e 5 opções."); } if (options.showResults && options.results.length !== options.pollOptions.length) { throw new Error("O número de resultados deve corresponder ao número de opções."); } // Valores padrão const defaults = { imageURL: "", backgroundColor: "#F0F2F5", textColor: "#333333", accentColor: "#007BFF", style: "standard", width: 1200, height: 628, font: "Poppins", showOverlay: false, overlayOpacity: 0.3, overlayColor: "#000000", showResults: false, results: [], }; // 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 drawMinimalPollStyle(ctx, settings); break; case "modern": await drawModernPollStyle(ctx, settings); break; case "standard": default: await drawStandardPollStyle(ctx, settings); break; } // Retorna o buffer da imagem return await utils.getBufferFromImage(img); } catch (error) { console.error("Erro ao criar banner de enquete:", error); throw error; } } /** * Desenha o estilo padrão do banner de enquete * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawStandardPollStyle(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.imageURL) { const image = await utils.loadImage(settings.imageURL); utils.drawImageProp(ctx, image, 0, 0, settings.width, settings.height); 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 const padding = settings.width * 0.05; const contentWidth = settings.width - padding * 2; // Pergunta ctx.font = `bold 42px ${settings.font}`; ctx.fillStyle = settings.textColor; ctx.textAlign = "center"; utils.wrapTextCentered( ctx, settings.question, settings.width / 2, settings.height * 0.2, contentWidth, 50 ); ctx.textAlign = "left"; // Opções da enquete const optionHeight = 60; const optionSpacing = 20; const totalOptionsHeight = settings.pollOptions.length * (optionHeight + optionSpacing) - optionSpacing; let startY = (settings.height - totalOptionsHeight) / 2 + 30; // Ajusta para centralizar melhor for (let i = 0; i < settings.pollOptions.length; i++) { const optionText = settings.pollOptions[i]; const optionY = startY + i * (optionHeight + optionSpacing); const optionBoxWidth = contentWidth * 0.8; const optionBoxX = (settings.width - optionBoxWidth) / 2; // Desenha a caixa da opção ctx.fillStyle = "#FFFFFF"; ctx.strokeStyle = settings.accentColor; ctx.lineWidth = 2; utils.roundRect(ctx, optionBoxX, optionY, optionBoxWidth, optionHeight, 8, true, true); // Se mostrar resultados, desenha a barra de progresso if (settings.showResults && settings.results[i] !== undefined) { const progress = settings.results[i] / 100; const progressBarWidth = optionBoxWidth * progress; ctx.fillStyle = utils.hexToRgba(settings.accentColor, 0.3); utils.roundRect(ctx, optionBoxX, optionY, progressBarWidth, optionHeight, 8, true); } // Desenha o texto da opção ctx.font = `300 24px ${settings.font}`; ctx.fillStyle = settings.textColor; ctx.fillText(optionText, optionBoxX + 20, optionY + optionHeight / 2 + 8); // Se mostrar resultados, desenha a porcentagem if (settings.showResults && settings.results[i] !== undefined) { ctx.font = `bold 20px ${settings.font}`; ctx.fillStyle = settings.accentColor; ctx.textAlign = "right"; ctx.fillText(`${settings.results[i]}%`, optionBoxX + optionBoxWidth - 20, optionY + optionHeight / 2 + 8); ctx.textAlign = "left"; } } } /** * Desenha o estilo minimalista do banner de enquete * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawMinimalPollStyle(ctx, settings) { // Desenha o fundo ctx.fillStyle = "#FFFFFF"; ctx.fillRect(0, 0, settings.width, settings.height); // Desenha o conteúdo de texto const padding = settings.width * 0.08; const contentWidth = settings.width - padding * 2; // Pergunta ctx.font = `bold 36px ${settings.font}`; ctx.fillStyle = settings.textColor; utils.wrapText( ctx, settings.question, padding, settings.height * 0.2, contentWidth, 45 ); // Opções da enquete const optionHeight = 50; const optionSpacing = 15; const totalOptionsHeight = settings.pollOptions.length * (optionHeight + optionSpacing) - optionSpacing; let startY = settings.height * 0.35; for (let i = 0; i < settings.pollOptions.length; i++) { const optionText = settings.pollOptions[i]; const optionY = startY + i * (optionHeight + optionSpacing); // Desenha o texto da opção com um marcador ctx.font = `300 22px ${settings.font}`; ctx.fillStyle = settings.textColor; ctx.fillText(`□ ${optionText}`, padding, optionY + optionHeight / 2 + 8); // Se mostrar resultados, desenha a barra de progresso e porcentagem if (settings.showResults && settings.results[i] !== undefined) { const progress = settings.results[i] / 100; const progressBarX = padding + ctx.measureText(`□ ${optionText}`).width + 20; const progressBarWidthMax = contentWidth - (progressBarX - padding) - 80; // Espaço para porcentagem const progressBarWidth = progressBarWidthMax * progress; ctx.fillStyle = utils.hexToRgba(settings.accentColor, 0.2); ctx.fillRect(progressBarX, optionY + optionHeight / 2 - 5, progressBarWidthMax, 10); ctx.fillStyle = settings.accentColor; ctx.fillRect(progressBarX, optionY + optionHeight / 2 - 5, progressBarWidth, 10); ctx.font = `bold 18px ${settings.font}`; ctx.fillStyle = settings.accentColor; ctx.fillText(`${settings.results[i]}%`, progressBarX + progressBarWidthMax + 10, optionY + optionHeight / 2 + 8); } } } /** * Desenha o estilo moderno do banner de enquete * * @async * @param {CanvasRenderingContext2D} ctx - Contexto de renderização * @param {Object} settings - Configurações do banner */ async function drawModernPollStyle(ctx, settings) { // Desenha o fundo com gradiente const gradient = ctx.createLinearGradient(0, 0, settings.width, settings.height); gradient.addColorStop(0, settings.backgroundColor); gradient.addColorStop(1, utils.adjustColor(settings.backgroundColor, -0.15)); ctx.fillStyle = gradient; ctx.fillRect(0, 0, settings.width, settings.height); // Adiciona um padrão sutil effects.addNoisePattern(ctx, settings.width, settings.height, 0.02); // Desenha o conteúdo de texto const padding = settings.width * 0.06; const contentWidth = settings.width - padding * 2; // Pergunta ctx.font = `bold 48px ${settings.font}`; ctx.fillStyle = settings.textColor; ctx.textAlign = "center"; utils.wrapTextCentered( ctx, settings.question, settings.width / 2, settings.height * 0.2, contentWidth * 0.9, // Um pouco mais estreito para centralizar melhor 55 ); ctx.textAlign = "left"; // Opções da enquete const optionHeight = 70; const optionSpacing = 25; const totalOptionsHeight = settings.pollOptions.length * (optionHeight + optionSpacing) - optionSpacing; let startY = (settings.height - totalOptionsHeight) / 2 + 40; for (let i = 0; i < settings.pollOptions.length; i++) { const optionText = settings.pollOptions[i]; con