@cognima/banners 0.0.1-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/fonts/Manrope/Manrope-Bold.ttf +0 -0
- package/assets/fonts/Manrope/Manrope-Regular.ttf +0 -0
- package/assets/fonts/Others/AbyssinicaSIL-Regular.ttf +0 -0
- package/assets/fonts/Others/ChirpRegular.ttf +0 -0
- package/assets/fonts/Poppins/Poppins-Bold.ttf +0 -0
- package/assets/fonts/Poppins/Poppins-Medium.ttf +0 -0
- package/assets/fonts/Poppins/Poppins-Regular.ttf +0 -0
- package/assets/placeholders/album_art.png +0 -0
- package/assets/placeholders/avatar.png +0 -0
- package/assets/placeholders/badge.jpg +0 -0
- package/assets/placeholders/badge.png +0 -0
- package/assets/placeholders/badge_2.jpg +0 -0
- package/assets/placeholders/badge_3.jpg +0 -0
- package/assets/placeholders/badge_4.jpg +0 -0
- package/assets/placeholders/badge_5.jpg +0 -0
- package/assets/placeholders/banner.jpeg +0 -0
- package/assets/placeholders/images.jpeg +0 -0
- package/index.js +153 -0
- package/package.json +34 -0
- package/src/animation-effects.js +631 -0
- package/src/cache-manager.js +258 -0
- package/src/community-banner.js +1536 -0
- package/src/constants.js +208 -0
- package/src/discord-profile.js +584 -0
- package/src/e-commerce-banner.js +1214 -0
- package/src/effects.js +355 -0
- package/src/error-handler.js +305 -0
- package/src/event-banner.js +1319 -0
- package/src/facebook-post.js +679 -0
- package/src/gradient-welcome.js +430 -0
- package/src/image-filters.js +1034 -0
- package/src/image-processor.js +1014 -0
- package/src/instagram-post.js +504 -0
- package/src/interactive-elements.js +1208 -0
- package/src/linkedin-post.js +658 -0
- package/src/marketing-banner.js +1089 -0
- package/src/minimalist-banner.js +892 -0
- package/src/modern-profile.js +755 -0
- package/src/performance-optimizer.js +216 -0
- package/src/telegram-header.js +544 -0
- package/src/test-runner.js +645 -0
- package/src/tiktok-post.js +713 -0
- package/src/twitter-header.js +604 -0
- package/src/validator.js +442 -0
- package/src/welcome-leave.js +445 -0
- package/src/whatsapp-status.js +386 -0
- package/src/youtube-thumbnail.js +681 -0
- package/utils.js +710 -0
|
@@ -0,0 +1,1536 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Módulo de banners para Comunidade e Engajamento
|
|
5
|
+
*
|
|
6
|
+
* Este módulo fornece funções para criar banners para comunidades online,
|
|
7
|
+
* como anúncios, eventos, citações e enquetes.
|
|
8
|
+
*
|
|
9
|
+
* @module community-banner
|
|
10
|
+
* @author Cognima Team (melhorado)
|
|
11
|
+
* @version 2.0.0
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const PImage = require("pureimage");
|
|
15
|
+
const fs = require("fs");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const utils = require("../utils");
|
|
18
|
+
const constants = require("./constants");
|
|
19
|
+
const effects = require("./effects");
|
|
20
|
+
const imageProcessor = require("./image-processor");
|
|
21
|
+
const imageFilters = require("./image-filters");
|
|
22
|
+
const validator = require("./validator");
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Cria um banner para evento
|
|
26
|
+
*
|
|
27
|
+
* @async
|
|
28
|
+
* @param {Object} options - Opções de configuração
|
|
29
|
+
* @param {string} options.title - Título do evento
|
|
30
|
+
* @param {string} [options.date] - Data do evento
|
|
31
|
+
* @param {string} [options.time] - Horário do evento
|
|
32
|
+
* @param {string} [options.location] - Local do evento
|
|
33
|
+
* @param {string} [options.description] - Descrição do evento
|
|
34
|
+
* @param {string} [options.imageURL] - URL da imagem do evento
|
|
35
|
+
* @param {string} [options.ctaText="PARTICIPAR"] - Texto do botão de call-to-action
|
|
36
|
+
* @param {string} [options.ctaURL] - URL para o botão de call-to-action
|
|
37
|
+
* @param {string} [options.backgroundColor="#4A90E2"] - Cor de fundo
|
|
38
|
+
* @param {string} [options.textColor="#FFFFFF"] - Cor do texto
|
|
39
|
+
* @param {string} [options.accentColor="#FFFFFF"] - Cor de destaque
|
|
40
|
+
* @param {string} [options.style="standard"] - Estilo do banner ("standard", "minimal", "modern", "themed")
|
|
41
|
+
* @param {number} [options.width=1200] - Largura do banner em pixels
|
|
42
|
+
* @param {number} [options.height=628] - Altura do banner em pixels
|
|
43
|
+
* @param {string} [options.font="Poppins"] - Nome da fonte
|
|
44
|
+
* @param {boolean} [options.showOverlay=true] - Se deve mostrar sobreposição na imagem
|
|
45
|
+
* @param {number} [options.overlayOpacity=0.5] - Opacidade da sobreposição
|
|
46
|
+
* @param {string} [options.overlayColor="#000000"] - Cor da sobreposição
|
|
47
|
+
* @param {string} [options.theme="default"] - Tema para o estilo themed ("conference", "webinar", "meetup", "party")
|
|
48
|
+
* @returns {Promise<Buffer>} - Buffer da imagem gerada
|
|
49
|
+
*/
|
|
50
|
+
async function createEventBanner(options) {
|
|
51
|
+
|
|
52
|
+
// Valores padrão
|
|
53
|
+
const defaults = {
|
|
54
|
+
date: "",
|
|
55
|
+
time: "",
|
|
56
|
+
location: "",
|
|
57
|
+
description: "",
|
|
58
|
+
imageURL: "",
|
|
59
|
+
ctaText: "PARTICIPAR",
|
|
60
|
+
ctaURL: "",
|
|
61
|
+
backgroundColor: "#4A90E2",
|
|
62
|
+
textColor: "#FFFFFF",
|
|
63
|
+
accentColor: "#FFFFFF",
|
|
64
|
+
style: "standard",
|
|
65
|
+
width: 1200,
|
|
66
|
+
height: 628,
|
|
67
|
+
font: "Poppins",
|
|
68
|
+
showOverlay: true,
|
|
69
|
+
overlayOpacity: 0.5,
|
|
70
|
+
overlayColor: "#000000",
|
|
71
|
+
theme: "default",
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Mescla as opções com os valores padrão
|
|
75
|
+
const settings = { ...defaults, ...options };
|
|
76
|
+
|
|
77
|
+
// Cria a imagem
|
|
78
|
+
const img = PImage.make(settings.width, settings.height);
|
|
79
|
+
const ctx = img.getContext("2d");
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Aplica o estilo de acordo com a opção selecionada
|
|
83
|
+
switch (settings.style) {
|
|
84
|
+
case "minimal":
|
|
85
|
+
await drawMinimalEventStyle(ctx, settings);
|
|
86
|
+
break;
|
|
87
|
+
case "modern":
|
|
88
|
+
await drawModernEventStyle(ctx, settings);
|
|
89
|
+
break;
|
|
90
|
+
case "themed":
|
|
91
|
+
await drawThemedEventStyle(ctx, settings);
|
|
92
|
+
break;
|
|
93
|
+
case "standard":
|
|
94
|
+
default:
|
|
95
|
+
await drawStandardEventStyle(ctx, settings);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Retorna o buffer da imagem
|
|
100
|
+
return await utils.encodeToBuffer(img);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error("Erro ao criar banner de evento:", error);
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Desenha o estilo padrão do banner de evento
|
|
109
|
+
*
|
|
110
|
+
* @async
|
|
111
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
112
|
+
* @param {Object} settings - Configurações do banner
|
|
113
|
+
*/
|
|
114
|
+
async function drawStandardEventStyle(ctx, settings) {
|
|
115
|
+
// Desenha o fundo
|
|
116
|
+
ctx.fillStyle = settings.backgroundColor;
|
|
117
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
118
|
+
|
|
119
|
+
// Se tiver imagem, desenha com sobreposição
|
|
120
|
+
if (settings.imageURL) {
|
|
121
|
+
const image = await utils.loadImage(settings.imageURL);
|
|
122
|
+
utils.drawImageProp(ctx, image, 0, 0, settings.width, settings.height);
|
|
123
|
+
|
|
124
|
+
if (settings.showOverlay) {
|
|
125
|
+
ctx.fillStyle = utils.hexToRgba(
|
|
126
|
+
settings.overlayColor,
|
|
127
|
+
settings.overlayOpacity
|
|
128
|
+
);
|
|
129
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Desenha o conteúdo de texto
|
|
134
|
+
const textX = settings.width * 0.1;
|
|
135
|
+
const textWidth = settings.width * 0.8;
|
|
136
|
+
|
|
137
|
+
// Título
|
|
138
|
+
ctx.font = `bold 64px ${settings.font}`;
|
|
139
|
+
ctx.fillStyle = settings.textColor;
|
|
140
|
+
utils.wrapText(ctx, settings.title, textX, settings.height * 0.3, textWidth, 70);
|
|
141
|
+
|
|
142
|
+
// Data e Hora
|
|
143
|
+
let dateTimeString = "";
|
|
144
|
+
if (settings.date) dateTimeString += settings.date;
|
|
145
|
+
if (settings.time) dateTimeString += (settings.date ? " | " : "") + settings.time;
|
|
146
|
+
|
|
147
|
+
if (dateTimeString) {
|
|
148
|
+
ctx.font = `300 32px ${settings.font}`;
|
|
149
|
+
ctx.fillStyle = settings.textColor;
|
|
150
|
+
ctx.fillText(dateTimeString, textX, settings.height * 0.45);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Local
|
|
154
|
+
if (settings.location) {
|
|
155
|
+
ctx.font = `300 28px ${settings.font}`;
|
|
156
|
+
ctx.fillStyle = settings.textColor;
|
|
157
|
+
ctx.fillText(settings.location, textX, settings.height * 0.55);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Descrição
|
|
161
|
+
if (settings.description) {
|
|
162
|
+
ctx.font = `300 24px ${settings.font}`;
|
|
163
|
+
ctx.fillStyle = settings.textColor;
|
|
164
|
+
utils.wrapText(
|
|
165
|
+
ctx,
|
|
166
|
+
settings.description,
|
|
167
|
+
textX,
|
|
168
|
+
settings.height * 0.65,
|
|
169
|
+
textWidth,
|
|
170
|
+
30
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Botão CTA
|
|
175
|
+
if (settings.ctaText) {
|
|
176
|
+
const ctaWidth = 200;
|
|
177
|
+
const ctaHeight = 60;
|
|
178
|
+
const ctaX = textX;
|
|
179
|
+
const ctaY = settings.height * 0.8;
|
|
180
|
+
|
|
181
|
+
ctx.fillStyle = settings.accentColor;
|
|
182
|
+
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 8, true);
|
|
183
|
+
|
|
184
|
+
ctx.font = `bold 24px ${settings.font}`;
|
|
185
|
+
ctx.fillStyle = settings.backgroundColor; // Cor do texto do botão
|
|
186
|
+
ctx.textAlign = "center";
|
|
187
|
+
ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 38);
|
|
188
|
+
ctx.textAlign = "left";
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Desenha o estilo minimalista do banner de evento
|
|
194
|
+
*
|
|
195
|
+
* @async
|
|
196
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
197
|
+
* @param {Object} settings - Configurações do banner
|
|
198
|
+
*/
|
|
199
|
+
async function drawMinimalEventStyle(ctx, settings) {
|
|
200
|
+
// Desenha o fundo
|
|
201
|
+
ctx.fillStyle = "#FFFFFF";
|
|
202
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
203
|
+
|
|
204
|
+
// Adiciona uma borda fina
|
|
205
|
+
ctx.strokeStyle = settings.backgroundColor;
|
|
206
|
+
ctx.lineWidth = 3;
|
|
207
|
+
ctx.strokeRect(20, 20, settings.width - 40, settings.height - 40);
|
|
208
|
+
|
|
209
|
+
// Desenha o conteúdo de texto
|
|
210
|
+
const textX = settings.width * 0.1;
|
|
211
|
+
const textWidth = settings.width * 0.8;
|
|
212
|
+
|
|
213
|
+
// Título
|
|
214
|
+
ctx.font = `bold 54px ${settings.font}`;
|
|
215
|
+
ctx.fillStyle = settings.backgroundColor;
|
|
216
|
+
utils.wrapText(ctx, settings.title, textX, settings.height * 0.3, textWidth, 60);
|
|
217
|
+
|
|
218
|
+
// Data e Hora
|
|
219
|
+
let dateTimeString = "";
|
|
220
|
+
if (settings.date) dateTimeString += settings.date;
|
|
221
|
+
if (settings.time) dateTimeString += (settings.date ? " | " : "") + settings.time;
|
|
222
|
+
|
|
223
|
+
if (dateTimeString) {
|
|
224
|
+
ctx.font = `300 28px ${settings.font}`;
|
|
225
|
+
ctx.fillStyle = "#333333";
|
|
226
|
+
ctx.fillText(dateTimeString, textX, settings.height * 0.45);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Local
|
|
230
|
+
if (settings.location) {
|
|
231
|
+
ctx.font = `300 24px ${settings.font}`;
|
|
232
|
+
ctx.fillStyle = "#333333";
|
|
233
|
+
ctx.fillText(settings.location, textX, settings.height * 0.55);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Botão CTA
|
|
237
|
+
if (settings.ctaText) {
|
|
238
|
+
const ctaWidth = 180;
|
|
239
|
+
const ctaHeight = 50;
|
|
240
|
+
const ctaX = textX;
|
|
241
|
+
const ctaY = settings.height * 0.7;
|
|
242
|
+
|
|
243
|
+
ctx.strokeStyle = settings.backgroundColor;
|
|
244
|
+
ctx.lineWidth = 2;
|
|
245
|
+
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 0, false, true);
|
|
246
|
+
|
|
247
|
+
ctx.font = `bold 20px ${settings.font}`;
|
|
248
|
+
ctx.fillStyle = settings.backgroundColor;
|
|
249
|
+
ctx.textAlign = "center";
|
|
250
|
+
ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 33);
|
|
251
|
+
ctx.textAlign = "left";
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Desenha o estilo moderno do banner de evento
|
|
257
|
+
*
|
|
258
|
+
* @async
|
|
259
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
260
|
+
* @param {Object} settings - Configurações do banner
|
|
261
|
+
*/
|
|
262
|
+
async function drawModernEventStyle(ctx, settings) {
|
|
263
|
+
// Desenha o fundo com gradiente
|
|
264
|
+
const gradient = ctx.createLinearGradient(0, 0, settings.width, settings.height);
|
|
265
|
+
gradient.addColorStop(0, settings.backgroundColor);
|
|
266
|
+
gradient.addColorStop(1, utils.adjustColor(settings.backgroundColor, -0.3));
|
|
267
|
+
ctx.fillStyle = gradient;
|
|
268
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
269
|
+
|
|
270
|
+
// Adiciona padrão de pontos
|
|
271
|
+
effects.addDotPattern(ctx, settings.width, settings.height, 20, "rgba(255,255,255,0.1)");
|
|
272
|
+
|
|
273
|
+
// Se tiver imagem, desenha na metade direita
|
|
274
|
+
if (settings.imageURL) {
|
|
275
|
+
const image = await utils.loadImage(settings.imageURL);
|
|
276
|
+
const imageX = settings.width * 0.55;
|
|
277
|
+
const imageWidth = settings.width * 0.4;
|
|
278
|
+
const imageHeight = settings.height * 0.7;
|
|
279
|
+
const imageY = (settings.height - imageHeight) / 2;
|
|
280
|
+
|
|
281
|
+
// Adiciona sombra à imagem
|
|
282
|
+
ctx.shadowColor = "rgba(0,0,0,0.2)";
|
|
283
|
+
ctx.shadowBlur = 20;
|
|
284
|
+
ctx.shadowOffsetX = 5;
|
|
285
|
+
ctx.shadowOffsetY = 5;
|
|
286
|
+
|
|
287
|
+
utils.drawImageProp(ctx, image, imageX, imageY, imageWidth, imageHeight);
|
|
288
|
+
|
|
289
|
+
// Remove a sombra
|
|
290
|
+
ctx.shadowColor = "transparent";
|
|
291
|
+
ctx.shadowBlur = 0;
|
|
292
|
+
ctx.shadowOffsetX = 0;
|
|
293
|
+
ctx.shadowOffsetY = 0;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Desenha o conteúdo de texto na metade esquerda
|
|
297
|
+
const textX = settings.width * 0.08;
|
|
298
|
+
const textWidth = settings.width * 0.4;
|
|
299
|
+
|
|
300
|
+
// Título
|
|
301
|
+
ctx.font = `bold 60px ${settings.font}`;
|
|
302
|
+
ctx.fillStyle = settings.textColor;
|
|
303
|
+
utils.wrapText(ctx, settings.title, textX, settings.height * 0.3, textWidth, 70);
|
|
304
|
+
|
|
305
|
+
// Data e Hora
|
|
306
|
+
let dateTimeString = "";
|
|
307
|
+
if (settings.date) dateTimeString += settings.date;
|
|
308
|
+
if (settings.time) dateTimeString += (settings.date ? " | " : "") + settings.time;
|
|
309
|
+
|
|
310
|
+
if (dateTimeString) {
|
|
311
|
+
ctx.font = `300 30px ${settings.font}`;
|
|
312
|
+
ctx.fillStyle = settings.textColor;
|
|
313
|
+
ctx.fillText(dateTimeString, textX, settings.height * 0.5);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Local
|
|
317
|
+
if (settings.location) {
|
|
318
|
+
ctx.font = `300 26px ${settings.font}`;
|
|
319
|
+
ctx.fillStyle = settings.textColor;
|
|
320
|
+
ctx.fillText(settings.location, textX, settings.height * 0.6);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Botão CTA
|
|
324
|
+
if (settings.ctaText) {
|
|
325
|
+
const ctaWidth = 220;
|
|
326
|
+
const ctaHeight = 60;
|
|
327
|
+
const ctaX = textX;
|
|
328
|
+
const ctaY = settings.height * 0.75;
|
|
329
|
+
|
|
330
|
+
ctx.fillStyle = settings.accentColor;
|
|
331
|
+
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 30, true);
|
|
332
|
+
|
|
333
|
+
ctx.font = `bold 24px ${settings.font}`;
|
|
334
|
+
ctx.fillStyle = settings.backgroundColor; // Cor do texto do botão
|
|
335
|
+
ctx.textAlign = "center";
|
|
336
|
+
ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 38);
|
|
337
|
+
ctx.textAlign = "left";
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Desenha o estilo temático do banner de evento
|
|
343
|
+
*
|
|
344
|
+
* @async
|
|
345
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
346
|
+
* @param {Object} settings - Configurações do banner
|
|
347
|
+
*/
|
|
348
|
+
async function drawThemedEventStyle(ctx, settings) {
|
|
349
|
+
// Configurações específicas para cada tema
|
|
350
|
+
const themes = {
|
|
351
|
+
conference: {
|
|
352
|
+
backgroundColor: "#1E3A5F",
|
|
353
|
+
accentColor: "#FFC107",
|
|
354
|
+
textColor: "#FFFFFF",
|
|
355
|
+
font: "Roboto",
|
|
356
|
+
decorations: ["geometric-shapes", "lines"],
|
|
357
|
+
},
|
|
358
|
+
webinar: {
|
|
359
|
+
backgroundColor: "#F5F5F5",
|
|
360
|
+
accentColor: "#007BFF",
|
|
361
|
+
textColor: "#333333",
|
|
362
|
+
font: "Lato",
|
|
363
|
+
decorations: ["laptop-icon", "speech-bubble"],
|
|
364
|
+
},
|
|
365
|
+
meetup: {
|
|
366
|
+
backgroundColor: "#6C63FF",
|
|
367
|
+
accentColor: "#FFFFFF",
|
|
368
|
+
textColor: "#FFFFFF",
|
|
369
|
+
font: "Montserrat",
|
|
370
|
+
decorations: ["map-pin", "calendar-icon"],
|
|
371
|
+
},
|
|
372
|
+
party: {
|
|
373
|
+
backgroundColor: "#FF4081",
|
|
374
|
+
accentColor: "#FFD700",
|
|
375
|
+
textColor: "#FFFFFF",
|
|
376
|
+
font: "Pacifico",
|
|
377
|
+
decorations: ["confetti", "balloons"],
|
|
378
|
+
},
|
|
379
|
+
default: {
|
|
380
|
+
backgroundColor: settings.backgroundColor,
|
|
381
|
+
accentColor: settings.accentColor,
|
|
382
|
+
textColor: settings.textColor,
|
|
383
|
+
font: settings.font,
|
|
384
|
+
decorations: [],
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const currentTheme = themes[settings.theme.toLowerCase()] || themes.default;
|
|
389
|
+
|
|
390
|
+
// Aplica as configurações do tema
|
|
391
|
+
settings.backgroundColor = currentTheme.backgroundColor;
|
|
392
|
+
settings.accentColor = currentTheme.accentColor;
|
|
393
|
+
settings.textColor = currentTheme.textColor;
|
|
394
|
+
settings.font = currentTheme.font;
|
|
395
|
+
|
|
396
|
+
// Desenha o fundo
|
|
397
|
+
ctx.fillStyle = settings.backgroundColor;
|
|
398
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
399
|
+
|
|
400
|
+
// Adiciona decorações temáticas
|
|
401
|
+
if (currentTheme.decorations.length > 0) {
|
|
402
|
+
await addThemedDecorations(ctx, settings, currentTheme.decorations);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Se tiver imagem, desenha com sobreposição
|
|
406
|
+
if (settings.imageURL) {
|
|
407
|
+
const image = await utils.loadImage(settings.imageURL);
|
|
408
|
+
utils.drawImageProp(ctx, image, 0, 0, settings.width, settings.height);
|
|
409
|
+
|
|
410
|
+
if (settings.showOverlay) {
|
|
411
|
+
ctx.fillStyle = utils.hexToRgba(
|
|
412
|
+
settings.overlayColor,
|
|
413
|
+
settings.overlayOpacity
|
|
414
|
+
);
|
|
415
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Desenha o conteúdo de texto
|
|
420
|
+
const textX = settings.width * 0.1;
|
|
421
|
+
const textWidth = settings.width * 0.8;
|
|
422
|
+
|
|
423
|
+
// Título
|
|
424
|
+
ctx.font = `bold 64px ${settings.font}`;
|
|
425
|
+
ctx.fillStyle = settings.textColor;
|
|
426
|
+
utils.wrapText(ctx, settings.title, textX, settings.height * 0.3, textWidth, 70);
|
|
427
|
+
|
|
428
|
+
// Data e Hora
|
|
429
|
+
let dateTimeString = "";
|
|
430
|
+
if (settings.date) dateTimeString += settings.date;
|
|
431
|
+
if (settings.time) dateTimeString += (settings.date ? " | " : "") + settings.time;
|
|
432
|
+
|
|
433
|
+
if (dateTimeString) {
|
|
434
|
+
ctx.font = `300 32px ${settings.font}`;
|
|
435
|
+
ctx.fillStyle = settings.textColor;
|
|
436
|
+
ctx.fillText(dateTimeString, textX, settings.height * 0.45);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Local
|
|
440
|
+
if (settings.location) {
|
|
441
|
+
ctx.font = `300 28px ${settings.font}`;
|
|
442
|
+
ctx.fillStyle = settings.textColor;
|
|
443
|
+
ctx.fillText(settings.location, textX, settings.height * 0.55);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Botão CTA
|
|
447
|
+
if (settings.ctaText) {
|
|
448
|
+
const ctaWidth = 200;
|
|
449
|
+
const ctaHeight = 60;
|
|
450
|
+
const ctaX = textX;
|
|
451
|
+
const ctaY = settings.height * 0.7;
|
|
452
|
+
|
|
453
|
+
ctx.fillStyle = settings.accentColor;
|
|
454
|
+
utils.roundRect(ctx, ctaX, ctaY, ctaWidth, ctaHeight, 8, true);
|
|
455
|
+
|
|
456
|
+
ctx.font = `bold 24px ${settings.font}`;
|
|
457
|
+
ctx.fillStyle = settings.backgroundColor; // Cor do texto do botão
|
|
458
|
+
ctx.textAlign = "center";
|
|
459
|
+
ctx.fillText(settings.ctaText, ctaX + ctaWidth / 2, ctaY + 38);
|
|
460
|
+
ctx.textAlign = "left";
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Adiciona decorações temáticas ao banner
|
|
466
|
+
*
|
|
467
|
+
* @async
|
|
468
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
469
|
+
* @param {Object} settings - Configurações do banner
|
|
470
|
+
* @param {Array<string>} decorations - Lista de decorações a serem adicionadas
|
|
471
|
+
*/
|
|
472
|
+
async function addThemedDecorations(ctx, settings, decorations) {
|
|
473
|
+
// Implementação simplificada - em uma versão real, carregaria imagens de decorações
|
|
474
|
+
// ou desenharia formas específicas para cada tipo de decoração
|
|
475
|
+
|
|
476
|
+
for (let i = 0; i < 5; i++) {
|
|
477
|
+
const size = Math.random() * 80 + 40;
|
|
478
|
+
const x = Math.random() * settings.width;
|
|
479
|
+
const y = Math.random() * settings.height;
|
|
480
|
+
|
|
481
|
+
ctx.fillStyle = utils.hexToRgba(settings.accentColor, 0.3);
|
|
482
|
+
|
|
483
|
+
// Escolhe uma decoração aleatória da lista
|
|
484
|
+
const decoration = decorations[Math.floor(Math.random() * decorations.length)];
|
|
485
|
+
|
|
486
|
+
switch (decoration) {
|
|
487
|
+
case "geometric-shapes":
|
|
488
|
+
drawGeometricShape(ctx, x, y, size);
|
|
489
|
+
break;
|
|
490
|
+
case "lines":
|
|
491
|
+
drawRandomLines(ctx, x, y, size);
|
|
492
|
+
break;
|
|
493
|
+
case "laptop-icon":
|
|
494
|
+
// Implementar desenho de ícone de laptop
|
|
495
|
+
break;
|
|
496
|
+
case "speech-bubble":
|
|
497
|
+
// Implementar desenho de balão de fala
|
|
498
|
+
break;
|
|
499
|
+
case "map-pin":
|
|
500
|
+
// Implementar desenho de pino de mapa
|
|
501
|
+
break;
|
|
502
|
+
case "calendar-icon":
|
|
503
|
+
// Implementar desenho de ícone de calendário
|
|
504
|
+
break;
|
|
505
|
+
case "confetti":
|
|
506
|
+
drawConfetti(ctx, x, y, size);
|
|
507
|
+
break;
|
|
508
|
+
case "balloons":
|
|
509
|
+
drawBalloons(ctx, x, y, size);
|
|
510
|
+
break;
|
|
511
|
+
default:
|
|
512
|
+
ctx.beginPath();
|
|
513
|
+
ctx.arc(x, y, size / 2, 0, Math.PI * 2);
|
|
514
|
+
ctx.fill();
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Funções auxiliares para desenhar decorações temáticas
|
|
521
|
+
function drawGeometricShape(ctx, x, y, size) {
|
|
522
|
+
const shapes = ["circle", "square", "triangle"];
|
|
523
|
+
const shape = shapes[Math.floor(Math.random() * shapes.length)];
|
|
524
|
+
|
|
525
|
+
ctx.beginPath();
|
|
526
|
+
switch (shape) {
|
|
527
|
+
case "circle":
|
|
528
|
+
ctx.arc(x, y, size / 2, 0, Math.PI * 2);
|
|
529
|
+
break;
|
|
530
|
+
case "square":
|
|
531
|
+
ctx.rect(x - size / 2, y - size / 2, size, size);
|
|
532
|
+
break;
|
|
533
|
+
case "triangle":
|
|
534
|
+
ctx.moveTo(x, y - size / 2);
|
|
535
|
+
ctx.lineTo(x + size / 2, y + size / 2);
|
|
536
|
+
ctx.lineTo(x - size / 2, y + size / 2);
|
|
537
|
+
ctx.closePath();
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
ctx.fill();
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function drawRandomLines(ctx, x, y, size) {
|
|
544
|
+
ctx.beginPath();
|
|
545
|
+
for (let i = 0; i < 5; i++) {
|
|
546
|
+
ctx.moveTo(x + (Math.random() - 0.5) * size, y + (Math.random() - 0.5) * size);
|
|
547
|
+
ctx.lineTo(x + (Math.random() - 0.5) * size, y + (Math.random() - 0.5) * size);
|
|
548
|
+
}
|
|
549
|
+
ctx.stroke();
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function drawConfetti(ctx, x, y, size) {
|
|
553
|
+
for (let i = 0; i < 10; i++) {
|
|
554
|
+
const confettiX = x + (Math.random() - 0.5) * size;
|
|
555
|
+
const confettiY = y + (Math.random() - 0.5) * size;
|
|
556
|
+
const confettiSize = Math.random() * 10 + 5;
|
|
557
|
+
ctx.fillStyle = utils.getRandomColor();
|
|
558
|
+
ctx.fillRect(confettiX, confettiY, confettiSize, confettiSize);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function drawBalloons(ctx, x, y, size) {
|
|
563
|
+
for (let i = 0; i < 3; i++) {
|
|
564
|
+
const balloonX = x + (Math.random() - 0.5) * size;
|
|
565
|
+
const balloonY = y + (Math.random() - 0.5) * size;
|
|
566
|
+
const balloonRadius = Math.random() * 20 + 10;
|
|
567
|
+
ctx.fillStyle = utils.getRandomColor();
|
|
568
|
+
ctx.beginPath();
|
|
569
|
+
ctx.arc(balloonX, balloonY, balloonRadius, 0, Math.PI * 2);
|
|
570
|
+
ctx.fill();
|
|
571
|
+
// Desenha a corda do balão
|
|
572
|
+
ctx.beginPath();
|
|
573
|
+
ctx.moveTo(balloonX, balloonY + balloonRadius);
|
|
574
|
+
ctx.lineTo(balloonX, balloonY + balloonRadius + 30);
|
|
575
|
+
ctx.stroke();
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Cria um banner para anúncio
|
|
581
|
+
*
|
|
582
|
+
* @async
|
|
583
|
+
* @param {Object} options - Opções de configuração
|
|
584
|
+
* @param {string} options.title - Título do anúncio
|
|
585
|
+
* @param {string} [options.message] - Mensagem do anúncio
|
|
586
|
+
* @param {string} [options.imageURL] - URL da imagem de fundo
|
|
587
|
+
* @param {string} [options.backgroundColor="#FFC107"] - Cor de fundo
|
|
588
|
+
* @param {string} [options.textColor="#333333"] - Cor do texto
|
|
589
|
+
* @param {string} [options.accentColor="#333333"] - Cor de destaque
|
|
590
|
+
* @param {string} [options.style="standard"] - Estilo do banner ("standard", "minimal", "bold", "urgent")
|
|
591
|
+
* @param {number} [options.width=1200] - Largura do banner em pixels
|
|
592
|
+
* @param {number} [options.height=400] - Altura do banner em pixels
|
|
593
|
+
* @param {string} [options.font="Poppins"] - Nome da fonte
|
|
594
|
+
* @param {boolean} [options.showIcon=true] - Se deve mostrar um ícone
|
|
595
|
+
* @param {string} [options.iconType="megaphone"] - Tipo de ícone ("megaphone", "info", "warning", "star")
|
|
596
|
+
* @returns {Promise<Buffer>} - Buffer da imagem gerada
|
|
597
|
+
*/
|
|
598
|
+
async function createAnnouncementBanner(options) {
|
|
599
|
+
// Validação de parâmetros
|
|
600
|
+
validator.validateRequiredParams(options, ["title"]);
|
|
601
|
+
|
|
602
|
+
// Valores padrão
|
|
603
|
+
const defaults = {
|
|
604
|
+
message: "",
|
|
605
|
+
imageURL: "",
|
|
606
|
+
backgroundColor: "#FFC107",
|
|
607
|
+
textColor: "#333333",
|
|
608
|
+
accentColor: "#333333",
|
|
609
|
+
style: "standard",
|
|
610
|
+
width: 1200,
|
|
611
|
+
height: 400,
|
|
612
|
+
font: "Poppins",
|
|
613
|
+
showIcon: true,
|
|
614
|
+
iconType: "megaphone",
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
// Mescla as opções com os valores padrão
|
|
618
|
+
const settings = { ...defaults, ...options };
|
|
619
|
+
|
|
620
|
+
// Cria a imagem
|
|
621
|
+
const img = PImage.make(settings.width, settings.height);
|
|
622
|
+
const ctx = img.getContext("2d");
|
|
623
|
+
|
|
624
|
+
try {
|
|
625
|
+
// Aplica o estilo de acordo com a opção selecionada
|
|
626
|
+
switch (settings.style) {
|
|
627
|
+
case "minimal":
|
|
628
|
+
await drawMinimalAnnouncementStyle(ctx, settings);
|
|
629
|
+
break;
|
|
630
|
+
case "bold":
|
|
631
|
+
await drawBoldAnnouncementStyle(ctx, settings);
|
|
632
|
+
break;
|
|
633
|
+
case "urgent":
|
|
634
|
+
await drawUrgentAnnouncementStyle(ctx, settings);
|
|
635
|
+
break;
|
|
636
|
+
case "standard":
|
|
637
|
+
default:
|
|
638
|
+
await drawStandardAnnouncementStyle(ctx, settings);
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Retorna o buffer da imagem
|
|
643
|
+
return await utils.getBufferFromImage(img);
|
|
644
|
+
} catch (error) {
|
|
645
|
+
console.error("Erro ao criar banner de anúncio:", error);
|
|
646
|
+
throw error;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Desenha o estilo padrão do banner de anúncio
|
|
652
|
+
*
|
|
653
|
+
* @async
|
|
654
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
655
|
+
* @param {Object} settings - Configurações do banner
|
|
656
|
+
*/
|
|
657
|
+
async function drawStandardAnnouncementStyle(ctx, settings) {
|
|
658
|
+
// Desenha o fundo
|
|
659
|
+
ctx.fillStyle = settings.backgroundColor;
|
|
660
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
661
|
+
|
|
662
|
+
// Se tiver imagem de fundo, desenha
|
|
663
|
+
if (settings.imageURL) {
|
|
664
|
+
const image = await utils.loadImage(settings.imageURL);
|
|
665
|
+
utils.drawImageProp(ctx, image, 0, 0, settings.width, settings.height);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Desenha o ícone se necessário
|
|
669
|
+
let iconX = settings.width * 0.1;
|
|
670
|
+
if (settings.showIcon) {
|
|
671
|
+
await drawAnnouncementIcon(ctx, settings, iconX, settings.height / 2);
|
|
672
|
+
iconX += 80; // Ajusta a posição do texto após o ícone
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Desenha o conteúdo de texto
|
|
676
|
+
const textWidth = settings.width * 0.8 - (settings.showIcon ? 80 : 0);
|
|
677
|
+
|
|
678
|
+
// Título
|
|
679
|
+
ctx.font = `bold 48px ${settings.font}`;
|
|
680
|
+
ctx.fillStyle = settings.textColor;
|
|
681
|
+
utils.wrapText(
|
|
682
|
+
ctx,
|
|
683
|
+
settings.title,
|
|
684
|
+
iconX,
|
|
685
|
+
settings.height * 0.4,
|
|
686
|
+
textWidth,
|
|
687
|
+
50
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
// Mensagem
|
|
691
|
+
if (settings.message) {
|
|
692
|
+
ctx.font = `300 28px ${settings.font}`;
|
|
693
|
+
ctx.fillStyle = settings.textColor;
|
|
694
|
+
utils.wrapText(
|
|
695
|
+
ctx,
|
|
696
|
+
settings.message,
|
|
697
|
+
iconX,
|
|
698
|
+
settings.height * 0.6,
|
|
699
|
+
textWidth,
|
|
700
|
+
35
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Desenha o estilo minimalista do banner de anúncio
|
|
707
|
+
*
|
|
708
|
+
* @async
|
|
709
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
710
|
+
* @param {Object} settings - Configurações do banner
|
|
711
|
+
*/
|
|
712
|
+
async function drawMinimalAnnouncementStyle(ctx, settings) {
|
|
713
|
+
// Desenha o fundo
|
|
714
|
+
ctx.fillStyle = "#F5F5F5";
|
|
715
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
716
|
+
|
|
717
|
+
// Adiciona uma linha de destaque
|
|
718
|
+
ctx.fillStyle = settings.backgroundColor;
|
|
719
|
+
ctx.fillRect(0, 0, settings.width, 10);
|
|
720
|
+
|
|
721
|
+
// Desenha o ícone se necessário
|
|
722
|
+
let iconX = settings.width * 0.05;
|
|
723
|
+
if (settings.showIcon) {
|
|
724
|
+
await drawAnnouncementIcon(ctx, settings, iconX, settings.height / 2);
|
|
725
|
+
iconX += 60;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Desenha o conteúdo de texto
|
|
729
|
+
const textWidth = settings.width * 0.85 - (settings.showIcon ? 60 : 0);
|
|
730
|
+
|
|
731
|
+
// Título
|
|
732
|
+
ctx.font = `bold 42px ${settings.font}`;
|
|
733
|
+
ctx.fillStyle = settings.textColor;
|
|
734
|
+
utils.wrapText(
|
|
735
|
+
ctx,
|
|
736
|
+
settings.title,
|
|
737
|
+
iconX,
|
|
738
|
+
settings.height * 0.4,
|
|
739
|
+
textWidth,
|
|
740
|
+
45
|
|
741
|
+
);
|
|
742
|
+
|
|
743
|
+
// Mensagem
|
|
744
|
+
if (settings.message) {
|
|
745
|
+
ctx.font = `300 24px ${settings.font}`;
|
|
746
|
+
ctx.fillStyle = "#555555";
|
|
747
|
+
utils.wrapText(
|
|
748
|
+
ctx,
|
|
749
|
+
settings.message,
|
|
750
|
+
iconX,
|
|
751
|
+
settings.height * 0.6,
|
|
752
|
+
textWidth,
|
|
753
|
+
30
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Desenha o estilo bold do banner de anúncio
|
|
760
|
+
*
|
|
761
|
+
* @async
|
|
762
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
763
|
+
* @param {Object} settings - Configurações do banner
|
|
764
|
+
*/
|
|
765
|
+
async function drawBoldAnnouncementStyle(ctx, settings) {
|
|
766
|
+
// Desenha o fundo com cor de destaque
|
|
767
|
+
ctx.fillStyle = settings.accentColor;
|
|
768
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
769
|
+
|
|
770
|
+
// Adiciona elementos decorativos
|
|
771
|
+
for (let i = 0; i < 3; i++) {
|
|
772
|
+
const size = Math.random() * 150 + 80;
|
|
773
|
+
const x = Math.random() * settings.width;
|
|
774
|
+
const y = Math.random() * settings.height;
|
|
775
|
+
|
|
776
|
+
ctx.fillStyle = utils.hexToRgba(settings.backgroundColor, 0.2);
|
|
777
|
+
ctx.beginPath();
|
|
778
|
+
ctx.arc(x, y, size, 0, Math.PI * 2);
|
|
779
|
+
ctx.fill();
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Desenha o ícone se necessário
|
|
783
|
+
let iconX = settings.width * 0.1;
|
|
784
|
+
if (settings.showIcon) {
|
|
785
|
+
// Altera a cor do ícone para combinar com o fundo
|
|
786
|
+
const originalAccentColor = settings.accentColor;
|
|
787
|
+
settings.accentColor = settings.backgroundColor;
|
|
788
|
+
await drawAnnouncementIcon(ctx, settings, iconX, settings.height / 2, 60);
|
|
789
|
+
settings.accentColor = originalAccentColor;
|
|
790
|
+
iconX += 100;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Desenha o conteúdo de texto
|
|
794
|
+
const textWidth = settings.width * 0.8 - (settings.showIcon ? 100 : 0);
|
|
795
|
+
|
|
796
|
+
// Título
|
|
797
|
+
ctx.font = `bold 54px ${settings.font}`;
|
|
798
|
+
ctx.fillStyle = settings.backgroundColor;
|
|
799
|
+
utils.wrapText(
|
|
800
|
+
ctx,
|
|
801
|
+
settings.title.toUpperCase(),
|
|
802
|
+
iconX,
|
|
803
|
+
settings.height * 0.4,
|
|
804
|
+
textWidth,
|
|
805
|
+
60
|
|
806
|
+
);
|
|
807
|
+
|
|
808
|
+
// Mensagem
|
|
809
|
+
if (settings.message) {
|
|
810
|
+
ctx.font = `300 30px ${settings.font}`;
|
|
811
|
+
ctx.fillStyle = settings.backgroundColor;
|
|
812
|
+
utils.wrapText(
|
|
813
|
+
ctx,
|
|
814
|
+
settings.message,
|
|
815
|
+
iconX,
|
|
816
|
+
settings.height * 0.6,
|
|
817
|
+
textWidth,
|
|
818
|
+
38
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Desenha o estilo urgente do banner de anúncio
|
|
825
|
+
*
|
|
826
|
+
* @async
|
|
827
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
828
|
+
* @param {Object} settings - Configurações do banner
|
|
829
|
+
*/
|
|
830
|
+
async function drawUrgentAnnouncementStyle(ctx, settings) {
|
|
831
|
+
// Desenha o fundo com gradiente de alerta
|
|
832
|
+
const gradient = ctx.createLinearGradient(0, 0, settings.width, settings.height);
|
|
833
|
+
gradient.addColorStop(0, "#FF416C"); // Rosa avermelhado
|
|
834
|
+
gradient.addColorStop(1, "#FF4B2B"); // Laranja avermelhado
|
|
835
|
+
ctx.fillStyle = gradient;
|
|
836
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
837
|
+
|
|
838
|
+
// Adiciona um padrão de listras diagonais
|
|
839
|
+
effects.addDiagonalStripePattern(ctx, settings.width, settings.height, 20, "rgba(255,255,255,0.1)");
|
|
840
|
+
|
|
841
|
+
// Desenha o ícone se necessário (ícone de aviso)
|
|
842
|
+
let iconX = settings.width * 0.05;
|
|
843
|
+
if (settings.showIcon) {
|
|
844
|
+
const originalIconType = settings.iconType;
|
|
845
|
+
settings.iconType = "warning"; // Força o ícone de aviso
|
|
846
|
+
await drawAnnouncementIcon(ctx, settings, iconX, settings.height / 2, 60, "#FFFFFF");
|
|
847
|
+
settings.iconType = originalIconType;
|
|
848
|
+
iconX += 100;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Desenha o conteúdo de texto
|
|
852
|
+
const textWidth = settings.width * 0.85 - (settings.showIcon ? 100 : 0);
|
|
853
|
+
|
|
854
|
+
// Título
|
|
855
|
+
ctx.font = `bold 52px ${settings.font}`;
|
|
856
|
+
ctx.fillStyle = "#FFFFFF";
|
|
857
|
+
utils.wrapText(
|
|
858
|
+
ctx,
|
|
859
|
+
settings.title.toUpperCase(),
|
|
860
|
+
iconX,
|
|
861
|
+
settings.height * 0.4,
|
|
862
|
+
textWidth,
|
|
863
|
+
55
|
|
864
|
+
);
|
|
865
|
+
|
|
866
|
+
// Mensagem
|
|
867
|
+
if (settings.message) {
|
|
868
|
+
ctx.font = `300 28px ${settings.font}`;
|
|
869
|
+
ctx.fillStyle = "#FFFFFF";
|
|
870
|
+
utils.wrapText(
|
|
871
|
+
ctx,
|
|
872
|
+
settings.message,
|
|
873
|
+
iconX,
|
|
874
|
+
settings.height * 0.6,
|
|
875
|
+
textWidth,
|
|
876
|
+
35
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Desenha o ícone do banner de anúncio
|
|
883
|
+
*
|
|
884
|
+
* @async
|
|
885
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
886
|
+
* @param {Object} settings - Configurações do banner
|
|
887
|
+
* @param {number} x - Posição X do ícone
|
|
888
|
+
* @param {number} y - Posição Y do ícone
|
|
889
|
+
* @param {number} [size=50] - Tamanho do ícone
|
|
890
|
+
* @param {string} [color] - Cor do ícone (opcional)
|
|
891
|
+
*/
|
|
892
|
+
async function drawAnnouncementIcon(ctx, settings, x, y, size = 50, color) {
|
|
893
|
+
const iconColor = color || settings.accentColor;
|
|
894
|
+
ctx.fillStyle = iconColor;
|
|
895
|
+
ctx.strokeStyle = iconColor;
|
|
896
|
+
ctx.lineWidth = 3;
|
|
897
|
+
|
|
898
|
+
// Centraliza o ícone verticalmente
|
|
899
|
+
y -= size / 2;
|
|
900
|
+
|
|
901
|
+
switch (settings.iconType) {
|
|
902
|
+
case "megaphone":
|
|
903
|
+
// Desenha um megafone (simplificado)
|
|
904
|
+
ctx.beginPath();
|
|
905
|
+
ctx.moveTo(x, y + size * 0.3);
|
|
906
|
+
ctx.lineTo(x + size * 0.4, y);
|
|
907
|
+
ctx.lineTo(x + size, y + size * 0.2);
|
|
908
|
+
ctx.lineTo(x + size, y + size * 0.8);
|
|
909
|
+
ctx.lineTo(x + size * 0.4, y + size);
|
|
910
|
+
ctx.lineTo(x, y + size * 0.7);
|
|
911
|
+
ctx.closePath();
|
|
912
|
+
ctx.fill();
|
|
913
|
+
break;
|
|
914
|
+
case "info":
|
|
915
|
+
// Desenha um círculo com "i"
|
|
916
|
+
ctx.beginPath();
|
|
917
|
+
ctx.arc(x + size / 2, y + size / 2, size / 2, 0, Math.PI * 2);
|
|
918
|
+
ctx.stroke();
|
|
919
|
+
ctx.font = `bold ${size * 0.6}px ${settings.font}`;
|
|
920
|
+
ctx.textAlign = "center";
|
|
921
|
+
ctx.fillText("i", x + size / 2, y + size * 0.7);
|
|
922
|
+
ctx.textAlign = "left";
|
|
923
|
+
break;
|
|
924
|
+
case "warning":
|
|
925
|
+
// Desenha um triângulo com "!"
|
|
926
|
+
ctx.beginPath();
|
|
927
|
+
ctx.moveTo(x + size / 2, y);
|
|
928
|
+
ctx.lineTo(x + size, y + size);
|
|
929
|
+
ctx.lineTo(x, y + size);
|
|
930
|
+
ctx.closePath();
|
|
931
|
+
ctx.stroke();
|
|
932
|
+
ctx.font = `bold ${size * 0.6}px ${settings.font}`;
|
|
933
|
+
ctx.textAlign = "center";
|
|
934
|
+
ctx.fillText("!", x + size / 2, y + size * 0.75);
|
|
935
|
+
ctx.textAlign = "left";
|
|
936
|
+
break;
|
|
937
|
+
case "star":
|
|
938
|
+
// Desenha uma estrela
|
|
939
|
+
utils.drawStar(ctx, x, y, size, 5, 0.5, iconColor);
|
|
940
|
+
break;
|
|
941
|
+
default:
|
|
942
|
+
// Ícone padrão (círculo)
|
|
943
|
+
ctx.beginPath();
|
|
944
|
+
ctx.arc(x + size / 2, y + size / 2, size / 2, 0, Math.PI * 2);
|
|
945
|
+
ctx.fill();
|
|
946
|
+
break;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Cria um banner para citação
|
|
952
|
+
*
|
|
953
|
+
* @async
|
|
954
|
+
* @param {Object} options - Opções de configuração
|
|
955
|
+
* @param {string} options.quote - Texto da citação
|
|
956
|
+
* @param {string} [options.author] - Autor da citação
|
|
957
|
+
* @param {string} [options.imageURL] - URL da imagem de fundo
|
|
958
|
+
* @param {string} [options.backgroundColor="#333333"] - Cor de fundo
|
|
959
|
+
* @param {string} [options.textColor="#FFFFFF"] - Cor do texto
|
|
960
|
+
* @param {string} [options.accentColor="#FFC107"] - Cor de destaque (para aspas)
|
|
961
|
+
* @param {string} [options.style="standard"] - Estilo do banner ("standard", "minimal", "modern", "image-focus")
|
|
962
|
+
* @param {number} [options.width=1200] - Largura do banner em pixels
|
|
963
|
+
* @param {number} [options.height=628] - Altura do banner em pixels
|
|
964
|
+
* @param {string} [options.font="Merriweather"] - Nome da fonte (ideal para citações)
|
|
965
|
+
* @param {boolean} [options.showOverlay=true] - Se deve mostrar sobreposição na imagem
|
|
966
|
+
* @param {number} [options.overlayOpacity=0.6] - Opacidade da sobreposição
|
|
967
|
+
* @param {string} [options.overlayColor="#000000"] - Cor da sobreposição
|
|
968
|
+
* @returns {Promise<Buffer>} - Buffer da imagem gerada
|
|
969
|
+
*/
|
|
970
|
+
async function createQuoteBanner(options) {
|
|
971
|
+
// Validação de parâmetros
|
|
972
|
+
validator.validateRequiredParams(options, ["quote"]);
|
|
973
|
+
|
|
974
|
+
// Valores padrão
|
|
975
|
+
const defaults = {
|
|
976
|
+
author: "",
|
|
977
|
+
imageURL: "",
|
|
978
|
+
backgroundColor: "#333333",
|
|
979
|
+
textColor: "#FFFFFF",
|
|
980
|
+
accentColor: "#FFC107",
|
|
981
|
+
style: "standard",
|
|
982
|
+
width: 1200,
|
|
983
|
+
height: 628,
|
|
984
|
+
font: "Merriweather",
|
|
985
|
+
showOverlay: true,
|
|
986
|
+
overlayOpacity: 0.6,
|
|
987
|
+
overlayColor: "#000000",
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
// Mescla as opções com os valores padrão
|
|
991
|
+
const settings = { ...defaults, ...options };
|
|
992
|
+
|
|
993
|
+
// Cria a imagem
|
|
994
|
+
const img = PImage.make(settings.width, settings.height);
|
|
995
|
+
const ctx = img.getContext("2d");
|
|
996
|
+
|
|
997
|
+
try {
|
|
998
|
+
// Aplica o estilo de acordo com a opção selecionada
|
|
999
|
+
switch (settings.style) {
|
|
1000
|
+
case "minimal":
|
|
1001
|
+
await drawMinimalQuoteStyle(ctx, settings);
|
|
1002
|
+
break;
|
|
1003
|
+
case "modern":
|
|
1004
|
+
await drawModernQuoteStyle(ctx, settings);
|
|
1005
|
+
break;
|
|
1006
|
+
case "image-focus":
|
|
1007
|
+
await drawImageFocusQuoteStyle(ctx, settings);
|
|
1008
|
+
break;
|
|
1009
|
+
case "standard":
|
|
1010
|
+
default:
|
|
1011
|
+
await drawStandardQuoteStyle(ctx, settings);
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Retorna o buffer da imagem
|
|
1016
|
+
return await utils.getBufferFromImage(img);
|
|
1017
|
+
} catch (error) {
|
|
1018
|
+
console.error("Erro ao criar banner de citação:", error);
|
|
1019
|
+
throw error;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Desenha o estilo padrão do banner de citação
|
|
1025
|
+
*
|
|
1026
|
+
* @async
|
|
1027
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
1028
|
+
* @param {Object} settings - Configurações do banner
|
|
1029
|
+
*/
|
|
1030
|
+
async function drawStandardQuoteStyle(ctx, settings) {
|
|
1031
|
+
// Desenha o fundo
|
|
1032
|
+
ctx.fillStyle = settings.backgroundColor;
|
|
1033
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
1034
|
+
|
|
1035
|
+
// Se tiver imagem de fundo, desenha com sobreposição
|
|
1036
|
+
if (settings.imageURL) {
|
|
1037
|
+
const image = await utils.loadImage(settings.imageURL);
|
|
1038
|
+
utils.drawImageProp(ctx, image, 0, 0, settings.width, settings.height);
|
|
1039
|
+
|
|
1040
|
+
if (settings.showOverlay) {
|
|
1041
|
+
ctx.fillStyle = utils.hexToRgba(
|
|
1042
|
+
settings.overlayColor,
|
|
1043
|
+
settings.overlayOpacity
|
|
1044
|
+
);
|
|
1045
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// Desenha o conteúdo de texto centralizado
|
|
1050
|
+
ctx.textAlign = "center";
|
|
1051
|
+
|
|
1052
|
+
// Aspas de abertura
|
|
1053
|
+
ctx.font = `bold 120px ${settings.font}`;
|
|
1054
|
+
ctx.fillStyle = settings.accentColor;
|
|
1055
|
+
ctx.fillText("“", settings.width / 2, settings.height * 0.3);
|
|
1056
|
+
|
|
1057
|
+
// Citação
|
|
1058
|
+
ctx.font = `italic 48px ${settings.font}`;
|
|
1059
|
+
ctx.fillStyle = settings.textColor;
|
|
1060
|
+
utils.wrapTextCentered(
|
|
1061
|
+
ctx,
|
|
1062
|
+
settings.quote,
|
|
1063
|
+
settings.width / 2,
|
|
1064
|
+
settings.height * 0.5,
|
|
1065
|
+
settings.width * 0.7,
|
|
1066
|
+
60
|
|
1067
|
+
);
|
|
1068
|
+
|
|
1069
|
+
// Autor
|
|
1070
|
+
if (settings.author) {
|
|
1071
|
+
ctx.font = `300 28px ${settings.font}`;
|
|
1072
|
+
ctx.fillStyle = settings.textColor;
|
|
1073
|
+
ctx.fillText(`— ${settings.author}`, settings.width / 2, settings.height * 0.75);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// Restaura o alinhamento de texto
|
|
1077
|
+
ctx.textAlign = "left";
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Desenha o estilo minimalista do banner de citação
|
|
1082
|
+
*
|
|
1083
|
+
* @async
|
|
1084
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
1085
|
+
* @param {Object} settings - Configurações do banner
|
|
1086
|
+
*/
|
|
1087
|
+
async function drawMinimalQuoteStyle(ctx, settings) {
|
|
1088
|
+
// Desenha o fundo
|
|
1089
|
+
ctx.fillStyle = "#FFFFFF";
|
|
1090
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
1091
|
+
|
|
1092
|
+
// Desenha o conteúdo de texto
|
|
1093
|
+
const textX = settings.width * 0.1;
|
|
1094
|
+
const textWidth = settings.width * 0.8;
|
|
1095
|
+
|
|
1096
|
+
// Aspas de abertura (menores e sutis)
|
|
1097
|
+
ctx.font = `bold 60px ${settings.font}`;
|
|
1098
|
+
ctx.fillStyle = settings.accentColor;
|
|
1099
|
+
ctx.fillText("“", textX, settings.height * 0.3);
|
|
1100
|
+
|
|
1101
|
+
// Citação
|
|
1102
|
+
ctx.font = `italic 42px ${settings.font}`;
|
|
1103
|
+
ctx.fillStyle = "#333333";
|
|
1104
|
+
utils.wrapText(
|
|
1105
|
+
ctx,
|
|
1106
|
+
settings.quote,
|
|
1107
|
+
textX + 30, // Pequeno deslocamento após aspas
|
|
1108
|
+
settings.height * 0.4,
|
|
1109
|
+
textWidth - 30,
|
|
1110
|
+
50
|
|
1111
|
+
);
|
|
1112
|
+
|
|
1113
|
+
// Autor
|
|
1114
|
+
if (settings.author) {
|
|
1115
|
+
ctx.font = `300 24px ${settings.font}`;
|
|
1116
|
+
ctx.fillStyle = "#555555";
|
|
1117
|
+
ctx.textAlign = "right";
|
|
1118
|
+
ctx.fillText(`— ${settings.author}`, textX + textWidth, settings.height * 0.75);
|
|
1119
|
+
ctx.textAlign = "left";
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Desenha o estilo moderno do banner de citação
|
|
1125
|
+
*
|
|
1126
|
+
* @async
|
|
1127
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
1128
|
+
* @param {Object} settings - Configurações do banner
|
|
1129
|
+
*/
|
|
1130
|
+
async function drawModernQuoteStyle(ctx, settings) {
|
|
1131
|
+
// Desenha o fundo com gradiente
|
|
1132
|
+
const gradient = ctx.createLinearGradient(0, 0, settings.width, settings.height);
|
|
1133
|
+
gradient.addColorStop(0, settings.backgroundColor);
|
|
1134
|
+
gradient.addColorStop(1, utils.adjustColor(settings.backgroundColor, -0.2));
|
|
1135
|
+
ctx.fillStyle = gradient;
|
|
1136
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
1137
|
+
|
|
1138
|
+
// Adiciona um padrão de linhas sutis
|
|
1139
|
+
effects.addHorizontalLinePattern(ctx, settings.width, settings.height, 50, "rgba(255,255,255,0.05)");
|
|
1140
|
+
|
|
1141
|
+
// Desenha o conteúdo de texto
|
|
1142
|
+
const textX = settings.width * 0.1;
|
|
1143
|
+
const textWidth = settings.width * 0.8;
|
|
1144
|
+
|
|
1145
|
+
// Citação (grande e centralizada)
|
|
1146
|
+
ctx.font = `bold 72px ${settings.font}`;
|
|
1147
|
+
ctx.fillStyle = settings.textColor;
|
|
1148
|
+
ctx.textAlign = "center";
|
|
1149
|
+
utils.wrapTextCentered(
|
|
1150
|
+
ctx,
|
|
1151
|
+
`“${settings.quote}”`,
|
|
1152
|
+
settings.width / 2,
|
|
1153
|
+
settings.height * 0.5,
|
|
1154
|
+
textWidth,
|
|
1155
|
+
80
|
|
1156
|
+
);
|
|
1157
|
+
|
|
1158
|
+
// Autor (abaixo e menor)
|
|
1159
|
+
if (settings.author) {
|
|
1160
|
+
ctx.font = `300 32px ${settings.font}`;
|
|
1161
|
+
ctx.fillStyle = utils.hexToRgba(settings.textColor, 0.8);
|
|
1162
|
+
ctx.fillText(`— ${settings.author}`, settings.width / 2, settings.height * 0.8);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// Restaura o alinhamento de texto
|
|
1166
|
+
ctx.textAlign = "left";
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* Desenha o estilo image-focus do banner de citação
|
|
1171
|
+
*
|
|
1172
|
+
* @async
|
|
1173
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
1174
|
+
* @param {Object} settings - Configurações do banner
|
|
1175
|
+
*/
|
|
1176
|
+
async function drawImageFocusQuoteStyle(ctx, settings) {
|
|
1177
|
+
// Se tiver imagem de fundo, desenha com sobreposição forte
|
|
1178
|
+
if (settings.imageURL) {
|
|
1179
|
+
const image = await utils.loadImage(settings.imageURL);
|
|
1180
|
+
utils.drawImageProp(ctx, image, 0, 0, settings.width, settings.height);
|
|
1181
|
+
|
|
1182
|
+
// Sobreposição mais escura para destacar o texto
|
|
1183
|
+
ctx.fillStyle = utils.hexToRgba(settings.overlayColor, Math.max(0.7, settings.overlayOpacity));
|
|
1184
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
1185
|
+
} else {
|
|
1186
|
+
// Fallback para fundo sólido se não houver imagem
|
|
1187
|
+
ctx.fillStyle = settings.backgroundColor;
|
|
1188
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// Desenha o conteúdo de texto centralizado
|
|
1192
|
+
ctx.textAlign = "center";
|
|
1193
|
+
|
|
1194
|
+
// Citação
|
|
1195
|
+
ctx.font = `italic 54px ${settings.font}`;
|
|
1196
|
+
ctx.fillStyle = "#FFFFFF"; // Texto sempre branco para contraste
|
|
1197
|
+
utils.wrapTextCentered(
|
|
1198
|
+
ctx,
|
|
1199
|
+
`“${settings.quote}”`,
|
|
1200
|
+
settings.width / 2,
|
|
1201
|
+
settings.height * 0.5,
|
|
1202
|
+
settings.width * 0.7,
|
|
1203
|
+
65
|
|
1204
|
+
);
|
|
1205
|
+
|
|
1206
|
+
// Autor
|
|
1207
|
+
if (settings.author) {
|
|
1208
|
+
ctx.font = `300 30px ${settings.font}`;
|
|
1209
|
+
ctx.fillStyle = "#DDDDDD";
|
|
1210
|
+
ctx.fillText(`— ${settings.author}`, settings.width / 2, settings.height * 0.75);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// Restaura o alinhamento de texto
|
|
1214
|
+
ctx.textAlign = "left";
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* Cria um banner para enquete
|
|
1219
|
+
*
|
|
1220
|
+
* @async
|
|
1221
|
+
* @param {Object} options - Opções de configuração
|
|
1222
|
+
* @param {string} options.question - Pergunta da enquete
|
|
1223
|
+
* @param {Array<string>} options.pollOptions - Opções da enquete (máximo 5)
|
|
1224
|
+
* @param {string} [options.imageURL] - URL da imagem de fundo
|
|
1225
|
+
* @param {string} [options.backgroundColor="#F0F2F5"] - Cor de fundo
|
|
1226
|
+
* @param {string} [options.textColor="#333333"] - Cor do texto
|
|
1227
|
+
* @param {string} [options.accentColor="#007BFF"] - Cor de destaque (para opções)
|
|
1228
|
+
* @param {string} [options.style="standard"] - Estilo do banner ("standard", "minimal", "modern")
|
|
1229
|
+
* @param {number} [options.width=1200] - Largura do banner em pixels
|
|
1230
|
+
* @param {number} [options.height=628] - Altura do banner em pixels
|
|
1231
|
+
* @param {string} [options.font="Poppins"] - Nome da fonte
|
|
1232
|
+
* @param {boolean} [options.showOverlay=false] - Se deve mostrar sobreposição na imagem
|
|
1233
|
+
* @param {number} [options.overlayOpacity=0.3] - Opacidade da sobreposição
|
|
1234
|
+
* @param {string} [options.overlayColor="#000000"] - Cor da sobreposição
|
|
1235
|
+
* @param {boolean} [options.showResults=false] - Se deve mostrar resultados (simulado)
|
|
1236
|
+
* @param {Array<number>} [options.results=[]] - Resultados percentuais para cada opção
|
|
1237
|
+
* @returns {Promise<Buffer>} - Buffer da imagem gerada
|
|
1238
|
+
*/
|
|
1239
|
+
async function createPollBanner(options) {
|
|
1240
|
+
// Validação de parâmetros
|
|
1241
|
+
validator.validateRequiredParams(options, ["question", "pollOptions"]);
|
|
1242
|
+
if (options.pollOptions.length < 2 || options.pollOptions.length > 5) {
|
|
1243
|
+
throw new Error("A enquete deve ter entre 2 e 5 opções.");
|
|
1244
|
+
}
|
|
1245
|
+
if (options.showResults && options.results.length !== options.pollOptions.length) {
|
|
1246
|
+
throw new Error("O número de resultados deve corresponder ao número de opções.");
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// Valores padrão
|
|
1250
|
+
const defaults = {
|
|
1251
|
+
imageURL: "",
|
|
1252
|
+
backgroundColor: "#F0F2F5",
|
|
1253
|
+
textColor: "#333333",
|
|
1254
|
+
accentColor: "#007BFF",
|
|
1255
|
+
style: "standard",
|
|
1256
|
+
width: 1200,
|
|
1257
|
+
height: 628,
|
|
1258
|
+
font: "Poppins",
|
|
1259
|
+
showOverlay: false,
|
|
1260
|
+
overlayOpacity: 0.3,
|
|
1261
|
+
overlayColor: "#000000",
|
|
1262
|
+
showResults: false,
|
|
1263
|
+
results: [],
|
|
1264
|
+
};
|
|
1265
|
+
|
|
1266
|
+
// Mescla as opções com os valores padrão
|
|
1267
|
+
const settings = { ...defaults, ...options };
|
|
1268
|
+
|
|
1269
|
+
// Cria a imagem
|
|
1270
|
+
const img = PImage.make(settings.width, settings.height);
|
|
1271
|
+
const ctx = img.getContext("2d");
|
|
1272
|
+
|
|
1273
|
+
try {
|
|
1274
|
+
// Aplica o estilo de acordo com a opção selecionada
|
|
1275
|
+
switch (settings.style) {
|
|
1276
|
+
case "minimal":
|
|
1277
|
+
await drawMinimalPollStyle(ctx, settings);
|
|
1278
|
+
break;
|
|
1279
|
+
case "modern":
|
|
1280
|
+
await drawModernPollStyle(ctx, settings);
|
|
1281
|
+
break;
|
|
1282
|
+
case "standard":
|
|
1283
|
+
default:
|
|
1284
|
+
await drawStandardPollStyle(ctx, settings);
|
|
1285
|
+
break;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// Retorna o buffer da imagem
|
|
1289
|
+
return await utils.getBufferFromImage(img);
|
|
1290
|
+
} catch (error) {
|
|
1291
|
+
console.error("Erro ao criar banner de enquete:", error);
|
|
1292
|
+
throw error;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
/**
|
|
1297
|
+
* Desenha o estilo padrão do banner de enquete
|
|
1298
|
+
*
|
|
1299
|
+
* @async
|
|
1300
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
1301
|
+
* @param {Object} settings - Configurações do banner
|
|
1302
|
+
*/
|
|
1303
|
+
async function drawStandardPollStyle(ctx, settings) {
|
|
1304
|
+
// Desenha o fundo
|
|
1305
|
+
ctx.fillStyle = settings.backgroundColor;
|
|
1306
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
1307
|
+
|
|
1308
|
+
// Se tiver imagem de fundo, desenha com sobreposição
|
|
1309
|
+
if (settings.imageURL) {
|
|
1310
|
+
const image = await utils.loadImage(settings.imageURL);
|
|
1311
|
+
utils.drawImageProp(ctx, image, 0, 0, settings.width, settings.height);
|
|
1312
|
+
|
|
1313
|
+
if (settings.showOverlay) {
|
|
1314
|
+
ctx.fillStyle = utils.hexToRgba(
|
|
1315
|
+
settings.overlayColor,
|
|
1316
|
+
settings.overlayOpacity
|
|
1317
|
+
);
|
|
1318
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// Desenha o conteúdo de texto
|
|
1323
|
+
const padding = settings.width * 0.05;
|
|
1324
|
+
const contentWidth = settings.width - padding * 2;
|
|
1325
|
+
|
|
1326
|
+
// Pergunta
|
|
1327
|
+
ctx.font = `bold 42px ${settings.font}`;
|
|
1328
|
+
ctx.fillStyle = settings.textColor;
|
|
1329
|
+
ctx.textAlign = "center";
|
|
1330
|
+
utils.wrapTextCentered(
|
|
1331
|
+
ctx,
|
|
1332
|
+
settings.question,
|
|
1333
|
+
settings.width / 2,
|
|
1334
|
+
settings.height * 0.2,
|
|
1335
|
+
contentWidth,
|
|
1336
|
+
50
|
|
1337
|
+
);
|
|
1338
|
+
ctx.textAlign = "left";
|
|
1339
|
+
|
|
1340
|
+
// Opções da enquete
|
|
1341
|
+
const optionHeight = 60;
|
|
1342
|
+
const optionSpacing = 20;
|
|
1343
|
+
const totalOptionsHeight = settings.pollOptions.length * (optionHeight + optionSpacing) - optionSpacing;
|
|
1344
|
+
let startY = (settings.height - totalOptionsHeight) / 2 + 30; // Ajusta para centralizar melhor
|
|
1345
|
+
|
|
1346
|
+
for (let i = 0; i < settings.pollOptions.length; i++) {
|
|
1347
|
+
const optionText = settings.pollOptions[i];
|
|
1348
|
+
const optionY = startY + i * (optionHeight + optionSpacing);
|
|
1349
|
+
const optionBoxWidth = contentWidth * 0.8;
|
|
1350
|
+
const optionBoxX = (settings.width - optionBoxWidth) / 2;
|
|
1351
|
+
|
|
1352
|
+
// Desenha a caixa da opção
|
|
1353
|
+
ctx.fillStyle = "#FFFFFF";
|
|
1354
|
+
ctx.strokeStyle = settings.accentColor;
|
|
1355
|
+
ctx.lineWidth = 2;
|
|
1356
|
+
utils.roundRect(ctx, optionBoxX, optionY, optionBoxWidth, optionHeight, 8, true, true);
|
|
1357
|
+
|
|
1358
|
+
// Se mostrar resultados, desenha a barra de progresso
|
|
1359
|
+
if (settings.showResults && settings.results[i] !== undefined) {
|
|
1360
|
+
const progress = settings.results[i] / 100;
|
|
1361
|
+
const progressBarWidth = optionBoxWidth * progress;
|
|
1362
|
+
ctx.fillStyle = utils.hexToRgba(settings.accentColor, 0.3);
|
|
1363
|
+
utils.roundRect(ctx, optionBoxX, optionY, progressBarWidth, optionHeight, 8, true);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// Desenha o texto da opção
|
|
1367
|
+
ctx.font = `300 24px ${settings.font}`;
|
|
1368
|
+
ctx.fillStyle = settings.textColor;
|
|
1369
|
+
ctx.fillText(optionText, optionBoxX + 20, optionY + optionHeight / 2 + 8);
|
|
1370
|
+
|
|
1371
|
+
// Se mostrar resultados, desenha a porcentagem
|
|
1372
|
+
if (settings.showResults && settings.results[i] !== undefined) {
|
|
1373
|
+
ctx.font = `bold 20px ${settings.font}`;
|
|
1374
|
+
ctx.fillStyle = settings.accentColor;
|
|
1375
|
+
ctx.textAlign = "right";
|
|
1376
|
+
ctx.fillText(`${settings.results[i]}%`, optionBoxX + optionBoxWidth - 20, optionY + optionHeight / 2 + 8);
|
|
1377
|
+
ctx.textAlign = "left";
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
/**
|
|
1383
|
+
* Desenha o estilo minimalista do banner de enquete
|
|
1384
|
+
*
|
|
1385
|
+
* @async
|
|
1386
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
1387
|
+
* @param {Object} settings - Configurações do banner
|
|
1388
|
+
*/
|
|
1389
|
+
async function drawMinimalPollStyle(ctx, settings) {
|
|
1390
|
+
// Desenha o fundo
|
|
1391
|
+
ctx.fillStyle = "#FFFFFF";
|
|
1392
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
1393
|
+
|
|
1394
|
+
// Desenha o conteúdo de texto
|
|
1395
|
+
const padding = settings.width * 0.08;
|
|
1396
|
+
const contentWidth = settings.width - padding * 2;
|
|
1397
|
+
|
|
1398
|
+
// Pergunta
|
|
1399
|
+
ctx.font = `bold 36px ${settings.font}`;
|
|
1400
|
+
ctx.fillStyle = settings.textColor;
|
|
1401
|
+
utils.wrapText(
|
|
1402
|
+
ctx,
|
|
1403
|
+
settings.question,
|
|
1404
|
+
padding,
|
|
1405
|
+
settings.height * 0.2,
|
|
1406
|
+
contentWidth,
|
|
1407
|
+
45
|
|
1408
|
+
);
|
|
1409
|
+
|
|
1410
|
+
// Opções da enquete
|
|
1411
|
+
const optionHeight = 50;
|
|
1412
|
+
const optionSpacing = 15;
|
|
1413
|
+
const totalOptionsHeight = settings.pollOptions.length * (optionHeight + optionSpacing) - optionSpacing;
|
|
1414
|
+
let startY = settings.height * 0.35;
|
|
1415
|
+
|
|
1416
|
+
for (let i = 0; i < settings.pollOptions.length; i++) {
|
|
1417
|
+
const optionText = settings.pollOptions[i];
|
|
1418
|
+
const optionY = startY + i * (optionHeight + optionSpacing);
|
|
1419
|
+
|
|
1420
|
+
// Desenha o texto da opção com um marcador
|
|
1421
|
+
ctx.font = `300 22px ${settings.font}`;
|
|
1422
|
+
ctx.fillStyle = settings.textColor;
|
|
1423
|
+
ctx.fillText(`□ ${optionText}`, padding, optionY + optionHeight / 2 + 8);
|
|
1424
|
+
|
|
1425
|
+
// Se mostrar resultados, desenha a barra de progresso e porcentagem
|
|
1426
|
+
if (settings.showResults && settings.results[i] !== undefined) {
|
|
1427
|
+
const progress = settings.results[i] / 100;
|
|
1428
|
+
const progressBarX = padding + ctx.measureText(`□ ${optionText}`).width + 20;
|
|
1429
|
+
const progressBarWidthMax = contentWidth - (progressBarX - padding) - 80; // Espaço para porcentagem
|
|
1430
|
+
const progressBarWidth = progressBarWidthMax * progress;
|
|
1431
|
+
|
|
1432
|
+
ctx.fillStyle = utils.hexToRgba(settings.accentColor, 0.2);
|
|
1433
|
+
ctx.fillRect(progressBarX, optionY + optionHeight / 2 - 5, progressBarWidthMax, 10);
|
|
1434
|
+
ctx.fillStyle = settings.accentColor;
|
|
1435
|
+
ctx.fillRect(progressBarX, optionY + optionHeight / 2 - 5, progressBarWidth, 10);
|
|
1436
|
+
|
|
1437
|
+
ctx.font = `bold 18px ${settings.font}`;
|
|
1438
|
+
ctx.fillStyle = settings.accentColor;
|
|
1439
|
+
ctx.fillText(`${settings.results[i]}%`, progressBarX + progressBarWidthMax + 10, optionY + optionHeight / 2 + 8);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
/**
|
|
1445
|
+
* Desenha o estilo moderno do banner de enquete
|
|
1446
|
+
*
|
|
1447
|
+
* @async
|
|
1448
|
+
* @param {CanvasRenderingContext2D} ctx - Contexto de renderização
|
|
1449
|
+
* @param {Object} settings - Configurações do banner
|
|
1450
|
+
*/
|
|
1451
|
+
async function drawModernPollStyle(ctx, settings) {
|
|
1452
|
+
// Desenha o fundo com gradiente
|
|
1453
|
+
const gradient = ctx.createLinearGradient(0, 0, settings.width, settings.height);
|
|
1454
|
+
gradient.addColorStop(0, settings.backgroundColor);
|
|
1455
|
+
gradient.addColorStop(1, utils.adjustColor(settings.backgroundColor, -0.15));
|
|
1456
|
+
ctx.fillStyle = gradient;
|
|
1457
|
+
ctx.fillRect(0, 0, settings.width, settings.height);
|
|
1458
|
+
|
|
1459
|
+
// Adiciona um padrão sutil
|
|
1460
|
+
effects.addNoisePattern(ctx, settings.width, settings.height, 0.02);
|
|
1461
|
+
|
|
1462
|
+
// Desenha o conteúdo de texto
|
|
1463
|
+
const padding = settings.width * 0.06;
|
|
1464
|
+
const contentWidth = settings.width - padding * 2;
|
|
1465
|
+
|
|
1466
|
+
// Pergunta
|
|
1467
|
+
ctx.font = `bold 48px ${settings.font}`;
|
|
1468
|
+
ctx.fillStyle = settings.textColor;
|
|
1469
|
+
ctx.textAlign = "center";
|
|
1470
|
+
utils.wrapTextCentered(
|
|
1471
|
+
ctx,
|
|
1472
|
+
settings.question,
|
|
1473
|
+
settings.width / 2,
|
|
1474
|
+
settings.height * 0.2,
|
|
1475
|
+
contentWidth * 0.9, // Um pouco mais estreito para centralizar melhor
|
|
1476
|
+
55
|
|
1477
|
+
);
|
|
1478
|
+
ctx.textAlign = "left";
|
|
1479
|
+
|
|
1480
|
+
// Opções da enquete
|
|
1481
|
+
const optionHeight = 70;
|
|
1482
|
+
const optionSpacing = 25;
|
|
1483
|
+
const totalOptionsHeight = settings.pollOptions.length * (optionHeight + optionSpacing) - optionSpacing;
|
|
1484
|
+
let startY = (settings.height - totalOptionsHeight) / 2 + 40;
|
|
1485
|
+
|
|
1486
|
+
for (let i = 0; i < settings.pollOptions.length; i++) {
|
|
1487
|
+
const optionText = settings.pollOptions[i];
|
|
1488
|
+
const optionY = startY + i * (optionHeight + optionSpacing);
|
|
1489
|
+
const optionBoxWidth = contentWidth * 0.7;
|
|
1490
|
+
const optionBoxX = (settings.width - optionBoxWidth) / 2;
|
|
1491
|
+
|
|
1492
|
+
// Desenha a caixa da opção com sombra
|
|
1493
|
+
ctx.shadowColor = "rgba(0,0,0,0.1)";
|
|
1494
|
+
ctx.shadowBlur = 10;
|
|
1495
|
+
ctx.shadowOffsetX = 3;
|
|
1496
|
+
ctx.shadowOffsetY = 3;
|
|
1497
|
+
|
|
1498
|
+
ctx.fillStyle = "#FFFFFF";
|
|
1499
|
+
utils.roundRect(ctx, optionBoxX, optionY, optionBoxWidth, optionHeight, 12, true);
|
|
1500
|
+
|
|
1501
|
+
ctx.shadowColor = "transparent"; // Remove sombra para elementos internos
|
|
1502
|
+
|
|
1503
|
+
// Se mostrar resultados, desenha a barra de progresso
|
|
1504
|
+
if (settings.showResults && settings.results[i] !== undefined) {
|
|
1505
|
+
const progress = settings.results[i] / 100;
|
|
1506
|
+
const progressBarWidth = (optionBoxWidth - 4) * progress; // -4 para padding interno
|
|
1507
|
+
ctx.fillStyle = settings.accentColor;
|
|
1508
|
+
utils.roundRect(ctx, optionBoxX + 2, optionY + 2, progressBarWidth, optionHeight - 4, 10, true);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
// Desenha o texto da opção
|
|
1512
|
+
ctx.font = `bold 26px ${settings.font}`;
|
|
1513
|
+
// Cor do texto ajustada se a barra de progresso estiver presente e cobrir o texto
|
|
1514
|
+
const textColor = (settings.showResults && settings.results[i] > 50) ? "#FFFFFF" : settings.textColor;
|
|
1515
|
+
ctx.fillStyle = textColor;
|
|
1516
|
+
ctx.fillText(optionText, optionBoxX + 25, optionY + optionHeight / 2 + 10);
|
|
1517
|
+
|
|
1518
|
+
// Se mostrar resultados, desenha a porcentagem
|
|
1519
|
+
if (settings.showResults && settings.results[i] !== undefined) {
|
|
1520
|
+
ctx.font = `bold 22px ${settings.font}`;
|
|
1521
|
+
const percentColor = (settings.showResults && settings.results[i] > 50) ? "#FFFFFF" : settings.accentColor;
|
|
1522
|
+
ctx.fillStyle = percentColor;
|
|
1523
|
+
ctx.textAlign = "right";
|
|
1524
|
+
ctx.fillText(`${settings.results[i]}%`, optionBoxX + optionBoxWidth - 25, optionY + optionHeight / 2 + 10);
|
|
1525
|
+
ctx.textAlign = "left";
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
module.exports = {
|
|
1531
|
+
createEventBanner,
|
|
1532
|
+
createAnnouncementBanner,
|
|
1533
|
+
createQuoteBanner,
|
|
1534
|
+
createPollBanner,
|
|
1535
|
+
};
|
|
1536
|
+
|