@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,604 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Módulo de Banner de Cabeçalho do Twitter
|
|
5
|
+
*
|
|
6
|
+
* Este módulo gera banners no estilo de cabeçalho de perfil do Twitter com
|
|
7
|
+
* imagem de capa, avatar, nome, username, bio e estatísticas.
|
|
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
|
+
formatNumber
|
|
30
|
+
} = require("../utils");
|
|
31
|
+
|
|
32
|
+
const {
|
|
33
|
+
DEFAULT_COLORS,
|
|
34
|
+
LAYOUT,
|
|
35
|
+
DEFAULT_DIMENSIONS
|
|
36
|
+
} = require("./constants");
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @class TwitterHeader
|
|
40
|
+
* @classdesc Gera um banner no estilo de cabeçalho de perfil do Twitter.
|
|
41
|
+
* @example const header = new TwitterHeader()
|
|
42
|
+
* .setName("Nome Completo")
|
|
43
|
+
* .setUsername("usuario")
|
|
44
|
+
* .setBio("Desenvolvedor | Designer | Criador de conteúdo")
|
|
45
|
+
* .setAvatar("avatar.png")
|
|
46
|
+
* .setCoverImage("cover.jpg")
|
|
47
|
+
* .setFollowers(5200)
|
|
48
|
+
* .setFollowing(420)
|
|
49
|
+
* .setVerified(true)
|
|
50
|
+
* .build();
|
|
51
|
+
*/
|
|
52
|
+
module.exports = class TwitterHeader {
|
|
53
|
+
constructor(options) {
|
|
54
|
+
// Dados Principais
|
|
55
|
+
this.name = "Nome Completo";
|
|
56
|
+
this.username = "usuario";
|
|
57
|
+
this.bio = null;
|
|
58
|
+
this.location = null;
|
|
59
|
+
this.website = null;
|
|
60
|
+
this.joinDate = null;
|
|
61
|
+
this.avatar = null;
|
|
62
|
+
this.coverImage = null;
|
|
63
|
+
this.followers = 0;
|
|
64
|
+
this.following = 0;
|
|
65
|
+
this.tweets = 0;
|
|
66
|
+
this.isVerified = false;
|
|
67
|
+
this.isPremium = false;
|
|
68
|
+
|
|
69
|
+
// Personalização Visual
|
|
70
|
+
this.font = { name: options?.font?.name ?? DEFAULT_FONT_FAMILY, path: options?.font?.path };
|
|
71
|
+
this.theme = "light"; // light, dark, dim
|
|
72
|
+
this.accentColor = DEFAULT_COLORS.twitter.primary;
|
|
73
|
+
this.useTextShadow = false;
|
|
74
|
+
this.useDarkMode = false;
|
|
75
|
+
|
|
76
|
+
// Configurações de Layout
|
|
77
|
+
this.cardWidth = DEFAULT_DIMENSIONS.banner.width;
|
|
78
|
+
this.cardHeight = 400;
|
|
79
|
+
this.avatarSize = 120;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// --- Setters para Dados Principais ---
|
|
83
|
+
/**
|
|
84
|
+
* Define o nome completo
|
|
85
|
+
* @param {string} text - Nome completo
|
|
86
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
87
|
+
*/
|
|
88
|
+
setName(text) {
|
|
89
|
+
if (!text || typeof text !== "string") throw new Error("O nome completo deve ser uma string não vazia.");
|
|
90
|
+
this.name = text;
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Define o nome de usuário
|
|
96
|
+
* @param {string} text - Nome de usuário (sem @)
|
|
97
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
98
|
+
*/
|
|
99
|
+
setUsername(text) {
|
|
100
|
+
if (!text || typeof text !== "string") throw new Error("O nome de usuário deve ser uma string não vazia.");
|
|
101
|
+
this.username = text.replace(/^@/, ''); // Remove @ se presente
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Define a bio
|
|
107
|
+
* @param {string} text - Texto da bio
|
|
108
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
109
|
+
*/
|
|
110
|
+
setBio(text) {
|
|
111
|
+
if (!text || typeof text !== "string") throw new Error("A bio deve ser uma string não vazia.");
|
|
112
|
+
this.bio = text;
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Define a localização
|
|
118
|
+
* @param {string} text - Texto da localização
|
|
119
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
120
|
+
*/
|
|
121
|
+
setLocation(text) {
|
|
122
|
+
if (!text || typeof text !== "string") throw new Error("A localização deve ser uma string não vazia.");
|
|
123
|
+
this.location = text;
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Define o website
|
|
129
|
+
* @param {string} text - URL do website
|
|
130
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
131
|
+
*/
|
|
132
|
+
setWebsite(text) {
|
|
133
|
+
if (!text || typeof text !== "string") throw new Error("O website deve ser uma string não vazia.");
|
|
134
|
+
this.website = text;
|
|
135
|
+
return this;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Define a data de entrada
|
|
140
|
+
* @param {string} text - Data de entrada (ex: "Entrou em maio de 2020")
|
|
141
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
142
|
+
*/
|
|
143
|
+
setJoinDate(text) {
|
|
144
|
+
if (!text || typeof text !== "string") throw new Error("A data de entrada deve ser uma string não vazia.");
|
|
145
|
+
this.joinDate = text;
|
|
146
|
+
return this;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Define o avatar
|
|
151
|
+
* @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem do avatar
|
|
152
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
153
|
+
*/
|
|
154
|
+
setAvatar(image) {
|
|
155
|
+
if (!image) throw new Error("A fonte da imagem do avatar não pode estar vazia.");
|
|
156
|
+
this.avatar = image;
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Define a imagem de capa
|
|
162
|
+
* @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem de capa
|
|
163
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
164
|
+
*/
|
|
165
|
+
setCoverImage(image) {
|
|
166
|
+
if (!image) throw new Error("A fonte da imagem de capa não pode estar vazia.");
|
|
167
|
+
this.coverImage = image;
|
|
168
|
+
return this;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Define o número de seguidores
|
|
173
|
+
* @param {number} count - Número de seguidores
|
|
174
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
175
|
+
*/
|
|
176
|
+
setFollowers(count) {
|
|
177
|
+
if (typeof count !== "number" || count < 0) throw new Error("O número de seguidores deve ser um número não negativo.");
|
|
178
|
+
this.followers = count;
|
|
179
|
+
return this;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Define o número de pessoas seguidas
|
|
184
|
+
* @param {number} count - Número de pessoas seguidas
|
|
185
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
186
|
+
*/
|
|
187
|
+
setFollowing(count) {
|
|
188
|
+
if (typeof count !== "number" || count < 0) throw new Error("O número de pessoas seguidas deve ser um número não negativo.");
|
|
189
|
+
this.following = count;
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Define o número de tweets
|
|
195
|
+
* @param {number} count - Número de tweets
|
|
196
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
197
|
+
*/
|
|
198
|
+
setTweets(count) {
|
|
199
|
+
if (typeof count !== "number" || count < 0) throw new Error("O número de tweets deve ser um número não negativo.");
|
|
200
|
+
this.tweets = count;
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Define se o perfil é verificado
|
|
206
|
+
* @param {boolean} isVerified - Se o perfil é verificado
|
|
207
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
208
|
+
*/
|
|
209
|
+
setVerified(isVerified = true) {
|
|
210
|
+
this.isVerified = !!isVerified;
|
|
211
|
+
return this;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Define se o perfil é premium
|
|
216
|
+
* @param {boolean} isPremium - Se o perfil é premium
|
|
217
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
218
|
+
*/
|
|
219
|
+
setPremium(isPremium = true) {
|
|
220
|
+
this.isPremium = !!isPremium;
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// --- Setters para Personalização Visual ---
|
|
225
|
+
/**
|
|
226
|
+
* Define o tema
|
|
227
|
+
* @param {string} theme - Tema ('light', 'dark', 'dim')
|
|
228
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
229
|
+
*/
|
|
230
|
+
setTheme(theme) {
|
|
231
|
+
const validThemes = ["light", "dark", "dim"];
|
|
232
|
+
if (!theme || !validThemes.includes(theme.toLowerCase())) {
|
|
233
|
+
throw new Error(`Tema inválido. Use um dos seguintes: ${validThemes.join(", ")}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.theme = theme.toLowerCase();
|
|
237
|
+
return this;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Define a cor de destaque
|
|
242
|
+
* @param {string} color - Cor hexadecimal
|
|
243
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
244
|
+
*/
|
|
245
|
+
setAccentColor(color) {
|
|
246
|
+
if (!color || !isValidHexColor(color)) throw new Error("Cor de destaque inválida. Use o formato hexadecimal.");
|
|
247
|
+
this.accentColor = color;
|
|
248
|
+
return this;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Ativa ou desativa a sombra de texto
|
|
253
|
+
* @param {boolean} enabled - Se a sombra de texto deve ser ativada
|
|
254
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
255
|
+
*/
|
|
256
|
+
enableTextShadow(enabled = true) {
|
|
257
|
+
this.useTextShadow = enabled;
|
|
258
|
+
return this;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Ativa ou desativa o modo escuro
|
|
263
|
+
* @param {boolean} enabled - Se o modo escuro deve ser ativado
|
|
264
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
265
|
+
*/
|
|
266
|
+
enableDarkMode(enabled = true) {
|
|
267
|
+
this.useDarkMode = enabled;
|
|
268
|
+
this.theme = enabled ? "dark" : "light";
|
|
269
|
+
return this;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Define as dimensões do card
|
|
274
|
+
* @param {number} width - Largura do card em pixels
|
|
275
|
+
* @param {number} height - Altura do card em pixels
|
|
276
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
277
|
+
*/
|
|
278
|
+
setCardDimensions(width, height) {
|
|
279
|
+
if (typeof width !== "number" || width < 600 || width > 1920) {
|
|
280
|
+
throw new Error("A largura do card deve estar entre 600 e 1920 pixels.");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (typeof height !== "number" || height < 300 || height > 800) {
|
|
284
|
+
throw new Error("A altura do card deve estar entre 300 e 800 pixels.");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
this.cardWidth = width;
|
|
288
|
+
this.cardHeight = height;
|
|
289
|
+
|
|
290
|
+
return this;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Define o tamanho do avatar
|
|
295
|
+
* @param {number} size - Tamanho do avatar em pixels
|
|
296
|
+
* @returns {TwitterHeader} - Instância atual para encadeamento
|
|
297
|
+
*/
|
|
298
|
+
setAvatarSize(size) {
|
|
299
|
+
if (typeof size !== "number" || size < 80 || size > 200) {
|
|
300
|
+
throw new Error("O tamanho do avatar deve estar entre 80 e 200 pixels.");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
this.avatarSize = size;
|
|
304
|
+
return this;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// --- Método de Construção ---
|
|
308
|
+
/**
|
|
309
|
+
* Constrói o banner e retorna um buffer de imagem
|
|
310
|
+
* @returns {Promise<Buffer>} - Buffer contendo a imagem do banner
|
|
311
|
+
*/
|
|
312
|
+
async build() {
|
|
313
|
+
// --- Registro de Fonte ---
|
|
314
|
+
const registeredFontName = await registerFontIfNeeded(this.font);
|
|
315
|
+
|
|
316
|
+
// --- Configuração do Canvas ---
|
|
317
|
+
const cardWidth = this.cardWidth;
|
|
318
|
+
const cardHeight = this.cardHeight;
|
|
319
|
+
const coverHeight = cardHeight * 0.5;
|
|
320
|
+
const avatarSize = this.avatarSize;
|
|
321
|
+
const padding = 20;
|
|
322
|
+
|
|
323
|
+
const canvas = pureimage.make(cardWidth, cardHeight);
|
|
324
|
+
const ctx = canvas.getContext("2d");
|
|
325
|
+
|
|
326
|
+
// --- Configuração de Cores com base no Tema ---
|
|
327
|
+
const colors = this._getThemeColors();
|
|
328
|
+
|
|
329
|
+
// --- Desenha Plano de Fundo ---
|
|
330
|
+
ctx.fillStyle = colors.background;
|
|
331
|
+
ctx.fillRect(0, 0, cardWidth, cardHeight);
|
|
332
|
+
|
|
333
|
+
// --- Desenha Imagem de Capa ---
|
|
334
|
+
if (this.coverImage) {
|
|
335
|
+
try {
|
|
336
|
+
const img = await loadImageWithAxios(this.coverImage);
|
|
337
|
+
const aspect = img.width / img.height;
|
|
338
|
+
let drawWidth = cardWidth;
|
|
339
|
+
let drawHeight = cardWidth / aspect;
|
|
340
|
+
|
|
341
|
+
// Ajusta as dimensões para cobrir toda a área da capa
|
|
342
|
+
if (drawHeight < coverHeight) {
|
|
343
|
+
drawHeight = coverHeight;
|
|
344
|
+
drawWidth = coverHeight * aspect;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const offsetX = (cardWidth - drawWidth) / 2;
|
|
348
|
+
const offsetY = 0;
|
|
349
|
+
|
|
350
|
+
ctx.drawImage(img, offsetX, offsetY, drawWidth, coverHeight);
|
|
351
|
+
} catch (e) {
|
|
352
|
+
console.error("Falha ao desenhar imagem de capa:", e.message);
|
|
353
|
+
|
|
354
|
+
// Capa de fallback
|
|
355
|
+
ctx.fillStyle = this.accentColor;
|
|
356
|
+
ctx.fillRect(0, 0, cardWidth, coverHeight);
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
// Capa de fallback
|
|
360
|
+
ctx.fillStyle = this.accentColor;
|
|
361
|
+
ctx.fillRect(0, 0, cardWidth, coverHeight);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// --- Desenha Avatar ---
|
|
365
|
+
const avatarX = padding;
|
|
366
|
+
const avatarY = coverHeight - avatarSize / 2;
|
|
367
|
+
|
|
368
|
+
ctx.save();
|
|
369
|
+
ctx.beginPath();
|
|
370
|
+
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
|
|
371
|
+
ctx.closePath();
|
|
372
|
+
ctx.clip();
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
const avatarImg = await loadImageWithAxios(this.avatar || path.join(__dirname, "../assets/placeholders/avatar.png"));
|
|
376
|
+
ctx.drawImage(avatarImg, avatarX, avatarY, avatarSize, avatarSize);
|
|
377
|
+
} catch (e) {
|
|
378
|
+
console.error("Falha ao desenhar avatar:", e.message);
|
|
379
|
+
|
|
380
|
+
// Avatar de fallback
|
|
381
|
+
ctx.fillStyle = colors.secondary;
|
|
382
|
+
ctx.fillRect(avatarX, avatarY, avatarSize, avatarSize);
|
|
383
|
+
|
|
384
|
+
ctx.fillStyle = colors.text;
|
|
385
|
+
ctx.font = `bold ${avatarSize / 3}px ${registeredFontName}-Bold`;
|
|
386
|
+
ctx.textAlign = "center";
|
|
387
|
+
ctx.textBaseline = "middle";
|
|
388
|
+
ctx.fillText(this.name.charAt(0).toUpperCase(), avatarX + avatarSize / 2, avatarY + avatarSize / 2);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
ctx.restore();
|
|
392
|
+
|
|
393
|
+
// Borda do avatar
|
|
394
|
+
ctx.strokeStyle = colors.background;
|
|
395
|
+
ctx.lineWidth = 4;
|
|
396
|
+
ctx.beginPath();
|
|
397
|
+
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2 + ctx.lineWidth / 2, 0, Math.PI * 2);
|
|
398
|
+
ctx.stroke();
|
|
399
|
+
ctx.closePath();
|
|
400
|
+
|
|
401
|
+
// --- Desenha Botão de Seguir ---
|
|
402
|
+
const followButtonWidth = 120;
|
|
403
|
+
const followButtonHeight = 40;
|
|
404
|
+
const followButtonX = cardWidth - followButtonWidth - padding;
|
|
405
|
+
const followButtonY = coverHeight + padding;
|
|
406
|
+
|
|
407
|
+
// Fundo do botão
|
|
408
|
+
ctx.fillStyle = colors.text;
|
|
409
|
+
roundRect(ctx, followButtonX, followButtonY, followButtonWidth, followButtonHeight, 20, true, false);
|
|
410
|
+
|
|
411
|
+
// Texto do botão
|
|
412
|
+
ctx.fillStyle = colors.background;
|
|
413
|
+
ctx.font = `bold 16px ${registeredFontName}-Bold`;
|
|
414
|
+
ctx.textAlign = "center";
|
|
415
|
+
ctx.textBaseline = "middle";
|
|
416
|
+
ctx.fillText("Seguir", followButtonX + followButtonWidth / 2, followButtonY + followButtonHeight / 2);
|
|
417
|
+
|
|
418
|
+
// --- Desenha Nome e Username ---
|
|
419
|
+
const nameX = padding;
|
|
420
|
+
const nameY = coverHeight + avatarSize / 2 + padding;
|
|
421
|
+
|
|
422
|
+
// Nome
|
|
423
|
+
ctx.fillStyle = colors.text;
|
|
424
|
+
ctx.font = `bold 24px ${registeredFontName}-Bold`;
|
|
425
|
+
ctx.textAlign = "left";
|
|
426
|
+
ctx.textBaseline = "top";
|
|
427
|
+
|
|
428
|
+
// Aplica sombra de texto se ativada
|
|
429
|
+
if (this.useTextShadow) {
|
|
430
|
+
applyTextShadow(ctx, hexToRgba(colors.shadow, 0.5), 2, 1, 1);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const nameText = this.name;
|
|
434
|
+
const nameWidth = ctx.measureText(nameText).width;
|
|
435
|
+
ctx.fillText(nameText, nameX, nameY);
|
|
436
|
+
|
|
437
|
+
// Desenha ícone de verificado (se aplicável)
|
|
438
|
+
if (this.isVerified) {
|
|
439
|
+
const verifiedSize = 20;
|
|
440
|
+
const verifiedX = nameX + nameWidth + 5;
|
|
441
|
+
|
|
442
|
+
ctx.fillStyle = this.isPremium ? "#FFD700" : this.accentColor;
|
|
443
|
+
ctx.beginPath();
|
|
444
|
+
ctx.arc(verifiedX + verifiedSize / 2, nameY + 12, verifiedSize / 2, 0, Math.PI * 2);
|
|
445
|
+
ctx.fill();
|
|
446
|
+
|
|
447
|
+
ctx.fillStyle = "#FFFFFF";
|
|
448
|
+
ctx.font = `bold 14px ${registeredFontName}-Bold`;
|
|
449
|
+
ctx.textAlign = "center";
|
|
450
|
+
ctx.fillText("✓", verifiedX + verifiedSize / 2, nameY + 12);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Remove sombra para o próximo texto
|
|
454
|
+
if (this.useTextShadow) {
|
|
455
|
+
clearShadow(ctx);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Username
|
|
459
|
+
ctx.fillStyle = colors.textSecondary;
|
|
460
|
+
ctx.font = `regular 16px ${registeredFontName}-Regular`;
|
|
461
|
+
ctx.textAlign = "left";
|
|
462
|
+
ctx.fillText(`@${this.username}`, nameX, nameY + 30);
|
|
463
|
+
|
|
464
|
+
// --- Desenha Bio ---
|
|
465
|
+
if (this.bio) {
|
|
466
|
+
ctx.fillStyle = colors.text;
|
|
467
|
+
ctx.font = `regular 16px ${registeredFontName}-Regular`;
|
|
468
|
+
ctx.textAlign = "left";
|
|
469
|
+
|
|
470
|
+
const bioY = nameY + 60;
|
|
471
|
+
wrapText(ctx, this.bio, nameX, bioY, cardWidth - padding * 2, 20, registeredFontName);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// --- Desenha Informações Adicionais ---
|
|
475
|
+
const infoY = nameY + (this.bio ? 120 : 70);
|
|
476
|
+
let infoX = nameX;
|
|
477
|
+
const infoSpacing = 20;
|
|
478
|
+
|
|
479
|
+
// Localização
|
|
480
|
+
if (this.location) {
|
|
481
|
+
ctx.fillStyle = colors.textSecondary;
|
|
482
|
+
ctx.font = `regular 14px ${registeredFontName}-Regular`;
|
|
483
|
+
ctx.textAlign = "left";
|
|
484
|
+
|
|
485
|
+
const locationText = `📍 ${this.location}`;
|
|
486
|
+
ctx.fillText(locationText, infoX, infoY);
|
|
487
|
+
|
|
488
|
+
infoX += ctx.measureText(locationText).width + infoSpacing;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Website
|
|
492
|
+
if (this.website) {
|
|
493
|
+
ctx.fillStyle = this.accentColor;
|
|
494
|
+
ctx.font = `regular 14px ${registeredFontName}-Regular`;
|
|
495
|
+
ctx.textAlign = "left";
|
|
496
|
+
|
|
497
|
+
const websiteText = `🔗 ${this.website}`;
|
|
498
|
+
ctx.fillText(websiteText, infoX, infoY);
|
|
499
|
+
|
|
500
|
+
infoX += ctx.measureText(websiteText).width + infoSpacing;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Data de entrada
|
|
504
|
+
if (this.joinDate) {
|
|
505
|
+
ctx.fillStyle = colors.textSecondary;
|
|
506
|
+
ctx.font = `regular 14px ${registeredFontName}-Regular`;
|
|
507
|
+
ctx.textAlign = "left";
|
|
508
|
+
|
|
509
|
+
const joinDateText = `🗓️ ${this.joinDate}`;
|
|
510
|
+
ctx.fillText(joinDateText, infoX, infoY);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// --- Desenha Estatísticas ---
|
|
514
|
+
const statsY = infoY + 30;
|
|
515
|
+
let statsX = nameX;
|
|
516
|
+
const statsSpacing = 30;
|
|
517
|
+
|
|
518
|
+
// Seguidores
|
|
519
|
+
ctx.fillStyle = colors.text;
|
|
520
|
+
ctx.font = `bold 14px ${registeredFontName}-Bold`;
|
|
521
|
+
ctx.textAlign = "left";
|
|
522
|
+
|
|
523
|
+
const followersText = `${formatNumber(this.followers)}`;
|
|
524
|
+
ctx.fillText(followersText, statsX, statsY);
|
|
525
|
+
|
|
526
|
+
ctx.fillStyle = colors.textSecondary;
|
|
527
|
+
ctx.font = `regular 14px ${registeredFontName}-Regular`;
|
|
528
|
+
const followersLabelWidth = ctx.measureText(" Seguidores").width;
|
|
529
|
+
ctx.fillText(" Seguidores", statsX + ctx.measureText(followersText).width, statsY);
|
|
530
|
+
|
|
531
|
+
statsX += ctx.measureText(followersText).width + followersLabelWidth + statsSpacing;
|
|
532
|
+
|
|
533
|
+
// Seguindo
|
|
534
|
+
ctx.fillStyle = colors.text;
|
|
535
|
+
ctx.font = `bold 14px ${registeredFontName}-Bold`;
|
|
536
|
+
|
|
537
|
+
const followingText = `${formatNumber(this.following)}`;
|
|
538
|
+
ctx.fillText(followingText, statsX, statsY);
|
|
539
|
+
|
|
540
|
+
ctx.fillStyle = colors.textSecondary;
|
|
541
|
+
ctx.font = `regular 14px ${registeredFontName}-Regular`;
|
|
542
|
+
const followingLabelWidth = ctx.measureText(" Seguindo").width;
|
|
543
|
+
ctx.fillText(" Seguindo", statsX + ctx.measureText(followingText).width, statsY);
|
|
544
|
+
|
|
545
|
+
statsX += ctx.measureText(followingText).width + followingLabelWidth + statsSpacing;
|
|
546
|
+
|
|
547
|
+
// Tweets
|
|
548
|
+
if (this.tweets > 0) {
|
|
549
|
+
ctx.fillStyle = colors.text;
|
|
550
|
+
ctx.font = `bold 14px ${registeredFontName}-Bold`;
|
|
551
|
+
|
|
552
|
+
const tweetsText = `${formatNumber(this.tweets)}`;
|
|
553
|
+
ctx.fillText(tweetsText, statsX, statsY);
|
|
554
|
+
|
|
555
|
+
ctx.fillStyle = colors.textSecondary;
|
|
556
|
+
ctx.font = `regular 14px ${registeredFontName}-Regular`;
|
|
557
|
+
ctx.fillText(" Tweets", statsX + ctx.measureText(tweetsText).width, statsY);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// --- Codifica e Retorna Buffer ---
|
|
561
|
+
try {
|
|
562
|
+
return await encodeToBuffer(canvas);
|
|
563
|
+
} catch (err) {
|
|
564
|
+
console.error("Falha ao codificar o Cabeçalho do Twitter:", err);
|
|
565
|
+
throw new Error("Não foi possível gerar o buffer de imagem do Cabeçalho do Twitter.");
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// --- Métodos Auxiliares Privados ---
|
|
570
|
+
/**
|
|
571
|
+
* Obtém as cores com base no tema selecionado
|
|
572
|
+
* @private
|
|
573
|
+
*/
|
|
574
|
+
_getThemeColors() {
|
|
575
|
+
switch (this.theme) {
|
|
576
|
+
case "dark":
|
|
577
|
+
return {
|
|
578
|
+
background: "#000000",
|
|
579
|
+
secondary: "#16181c",
|
|
580
|
+
text: "#FFFFFF",
|
|
581
|
+
textSecondary: "#8B98A5",
|
|
582
|
+
shadow: "#000000"
|
|
583
|
+
};
|
|
584
|
+
case "dim":
|
|
585
|
+
return {
|
|
586
|
+
background: "#15202B",
|
|
587
|
+
secondary: "#1E2732",
|
|
588
|
+
text: "#FFFFFF",
|
|
589
|
+
textSecondary: "#8B98A5",
|
|
590
|
+
shadow: "#10171E"
|
|
591
|
+
};
|
|
592
|
+
case "light":
|
|
593
|
+
default:
|
|
594
|
+
return {
|
|
595
|
+
background: "#FFFFFF",
|
|
596
|
+
secondary: "#F7F9FA",
|
|
597
|
+
text: "#0F1419",
|
|
598
|
+
textSecondary: "#536471",
|
|
599
|
+
shadow: "#CCCCCC"
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
|