@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.
Files changed (48) hide show
  1. package/assets/fonts/Manrope/Manrope-Bold.ttf +0 -0
  2. package/assets/fonts/Manrope/Manrope-Regular.ttf +0 -0
  3. package/assets/fonts/Others/AbyssinicaSIL-Regular.ttf +0 -0
  4. package/assets/fonts/Others/ChirpRegular.ttf +0 -0
  5. package/assets/fonts/Poppins/Poppins-Bold.ttf +0 -0
  6. package/assets/fonts/Poppins/Poppins-Medium.ttf +0 -0
  7. package/assets/fonts/Poppins/Poppins-Regular.ttf +0 -0
  8. package/assets/placeholders/album_art.png +0 -0
  9. package/assets/placeholders/avatar.png +0 -0
  10. package/assets/placeholders/badge.jpg +0 -0
  11. package/assets/placeholders/badge.png +0 -0
  12. package/assets/placeholders/badge_2.jpg +0 -0
  13. package/assets/placeholders/badge_3.jpg +0 -0
  14. package/assets/placeholders/badge_4.jpg +0 -0
  15. package/assets/placeholders/badge_5.jpg +0 -0
  16. package/assets/placeholders/banner.jpeg +0 -0
  17. package/assets/placeholders/images.jpeg +0 -0
  18. package/index.js +153 -0
  19. package/package.json +34 -0
  20. package/src/animation-effects.js +631 -0
  21. package/src/cache-manager.js +258 -0
  22. package/src/community-banner.js +1536 -0
  23. package/src/constants.js +208 -0
  24. package/src/discord-profile.js +584 -0
  25. package/src/e-commerce-banner.js +1214 -0
  26. package/src/effects.js +355 -0
  27. package/src/error-handler.js +305 -0
  28. package/src/event-banner.js +1319 -0
  29. package/src/facebook-post.js +679 -0
  30. package/src/gradient-welcome.js +430 -0
  31. package/src/image-filters.js +1034 -0
  32. package/src/image-processor.js +1014 -0
  33. package/src/instagram-post.js +504 -0
  34. package/src/interactive-elements.js +1208 -0
  35. package/src/linkedin-post.js +658 -0
  36. package/src/marketing-banner.js +1089 -0
  37. package/src/minimalist-banner.js +892 -0
  38. package/src/modern-profile.js +755 -0
  39. package/src/performance-optimizer.js +216 -0
  40. package/src/telegram-header.js +544 -0
  41. package/src/test-runner.js +645 -0
  42. package/src/tiktok-post.js +713 -0
  43. package/src/twitter-header.js +604 -0
  44. package/src/validator.js +442 -0
  45. package/src/welcome-leave.js +445 -0
  46. package/src/whatsapp-status.js +386 -0
  47. package/src/youtube-thumbnail.js +681 -0
  48. 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
+