@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,1319 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Módulo de Banner de Evento
|
|
5
|
+
*
|
|
6
|
+
* Este módulo gera banners para eventos com informações como título,
|
|
7
|
+
* data, local, descrição e outros elementos visuais personalizáveis.
|
|
8
|
+
*
|
|
9
|
+
* @author Cognima Team (melhorado)
|
|
10
|
+
* @version 2.0.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
|
|
15
|
+
const pureimage = require("pureimage");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const {
|
|
18
|
+
loadImageWithAxios,
|
|
19
|
+
encodeToBuffer,
|
|
20
|
+
roundRect,
|
|
21
|
+
wrapText,
|
|
22
|
+
registerFontIfNeeded,
|
|
23
|
+
isValidHexColor,
|
|
24
|
+
DEFAULT_FONT_FAMILY,
|
|
25
|
+
applyTextShadow,
|
|
26
|
+
clearShadow,
|
|
27
|
+
createLinearGradient,
|
|
28
|
+
hexToRgba
|
|
29
|
+
} = require("../utils");
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
DEFAULT_COLORS,
|
|
33
|
+
LAYOUT,
|
|
34
|
+
DEFAULT_DIMENSIONS
|
|
35
|
+
} = require("./constants");
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
applyGlassmorphism,
|
|
39
|
+
applyMultiColorGradient,
|
|
40
|
+
applyGlow,
|
|
41
|
+
applyDotPattern,
|
|
42
|
+
applyLinePattern
|
|
43
|
+
} = require("./effects");
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @class EventBanner
|
|
47
|
+
* @classdesc Gera um banner para eventos com informações detalhadas e design personalizável.
|
|
48
|
+
* @example const banner = new EventBanner()
|
|
49
|
+
* .setTitle("Nome do Evento")
|
|
50
|
+
* .setDate("25 de Dezembro, 2025")
|
|
51
|
+
* .setTime("19:00 - 23:00")
|
|
52
|
+
* .setLocation("Local do Evento, Cidade")
|
|
53
|
+
* .setDescription("Descrição detalhada do evento com informações importantes.")
|
|
54
|
+
* .setBackground("image", "background.jpg")
|
|
55
|
+
* .setStyle("modern")
|
|
56
|
+
* .build();
|
|
57
|
+
*/
|
|
58
|
+
module.exports = class EventBanner {
|
|
59
|
+
constructor(options) {
|
|
60
|
+
// Dados Principais
|
|
61
|
+
this.title = "Nome do Evento";
|
|
62
|
+
this.date = null;
|
|
63
|
+
this.time = null;
|
|
64
|
+
this.location = null;
|
|
65
|
+
this.description = null;
|
|
66
|
+
this.organizer = null;
|
|
67
|
+
this.logo = null;
|
|
68
|
+
this.qrCode = null;
|
|
69
|
+
this.ticketPrice = null;
|
|
70
|
+
this.contactInfo = null;
|
|
71
|
+
this.speakers = [];
|
|
72
|
+
this.sponsors = [];
|
|
73
|
+
this.categories = [];
|
|
74
|
+
this.registrationUrl = null;
|
|
75
|
+
|
|
76
|
+
// Personalização Visual
|
|
77
|
+
this.font = { name: options?.font?.name ?? DEFAULT_FONT_FAMILY, path: options?.font?.path };
|
|
78
|
+
this.background = { type: "color", value: DEFAULT_COLORS.gradient.blue.start };
|
|
79
|
+
this.style = "modern"; // modern, classic, minimal, bold, festive
|
|
80
|
+
this.colorScheme = "blue"; // blue, green, purple, red, orange, custom
|
|
81
|
+
this.customColors = {
|
|
82
|
+
primary: null,
|
|
83
|
+
secondary: null,
|
|
84
|
+
accent: null,
|
|
85
|
+
text: "#FFFFFF",
|
|
86
|
+
background: null
|
|
87
|
+
};
|
|
88
|
+
this.useGradient = true;
|
|
89
|
+
this.useTextShadow = true;
|
|
90
|
+
this.usePatterns = false;
|
|
91
|
+
this.patternType = "dots"; // dots, lines
|
|
92
|
+
this.useGlassmorphism = false;
|
|
93
|
+
|
|
94
|
+
// Configurações de Layout
|
|
95
|
+
this.cardWidth = DEFAULT_DIMENSIONS.banner.width;
|
|
96
|
+
this.cardHeight = DEFAULT_DIMENSIONS.banner.height;
|
|
97
|
+
this.cornerRadius = LAYOUT.cornerRadius.medium;
|
|
98
|
+
this.orientation = "landscape"; // landscape, portrait, square
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// --- Setters para Dados Principais ---
|
|
102
|
+
/**
|
|
103
|
+
* Define o título do evento
|
|
104
|
+
* @param {string} text - Título do evento
|
|
105
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
106
|
+
*/
|
|
107
|
+
setTitle(text) {
|
|
108
|
+
if (!text || typeof text !== "string") throw new Error("O título do evento deve ser uma string não vazia.");
|
|
109
|
+
this.title = text;
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Define a data do evento
|
|
115
|
+
* @param {string} text - Data do evento
|
|
116
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
117
|
+
*/
|
|
118
|
+
setDate(text) {
|
|
119
|
+
if (!text || typeof text !== "string") throw new Error("A data do evento deve ser uma string não vazia.");
|
|
120
|
+
this.date = text;
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Define o horário do evento
|
|
126
|
+
* @param {string} text - Horário do evento
|
|
127
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
128
|
+
*/
|
|
129
|
+
setTime(text) {
|
|
130
|
+
if (!text || typeof text !== "string") throw new Error("O horário do evento deve ser uma string não vazia.");
|
|
131
|
+
this.time = text;
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Define o local do evento
|
|
137
|
+
* @param {string} text - Local do evento
|
|
138
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
139
|
+
*/
|
|
140
|
+
setLocation(text) {
|
|
141
|
+
if (!text || typeof text !== "string") throw new Error("O local do evento deve ser uma string não vazia.");
|
|
142
|
+
this.location = text;
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Define a descrição do evento
|
|
148
|
+
* @param {string} text - Descrição do evento
|
|
149
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
150
|
+
*/
|
|
151
|
+
setDescription(text) {
|
|
152
|
+
if (!text || typeof text !== "string") throw new Error("A descrição do evento deve ser uma string não vazia.");
|
|
153
|
+
this.description = text;
|
|
154
|
+
return this;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Define o organizador do evento
|
|
159
|
+
* @param {string} text - Nome do organizador
|
|
160
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
161
|
+
*/
|
|
162
|
+
setOrganizer(text) {
|
|
163
|
+
if (!text || typeof text !== "string") throw new Error("O organizador do evento deve ser uma string não vazia.");
|
|
164
|
+
this.organizer = text;
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Define o logo do evento
|
|
170
|
+
* @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem do logo
|
|
171
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
172
|
+
*/
|
|
173
|
+
setLogo(image) {
|
|
174
|
+
if (!image) throw new Error("A fonte da imagem do logo não pode estar vazia.");
|
|
175
|
+
this.logo = image;
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Define o código QR para o evento
|
|
181
|
+
* @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem do código QR
|
|
182
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
183
|
+
*/
|
|
184
|
+
setQRCode(image) {
|
|
185
|
+
if (!image) throw new Error("A fonte da imagem do código QR não pode estar vazia.");
|
|
186
|
+
this.qrCode = image;
|
|
187
|
+
return this;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Define o preço do ingresso
|
|
192
|
+
* @param {string} text - Preço do ingresso
|
|
193
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
194
|
+
*/
|
|
195
|
+
setTicketPrice(text) {
|
|
196
|
+
if (!text || typeof text !== "string") throw new Error("O preço do ingresso deve ser uma string não vazia.");
|
|
197
|
+
this.ticketPrice = text;
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Define as informações de contato
|
|
203
|
+
* @param {string} text - Informações de contato
|
|
204
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
205
|
+
*/
|
|
206
|
+
setContactInfo(text) {
|
|
207
|
+
if (!text || typeof text !== "string") throw new Error("As informações de contato devem ser uma string não vazia.");
|
|
208
|
+
this.contactInfo = text;
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Adiciona um palestrante ao evento
|
|
214
|
+
* @param {string} name - Nome do palestrante
|
|
215
|
+
* @param {string} role - Cargo/função do palestrante
|
|
216
|
+
* @param {string|Buffer|Object} avatar - URL, Buffer ou caminho da imagem do avatar (opcional)
|
|
217
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
218
|
+
*/
|
|
219
|
+
addSpeaker(name, role, avatar = null) {
|
|
220
|
+
if (!name || typeof name !== "string") throw new Error("O nome do palestrante deve ser uma string não vazia.");
|
|
221
|
+
if (!role || typeof role !== "string") throw new Error("O cargo/função do palestrante deve ser uma string não vazia.");
|
|
222
|
+
|
|
223
|
+
this.speakers.push({ name, role, avatar });
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Adiciona um patrocinador ao evento
|
|
229
|
+
* @param {string} name - Nome do patrocinador
|
|
230
|
+
* @param {string|Buffer|Object} logo - URL, Buffer ou caminho da imagem do logo (opcional)
|
|
231
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
232
|
+
*/
|
|
233
|
+
addSponsor(name, logo = null) {
|
|
234
|
+
if (!name || typeof name !== "string") throw new Error("O nome do patrocinador deve ser uma string não vazia.");
|
|
235
|
+
|
|
236
|
+
this.sponsors.push({ name, logo });
|
|
237
|
+
return this;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Define as categorias do evento
|
|
242
|
+
* @param {Array<string>} categories - Array de categorias
|
|
243
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
244
|
+
*/
|
|
245
|
+
setCategories(categories) {
|
|
246
|
+
if (!Array.isArray(categories)) throw new Error("As categorias devem ser um array de strings.");
|
|
247
|
+
|
|
248
|
+
this.categories = categories;
|
|
249
|
+
return this;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Define a URL de registro/inscrição
|
|
254
|
+
* @param {string} url - URL de registro
|
|
255
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
256
|
+
*/
|
|
257
|
+
setRegistrationUrl(url) {
|
|
258
|
+
if (!url || typeof url !== "string") throw new Error("A URL de registro deve ser uma string não vazia.");
|
|
259
|
+
this.registrationUrl = url;
|
|
260
|
+
return this;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// --- Setters para Personalização Visual ---
|
|
264
|
+
/**
|
|
265
|
+
* Define o plano de fundo
|
|
266
|
+
* @param {string} type - Tipo de plano de fundo ('color', 'image' ou 'gradient')
|
|
267
|
+
* @param {string|Array} value - Valor do plano de fundo (cor hexadecimal, URL/caminho da imagem ou array de cores para gradiente)
|
|
268
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
269
|
+
*/
|
|
270
|
+
setBackground(type, value) {
|
|
271
|
+
const types = ["color", "image", "gradient"];
|
|
272
|
+
if (!type || !types.includes(type.toLowerCase())) {
|
|
273
|
+
throw new Error("O tipo de plano de fundo deve ser 'color', 'image' ou 'gradient'.");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!value) throw new Error("O valor do plano de fundo não pode estar vazio.");
|
|
277
|
+
|
|
278
|
+
if (type.toLowerCase() === "color" && !isValidHexColor(value)) {
|
|
279
|
+
throw new Error("Cor de plano de fundo inválida. Use o formato hexadecimal.");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (type.toLowerCase() === "gradient") {
|
|
283
|
+
if (!Array.isArray(value) || value.length < 2) {
|
|
284
|
+
throw new Error("Para gradiente, forneça um array com pelo menos duas cores hexadecimais.");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
for (const color of value) {
|
|
288
|
+
if (!isValidHexColor(color)) {
|
|
289
|
+
throw new Error("Todas as cores do gradiente devem estar no formato hexadecimal.");
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
this.useGradient = true;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
this.background = { type: type.toLowerCase(), value };
|
|
297
|
+
return this;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Define o estilo do banner
|
|
302
|
+
* @param {string} style - Estilo ('modern', 'classic', 'minimal', 'bold', 'festive')
|
|
303
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
304
|
+
*/
|
|
305
|
+
setStyle(style) {
|
|
306
|
+
const validStyles = ["modern", "classic", "minimal", "bold", "festive"];
|
|
307
|
+
if (!style || !validStyles.includes(style.toLowerCase())) {
|
|
308
|
+
throw new Error(`Estilo inválido. Use um dos seguintes: ${validStyles.join(", ")}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
this.style = style.toLowerCase();
|
|
312
|
+
|
|
313
|
+
// Ajusta configurações com base no estilo
|
|
314
|
+
switch (this.style) {
|
|
315
|
+
case "modern":
|
|
316
|
+
this.useGradient = true;
|
|
317
|
+
this.useTextShadow = true;
|
|
318
|
+
this.usePatterns = false;
|
|
319
|
+
this.useGlassmorphism = true;
|
|
320
|
+
this.cornerRadius = LAYOUT.cornerRadius.medium;
|
|
321
|
+
break;
|
|
322
|
+
case "classic":
|
|
323
|
+
this.useGradient = false;
|
|
324
|
+
this.useTextShadow = false;
|
|
325
|
+
this.usePatterns = false;
|
|
326
|
+
this.useGlassmorphism = false;
|
|
327
|
+
this.cornerRadius = LAYOUT.cornerRadius.small;
|
|
328
|
+
break;
|
|
329
|
+
case "minimal":
|
|
330
|
+
this.useGradient = false;
|
|
331
|
+
this.useTextShadow = false;
|
|
332
|
+
this.usePatterns = false;
|
|
333
|
+
this.useGlassmorphism = false;
|
|
334
|
+
this.cornerRadius = 0;
|
|
335
|
+
break;
|
|
336
|
+
case "bold":
|
|
337
|
+
this.useGradient = true;
|
|
338
|
+
this.useTextShadow = true;
|
|
339
|
+
this.usePatterns = true;
|
|
340
|
+
this.useGlassmorphism = false;
|
|
341
|
+
this.cornerRadius = LAYOUT.cornerRadius.large;
|
|
342
|
+
break;
|
|
343
|
+
case "festive":
|
|
344
|
+
this.useGradient = true;
|
|
345
|
+
this.useTextShadow = true;
|
|
346
|
+
this.usePatterns = true;
|
|
347
|
+
this.useGlassmorphism = false;
|
|
348
|
+
this.cornerRadius = LAYOUT.cornerRadius.medium;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return this;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Define o esquema de cores
|
|
357
|
+
* @param {string} scheme - Esquema de cores ('blue', 'green', 'purple', 'red', 'orange', 'custom')
|
|
358
|
+
* @param {Object} customColors - Cores personalizadas (apenas para esquema 'custom')
|
|
359
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
360
|
+
*/
|
|
361
|
+
setColorScheme(scheme, customColors = null) {
|
|
362
|
+
const validSchemes = ["blue", "green", "purple", "red", "orange", "custom"];
|
|
363
|
+
if (!scheme || !validSchemes.includes(scheme.toLowerCase())) {
|
|
364
|
+
throw new Error(`Esquema de cores inválido. Use um dos seguintes: ${validSchemes.join(", ")}`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
this.colorScheme = scheme.toLowerCase();
|
|
368
|
+
|
|
369
|
+
if (this.colorScheme === "custom") {
|
|
370
|
+
if (!customColors || typeof customColors !== "object") {
|
|
371
|
+
throw new Error("Para o esquema de cores personalizado, forneça um objeto com as cores.");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
this.customColors = {
|
|
375
|
+
primary: customColors.primary || this.customColors.primary,
|
|
376
|
+
secondary: customColors.secondary || this.customColors.secondary,
|
|
377
|
+
accent: customColors.accent || this.customColors.accent,
|
|
378
|
+
text: customColors.text || this.customColors.text,
|
|
379
|
+
background: customColors.background || this.customColors.background
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return this;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Ativa ou desativa o uso de gradiente
|
|
388
|
+
* @param {boolean} enabled - Se o gradiente deve ser ativado
|
|
389
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
390
|
+
*/
|
|
391
|
+
enableGradient(enabled = true) {
|
|
392
|
+
this.useGradient = enabled;
|
|
393
|
+
return this;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Ativa ou desativa a sombra de texto
|
|
398
|
+
* @param {boolean} enabled - Se a sombra de texto deve ser ativada
|
|
399
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
400
|
+
*/
|
|
401
|
+
enableTextShadow(enabled = true) {
|
|
402
|
+
this.useTextShadow = enabled;
|
|
403
|
+
return this;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Ativa ou desativa o uso de padrões decorativos
|
|
408
|
+
* @param {boolean} enabled - Se os padrões devem ser ativados
|
|
409
|
+
* @param {string} type - Tipo de padrão ('dots', 'lines')
|
|
410
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
411
|
+
*/
|
|
412
|
+
enablePatterns(enabled = true, type = "dots") {
|
|
413
|
+
this.usePatterns = enabled;
|
|
414
|
+
|
|
415
|
+
const validTypes = ["dots", "lines"];
|
|
416
|
+
if (validTypes.includes(type.toLowerCase())) {
|
|
417
|
+
this.patternType = type.toLowerCase();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return this;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Ativa ou desativa o efeito de glassmorphism
|
|
425
|
+
* @param {boolean} enabled - Se o efeito deve ser ativado
|
|
426
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
427
|
+
*/
|
|
428
|
+
enableGlassmorphism(enabled = true) {
|
|
429
|
+
this.useGlassmorphism = enabled;
|
|
430
|
+
return this;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Define o raio dos cantos arredondados
|
|
435
|
+
* @param {number} radius - Raio dos cantos em pixels
|
|
436
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
437
|
+
*/
|
|
438
|
+
setCornerRadius(radius) {
|
|
439
|
+
if (typeof radius !== "number" || radius < 0) throw new Error("O raio dos cantos deve ser um número não negativo.");
|
|
440
|
+
this.cornerRadius = radius;
|
|
441
|
+
return this;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Define a orientação do banner
|
|
446
|
+
* @param {string} orientation - Orientação ('landscape', 'portrait', 'square')
|
|
447
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
448
|
+
*/
|
|
449
|
+
setOrientation(orientation) {
|
|
450
|
+
const validOrientations = ["landscape", "portrait", "square"];
|
|
451
|
+
if (!orientation || !validOrientations.includes(orientation.toLowerCase())) {
|
|
452
|
+
throw new Error(`Orientação inválida. Use uma das seguintes: ${validOrientations.join(", ")}`);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
this.orientation = orientation.toLowerCase();
|
|
456
|
+
|
|
457
|
+
// Ajusta as dimensões com base na orientação
|
|
458
|
+
switch (this.orientation) {
|
|
459
|
+
case "landscape":
|
|
460
|
+
this.cardWidth = DEFAULT_DIMENSIONS.banner.width;
|
|
461
|
+
this.cardHeight = DEFAULT_DIMENSIONS.banner.height;
|
|
462
|
+
break;
|
|
463
|
+
case "portrait":
|
|
464
|
+
this.cardWidth = DEFAULT_DIMENSIONS.banner.height;
|
|
465
|
+
this.cardHeight = DEFAULT_DIMENSIONS.banner.width;
|
|
466
|
+
break;
|
|
467
|
+
case "square":
|
|
468
|
+
this.cardWidth = Math.min(DEFAULT_DIMENSIONS.banner.width, DEFAULT_DIMENSIONS.banner.height);
|
|
469
|
+
this.cardHeight = this.cardWidth;
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return this;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Define as dimensões do card
|
|
478
|
+
* @param {number} width - Largura do card em pixels
|
|
479
|
+
* @param {number} height - Altura do card em pixels
|
|
480
|
+
* @returns {EventBanner} - Instância atual para encadeamento
|
|
481
|
+
*/
|
|
482
|
+
setCardDimensions(width, height) {
|
|
483
|
+
if (typeof width !== "number" || width < 400 || width > 1920) {
|
|
484
|
+
throw new Error("A largura do card deve estar entre 400 e 1920 pixels.");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (typeof height !== "number" || height < 400 || height > 1920) {
|
|
488
|
+
throw new Error("A altura do card deve estar entre 400 e 1920 pixels.");
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
this.cardWidth = width;
|
|
492
|
+
this.cardHeight = height;
|
|
493
|
+
|
|
494
|
+
return this;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// --- Método de Construção ---
|
|
498
|
+
/**
|
|
499
|
+
* Constrói o banner e retorna um buffer de imagem
|
|
500
|
+
* @returns {Promise<Buffer>} - Buffer contendo a imagem do banner
|
|
501
|
+
*/
|
|
502
|
+
async build() {
|
|
503
|
+
// --- Registro de Fonte ---
|
|
504
|
+
const registeredFontName = await registerFontIfNeeded(this.font);
|
|
505
|
+
|
|
506
|
+
// --- Configuração do Canvas ---
|
|
507
|
+
const cardWidth = this.cardWidth;
|
|
508
|
+
const cardHeight = this.cardHeight;
|
|
509
|
+
const cornerRadius = this.cornerRadius;
|
|
510
|
+
const padding = 30;
|
|
511
|
+
|
|
512
|
+
const canvas = pureimage.make(cardWidth, cardHeight);
|
|
513
|
+
const ctx = canvas.getContext("2d");
|
|
514
|
+
|
|
515
|
+
// --- Configuração de Cores com base no Esquema ---
|
|
516
|
+
const colors = this._getColorScheme();
|
|
517
|
+
|
|
518
|
+
// --- Desenha Plano de Fundo ---
|
|
519
|
+
if (this.background.type === "image") {
|
|
520
|
+
try {
|
|
521
|
+
ctx.save();
|
|
522
|
+
|
|
523
|
+
if (cornerRadius > 0) {
|
|
524
|
+
roundRect(ctx, 0, 0, cardWidth, cardHeight, cornerRadius, false, false);
|
|
525
|
+
ctx.clip();
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const img = await loadImageWithAxios(this.background.value);
|
|
529
|
+
const aspect = img.width / img.height;
|
|
530
|
+
let drawWidth = cardWidth;
|
|
531
|
+
let drawHeight = cardWidth / aspect;
|
|
532
|
+
|
|
533
|
+
// Ajusta as dimensões para cobrir todo o card
|
|
534
|
+
if (drawHeight < cardHeight) {
|
|
535
|
+
drawHeight = cardHeight;
|
|
536
|
+
drawWidth = cardHeight * aspect;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const offsetX = (cardWidth - drawWidth) / 2;
|
|
540
|
+
const offsetY = (cardHeight - drawHeight) / 2;
|
|
541
|
+
|
|
542
|
+
ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
|
|
543
|
+
|
|
544
|
+
// Aplica sobreposição para melhorar legibilidade
|
|
545
|
+
ctx.fillStyle = hexToRgba(colors.background || "#000000", 0.5);
|
|
546
|
+
ctx.fillRect(0, 0, cardWidth, cardHeight);
|
|
547
|
+
|
|
548
|
+
ctx.restore();
|
|
549
|
+
} catch (e) {
|
|
550
|
+
console.error("Falha ao desenhar imagem de plano de fundo:", e.message);
|
|
551
|
+
|
|
552
|
+
// Plano de fundo de fallback
|
|
553
|
+
if (this.useGradient) {
|
|
554
|
+
const gradient = createLinearGradient(
|
|
555
|
+
ctx,
|
|
556
|
+
0,
|
|
557
|
+
0,
|
|
558
|
+
cardWidth,
|
|
559
|
+
cardHeight,
|
|
560
|
+
colors.primary,
|
|
561
|
+
colors.secondary,
|
|
562
|
+
"diagonal"
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
ctx.fillStyle = gradient;
|
|
566
|
+
} else {
|
|
567
|
+
ctx.fillStyle = colors.primary;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
roundRect(ctx, 0, 0, cardWidth, cardHeight, cornerRadius, true, false);
|
|
571
|
+
}
|
|
572
|
+
} else if (this.background.type === "gradient" || this.useGradient) {
|
|
573
|
+
// Plano de fundo com gradiente
|
|
574
|
+
let gradientColors;
|
|
575
|
+
|
|
576
|
+
if (this.background.type === "gradient" && Array.isArray(this.background.value)) {
|
|
577
|
+
gradientColors = this.background.value;
|
|
578
|
+
} else {
|
|
579
|
+
gradientColors = [colors.primary, colors.secondary];
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const gradient = applyMultiColorGradient(
|
|
583
|
+
ctx,
|
|
584
|
+
0,
|
|
585
|
+
0,
|
|
586
|
+
cardWidth,
|
|
587
|
+
cardHeight,
|
|
588
|
+
gradientColors,
|
|
589
|
+
"diagonal"
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
ctx.fillStyle = gradient;
|
|
593
|
+
roundRect(ctx, 0, 0, cardWidth, cardHeight, cornerRadius, true, false);
|
|
594
|
+
} else {
|
|
595
|
+
// Plano de fundo de cor sólida
|
|
596
|
+
ctx.fillStyle = this.background.type === "color" ? this.background.value : colors.primary;
|
|
597
|
+
roundRect(ctx, 0, 0, cardWidth, cardHeight, cornerRadius, true, false);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// --- Aplica Padrões Decorativos (se ativados) ---
|
|
601
|
+
if (this.usePatterns) {
|
|
602
|
+
if (this.patternType === "dots") {
|
|
603
|
+
applyDotPattern(
|
|
604
|
+
ctx,
|
|
605
|
+
0,
|
|
606
|
+
0,
|
|
607
|
+
cardWidth,
|
|
608
|
+
cardHeight,
|
|
609
|
+
40,
|
|
610
|
+
3,
|
|
611
|
+
"#FFFFFF",
|
|
612
|
+
0.1
|
|
613
|
+
);
|
|
614
|
+
} else if (this.patternType === "lines") {
|
|
615
|
+
applyLinePattern(
|
|
616
|
+
ctx,
|
|
617
|
+
0,
|
|
618
|
+
0,
|
|
619
|
+
cardWidth,
|
|
620
|
+
cardHeight,
|
|
621
|
+
40,
|
|
622
|
+
1,
|
|
623
|
+
"#FFFFFF",
|
|
624
|
+
0.1,
|
|
625
|
+
"diagonal"
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// --- Desenha Conteúdo com base no Estilo ---
|
|
631
|
+
switch (this.style) {
|
|
632
|
+
case "modern":
|
|
633
|
+
await this._drawModernStyle(ctx, registeredFontName, colors, cardWidth, cardHeight, padding);
|
|
634
|
+
break;
|
|
635
|
+
case "classic":
|
|
636
|
+
await this._drawClassicStyle(ctx, registeredFontName, colors, cardWidth, cardHeight, padding);
|
|
637
|
+
break;
|
|
638
|
+
case "minimal":
|
|
639
|
+
await this._drawMinimalStyle(ctx, registeredFontName, colors, cardWidth, cardHeight, padding);
|
|
640
|
+
break;
|
|
641
|
+
case "bold":
|
|
642
|
+
await this._drawBoldStyle(ctx, registeredFontName, colors, cardWidth, cardHeight, padding);
|
|
643
|
+
break;
|
|
644
|
+
case "festive":
|
|
645
|
+
await this._drawFestiveStyle(ctx, registeredFontName, colors, cardWidth, cardHeight, padding);
|
|
646
|
+
break;
|
|
647
|
+
default:
|
|
648
|
+
await this._drawModernStyle(ctx, registeredFontName, colors, cardWidth, cardHeight, padding);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// --- Codifica e Retorna Buffer ---
|
|
652
|
+
try {
|
|
653
|
+
return await encodeToBuffer(canvas);
|
|
654
|
+
} catch (err) {
|
|
655
|
+
console.error("Falha ao codificar o Banner de Evento:", err);
|
|
656
|
+
throw new Error("Não foi possível gerar o buffer de imagem do Banner de Evento.");
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// --- Métodos Auxiliares Privados ---
|
|
661
|
+
/**
|
|
662
|
+
* Obtém as cores com base no esquema selecionado
|
|
663
|
+
* @private
|
|
664
|
+
*/
|
|
665
|
+
_getColorScheme() {
|
|
666
|
+
if (this.colorScheme === "custom" && this.customColors.primary) {
|
|
667
|
+
return this.customColors;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
switch (this.colorScheme) {
|
|
671
|
+
case "green":
|
|
672
|
+
return {
|
|
673
|
+
primary: "#2ECC71",
|
|
674
|
+
secondary: "#27AE60",
|
|
675
|
+
accent: "#F1C40F",
|
|
676
|
+
text: "#FFFFFF",
|
|
677
|
+
background: "#1E8449"
|
|
678
|
+
};
|
|
679
|
+
case "purple":
|
|
680
|
+
return {
|
|
681
|
+
primary: "#9B59B6",
|
|
682
|
+
secondary: "#8E44AD",
|
|
683
|
+
accent: "#F1C40F",
|
|
684
|
+
text: "#FFFFFF",
|
|
685
|
+
background: "#6C3483"
|
|
686
|
+
};
|
|
687
|
+
case "red":
|
|
688
|
+
return {
|
|
689
|
+
primary: "#E74C3C",
|
|
690
|
+
secondary: "#C0392B",
|
|
691
|
+
accent: "#F1C40F",
|
|
692
|
+
text: "#FFFFFF",
|
|
693
|
+
background: "#922B21"
|
|
694
|
+
};
|
|
695
|
+
case "orange":
|
|
696
|
+
return {
|
|
697
|
+
primary: "#F39C12",
|
|
698
|
+
secondary: "#D35400",
|
|
699
|
+
accent: "#3498DB",
|
|
700
|
+
text: "#FFFFFF",
|
|
701
|
+
background: "#A04000"
|
|
702
|
+
};
|
|
703
|
+
case "blue":
|
|
704
|
+
default:
|
|
705
|
+
return {
|
|
706
|
+
primary: "#3498DB",
|
|
707
|
+
secondary: "#2980B9",
|
|
708
|
+
accent: "#F1C40F",
|
|
709
|
+
text: "#FFFFFF",
|
|
710
|
+
background: "#1F618D"
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Desenha o estilo moderno
|
|
717
|
+
* @private
|
|
718
|
+
*/
|
|
719
|
+
async _drawModernStyle(ctx, fontName, colors, width, height, padding) {
|
|
720
|
+
// --- Desenha Área de Conteúdo Principal ---
|
|
721
|
+
const contentWidth = width - padding * 2;
|
|
722
|
+
const contentHeight = height - padding * 2;
|
|
723
|
+
const contentX = padding;
|
|
724
|
+
const contentY = padding;
|
|
725
|
+
|
|
726
|
+
// Aplica efeito de glassmorphism se ativado
|
|
727
|
+
if (this.useGlassmorphism) {
|
|
728
|
+
applyGlassmorphism(
|
|
729
|
+
ctx,
|
|
730
|
+
contentX,
|
|
731
|
+
contentY,
|
|
732
|
+
contentWidth,
|
|
733
|
+
contentHeight,
|
|
734
|
+
this.cornerRadius,
|
|
735
|
+
0.2,
|
|
736
|
+
"#FFFFFF"
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// --- Desenha Logo (se fornecido) ---
|
|
741
|
+
let currentY = contentY + padding;
|
|
742
|
+
|
|
743
|
+
if (this.logo) {
|
|
744
|
+
try {
|
|
745
|
+
const logoSize = Math.min(contentWidth, contentHeight) * 0.15;
|
|
746
|
+
const logoX = contentX + padding;
|
|
747
|
+
|
|
748
|
+
const logoImg = await loadImageWithAxios(this.logo);
|
|
749
|
+
const aspect = logoImg.width / logoImg.height;
|
|
750
|
+
const logoHeight = logoSize;
|
|
751
|
+
const logoWidth = logoHeight * aspect;
|
|
752
|
+
|
|
753
|
+
ctx.drawImage(logoImg, logoX, currentY, logoWidth, logoHeight);
|
|
754
|
+
currentY += logoHeight + padding;
|
|
755
|
+
} catch (e) {
|
|
756
|
+
console.error("Falha ao desenhar logo:", e.message);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// --- Desenha Título ---
|
|
761
|
+
const titleFontSize = Math.min(contentWidth, contentHeight) * 0.08;
|
|
762
|
+
ctx.fillStyle = colors.text;
|
|
763
|
+
ctx.font = `bold ${titleFontSize}px ${fontName}-Bold`;
|
|
764
|
+
ctx.textAlign = "center";
|
|
765
|
+
ctx.textBaseline = "top";
|
|
766
|
+
|
|
767
|
+
// Aplica sombra de texto se ativada
|
|
768
|
+
if (this.useTextShadow) {
|
|
769
|
+
applyTextShadow(ctx, "rgba(0, 0, 0, 0.5)", 3, 1, 1);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const titleText = this.title;
|
|
773
|
+
ctx.fillText(titleText, contentX + contentWidth / 2, currentY);
|
|
774
|
+
currentY += titleFontSize * 1.2;
|
|
775
|
+
|
|
776
|
+
// Remove sombra para o próximo texto
|
|
777
|
+
if (this.useTextShadow) {
|
|
778
|
+
clearShadow(ctx);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// --- Desenha Data e Hora ---
|
|
782
|
+
if (this.date || this.time) {
|
|
783
|
+
const dateTimeFontSize = titleFontSize * 0.5;
|
|
784
|
+
ctx.fillStyle = colors.text;
|
|
785
|
+
ctx.font = `medium ${dateTimeFontSize}px ${fontName}-Medium`;
|
|
786
|
+
ctx.textAlign = "center";
|
|
787
|
+
|
|
788
|
+
let dateTimeText = "";
|
|
789
|
+
if (this.date && this.time) {
|
|
790
|
+
dateTimeText = `${this.date} • ${this.time}`;
|
|
791
|
+
} else if (this.date) {
|
|
792
|
+
dateTimeText = this.date;
|
|
793
|
+
} else if (this.time) {
|
|
794
|
+
dateTimeText = this.time;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
ctx.fillText(dateTimeText, contentX + contentWidth / 2, currentY);
|
|
798
|
+
currentY += dateTimeFontSize * 1.5;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// --- Desenha Local ---
|
|
802
|
+
if (this.location) {
|
|
803
|
+
const locationFontSize = titleFontSize * 0.4;
|
|
804
|
+
ctx.fillStyle = colors.text;
|
|
805
|
+
ctx.font = `regular ${locationFontSize}px ${fontName}-Regular`;
|
|
806
|
+
ctx.textAlign = "center";
|
|
807
|
+
|
|
808
|
+
ctx.fillText(`📍 ${this.location}`, contentX + contentWidth / 2, currentY);
|
|
809
|
+
currentY += locationFontSize * 1.5;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// --- Desenha Descrição ---
|
|
813
|
+
if (this.description) {
|
|
814
|
+
const descriptionFontSize = titleFontSize * 0.35;
|
|
815
|
+
ctx.fillStyle = colors.text;
|
|
816
|
+
ctx.font = `regular ${descriptionFontSize}px ${fontName}-Regular`;
|
|
817
|
+
ctx.textAlign = "center";
|
|
818
|
+
|
|
819
|
+
currentY = wrapText(
|
|
820
|
+
ctx,
|
|
821
|
+
this.description,
|
|
822
|
+
contentX + contentWidth / 2,
|
|
823
|
+
currentY + padding,
|
|
824
|
+
contentWidth - padding * 2,
|
|
825
|
+
descriptionFontSize * 1.5,
|
|
826
|
+
fontName,
|
|
827
|
+
true
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// --- Desenha Categorias ---
|
|
832
|
+
if (this.categories.length > 0) {
|
|
833
|
+
const categoryFontSize = titleFontSize * 0.3;
|
|
834
|
+
const categoryHeight = categoryFontSize * 1.5;
|
|
835
|
+
const categoryPadding = 10;
|
|
836
|
+
const categorySpacing = 10;
|
|
837
|
+
let categoryX = contentX + padding;
|
|
838
|
+
const categoryY = height - padding * 3 - categoryHeight;
|
|
839
|
+
|
|
840
|
+
ctx.font = `regular ${categoryFontSize}px ${fontName}-Regular`;
|
|
841
|
+
ctx.textAlign = "left";
|
|
842
|
+
ctx.textBaseline = "middle";
|
|
843
|
+
|
|
844
|
+
for (const category of this.categories) {
|
|
845
|
+
const categoryWidth = ctx.measureText(category).width + categoryPadding * 2;
|
|
846
|
+
|
|
847
|
+
if (categoryX + categoryWidth > contentX + contentWidth - padding) {
|
|
848
|
+
break; // Não há mais espaço para categorias
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Fundo da categoria
|
|
852
|
+
ctx.fillStyle = hexToRgba(colors.accent, 0.8);
|
|
853
|
+
roundRect(ctx, categoryX, categoryY, categoryWidth, categoryHeight, categoryHeight / 2, true, false);
|
|
854
|
+
|
|
855
|
+
// Texto da categoria
|
|
856
|
+
ctx.fillStyle = colors.text;
|
|
857
|
+
ctx.fillText(category, categoryX + categoryPadding, categoryY + categoryHeight / 2);
|
|
858
|
+
|
|
859
|
+
categoryX += categoryWidth + categorySpacing;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// --- Desenha Informações de Registro ---
|
|
864
|
+
if (this.registrationUrl) {
|
|
865
|
+
const registrationFontSize = titleFontSize * 0.35;
|
|
866
|
+
ctx.fillStyle = colors.text;
|
|
867
|
+
ctx.font = `bold ${registrationFontSize}px ${fontName}-Bold`;
|
|
868
|
+
ctx.textAlign = "center";
|
|
869
|
+
ctx.textBaseline = "bottom";
|
|
870
|
+
|
|
871
|
+
ctx.fillText("Registre-se: " + this.registrationUrl, contentX + contentWidth / 2, height - padding * 1.5);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// --- Desenha QR Code (se fornecido) ---
|
|
875
|
+
if (this.qrCode) {
|
|
876
|
+
try {
|
|
877
|
+
const qrSize = Math.min(contentWidth, contentHeight) * 0.15;
|
|
878
|
+
const qrX = width - qrSize - padding * 2;
|
|
879
|
+
const qrY = height - qrSize - padding * 2;
|
|
880
|
+
|
|
881
|
+
const qrImg = await loadImageWithAxios(this.qrCode);
|
|
882
|
+
ctx.drawImage(qrImg, qrX, qrY, qrSize, qrSize);
|
|
883
|
+
} catch (e) {
|
|
884
|
+
console.error("Falha ao desenhar QR code:", e.message);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Desenha o estilo clássico
|
|
891
|
+
* @private
|
|
892
|
+
*/
|
|
893
|
+
async _drawClassicStyle(ctx, fontName, colors, width, height, padding) {
|
|
894
|
+
// --- Desenha Borda Decorativa ---
|
|
895
|
+
const borderWidth = 5;
|
|
896
|
+
ctx.strokeStyle = colors.accent;
|
|
897
|
+
ctx.lineWidth = borderWidth;
|
|
898
|
+
roundRect(ctx, padding / 2, padding / 2, width - padding, height - padding, this.cornerRadius, false, true);
|
|
899
|
+
|
|
900
|
+
// --- Desenha Título ---
|
|
901
|
+
const titleFontSize = Math.min(width, height) * 0.07;
|
|
902
|
+
const titleY = padding * 2;
|
|
903
|
+
|
|
904
|
+
ctx.fillStyle = colors.text;
|
|
905
|
+
ctx.font = `bold ${titleFontSize}px ${fontName}-Bold`;
|
|
906
|
+
ctx.textAlign = "center";
|
|
907
|
+
ctx.textBaseline = "top";
|
|
908
|
+
|
|
909
|
+
const titleText = this.title;
|
|
910
|
+
ctx.fillText(titleText, width / 2, titleY);
|
|
911
|
+
|
|
912
|
+
// --- Desenha Linha Separadora ---
|
|
913
|
+
const lineY = titleY + titleFontSize * 1.5;
|
|
914
|
+
const lineWidth = width * 0.6;
|
|
915
|
+
const lineHeight = 2;
|
|
916
|
+
|
|
917
|
+
ctx.fillStyle = colors.accent;
|
|
918
|
+
ctx.fillRect((width - lineWidth) / 2, lineY, lineWidth, lineHeight);
|
|
919
|
+
|
|
920
|
+
// --- Desenha Data e Hora ---
|
|
921
|
+
const dateTimeFontSize = titleFontSize * 0.5;
|
|
922
|
+
const dateTimeY = lineY + lineHeight + padding;
|
|
923
|
+
|
|
924
|
+
ctx.fillStyle = colors.text;
|
|
925
|
+
ctx.font = `medium ${dateTimeFontSize}px ${fontName}-Medium`;
|
|
926
|
+
ctx.textAlign = "center";
|
|
927
|
+
|
|
928
|
+
if (this.date) {
|
|
929
|
+
ctx.fillText(this.date, width / 2, dateTimeY);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
if (this.time) {
|
|
933
|
+
ctx.fillText(this.time, width / 2, dateTimeY + dateTimeFontSize * 1.5);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// --- Desenha Local ---
|
|
937
|
+
const locationFontSize = titleFontSize * 0.4;
|
|
938
|
+
const locationY = dateTimeY + (this.time ? dateTimeFontSize * 3 : dateTimeFontSize * 1.5);
|
|
939
|
+
|
|
940
|
+
if (this.location) {
|
|
941
|
+
ctx.fillStyle = colors.text;
|
|
942
|
+
ctx.font = `regular ${locationFontSize}px ${fontName}-Regular`;
|
|
943
|
+
ctx.textAlign = "center";
|
|
944
|
+
|
|
945
|
+
ctx.fillText(this.location, width / 2, locationY);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// --- Desenha Descrição ---
|
|
949
|
+
const descriptionFontSize = titleFontSize * 0.35;
|
|
950
|
+
const descriptionY = locationY + locationFontSize * 2;
|
|
951
|
+
|
|
952
|
+
if (this.description) {
|
|
953
|
+
ctx.fillStyle = colors.text;
|
|
954
|
+
ctx.font = `regular ${descriptionFontSize}px ${fontName}-Regular`;
|
|
955
|
+
ctx.textAlign = "center";
|
|
956
|
+
|
|
957
|
+
wrapText(
|
|
958
|
+
ctx,
|
|
959
|
+
this.description,
|
|
960
|
+
width / 2,
|
|
961
|
+
descriptionY,
|
|
962
|
+
width - padding * 4,
|
|
963
|
+
descriptionFontSize * 1.5,
|
|
964
|
+
fontName,
|
|
965
|
+
true
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// --- Desenha Organizador ---
|
|
970
|
+
const organizerY = height - padding * 3;
|
|
971
|
+
|
|
972
|
+
if (this.organizer) {
|
|
973
|
+
ctx.fillStyle = colors.text;
|
|
974
|
+
ctx.font = `italic ${descriptionFontSize}px ${fontName}-Regular`;
|
|
975
|
+
ctx.textAlign = "center";
|
|
976
|
+
ctx.textBaseline = "bottom";
|
|
977
|
+
|
|
978
|
+
ctx.fillText(`Organizado por: ${this.organizer}`, width / 2, organizerY);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// --- Desenha Informações de Contato ---
|
|
982
|
+
const contactY = height - padding * 1.5;
|
|
983
|
+
|
|
984
|
+
if (this.contactInfo) {
|
|
985
|
+
ctx.fillStyle = colors.text;
|
|
986
|
+
ctx.font = `regular ${descriptionFontSize}px ${fontName}-Regular`;
|
|
987
|
+
ctx.textAlign = "center";
|
|
988
|
+
ctx.textBaseline = "bottom";
|
|
989
|
+
|
|
990
|
+
ctx.fillText(this.contactInfo, width / 2, contactY);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Desenha o estilo minimalista
|
|
996
|
+
* @private
|
|
997
|
+
*/
|
|
998
|
+
async _drawMinimalStyle(ctx, fontName, colors, width, height, padding) {
|
|
999
|
+
// --- Desenha Título ---
|
|
1000
|
+
const titleFontSize = Math.min(width, height) * 0.08;
|
|
1001
|
+
const titleY = height * 0.3;
|
|
1002
|
+
|
|
1003
|
+
ctx.fillStyle = colors.text;
|
|
1004
|
+
ctx.font = `bold ${titleFontSize}px ${fontName}-Bold`;
|
|
1005
|
+
ctx.textAlign = "center";
|
|
1006
|
+
ctx.textBaseline = "middle";
|
|
1007
|
+
|
|
1008
|
+
const titleText = this.title;
|
|
1009
|
+
ctx.fillText(titleText, width / 2, titleY);
|
|
1010
|
+
|
|
1011
|
+
// --- Desenha Data e Hora ---
|
|
1012
|
+
const dateTimeFontSize = titleFontSize * 0.5;
|
|
1013
|
+
const dateTimeY = titleY + titleFontSize;
|
|
1014
|
+
|
|
1015
|
+
ctx.fillStyle = colors.text;
|
|
1016
|
+
ctx.font = `regular ${dateTimeFontSize}px ${fontName}-Regular`;
|
|
1017
|
+
ctx.textAlign = "center";
|
|
1018
|
+
|
|
1019
|
+
let dateTimeText = "";
|
|
1020
|
+
if (this.date && this.time) {
|
|
1021
|
+
dateTimeText = `${this.date} • ${this.time}`;
|
|
1022
|
+
} else if (this.date) {
|
|
1023
|
+
dateTimeText = this.date;
|
|
1024
|
+
} else if (this.time) {
|
|
1025
|
+
dateTimeText = this.time;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
if (dateTimeText) {
|
|
1029
|
+
ctx.fillText(dateTimeText, width / 2, dateTimeY);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// --- Desenha Local ---
|
|
1033
|
+
const locationFontSize = titleFontSize * 0.4;
|
|
1034
|
+
const locationY = dateTimeY + dateTimeFontSize * 1.5;
|
|
1035
|
+
|
|
1036
|
+
if (this.location) {
|
|
1037
|
+
ctx.fillStyle = colors.text;
|
|
1038
|
+
ctx.font = `regular ${locationFontSize}px ${fontName}-Regular`;
|
|
1039
|
+
ctx.textAlign = "center";
|
|
1040
|
+
|
|
1041
|
+
ctx.fillText(this.location, width / 2, locationY);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* Desenha o estilo bold
|
|
1047
|
+
* @private
|
|
1048
|
+
*/
|
|
1049
|
+
async _drawBoldStyle(ctx, fontName, colors, width, height, padding) {
|
|
1050
|
+
// --- Desenha Título em Destaque ---
|
|
1051
|
+
const titleFontSize = Math.min(width, height) * 0.12;
|
|
1052
|
+
const titleY = padding * 2;
|
|
1053
|
+
|
|
1054
|
+
ctx.fillStyle = colors.text;
|
|
1055
|
+
ctx.font = `bold ${titleFontSize}px ${fontName}-Bold`;
|
|
1056
|
+
ctx.textAlign = "center";
|
|
1057
|
+
ctx.textBaseline = "top";
|
|
1058
|
+
|
|
1059
|
+
// Aplica sombra de texto se ativada
|
|
1060
|
+
if (this.useTextShadow) {
|
|
1061
|
+
applyTextShadow(ctx, "rgba(0, 0, 0, 0.5)", 5, 2, 2);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const titleText = this.title;
|
|
1065
|
+
ctx.fillText(titleText, width / 2, titleY);
|
|
1066
|
+
|
|
1067
|
+
// Remove sombra para o próximo texto
|
|
1068
|
+
if (this.useTextShadow) {
|
|
1069
|
+
clearShadow(ctx);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// --- Desenha Destaque para Data ---
|
|
1073
|
+
const dateY = titleY + titleFontSize * 1.5;
|
|
1074
|
+
const dateFontSize = titleFontSize * 0.6;
|
|
1075
|
+
|
|
1076
|
+
if (this.date) {
|
|
1077
|
+
// Fundo para a data
|
|
1078
|
+
const dateText = this.date;
|
|
1079
|
+
const dateWidth = ctx.measureText(dateText).width + padding * 2;
|
|
1080
|
+
const dateHeight = dateFontSize * 1.5;
|
|
1081
|
+
const dateX = (width - dateWidth) / 2;
|
|
1082
|
+
|
|
1083
|
+
ctx.fillStyle = colors.accent;
|
|
1084
|
+
roundRect(ctx, dateX, dateY, dateWidth, dateHeight, dateHeight / 2, true, false);
|
|
1085
|
+
|
|
1086
|
+
// Texto da data
|
|
1087
|
+
ctx.fillStyle = "#000000"; // Texto escuro para contraste
|
|
1088
|
+
ctx.font = `bold ${dateFontSize}px ${fontName}-Bold`;
|
|
1089
|
+
ctx.textAlign = "center";
|
|
1090
|
+
ctx.textBaseline = "middle";
|
|
1091
|
+
|
|
1092
|
+
ctx.fillText(dateText, width / 2, dateY + dateHeight / 2);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// --- Desenha Hora ---
|
|
1096
|
+
const timeFontSize = dateFontSize * 0.8;
|
|
1097
|
+
const timeY = dateY + dateFontSize * 2;
|
|
1098
|
+
|
|
1099
|
+
if (this.time) {
|
|
1100
|
+
ctx.fillStyle = colors.text;
|
|
1101
|
+
ctx.font = `medium ${timeFontSize}px ${fontName}-Medium`;
|
|
1102
|
+
ctx.textAlign = "center";
|
|
1103
|
+
ctx.textBaseline = "top";
|
|
1104
|
+
|
|
1105
|
+
ctx.fillText(this.time, width / 2, timeY);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// --- Desenha Local ---
|
|
1109
|
+
const locationFontSize = timeFontSize;
|
|
1110
|
+
const locationY = timeY + timeFontSize * 1.5;
|
|
1111
|
+
|
|
1112
|
+
if (this.location) {
|
|
1113
|
+
ctx.fillStyle = colors.text;
|
|
1114
|
+
ctx.font = `medium ${locationFontSize}px ${fontName}-Medium`;
|
|
1115
|
+
ctx.textAlign = "center";
|
|
1116
|
+
|
|
1117
|
+
ctx.fillText(`📍 ${this.location}`, width / 2, locationY);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// --- Desenha Preço do Ingresso ---
|
|
1121
|
+
const priceY = height - padding * 4;
|
|
1122
|
+
const priceFontSize = titleFontSize * 0.5;
|
|
1123
|
+
|
|
1124
|
+
if (this.ticketPrice) {
|
|
1125
|
+
// Fundo para o preço
|
|
1126
|
+
const priceText = this.ticketPrice;
|
|
1127
|
+
const priceWidth = ctx.measureText(priceText).width + padding * 2;
|
|
1128
|
+
const priceHeight = priceFontSize * 1.5;
|
|
1129
|
+
const priceX = (width - priceWidth) / 2;
|
|
1130
|
+
|
|
1131
|
+
ctx.fillStyle = colors.accent;
|
|
1132
|
+
roundRect(ctx, priceX, priceY, priceWidth, priceHeight, priceHeight / 2, true, false);
|
|
1133
|
+
|
|
1134
|
+
// Texto do preço
|
|
1135
|
+
ctx.fillStyle = "#000000"; // Texto escuro para contraste
|
|
1136
|
+
ctx.font = `bold ${priceFontSize}px ${fontName}-Bold`;
|
|
1137
|
+
ctx.textAlign = "center";
|
|
1138
|
+
ctx.textBaseline = "middle";
|
|
1139
|
+
|
|
1140
|
+
ctx.fillText(priceText, width / 2, priceY + priceHeight / 2);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// --- Desenha URL de Registro ---
|
|
1144
|
+
const registrationY = height - padding * 2;
|
|
1145
|
+
const registrationFontSize = priceFontSize * 0.8;
|
|
1146
|
+
|
|
1147
|
+
if (this.registrationUrl) {
|
|
1148
|
+
ctx.fillStyle = colors.text;
|
|
1149
|
+
ctx.font = `regular ${registrationFontSize}px ${fontName}-Regular`;
|
|
1150
|
+
ctx.textAlign = "center";
|
|
1151
|
+
ctx.textBaseline = "bottom";
|
|
1152
|
+
|
|
1153
|
+
ctx.fillText(this.registrationUrl, width / 2, registrationY);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Desenha o estilo festivo
|
|
1159
|
+
* @private
|
|
1160
|
+
*/
|
|
1161
|
+
async _drawFestiveStyle(ctx, fontName, colors, width, height, padding) {
|
|
1162
|
+
// --- Desenha Elementos Decorativos ---
|
|
1163
|
+
// Círculos decorativos
|
|
1164
|
+
const circleCount = 20;
|
|
1165
|
+
const maxRadius = Math.min(width, height) * 0.1;
|
|
1166
|
+
|
|
1167
|
+
for (let i = 0; i < circleCount; i++) {
|
|
1168
|
+
const radius = Math.random() * maxRadius + 5;
|
|
1169
|
+
const x = Math.random() * width;
|
|
1170
|
+
const y = Math.random() * height;
|
|
1171
|
+
|
|
1172
|
+
ctx.fillStyle = hexToRgba(colors.accent, Math.random() * 0.3 + 0.1);
|
|
1173
|
+
ctx.beginPath();
|
|
1174
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
1175
|
+
ctx.fill();
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// --- Desenha Área de Conteúdo Principal ---
|
|
1179
|
+
const contentWidth = width * 0.8;
|
|
1180
|
+
const contentHeight = height * 0.8;
|
|
1181
|
+
const contentX = (width - contentWidth) / 2;
|
|
1182
|
+
const contentY = (height - contentHeight) / 2;
|
|
1183
|
+
|
|
1184
|
+
// Aplica efeito de glassmorphism
|
|
1185
|
+
applyGlassmorphism(
|
|
1186
|
+
ctx,
|
|
1187
|
+
contentX,
|
|
1188
|
+
contentY,
|
|
1189
|
+
contentWidth,
|
|
1190
|
+
contentHeight,
|
|
1191
|
+
this.cornerRadius,
|
|
1192
|
+
0.3,
|
|
1193
|
+
"#FFFFFF"
|
|
1194
|
+
);
|
|
1195
|
+
|
|
1196
|
+
// --- Desenha Título ---
|
|
1197
|
+
const titleFontSize = Math.min(contentWidth, contentHeight) * 0.1;
|
|
1198
|
+
ctx.fillStyle = colors.text;
|
|
1199
|
+
ctx.font = `bold ${titleFontSize}px ${fontName}-Bold`;
|
|
1200
|
+
ctx.textAlign = "center";
|
|
1201
|
+
ctx.textBaseline = "top";
|
|
1202
|
+
|
|
1203
|
+
// Aplica sombra de texto
|
|
1204
|
+
applyTextShadow(ctx, "rgba(0, 0, 0, 0.5)", 3, 1, 1);
|
|
1205
|
+
|
|
1206
|
+
const titleText = this.title;
|
|
1207
|
+
ctx.fillText(titleText, width / 2, contentY + padding);
|
|
1208
|
+
|
|
1209
|
+
// Remove sombra para o próximo texto
|
|
1210
|
+
clearShadow(ctx);
|
|
1211
|
+
|
|
1212
|
+
// --- Desenha Data e Hora ---
|
|
1213
|
+
const dateTimeFontSize = titleFontSize * 0.5;
|
|
1214
|
+
const dateTimeY = contentY + padding + titleFontSize * 1.2;
|
|
1215
|
+
|
|
1216
|
+
ctx.fillStyle = colors.text;
|
|
1217
|
+
ctx.font = `medium ${dateTimeFontSize}px ${fontName}-Medium`;
|
|
1218
|
+
ctx.textAlign = "center";
|
|
1219
|
+
|
|
1220
|
+
if (this.date) {
|
|
1221
|
+
ctx.fillText(this.date, width / 2, dateTimeY);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
if (this.time) {
|
|
1225
|
+
ctx.fillText(this.time, width / 2, dateTimeY + dateTimeFontSize * 1.5);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// --- Desenha Local ---
|
|
1229
|
+
const locationFontSize = dateTimeFontSize * 0.8;
|
|
1230
|
+
const locationY = dateTimeY + (this.time ? dateTimeFontSize * 3 : dateTimeFontSize * 1.5);
|
|
1231
|
+
|
|
1232
|
+
if (this.location) {
|
|
1233
|
+
ctx.fillStyle = colors.text;
|
|
1234
|
+
ctx.font = `regular ${locationFontSize}px ${fontName}-Regular`;
|
|
1235
|
+
ctx.textAlign = "center";
|
|
1236
|
+
|
|
1237
|
+
ctx.fillText(`📍 ${this.location}`, width / 2, locationY);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// --- Desenha Descrição ---
|
|
1241
|
+
const descriptionFontSize = locationFontSize * 0.9;
|
|
1242
|
+
const descriptionY = locationY + locationFontSize * 2;
|
|
1243
|
+
|
|
1244
|
+
if (this.description) {
|
|
1245
|
+
ctx.fillStyle = colors.text;
|
|
1246
|
+
ctx.font = `regular ${descriptionFontSize}px ${fontName}-Regular`;
|
|
1247
|
+
ctx.textAlign = "center";
|
|
1248
|
+
|
|
1249
|
+
wrapText(
|
|
1250
|
+
ctx,
|
|
1251
|
+
this.description,
|
|
1252
|
+
width / 2,
|
|
1253
|
+
descriptionY,
|
|
1254
|
+
contentWidth - padding * 2,
|
|
1255
|
+
descriptionFontSize * 1.5,
|
|
1256
|
+
fontName,
|
|
1257
|
+
true
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// --- Desenha Informações de Registro ---
|
|
1262
|
+
const registrationY = contentY + contentHeight - padding * 2;
|
|
1263
|
+
|
|
1264
|
+
if (this.registrationUrl) {
|
|
1265
|
+
ctx.fillStyle = colors.text;
|
|
1266
|
+
ctx.font = `bold ${descriptionFontSize}px ${fontName}-Bold`;
|
|
1267
|
+
ctx.textAlign = "center";
|
|
1268
|
+
ctx.textBaseline = "bottom";
|
|
1269
|
+
|
|
1270
|
+
ctx.fillText("Registre-se: " + this.registrationUrl, width / 2, registrationY);
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// --- Desenha Efeito de Brilho nos Cantos ---
|
|
1274
|
+
applyGlow(
|
|
1275
|
+
ctx,
|
|
1276
|
+
contentX - 10,
|
|
1277
|
+
contentY - 10,
|
|
1278
|
+
30,
|
|
1279
|
+
30,
|
|
1280
|
+
15,
|
|
1281
|
+
colors.accent,
|
|
1282
|
+
10
|
|
1283
|
+
);
|
|
1284
|
+
|
|
1285
|
+
applyGlow(
|
|
1286
|
+
ctx,
|
|
1287
|
+
contentX + contentWidth - 20,
|
|
1288
|
+
contentY - 10,
|
|
1289
|
+
30,
|
|
1290
|
+
30,
|
|
1291
|
+
15,
|
|
1292
|
+
colors.accent,
|
|
1293
|
+
10
|
|
1294
|
+
);
|
|
1295
|
+
|
|
1296
|
+
applyGlow(
|
|
1297
|
+
ctx,
|
|
1298
|
+
contentX - 10,
|
|
1299
|
+
contentY + contentHeight - 20,
|
|
1300
|
+
30,
|
|
1301
|
+
30,
|
|
1302
|
+
15,
|
|
1303
|
+
colors.accent,
|
|
1304
|
+
10
|
|
1305
|
+
);
|
|
1306
|
+
|
|
1307
|
+
applyGlow(
|
|
1308
|
+
ctx,
|
|
1309
|
+
contentX + contentWidth - 20,
|
|
1310
|
+
contentY + contentHeight - 20,
|
|
1311
|
+
30,
|
|
1312
|
+
30,
|
|
1313
|
+
15,
|
|
1314
|
+
colors.accent,
|
|
1315
|
+
10
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
1319
|
+
|