@cognima/banners
Version:
Biblioteca avançada para geração de banners dinâmicos para diversas plataformas e aplicações
431 lines (368 loc) • 13.7 kB
JavaScript
"use strict";
/**
* Módulo de Banner de Boas-vindas com Gradiente
*
* Este módulo gera banners de boas-vindas modernos com gradientes personalizáveis,
* efeitos de sombra e design minimalista.
*
* @author Cognima Team (melhorado)
* @version 2.0.0
*/
Object.defineProperty(exports, "__esModule", { value: true });
const pureimage = require("pureimage");
const path = require("path");
const {
loadImageWithAxios,
encodeToBuffer,
roundRect,
wrapText,
registerFontIfNeeded,
isValidHexColor,
DEFAULT_FONT_FAMILY,
applyTextShadow,
clearShadow,
createLinearGradient,
hexToRgba
} = require("../utils");
/**
* @class GradientWelcome
* @classdesc Gera um banner de boas-vindas moderno com gradientes e design minimalista.
* @example const welcomeCard = new GradientWelcome()
* .setAvatar("avatar.png")
* .setUsername("NovoUsuário")
* .setMessage("Bem-vindo à nossa comunidade!")
* .setGradient("#3498db", "#8e44ad", "diagonal")
* .build();
*/
module.exports = class GradientWelcome {
constructor(options) {
// Dados Principais
this.avatar = null;
this.username = "Usuário";
this.message = "Bem-vindo à comunidade!";
this.memberCount = null;
// Personalização Visual
this.font = { name: options?.font?.name ?? DEFAULT_FONT_FAMILY, path: options?.font?.path };
this.gradientStart = "#3498db";
this.gradientEnd = "#8e44ad";
this.gradientDirection = "diagonal";
this.textColor = "#FFFFFF";
this.secondaryTextColor = "#E0E0E0";
this.avatarBorderColor = "#FFFFFF";
this.avatarBorderWidth = 4;
this.cornerRadius = 30;
this.useTextShadow = true;
this.useBlur = true;
this.blurAmount = 10;
// Configurações de Layout
this.cardWidth = 800;
this.cardHeight = 300;
this.avatarSize = 150;
}
// --- Setters para Dados Principais ---
/**
* Define a imagem do avatar
* @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem do avatar
* @returns {GradientWelcome} - Instância atual para encadeamento
*/
setAvatar(image) {
if (!image) throw new Error("A fonte da imagem do avatar não pode estar vazia.");
this.avatar = image;
return this;
}
/**
* Define o nome de usuário
* @param {string} name - Nome do usuário
* @returns {GradientWelcome} - Instância atual para encadeamento
*/
setUsername(name) {
if (!name || typeof name !== "string") throw new Error("O nome de usuário deve ser uma string não vazia.");
this.username = name;
return this;
}
/**
* Define o texto da mensagem
* @param {string} text - Texto da mensagem
* @returns {GradientWelcome} - Instância atual para encadeamento
*/
setMessage(text) {
if (!text || typeof text !== "string") throw new Error("O texto da mensagem deve ser uma string não vazia.");
this.message = text;
return this;
}
/**
* Define o contador de membros
* @param {number|string} count - Número de membros
* @returns {GradientWelcome} - Instância atual para encadeamento
*/
setMemberCount(count) {
if (count === null || count === undefined) {
this.memberCount = null;
return this;
}
if (typeof count !== "number" && typeof count !== "string") {
throw new Error("O contador de membros deve ser um número ou uma string.");
}
this.memberCount = count.toString();
return this;
}
// --- Setters para Personalização Visual ---
/**
* Define o gradiente de fundo
* @param {string} startColor - Cor inicial do gradiente (hexadecimal)
* @param {string} endColor - Cor final do gradiente (hexadecimal)
* @param {string} direction - Direção do gradiente ('vertical', 'horizontal', 'diagonal')
* @returns {GradientWelcome} - Instância atual para encadeamento
*/
setGradient(startColor, endColor, direction = "diagonal") {
if (!startColor || !isValidHexColor(startColor)) throw new Error("Cor inicial do gradiente inválida. Use o formato hexadecimal.");
if (!endColor || !isValidHexColor(endColor)) throw new Error("Cor final do gradiente inválida. Use o formato hexadecimal.");
const validDirections = ["vertical", "horizontal", "diagonal"];
if (!validDirections.includes(direction.toLowerCase())) {
throw new Error(`Direção do gradiente inválida. Use uma das seguintes: ${validDirections.join(", ")}`);
}
this.gradientStart = startColor;
this.gradientEnd = endColor;
this.gradientDirection = direction.toLowerCase();
return this;
}
/**
* Define a cor do texto principal
* @param {string} color - Cor hexadecimal
* @returns {GradientWelcome} - Instância atual para encadeamento
*/
setTextColor(color) {
if (!color || !isValidHexColor(color)) throw new Error("Cor de texto inválida. Use o formato hexadecimal.");
this.textColor = color;
return this;
}
/**
* Define a cor do texto secundário
* @param {string} color - Cor hexadecimal
* @returns {GradientWelcome} - Instância atual para encadeamento
*/
setSecondaryTextColor(color) {
if (!color || !isValidHexColor(color)) throw new Error("Cor de texto secundário inválida. Use o formato hexadecimal.");
this.secondaryTextColor = color;
return this;
}
/**
* Define a cor da borda do avatar
* @param {string} color - Cor hexadecimal
* @returns {GradientWelcome} - Instância atual para encadeamento
*/
setAvatarBorderColor(color) {
if (!color || !isValidHexColor(color)) throw new Error("Cor de borda do avatar inválida. Use o formato hexadecimal.");
this.avatarBorderColor = color;
return this;
}
/**
* Define a largura da borda do avatar
* @param {number} width - Largura da borda em pixels
* @returns {GradientWelcome} - Instância atual para encadeamento
*/
setAvatarBorderWidth(width) {
if (typeof width !== "number" || width < 0) throw new Error("A largura da borda do avatar deve ser um número não negativo.");
this.avatarBorderWidth = width;
return this;
}
/**
* Define o raio dos cantos arredondados
* @param {number} radius - Raio dos cantos em pixels
* @returns {GradientWelcome} - Instância atual para encadeamento
*/
setCornerRadius(radius) {
if (typeof radius !== "number" || radius < 0) throw new Error("O raio dos cantos deve ser um número não negativo.");
this.cornerRadius = radius;
return this;
}
/**
* Ativa ou desativa a sombra de texto
* @param {boolean} enabled - Se a sombra de texto deve ser ativada
* @returns {GradientWelcome} - Instância atual para encadeamento
*/
enableTextShadow(enabled = true) {
this.useTextShadow = enabled;
return this;
}
/**
* Ativa ou desativa o efeito de desfoque
* @param {boolean} enabled - Se o desfoque deve ser ativado
* @param {number} amount - Quantidade de desfoque (1-20)
* @returns {GradientWelcome} - Instância atual para encadeamento
*/
enableBlur(enabled = true, amount = 10) {
this.useBlur = enabled;
if (typeof amount === "number" && amount >= 1 && amount <= 20) {
this.blurAmount = amount;
}
return this;
}
/**
* Define as dimensões do card
* @param {number} width - Largura do card em pixels
* @param {number} height - Altura do card em pixels
* @returns {GradientWelcome} - Instância atual para encadeamento
*/
setCardDimensions(width, height) {
if (typeof width !== "number" || width < 400 || width > 1200) {
throw new Error("A largura do card deve estar entre 400 e 1200 pixels.");
}
if (typeof height !== "number" || height < 200 || height > 600) {
throw new Error("A altura do card deve estar entre 200 e 600 pixels.");
}
this.cardWidth = width;
this.cardHeight = height;
return this;
}
/**
* Define o tamanho do avatar
* @param {number} size - Tamanho do avatar em pixels
* @returns {GradientWelcome} - Instância atual para encadeamento
*/
setAvatarSize(size) {
if (typeof size !== "number" || size < 80 || size > 250) {
throw new Error("O tamanho do avatar deve estar entre 80 e 250 pixels.");
}
this.avatarSize = size;
return this;
}
// --- Método de Construção ---
/**
* Constrói o banner e retorna um buffer de imagem
* @returns {Promise<Buffer>} - Buffer contendo a imagem do banner
*/
async build() {
if (!this.avatar) throw new Error("A imagem do avatar deve ser definida usando setAvatar().");
// --- Registro de Fonte ---
const registeredFontName = await registerFontIfNeeded(this.font);
// --- Configuração do Canvas ---
const cardWidth = this.cardWidth;
const cardHeight = this.cardHeight;
const avatarSize = this.avatarSize;
const borderRadius = this.cornerRadius;
const padding = 40;
const canvas = pureimage.make(cardWidth, cardHeight);
const ctx = canvas.getContext("2d");
// --- Desenha Plano de Fundo com Gradiente ---
ctx.save();
roundRect(ctx, 0, 0, cardWidth, cardHeight, borderRadius, false, false);
ctx.clip();
// Cria e aplica o gradiente
const gradient = createLinearGradient(
ctx,
0,
0,
cardWidth,
cardHeight,
this.gradientStart,
this.gradientEnd,
this.gradientDirection
);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, cardWidth, cardHeight);
// Adiciona um efeito de "brilho" no canto superior
ctx.globalAlpha = 0.15;
ctx.fillStyle = "#FFFFFF";
ctx.beginPath();
ctx.arc(cardWidth * 0.2, cardHeight * 0.2, Math.min(cardWidth, cardHeight) * 0.5, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
ctx.globalAlpha = 1;
// --- Desenha Avatar ---
const avatarX = padding;
const avatarY = (cardHeight - avatarSize) / 2;
// Desenha sombra do avatar
ctx.shadowColor = "rgba(0, 0, 0, 0.3)";
ctx.shadowBlur = 15;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 5;
ctx.save();
ctx.beginPath();
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
ctx.closePath();
ctx.clip();
try {
const avatarImg = await loadImageWithAxios(this.avatar);
ctx.drawImage(avatarImg, avatarX, avatarY, avatarSize, avatarSize);
} catch (e) {
console.error("Falha ao desenhar imagem do avatar:", e.message);
ctx.fillStyle = "#555";
ctx.fillRect(avatarX, avatarY, avatarSize, avatarSize);
ctx.fillStyle = "#FFF";
ctx.font = `bold 30px ${registeredFontName}-Bold`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("?", avatarX + avatarSize / 2, avatarY + avatarSize / 2);
}
ctx.restore();
// Remove a sombra para os próximos elementos
clearShadow(ctx);
// --- Desenha Borda do Avatar ---
if (this.avatarBorderColor) {
ctx.strokeStyle = this.avatarBorderColor;
ctx.lineWidth = this.avatarBorderWidth;
ctx.beginPath();
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2 + ctx.lineWidth / 2, 0, Math.PI * 2);
ctx.stroke();
ctx.closePath();
}
// --- Desenha Texto --- (À direita do avatar)
const textX = avatarX + avatarSize + padding;
const textY = cardHeight / 2 - 40;
const textMaxWidth = cardWidth - textX - padding;
// Mensagem de Boas-vindas
ctx.fillStyle = this.textColor;
ctx.font = `bold 36px ${registeredFontName}-Bold`;
ctx.textAlign = "start";
ctx.textBaseline = "middle";
// Aplica sombra de texto se ativada
if (this.useTextShadow) {
applyTextShadow(ctx, "rgba(0, 0, 0, 0.5)", 3, 2, 2);
}
ctx.fillText(this.message, textX, textY);
wrapText(ctx, this.message, textX, textY, textMaxWidth, 28, registeredFontName);
// Remove sombra para o próximo texto
if (this.useTextShadow) {
clearShadow(ctx);
}
// Nome de Usuário
ctx.fillStyle = this.secondaryTextColor;
ctx.font = `medium 28px ${registeredFontName}-Medium`;
// Aplica sombra de texto se ativada
if (this.useTextShadow) {
applyTextShadow(ctx, "rgba(0, 0, 0, 0.4)", 2, 1, 1);
}
const usernameText = this.username.length > 25 ? this.username.slice(0, 22) + "..." : this.username;
ctx.fillText(usernameText, textX, textY + 50);
wrapText(ctx, usernameText, textX, textY + 50, textMaxWidth, 28, registeredFontName);
// Remove sombra para o próximo texto
if (this.useTextShadow) {
clearShadow(ctx);
}
// Contador de Membros (se fornecido)
if (this.memberCount !== null) {
ctx.fillStyle = this.secondaryTextColor;
ctx.font = `regular 20px ${registeredFontName}-Regular`;
ctx.textAlign = "start";
// Aplica sombra de texto se ativada
if (this.useTextShadow) {
applyTextShadow(ctx, "rgba(0, 0, 0, 0.3)", 2, 1, 1);
}
const memberText = `Membro #${this.memberCount}`;
ctx.fillText(memberText, textX, textY + 90);
wrapText(ctx, memberText, textX, textY + 90, textMaxWidth, 28, registeredFontName);
// Remove sombra
if (this.useTextShadow) {
clearShadow(ctx);
}
}
// --- Codifica e Retorna Buffer ---
try {
return await encodeToBuffer(canvas);
} catch (err) {
console.error("Falha ao codificar o card de Boas-vindas com Gradiente:", err);
throw new Error("Não foi possível gerar o buffer de imagem do card de Boas-vindas com Gradiente.");
}
}
};