UNPKG

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