@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
JavaScript
"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