@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,504 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Módulo de Banner de Post do Instagram
|
|
5
|
+
*
|
|
6
|
+
* Este módulo gera banners no estilo de posts do Instagram com suporte a
|
|
7
|
+
* imagem principal, informações de usuário e comentários.
|
|
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
|
+
/**
|
|
33
|
+
* @class InstagramPost
|
|
34
|
+
* @classdesc Gera um banner no estilo de post do Instagram.
|
|
35
|
+
* @example const postCard = new InstagramPost()
|
|
36
|
+
* .setUsername("usuario_instagram")
|
|
37
|
+
* .setUserAvatar("avatar.png")
|
|
38
|
+
* .setImage("post.jpg")
|
|
39
|
+
* .setCaption("Curtindo o dia na praia! #verao #ferias")
|
|
40
|
+
* .setLikes(1250)
|
|
41
|
+
* .addComment("amigo1", "Que lugar incrível!")
|
|
42
|
+
* .build();
|
|
43
|
+
*/
|
|
44
|
+
module.exports = class InstagramPost {
|
|
45
|
+
constructor(options) {
|
|
46
|
+
// Dados Principais
|
|
47
|
+
this.username = "usuario";
|
|
48
|
+
this.userAvatar = null;
|
|
49
|
+
this.verified = false;
|
|
50
|
+
this.location = null;
|
|
51
|
+
this.image = null;
|
|
52
|
+
this.caption = null;
|
|
53
|
+
this.likes = 0;
|
|
54
|
+
this.comments = [];
|
|
55
|
+
this.timestamp = "há 1 hora";
|
|
56
|
+
|
|
57
|
+
// Personalização Visual
|
|
58
|
+
this.font = { name: options?.font?.name ?? DEFAULT_FONT_FAMILY, path: options?.font?.path };
|
|
59
|
+
this.backgroundColor = "#FFFFFF";
|
|
60
|
+
this.textColor = "#262626";
|
|
61
|
+
this.secondaryTextColor = "#8e8e8e";
|
|
62
|
+
this.accentColor = "#0095F6";
|
|
63
|
+
this.showHeader = true;
|
|
64
|
+
this.showFooter = true;
|
|
65
|
+
|
|
66
|
+
// Configurações de Layout
|
|
67
|
+
this.cardWidth = 600;
|
|
68
|
+
this.imageHeight = 600;
|
|
69
|
+
this.cornerRadius = 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// --- Setters para Dados Principais ---
|
|
73
|
+
/**
|
|
74
|
+
* Define o nome de usuário
|
|
75
|
+
* @param {string} name - Nome do usuário
|
|
76
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
77
|
+
*/
|
|
78
|
+
setUsername(name) {
|
|
79
|
+
if (!name || typeof name !== "string") throw new Error("O nome de usuário deve ser uma string não vazia.");
|
|
80
|
+
this.username = name;
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Define o avatar do usuário
|
|
86
|
+
* @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem do avatar
|
|
87
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
88
|
+
*/
|
|
89
|
+
setUserAvatar(image) {
|
|
90
|
+
if (!image) throw new Error("A fonte da imagem do avatar não pode estar vazia.");
|
|
91
|
+
this.userAvatar = image;
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Define se o usuário é verificado
|
|
97
|
+
* @param {boolean} isVerified - Se o usuário é verificado
|
|
98
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
99
|
+
*/
|
|
100
|
+
setVerified(isVerified = true) {
|
|
101
|
+
this.verified = !!isVerified;
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Define a localização do post
|
|
107
|
+
* @param {string} location - Nome da localização
|
|
108
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
109
|
+
*/
|
|
110
|
+
setLocation(location) {
|
|
111
|
+
if (!location || typeof location !== "string") throw new Error("A localização deve ser uma string não vazia.");
|
|
112
|
+
this.location = location;
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Define a imagem principal do post
|
|
118
|
+
* @param {string|Buffer|Object} image - URL, Buffer ou caminho da imagem do post
|
|
119
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
120
|
+
*/
|
|
121
|
+
setImage(image) {
|
|
122
|
+
if (!image) throw new Error("A fonte da imagem do post não pode estar vazia.");
|
|
123
|
+
this.image = image;
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Define a legenda do post
|
|
129
|
+
* @param {string} text - Texto da legenda
|
|
130
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
131
|
+
*/
|
|
132
|
+
setCaption(text) {
|
|
133
|
+
if (!text || typeof text !== "string") throw new Error("A legenda deve ser uma string não vazia.");
|
|
134
|
+
this.caption = text;
|
|
135
|
+
return this;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Define o número de curtidas
|
|
140
|
+
* @param {number} count - Número de curtidas
|
|
141
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
142
|
+
*/
|
|
143
|
+
setLikes(count) {
|
|
144
|
+
if (typeof count !== "number" || count < 0) throw new Error("O número de curtidas deve ser um número não negativo.");
|
|
145
|
+
this.likes = count;
|
|
146
|
+
return this;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Adiciona um comentário ao post
|
|
151
|
+
* @param {string} username - Nome do usuário que comentou
|
|
152
|
+
* @param {string} text - Texto do comentário
|
|
153
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
154
|
+
*/
|
|
155
|
+
addComment(username, text) {
|
|
156
|
+
if (!username || typeof username !== "string") throw new Error("O nome de usuário do comentário deve ser uma string não vazia.");
|
|
157
|
+
if (!text || typeof text !== "string") throw new Error("O texto do comentário deve ser uma string não vazia.");
|
|
158
|
+
|
|
159
|
+
this.comments.push({ username, text });
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Define o timestamp do post
|
|
165
|
+
* @param {string} time - Texto do timestamp (ex: "há 2 horas")
|
|
166
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
167
|
+
*/
|
|
168
|
+
setTimestamp(time) {
|
|
169
|
+
if (!time || typeof time !== "string") throw new Error("O timestamp deve ser uma string não vazia.");
|
|
170
|
+
this.timestamp = time;
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// --- Setters para Personalização Visual ---
|
|
175
|
+
/**
|
|
176
|
+
* Define a cor de fundo
|
|
177
|
+
* @param {string} color - Cor hexadecimal
|
|
178
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
179
|
+
*/
|
|
180
|
+
setBackgroundColor(color) {
|
|
181
|
+
if (!color || !isValidHexColor(color)) throw new Error("Cor de fundo inválida. Use o formato hexadecimal.");
|
|
182
|
+
this.backgroundColor = color;
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Define a cor do texto principal
|
|
188
|
+
* @param {string} color - Cor hexadecimal
|
|
189
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
190
|
+
*/
|
|
191
|
+
setTextColor(color) {
|
|
192
|
+
if (!color || !isValidHexColor(color)) throw new Error("Cor de texto inválida. Use o formato hexadecimal.");
|
|
193
|
+
this.textColor = color;
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Define a cor do texto secundário
|
|
199
|
+
* @param {string} color - Cor hexadecimal
|
|
200
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
201
|
+
*/
|
|
202
|
+
setSecondaryTextColor(color) {
|
|
203
|
+
if (!color || !isValidHexColor(color)) throw new Error("Cor de texto secundário inválida. Use o formato hexadecimal.");
|
|
204
|
+
this.secondaryTextColor = color;
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Define a cor de destaque
|
|
210
|
+
* @param {string} color - Cor hexadecimal
|
|
211
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
212
|
+
*/
|
|
213
|
+
setAccentColor(color) {
|
|
214
|
+
if (!color || !isValidHexColor(color)) throw new Error("Cor de destaque inválida. Use o formato hexadecimal.");
|
|
215
|
+
this.accentColor = color;
|
|
216
|
+
return this;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Ativa ou desativa o cabeçalho
|
|
221
|
+
* @param {boolean} show - Se o cabeçalho deve ser exibido
|
|
222
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
223
|
+
*/
|
|
224
|
+
setShowHeader(show = true) {
|
|
225
|
+
this.showHeader = show;
|
|
226
|
+
return this;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Ativa ou desativa o rodapé
|
|
231
|
+
* @param {boolean} show - Se o rodapé deve ser exibido
|
|
232
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
233
|
+
*/
|
|
234
|
+
setShowFooter(show = true) {
|
|
235
|
+
this.showFooter = show;
|
|
236
|
+
return this;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Define as dimensões do card
|
|
241
|
+
* @param {number} width - Largura do card em pixels
|
|
242
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
243
|
+
*/
|
|
244
|
+
setCardWidth(width) {
|
|
245
|
+
if (typeof width !== "number" || width < 400 || width > 1080) {
|
|
246
|
+
throw new Error("A largura do card deve estar entre 400 e 1080 pixels.");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
this.cardWidth = width;
|
|
250
|
+
return this;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Define a altura da imagem
|
|
255
|
+
* @param {number} height - Altura da imagem em pixels
|
|
256
|
+
* @returns {InstagramPost} - Instância atual para encadeamento
|
|
257
|
+
*/
|
|
258
|
+
setImageHeight(height) {
|
|
259
|
+
if (typeof height !== "number" || height < 400 || height > 1080) {
|
|
260
|
+
throw new Error("A altura da imagem deve estar entre 400 e 1080 pixels.");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
this.imageHeight = height;
|
|
264
|
+
return this;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// --- Método de Construção ---
|
|
268
|
+
/**
|
|
269
|
+
* Constrói o banner e retorna um buffer de imagem
|
|
270
|
+
* @returns {Promise<Buffer>} - Buffer contendo a imagem do banner
|
|
271
|
+
*/
|
|
272
|
+
async build() {
|
|
273
|
+
if (!this.image) throw new Error("A imagem do post deve ser definida usando setImage().");
|
|
274
|
+
|
|
275
|
+
// --- Registro de Fonte ---
|
|
276
|
+
const registeredFontName = await registerFontIfNeeded(this.font);
|
|
277
|
+
|
|
278
|
+
// --- Configuração do Canvas ---
|
|
279
|
+
const cardWidth = this.cardWidth;
|
|
280
|
+
const headerHeight = this.showHeader ? 60 : 0;
|
|
281
|
+
const imageHeight = this.imageHeight;
|
|
282
|
+
|
|
283
|
+
// Calcula a altura do rodapé com base no conteúdo
|
|
284
|
+
let footerHeight = 0;
|
|
285
|
+
if (this.showFooter) {
|
|
286
|
+
footerHeight += 50; // Área de ícones e curtidas
|
|
287
|
+
|
|
288
|
+
if (this.caption) {
|
|
289
|
+
footerHeight += 60; // Espaço para legenda
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
footerHeight += this.comments.length * 40; // Espaço para comentários
|
|
293
|
+
footerHeight += 30; // Espaço para timestamp
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const cardHeight = headerHeight + imageHeight + footerHeight;
|
|
297
|
+
const padding = 15;
|
|
298
|
+
const avatarSize = 32;
|
|
299
|
+
|
|
300
|
+
const canvas = pureimage.make(cardWidth, cardHeight);
|
|
301
|
+
const ctx = canvas.getContext("2d");
|
|
302
|
+
|
|
303
|
+
// --- Desenha Plano de Fundo ---
|
|
304
|
+
ctx.fillStyle = this.backgroundColor;
|
|
305
|
+
ctx.fillRect(0, 0, cardWidth, cardHeight);
|
|
306
|
+
|
|
307
|
+
// --- Desenha Cabeçalho (se ativado) ---
|
|
308
|
+
if (this.showHeader) {
|
|
309
|
+
// Desenha linha divisória
|
|
310
|
+
ctx.fillStyle = "#DBDBDB";
|
|
311
|
+
ctx.fillRect(0, headerHeight - 1, cardWidth, 1);
|
|
312
|
+
|
|
313
|
+
// Desenha avatar do usuário
|
|
314
|
+
if (this.userAvatar) {
|
|
315
|
+
try {
|
|
316
|
+
ctx.save();
|
|
317
|
+
ctx.beginPath();
|
|
318
|
+
ctx.arc(padding + avatarSize / 2, headerHeight / 2, avatarSize / 2, 0, Math.PI * 2);
|
|
319
|
+
ctx.closePath();
|
|
320
|
+
ctx.clip();
|
|
321
|
+
|
|
322
|
+
const avatarImg = await loadImageWithAxios(this.userAvatar);
|
|
323
|
+
ctx.drawImage(avatarImg, padding, headerHeight / 2 - avatarSize / 2, avatarSize, avatarSize);
|
|
324
|
+
|
|
325
|
+
ctx.restore();
|
|
326
|
+
} catch (e) {
|
|
327
|
+
console.error("Falha ao desenhar avatar do usuário:", e.message);
|
|
328
|
+
|
|
329
|
+
// Avatar de fallback
|
|
330
|
+
ctx.fillStyle = "#DBDBDB";
|
|
331
|
+
ctx.beginPath();
|
|
332
|
+
ctx.arc(padding + avatarSize / 2, headerHeight / 2, avatarSize / 2, 0, Math.PI * 2);
|
|
333
|
+
ctx.fill();
|
|
334
|
+
|
|
335
|
+
ctx.fillStyle = "#8e8e8e";
|
|
336
|
+
ctx.font = `bold 16px ${registeredFontName}-Bold`;
|
|
337
|
+
ctx.textAlign = "center";
|
|
338
|
+
ctx.textBaseline = "middle";
|
|
339
|
+
ctx.fillText("?", padding + avatarSize / 2, headerHeight / 2);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Desenha nome de usuário
|
|
344
|
+
ctx.fillStyle = this.textColor;
|
|
345
|
+
ctx.font = `bold 14px ${registeredFontName}-Bold`;
|
|
346
|
+
ctx.textAlign = "left";
|
|
347
|
+
ctx.textBaseline = "middle";
|
|
348
|
+
|
|
349
|
+
const usernameX = padding + avatarSize + 10;
|
|
350
|
+
ctx.fillText(this.username, usernameX, headerHeight / 2 - (this.location ? 7 : 0));
|
|
351
|
+
|
|
352
|
+
// Desenha ícone de verificado (se aplicável)
|
|
353
|
+
if (this.verified) {
|
|
354
|
+
const verifiedSize = 14;
|
|
355
|
+
const verifiedX = usernameX + ctx.measureText(this.username).width + 5;
|
|
356
|
+
|
|
357
|
+
ctx.fillStyle = this.accentColor;
|
|
358
|
+
ctx.beginPath();
|
|
359
|
+
ctx.arc(verifiedX + verifiedSize / 2, headerHeight / 2 - (this.location ? 7 : 0), verifiedSize / 2, 0, Math.PI * 2);
|
|
360
|
+
ctx.fill();
|
|
361
|
+
|
|
362
|
+
ctx.fillStyle = "#FFFFFF";
|
|
363
|
+
ctx.font = `bold 10px ${registeredFontName}-Bold`;
|
|
364
|
+
ctx.textAlign = "center";
|
|
365
|
+
ctx.fillText("✓", verifiedX + verifiedSize / 2, headerHeight / 2 - (this.location ? 7 : 0));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Desenha localização (se fornecida)
|
|
369
|
+
if (this.location) {
|
|
370
|
+
ctx.fillStyle = this.secondaryTextColor;
|
|
371
|
+
ctx.font = `regular 12px ${registeredFontName}-Regular`;
|
|
372
|
+
ctx.textAlign = "left";
|
|
373
|
+
ctx.fillText(this.location, usernameX, headerHeight / 2 + 10);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Desenha ícone de opções
|
|
377
|
+
ctx.fillStyle = this.textColor;
|
|
378
|
+
ctx.font = `bold 18px ${registeredFontName}-Bold`;
|
|
379
|
+
ctx.textAlign = "center";
|
|
380
|
+
ctx.fillText("•••", cardWidth - padding - 10, headerHeight / 2);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// --- Desenha Imagem Principal ---
|
|
384
|
+
try {
|
|
385
|
+
const img = await loadImageWithAxios(this.image);
|
|
386
|
+
const aspect = img.width / img.height;
|
|
387
|
+
let drawWidth = cardWidth;
|
|
388
|
+
let drawHeight = drawWidth / aspect;
|
|
389
|
+
|
|
390
|
+
// Ajusta as dimensões para manter a proporção e preencher a altura desejada
|
|
391
|
+
if (drawHeight < imageHeight) {
|
|
392
|
+
drawHeight = imageHeight;
|
|
393
|
+
drawWidth = drawHeight * aspect;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const offsetX = (cardWidth - drawWidth) / 2;
|
|
397
|
+
const offsetY = headerHeight;
|
|
398
|
+
|
|
399
|
+
ctx.drawImage(img, offsetX, offsetY, drawWidth, imageHeight);
|
|
400
|
+
} catch (e) {
|
|
401
|
+
console.error("Falha ao desenhar imagem principal:", e.message);
|
|
402
|
+
|
|
403
|
+
// Imagem de fallback
|
|
404
|
+
ctx.fillStyle = "#DBDBDB";
|
|
405
|
+
ctx.fillRect(0, headerHeight, cardWidth, imageHeight);
|
|
406
|
+
|
|
407
|
+
ctx.fillStyle = "#8e8e8e";
|
|
408
|
+
ctx.font = `bold 24px ${registeredFontName}-Bold`;
|
|
409
|
+
ctx.textAlign = "center";
|
|
410
|
+
ctx.textBaseline = "middle";
|
|
411
|
+
ctx.fillText("Imagem não disponível", cardWidth / 2, headerHeight + imageHeight / 2);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// --- Desenha Rodapé (se ativado) ---
|
|
415
|
+
if (this.showFooter) {
|
|
416
|
+
let currentY = headerHeight + imageHeight;
|
|
417
|
+
|
|
418
|
+
// Desenha linha divisória
|
|
419
|
+
ctx.fillStyle = "#DBDBDB";
|
|
420
|
+
ctx.fillRect(0, currentY, cardWidth, 1);
|
|
421
|
+
currentY += 1;
|
|
422
|
+
|
|
423
|
+
// Desenha ícones de ação
|
|
424
|
+
const iconSize = 24;
|
|
425
|
+
const iconSpacing = 15;
|
|
426
|
+
let iconX = padding;
|
|
427
|
+
|
|
428
|
+
// Ícone de curtir
|
|
429
|
+
ctx.fillStyle = this.textColor;
|
|
430
|
+
ctx.font = `bold ${iconSize}px Arial`;
|
|
431
|
+
ctx.textAlign = "center";
|
|
432
|
+
ctx.textBaseline = "middle";
|
|
433
|
+
ctx.fillText("♡", iconX + iconSize / 2, currentY + 25);
|
|
434
|
+
iconX += iconSize + iconSpacing;
|
|
435
|
+
|
|
436
|
+
// Ícone de comentar
|
|
437
|
+
ctx.fillText("💬", iconX + iconSize / 2, currentY + 25);
|
|
438
|
+
iconX += iconSize + iconSpacing;
|
|
439
|
+
|
|
440
|
+
// Ícone de compartilhar
|
|
441
|
+
ctx.fillText("➤", iconX + iconSize / 2, currentY + 25);
|
|
442
|
+
|
|
443
|
+
// Ícone de salvar (à direita)
|
|
444
|
+
ctx.fillText("⊕", cardWidth - padding - iconSize / 2, currentY + 25);
|
|
445
|
+
|
|
446
|
+
currentY += 50;
|
|
447
|
+
|
|
448
|
+
// Desenha contador de curtidas
|
|
449
|
+
ctx.fillStyle = this.textColor;
|
|
450
|
+
ctx.font = `bold 14px ${registeredFontName}-Bold`;
|
|
451
|
+
ctx.textAlign = "left";
|
|
452
|
+
ctx.textBaseline = "top";
|
|
453
|
+
ctx.fillText(`${formatNumber(this.likes)} curtidas`, padding, currentY);
|
|
454
|
+
currentY += 25;
|
|
455
|
+
|
|
456
|
+
// Desenha legenda (se fornecida)
|
|
457
|
+
if (this.caption) {
|
|
458
|
+
ctx.fillStyle = this.textColor;
|
|
459
|
+
ctx.font = `bold 14px ${registeredFontName}-Bold`;
|
|
460
|
+
ctx.textAlign = "left";
|
|
461
|
+
ctx.fillText(this.username, padding, currentY);
|
|
462
|
+
|
|
463
|
+
const usernameWidth = ctx.measureText(this.username).width;
|
|
464
|
+
|
|
465
|
+
ctx.font = `regular 14px ${registeredFontName}-Regular`;
|
|
466
|
+
const captionY = wrapText(ctx, this.caption, padding + usernameWidth + 5, currentY, cardWidth - padding * 2 - usernameWidth - 5, 18, registeredFontName);
|
|
467
|
+
|
|
468
|
+
currentY = captionY;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Desenha comentários (se houver)
|
|
472
|
+
if (this.comments.length > 0) {
|
|
473
|
+
for (const comment of this.comments) {
|
|
474
|
+
ctx.fillStyle = this.textColor;
|
|
475
|
+
ctx.font = `bold 14px ${registeredFontName}-Bold`;
|
|
476
|
+
ctx.textAlign = "left";
|
|
477
|
+
ctx.fillText(comment.username, padding, currentY);
|
|
478
|
+
|
|
479
|
+
const commentUsernameWidth = ctx.measureText(comment.username).width;
|
|
480
|
+
|
|
481
|
+
ctx.font = `regular 14px ${registeredFontName}-Regular`;
|
|
482
|
+
const commentY = wrapText(ctx, comment.text, padding + commentUsernameWidth + 5, currentY, cardWidth - padding * 2 - commentUsernameWidth - 5, 18, registeredFontName);
|
|
483
|
+
|
|
484
|
+
currentY = commentY;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Desenha timestamp
|
|
489
|
+
ctx.fillStyle = this.secondaryTextColor;
|
|
490
|
+
ctx.font = `regular 12px ${registeredFontName}-Regular`;
|
|
491
|
+
ctx.textAlign = "left";
|
|
492
|
+
ctx.fillText(this.timestamp, padding, currentY);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// --- Codifica e Retorna Buffer ---
|
|
496
|
+
try {
|
|
497
|
+
return await encodeToBuffer(canvas);
|
|
498
|
+
} catch (err) {
|
|
499
|
+
console.error("Falha ao codificar o card de Post do Instagram:", err);
|
|
500
|
+
throw new Error("Não foi possível gerar o buffer de imagem do card de Post do Instagram.");
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
|