@cognima/banners
Version:
Biblioteca avançada para geração de banners dinâmicos para diversas plataformas e aplicações
893 lines (760 loc) • 28.9 kB
JavaScript
"use strict";
/**
* Módulo de Banner Minimalista
*
* Este módulo gera banners com design minimalista, utilizando espaços em branco,
* tipografia elegante e elementos visuais sutis.
*
* @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");
const {
DEFAULT_COLORS,
LAYOUT,
DEFAULT_DIMENSIONS
} = require("./constants");
const {
applyGlassmorphism,
applyLinePattern
} = require("./effects");
/**
* @class MinimalistBanner
* @classdesc Gera um banner com design minimalista e elegante.
* @example const banner = new MinimalistBanner()
* .setTitle("Título Elegante")
* .setSubtitle("Subtítulo complementar")
* .setText("Texto adicional com informações relevantes")
* .setBackground("color", "#FFFFFF")
* .setAccentColor("#000000")
* .build();
*/
module.exports = class MinimalistBanner {
constructor(options) {
// Dados Principais
this.title = "Título";
this.subtitle = null;
this.text = null;
this.logo = null;
this.image = null;
this.qrCode = null;
this.ctaText = null;
this.ctaUrl = null;
// Personalização Visual
this.font = { name: options?.font?.name ?? DEFAULT_FONT_FAMILY, path: options?.font?.path };
this.background = { type: "color", value: "#FFFFFF" };
this.textColor = "#000000";
this.accentColor = "#000000";
this.secondaryColor = "#888888";
this.layout = "centered"; // centered, left, right, split
this.style = "clean"; // clean, bordered, lined, dotted
// Configurações de Layout
this.cardWidth = DEFAULT_DIMENSIONS.banner.width;
this.cardHeight = 400;
this.cornerRadius = 0;
this.padding = LAYOUT.padding.large;
}
// --- Setters para Dados Principais ---
/**
* Define o título principal
* @param {string} text - Texto do título
* @returns {MinimalistBanner} - Instância atual para encadeamento
*/
setTitle(text) {
if (!text || typeof text !== "string") throw new Error("O título deve ser uma string não vazia.");
this.title = text;
return this;
}
/**
* Define o subtítulo
* @param {string} text - Texto do subtítulo
* @returns {MinimalistBanner} - Instância atual para encadeamento
*/
setSubtitle(text) {
if (!text || typeof text !== "string") throw new Error("O subtítulo deve ser uma string não vazia.");
this.subtitle = text;
return this;
}
/**
* Define o texto adicional
* @param {string} text - Texto adicional
* @returns {MinimalistBanner} - Instância atual para encadeamento
*/
setText(text) {
if (!text || typeof text !== "string") throw new Error("O texto deve ser uma string não vazia.");
this.text = text;
return this;
}
/**
* Define o logo
* @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem do logo
* @returns {MinimalistBanner} - Instância atual para encadeamento
*/
setLogo(image) {
if (!image) throw new Error("A fonte da imagem do logo não pode estar vazia.");
this.logo = image;
return this;
}
/**
* Define a imagem complementar
* @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem
* @returns {MinimalistBanner} - Instância atual para encadeamento
*/
setImage(image) {
if (!image) throw new Error("A fonte da imagem não pode estar vazia.");
this.image = image;
return this;
}
/**
* Define o código QR
* @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem do código QR
* @returns {MinimalistBanner} - Instância atual para encadeamento
*/
setQRCode(image) {
if (!image) throw new Error("A fonte da imagem do código QR não pode estar vazia.");
this.qrCode = image;
return this;
}
/**
* Define o texto de chamada para ação (CTA)
* @param {string} text - Texto do CTA
* @param {string} url - URL do CTA
* @returns {MinimalistBanner} - Instância atual para encadeamento
*/
setCTA(text, url) {
if (!text || typeof text !== "string") throw new Error("O texto do CTA deve ser uma string não vazia.");
if (url && typeof url !== "string") throw new Error("A URL do CTA deve ser uma string.");
this.ctaText = text;
this.ctaUrl = url;
return this;
}
// --- Setters para Personalização Visual ---
/**
* Define o plano de fundo
* @param {string} type - Tipo de plano de fundo ('color' ou 'image')
* @param {string} value - Valor do plano de fundo (cor hexadecimal ou URL/caminho da imagem)
* @returns {MinimalistBanner} - Instância atual para encadeamento
*/
setBackground(type, value) {
const types = ["color", "image"];
if (!type || !types.includes(type.toLowerCase())) throw new Error("O tipo de plano de fundo deve ser 'color' ou 'image'.");
if (!value) throw new Error("O valor do plano de fundo não pode estar vazio.");
if (type.toLowerCase() === "color" && !isValidHexColor(value)) throw new Error("Cor de plano de fundo inválida. Use o formato hexadecimal.");
this.background = { type: type.toLowerCase(), value };
return this;
}
/**
* Define a cor do texto
* @param {string} color - Cor hexadecimal
* @returns {MinimalistBanner} - 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 de destaque
* @param {string} color - Cor hexadecimal
* @returns {MinimalistBanner} - Instância atual para encadeamento
*/
setAccentColor(color) {
if (!color || !isValidHexColor(color)) throw new Error("Cor de destaque inválida. Use o formato hexadecimal.");
this.accentColor = color;
return this;
}
/**
* Define a cor secundária
* @param {string} color - Cor hexadecimal
* @returns {MinimalistBanner} - Instância atual para encadeamento
*/
setSecondaryColor(color) {
if (!color || !isValidHexColor(color)) throw new Error("Cor secundária inválida. Use o formato hexadecimal.");
this.secondaryColor = color;
return this;
}
/**
* Define o layout
* @param {string} layout - Tipo de layout ('centered', 'left', 'right', 'split')
* @returns {MinimalistBanner} - Instância atual para encadeamento
*/
setLayout(layout) {
const validLayouts = ["centered", "left", "right", "split"];
if (!layout || !validLayouts.includes(layout.toLowerCase())) {
throw new Error(`Layout inválido. Use um dos seguintes: ${validLayouts.join(", ")}`);
}
this.layout = layout.toLowerCase();
return this;
}
/**
* Define o estilo
* @param {string} style - Tipo de estilo ('clean', 'bordered', 'lined', 'dotted')
* @returns {MinimalistBanner} - Instância atual para encadeamento
*/
setStyle(style) {
const validStyles = ["clean", "bordered", "lined", "dotted"];
if (!style || !validStyles.includes(style.toLowerCase())) {
throw new Error(`Estilo inválido. Use um dos seguintes: ${validStyles.join(", ")}`);
}
this.style = style.toLowerCase();
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 {MinimalistBanner} - Instância atual para encadeamento
*/
setCardDimensions(width, height) {
if (typeof width !== "number" || width < 400 || width > 1920) {
throw new Error("A largura do card deve estar entre 400 e 1920 pixels.");
}
if (typeof height !== "number" || height < 200 || height > 1080) {
throw new Error("A altura do card deve estar entre 200 e 1080 pixels.");
}
this.cardWidth = width;
this.cardHeight = height;
return this;
}
/**
* Define o raio dos cantos arredondados
* @param {number} radius - Raio dos cantos em pixels
* @returns {MinimalistBanner} - 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;
}
/**
* Define o padding
* @param {number} padding - Padding em pixels
* @returns {MinimalistBanner} - Instância atual para encadeamento
*/
setPadding(padding) {
if (typeof padding !== "number" || padding < 0) throw new Error("O padding deve ser um número não negativo.");
this.padding = padding;
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() {
// --- Registro de Fonte ---
const registeredFontName = await registerFontIfNeeded(this.font);
// --- Configuração do Canvas ---
const cardWidth = this.cardWidth;
const cardHeight = this.cardHeight;
const padding = this.padding;
const cornerRadius = this.cornerRadius;
const canvas = pureimage.make(cardWidth, cardHeight);
const ctx = canvas.getContext("2d");
// --- Desenha Plano de Fundo ---
if (this.background.type === "color") {
// Plano de fundo de cor sólida
ctx.fillStyle = this.background.value;
roundRect(ctx, 0, 0, cardWidth, cardHeight, cornerRadius, true, false);
} else {
// Plano de fundo de imagem
try {
ctx.save();
if (cornerRadius > 0) {
roundRect(ctx, 0, 0, cardWidth, cardHeight, cornerRadius, false, false);
ctx.clip();
}
const img = await loadImageWithAxios(this.background.value);
const aspect = img.width / img.height;
let drawWidth = cardWidth;
let drawHeight = cardWidth / aspect;
// Ajusta as dimensões para cobrir todo o card
if (drawHeight < cardHeight) {
drawHeight = cardHeight;
drawWidth = cardHeight * aspect;
}
const offsetX = (cardWidth - drawWidth) / 2;
const offsetY = (cardHeight - drawHeight) / 2;
ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
ctx.restore();
} catch (e) {
console.error("Falha ao desenhar imagem de plano de fundo:", e.message);
ctx.fillStyle = "#FFFFFF";
roundRect(ctx, 0, 0, cardWidth, cardHeight, cornerRadius, true, false);
}
}
// --- Aplica Estilo ---
switch (this.style) {
case "bordered":
// Borda simples
ctx.strokeStyle = this.accentColor;
ctx.lineWidth = 2;
roundRect(ctx, padding / 2, padding / 2, cardWidth - padding, cardHeight - padding, cornerRadius > 0 ? cornerRadius - padding / 4 : 0, false, true);
break;
case "lined":
// Padrão de linhas
applyLinePattern(
ctx,
0,
0,
cardWidth,
cardHeight,
20,
1,
this.accentColor,
0.1,
"horizontal"
);
break;
case "dotted":
// Padrão de pontos (implementado como linhas pontilhadas)
ctx.strokeStyle = hexToRgba(this.accentColor, 0.2);
ctx.lineWidth = 1;
ctx.setLineDash([2, 4]);
roundRect(ctx, padding / 2, padding / 2, cardWidth - padding, cardHeight - padding, cornerRadius > 0 ? cornerRadius - padding / 4 : 0, false, true);
ctx.setLineDash([]);
break;
}
// --- Desenha Conteúdo com base no Layout ---
switch (this.layout) {
case "left":
await this._drawLeftLayout(ctx, registeredFontName, cardWidth, cardHeight, padding);
break;
case "right":
await this._drawRightLayout(ctx, registeredFontName, cardWidth, cardHeight, padding);
break;
case "split":
await this._drawSplitLayout(ctx, registeredFontName, cardWidth, cardHeight, padding);
break;
case "centered":
default:
await this._drawCenteredLayout(ctx, registeredFontName, cardWidth, cardHeight, padding);
break;
}
// --- Codifica e Retorna Buffer ---
try {
return await encodeToBuffer(canvas);
} catch (err) {
console.error("Falha ao codificar o Banner Minimalista:", err);
throw new Error("Não foi possível gerar o buffer de imagem do Banner Minimalista.");
}
}
// --- Métodos Auxiliares Privados ---
/**
* Desenha layout centralizado
* @private
*/
async _drawCenteredLayout(ctx, fontName, width, height, padding) {
// --- Desenha Logo (se fornecido) ---
let currentY = padding * 1.5;
if (this.logo) {
try {
const logoSize = Math.min(width, height) * 0.15;
const logoX = (width - logoSize) / 2;
const logoImg = await loadImageWithAxios(this.logo);
const aspect = logoImg.width / logoImg.height;
const logoHeight = logoSize;
const logoWidth = logoHeight * aspect;
ctx.drawImage(logoImg, logoX, currentY, logoWidth, logoHeight);
currentY += logoHeight + padding;
} catch (e) {
console.error("Falha ao desenhar logo:", e.message);
}
}
// --- Desenha Título ---
const titleFontSize = Math.min(width, height) * 0.08;
ctx.fillStyle = this.textColor;
ctx.font = `bold ${titleFontSize}px ${fontName}-Bold`;
ctx.textAlign = "center";
ctx.textBaseline = "top";
const titleText = this.title;
ctx.fillText(titleText, width / 2, currentY);
currentY += titleFontSize * 1.2;
// --- Desenha Linha Decorativa ---
const lineWidth = width * 0.1;
const lineHeight = 2;
const lineX = (width - lineWidth) / 2;
const lineY = currentY + padding / 2;
ctx.fillStyle = this.accentColor;
ctx.fillRect(lineX, lineY, lineWidth, lineHeight);
currentY = lineY + lineHeight + padding;
// --- Desenha Subtítulo (se fornecido) ---
if (this.subtitle) {
const subtitleFontSize = titleFontSize * 0.6;
ctx.fillStyle = this.secondaryColor;
ctx.font = `medium ${subtitleFontSize}px ${fontName}-Medium`;
ctx.textAlign = "center";
ctx.fillText(this.subtitle, width / 2, currentY);
currentY += subtitleFontSize * 1.5;
}
// --- Desenha Texto (se fornecido) ---
if (this.text) {
const textFontSize = titleFontSize * 0.4;
ctx.fillStyle = this.textColor;
ctx.font = `regular ${textFontSize}px ${fontName}-Regular`;
ctx.textAlign = "center";
currentY = wrapText(ctx, this.text, width / 2, currentY + padding, width - padding * 4, textFontSize * 1.5, fontName);
}
// --- Desenha Imagem (se fornecida) ---
if (this.image) {
try {
const remainingHeight = height - currentY - padding * 2;
const imageHeight = Math.min(remainingHeight, height * 0.3);
const imageWidth = width * 0.6;
const imageX = (width - imageWidth) / 2;
const imageY = currentY + padding;
const img = await loadImageWithAxios(this.image);
ctx.drawImage(img, imageX, imageY, imageWidth, imageHeight);
currentY = imageY + imageHeight;
} catch (e) {
console.error("Falha ao desenhar imagem:", e.message);
}
}
// --- Desenha CTA (se fornecido) ---
if (this.ctaText) {
const ctaFontSize = titleFontSize * 0.5;
const ctaY = height - padding * 2;
ctx.fillStyle = this.accentColor;
ctx.font = `bold ${ctaFontSize}px ${fontName}-Bold`;
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
ctx.fillText(this.ctaText, width / 2, ctaY);
// Desenha URL (se fornecida)
if (this.ctaUrl) {
const urlFontSize = ctaFontSize * 0.7;
ctx.fillStyle = this.secondaryColor;
ctx.font = `regular ${urlFontSize}px ${fontName}-Regular`;
ctx.fillText(this.ctaUrl, width / 2, ctaY + urlFontSize * 1.2);
}
}
// --- Desenha QR Code (se fornecido) ---
if (this.qrCode) {
try {
const qrSize = Math.min(width, height) * 0.15;
const qrX = width - qrSize - padding;
const qrY = height - qrSize - padding;
const qrImg = await loadImageWithAxios(this.qrCode);
ctx.drawImage(qrImg, qrX, qrY, qrSize, qrSize);
} catch (e) {
console.error("Falha ao desenhar QR code:", e.message);
}
}
}
/**
* Desenha layout alinhado à esquerda
* @private
*/
async _drawLeftLayout(ctx, fontName, width, height, padding) {
const contentX = padding * 2;
let currentY = padding * 2;
const contentWidth = width * 0.6;
// --- Desenha Logo (se fornecido) ---
if (this.logo) {
try {
const logoSize = Math.min(width, height) * 0.1;
const logoImg = await loadImageWithAxios(this.logo);
const aspect = logoImg.width / logoImg.height;
const logoHeight = logoSize;
const logoWidth = logoHeight * aspect;
ctx.drawImage(logoImg, contentX, currentY, logoWidth, logoHeight);
currentY += logoHeight + padding;
} catch (e) {
console.error("Falha ao desenhar logo:", e.message);
}
}
// --- Desenha Título ---
const titleFontSize = Math.min(width, height) * 0.07;
ctx.fillStyle = this.textColor;
ctx.font = `bold ${titleFontSize}px ${fontName}-Bold`;
ctx.textAlign = "left";
ctx.textBaseline = "top";
const titleText = this.title;
ctx.fillText(titleText, contentX, currentY);
currentY += titleFontSize * 1.2;
// --- Desenha Linha Decorativa ---
const lineWidth = contentWidth * 0.2;
const lineHeight = 2;
const lineY = currentY + padding / 2;
ctx.fillStyle = this.accentColor;
ctx.fillRect(contentX, lineY, lineWidth, lineHeight);
currentY = lineY + lineHeight + padding;
// --- Desenha Subtítulo (se fornecido) ---
if (this.subtitle) {
const subtitleFontSize = titleFontSize * 0.6;
ctx.fillStyle = this.secondaryColor;
ctx.font = `medium ${subtitleFontSize}px ${fontName}-Medium`;
ctx.textAlign = "left";
ctx.fillText(this.subtitle, contentX, currentY);
currentY += subtitleFontSize * 1.5;
}
// --- Desenha Texto (se fornecido) ---
if (this.text) {
const textFontSize = titleFontSize * 0.4;
ctx.fillStyle = this.textColor;
ctx.font = `regular ${textFontSize}px ${fontName}-Regular`;
ctx.textAlign = "left";
currentY = wrapText(ctx, this.text, contentX, currentY + padding, contentWidth - padding, textFontSize * 1.5, fontName);
}
// --- Desenha CTA (se fornecido) ---
if (this.ctaText) {
const ctaFontSize = titleFontSize * 0.5;
const ctaY = height - padding * 2;
ctx.fillStyle = this.accentColor;
ctx.font = `bold ${ctaFontSize}px ${fontName}-Bold`;
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
ctx.fillText(this.ctaText, contentX, ctaY);
// Desenha URL (se fornecida)
if (this.ctaUrl) {
const urlFontSize = ctaFontSize * 0.7;
ctx.fillStyle = this.secondaryColor;
ctx.font = `regular ${urlFontSize}px ${fontName}-Regular`;
ctx.fillText(this.ctaUrl, contentX, ctaY + urlFontSize * 1.2);
}
}
// --- Desenha Imagem (se fornecida) ---
if (this.image) {
try {
const imageWidth = width * 0.35;
const imageHeight = height * 0.6;
const imageX = width - imageWidth - padding * 2;
const imageY = (height - imageHeight) / 2;
const img = await loadImageWithAxios(this.image);
ctx.drawImage(img, imageX, imageY, imageWidth, imageHeight);
} catch (e) {
console.error("Falha ao desenhar imagem:", e.message);
}
}
// --- Desenha QR Code (se fornecido) ---
if (this.qrCode) {
try {
const qrSize = Math.min(width, height) * 0.15;
const qrX = width - qrSize - padding;
const qrY = height - qrSize - padding;
const qrImg = await loadImageWithAxios(this.qrCode);
ctx.drawImage(qrImg, qrX, qrY, qrSize, qrSize);
} catch (e) {
console.error("Falha ao desenhar QR code:", e.message);
}
}
}
/**
* Desenha layout alinhado à direita
* @private
*/
async _drawRightLayout(ctx, fontName, width, height, padding) {
const contentWidth = width * 0.6;
const contentX = width - contentWidth - padding * 2;
let currentY = padding * 2;
// --- Desenha Logo (se fornecido) ---
if (this.logo) {
try {
const logoSize = Math.min(width, height) * 0.1;
const logoImg = await loadImageWithAxios(this.logo);
const aspect = logoImg.width / logoImg.height;
const logoHeight = logoSize;
const logoWidth = logoHeight * aspect;
ctx.drawImage(logoImg, contentX, currentY, logoWidth, logoHeight);
currentY += logoHeight + padding;
} catch (e) {
console.error("Falha ao desenhar logo:", e.message);
}
}
// --- Desenha Título ---
const titleFontSize = Math.min(width, height) * 0.07;
ctx.fillStyle = this.textColor;
ctx.font = `bold ${titleFontSize}px ${fontName}-Bold`;
ctx.textAlign = "left";
ctx.textBaseline = "top";
const titleText = this.title;
ctx.fillText(titleText, contentX, currentY);
currentY += titleFontSize * 1.2;
// --- Desenha Linha Decorativa ---
const lineWidth = contentWidth * 0.2;
const lineHeight = 2;
const lineY = currentY + padding / 2;
ctx.fillStyle = this.accentColor;
ctx.fillRect(contentX, lineY, lineWidth, lineHeight);
currentY = lineY + lineHeight + padding;
// --- Desenha Subtítulo (se fornecido) ---
if (this.subtitle) {
const subtitleFontSize = titleFontSize * 0.6;
ctx.fillStyle = this.secondaryColor;
ctx.font = `medium ${subtitleFontSize}px ${fontName}-Medium`;
ctx.textAlign = "left";
ctx.fillText(this.subtitle, contentX, currentY);
currentY += subtitleFontSize * 1.5;
}
// --- Desenha Texto (se fornecido) ---
if (this.text) {
const textFontSize = titleFontSize * 0.4;
ctx.fillStyle = this.textColor;
ctx.font = `regular ${textFontSize}px ${fontName}-Regular`;
ctx.textAlign = "left";
currentY = wrapText(ctx, this.text, contentX, currentY + padding, contentWidth - padding, textFontSize * 1.5, fontName);
}
// --- Desenha CTA (se fornecido) ---
if (this.ctaText) {
const ctaFontSize = titleFontSize * 0.5;
const ctaY = height - padding * 2;
ctx.fillStyle = this.accentColor;
ctx.font = `bold ${ctaFontSize}px ${fontName}-Bold`;
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
ctx.fillText(this.ctaText, contentX, ctaY);
// Desenha URL (se fornecida)
if (this.ctaUrl) {
const urlFontSize = ctaFontSize * 0.7;
ctx.fillStyle = this.secondaryColor;
ctx.font = `regular ${urlFontSize}px ${fontName}-Regular`;
ctx.fillText(this.ctaUrl, contentX, ctaY + urlFontSize * 1.2);
}
}
// --- Desenha Imagem (se fornecida) ---
if (this.image) {
try {
const imageWidth = width * 0.35;
const imageHeight = height * 0.6;
const imageX = padding * 2;
const imageY = (height - imageHeight) / 2;
const img = await loadImageWithAxios(this.image);
ctx.drawImage(img, imageX, imageY, imageWidth, imageHeight);
} catch (e) {
console.error("Falha ao desenhar imagem:", e.message);
}
}
// --- Desenha QR Code (se fornecido) ---
if (this.qrCode) {
try {
const qrSize = Math.min(width, height) * 0.15;
const qrX = padding;
const qrY = height - qrSize - padding;
const qrImg = await loadImageWithAxios(this.qrCode);
ctx.drawImage(qrImg, qrX, qrY, qrSize, qrSize);
} catch (e) {
console.error("Falha ao desenhar QR code:", e.message);
}
}
}
/**
* Desenha layout dividido
* @private
*/
async _drawSplitLayout(ctx, fontName, width, height, padding) {
// Divide o canvas em duas partes
const halfWidth = width / 2;
// --- Desenha Divisão Visual ---
if (this.style !== "clean") {
ctx.fillStyle = this.accentColor;
ctx.fillRect(halfWidth - 1, 0, 2, height);
}
// --- Lado Esquerdo (Texto) ---
const leftContentX = padding * 2;
let leftCurrentY = padding * 2;
const leftContentWidth = halfWidth - padding * 3;
// Logo (se fornecido)
if (this.logo) {
try {
const logoSize = Math.min(halfWidth, height) * 0.15;
const logoImg = await loadImageWithAxios(this.logo);
const aspect = logoImg.width / logoImg.height;
const logoHeight = logoSize;
const logoWidth = logoHeight * aspect;
ctx.drawImage(logoImg, leftContentX, leftCurrentY, logoWidth, logoHeight);
leftCurrentY += logoHeight + padding;
} catch (e) {
console.error("Falha ao desenhar logo:", e.message);
}
}
// Título
const titleFontSize = Math.min(halfWidth, height) * 0.08;
ctx.fillStyle = this.textColor;
ctx.font = `bold ${titleFontSize}px ${fontName}-Bold`;
ctx.textAlign = "left";
ctx.textBaseline = "top";
const titleText = this.title;
ctx.fillText(titleText, leftContentX, leftCurrentY);
leftCurrentY += titleFontSize * 1.2;
// Linha Decorativa
const lineWidth = leftContentWidth * 0.3;
const lineHeight = 2;
const lineY = leftCurrentY + padding / 2;
ctx.fillStyle = this.accentColor;
ctx.fillRect(leftContentX, lineY, lineWidth, lineHeight);
leftCurrentY = lineY + lineHeight + padding;
// Subtítulo (se fornecido)
if (this.subtitle) {
const subtitleFontSize = titleFontSize * 0.6;
ctx.fillStyle = this.secondaryColor;
ctx.font = `medium ${subtitleFontSize}px ${fontName}-Medium`;
ctx.textAlign = "left";
ctx.fillText(this.subtitle, leftContentX, leftCurrentY);
leftCurrentY += subtitleFontSize * 1.5;
}
// Texto (se fornecido)
if (this.text) {
const textFontSize = titleFontSize * 0.4;
ctx.fillStyle = this.textColor;
ctx.font = `regular ${textFontSize}px ${fontName}-Regular`;
ctx.textAlign = "left";
leftCurrentY = wrapText(ctx, this.text, leftContentX, leftCurrentY + padding, leftContentWidth, textFontSize * 1.5, fontName);
}
// CTA (se fornecido)
if (this.ctaText) {
const ctaFontSize = titleFontSize * 0.5;
const ctaY = height - padding * 2;
ctx.fillStyle = this.accentColor;
ctx.font = `bold ${ctaFontSize}px ${fontName}-Bold`;
ctx.textAlign = "left";
ctx.textBaseline = "bottom";
ctx.fillText(this.ctaText, leftContentX, ctaY);
// URL (se fornecida)
if (this.ctaUrl) {
const urlFontSize = ctaFontSize * 0.7;
ctx.fillStyle = this.secondaryColor;
ctx.font = `regular ${urlFontSize}px ${fontName}-Regular`;
ctx.fillText(this.ctaUrl, leftContentX, ctaY + urlFontSize * 1.2);
}
}
// --- Lado Direito (Imagem) ---
if (this.image) {
try {
const rightContentX = halfWidth + padding;
const imageWidth = halfWidth - padding * 2;
const imageHeight = height - padding * 2;
const imageY = padding;
const img = await loadImageWithAxios(this.image);
ctx.drawImage(img, rightContentX, imageY, imageWidth, imageHeight);
} catch (e) {
console.error("Falha ao desenhar imagem:", e.message);
}
}
// QR Code (se fornecido)
if (this.qrCode) {
try {
const qrSize = Math.min(halfWidth, height) * 0.2;
const qrX = width - qrSize - padding;
const qrY = height - qrSize - padding;
const qrImg = await loadImageWithAxios(this.qrCode);
ctx.drawImage(qrImg, qrX, qrY, qrSize, qrSize);
} catch (e) {
console.error("Falha ao desenhar QR code:", e.message);
}
}
}
};