medias-fakerator 1.0.0-dra → 1.0.0-dra.1
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/Frutiger.ttf +0 -0
- package/assets/fonts/Ocr.ttf +0 -0
- package/assets/fonts/Sign.ttf +0 -0
- package/assets/ktp/output.png +0 -0
- package/assets/test-assets/profile.jpg +0 -0
- package/assets/test-output/brat-animated.webp +0 -0
- package/assets/test-output/brat-animatedHd.webp +0 -0
- package/assets/test-output/brat-video-frame-1.png +0 -0
- package/assets/test-output/brat-video-frame-1Hd.png +0 -0
- package/assets/test-output/brat.png +0 -0
- package/assets/test-output/bratHd.png +0 -0
- package/assets/test-output/fake-call-android.png +0 -0
- package/assets/test-output/fake-call-iphone.png +0 -0
- package/assets/test-output/fake-chat-iphone.png +0 -0
- package/assets/test-output/fake-story.png +0 -0
- package/assets/test-output/fake-tweet.png +0 -0
- package/assets/test-output/google-lyrics.png +0 -0
- package/assets/test-output/google-search.png +0 -0
- package/assets/test-output/instagram-profile.png +0 -0
- package/assets/test-output/ktp-card.png +0 -0
- package/assets/test-output/meme.png +0 -0
- package/assets/test-output/quote.png +0 -0
- package/assets/test-output/tiktok-profile.png +0 -0
- package/assets/test-output/twitter-profile.png +0 -0
- package/assets/test-output/youtube-profile.png +0 -0
- package/index.js +5 -0
- package/lib/generators.js +425 -0
- package/lib/ktpGenerator.js +185 -0
- package/lib/utils.js +106 -43
- package/package.json +2 -2
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/index.js
CHANGED
|
@@ -17,6 +17,7 @@ const {
|
|
|
17
17
|
bratVidGenerator,
|
|
18
18
|
generateAnimatedBratVid,
|
|
19
19
|
} = require("./lib/generators.js");
|
|
20
|
+
const generateCard = require("./lib/ktpGenerator.js");
|
|
20
21
|
|
|
21
22
|
module.exports = {
|
|
22
23
|
generateQuote,
|
|
@@ -34,6 +35,10 @@ module.exports = {
|
|
|
34
35
|
generateGoogleLyrics,
|
|
35
36
|
bratGenerator,
|
|
36
37
|
bratVidGenerator,
|
|
38
|
+
bratHdGenerator,
|
|
39
|
+
bratVidHdGenerator,
|
|
37
40
|
generateAnimatedBratVid,
|
|
41
|
+
generateAnimatedBratVidHd,
|
|
42
|
+
generateCard,
|
|
38
43
|
QuoteGenerator,
|
|
39
44
|
};
|
package/lib/generators.js
CHANGED
|
@@ -2799,6 +2799,31 @@ function generateAnimatedBratVid(tempFrameDir, outputPath) {
|
|
|
2799
2799
|
}
|
|
2800
2800
|
});
|
|
2801
2801
|
}
|
|
2802
|
+
function generateAnimatedBratVidHd(tempFrameDir, outputPath) {
|
|
2803
|
+
return new Promise((resolve, reject) => {
|
|
2804
|
+
try {
|
|
2805
|
+
ffmpeg()
|
|
2806
|
+
.input(path.join(tempFrameDir, "frame_%d.png"))
|
|
2807
|
+
.inputOptions("-framerate", "1.5")
|
|
2808
|
+
.videoCodec("libwebp")
|
|
2809
|
+
.outputOptions(
|
|
2810
|
+
"-loop",
|
|
2811
|
+
"0",
|
|
2812
|
+
"-lossless",
|
|
2813
|
+
"1",
|
|
2814
|
+
"-preset",
|
|
2815
|
+
"picture",
|
|
2816
|
+
"-an"
|
|
2817
|
+
)
|
|
2818
|
+
.output(outputPath)
|
|
2819
|
+
.on("end", resolve)
|
|
2820
|
+
.on("error", reject)
|
|
2821
|
+
.run();
|
|
2822
|
+
} catch (err) {
|
|
2823
|
+
reject(err);
|
|
2824
|
+
}
|
|
2825
|
+
});
|
|
2826
|
+
}
|
|
2802
2827
|
|
|
2803
2828
|
async function bratVidGenerator(
|
|
2804
2829
|
text,
|
|
@@ -3199,6 +3224,403 @@ async function bratGenerator(teks, highlightWords = []) {
|
|
|
3199
3224
|
throw error;
|
|
3200
3225
|
}
|
|
3201
3226
|
}
|
|
3227
|
+
async function bratVidHdGenerator(
|
|
3228
|
+
text,
|
|
3229
|
+
width,
|
|
3230
|
+
height,
|
|
3231
|
+
bgColor = "#FFFFFF",
|
|
3232
|
+
textColor = "#000000",
|
|
3233
|
+
highlightWords = []
|
|
3234
|
+
) {
|
|
3235
|
+
try {
|
|
3236
|
+
if (typeof text !== "string" || text.trim().length === 0)
|
|
3237
|
+
throw new Error("Text must be a non-empty string");
|
|
3238
|
+
if (!Array.isArray(highlightWords))
|
|
3239
|
+
throw new TypeError("highlightWords must be an array.");
|
|
3240
|
+
if (
|
|
3241
|
+
!Number.isInteger(width) ||
|
|
3242
|
+
!Number.isInteger(height) ||
|
|
3243
|
+
width <= 0 ||
|
|
3244
|
+
height <= 0
|
|
3245
|
+
)
|
|
3246
|
+
throw new Error("Width and height must be positive integers");
|
|
3247
|
+
if (!/^#[0-9A-F]{6}$/i.test(bgColor) || !/^#[0-9A-F]{6}$/i.test(textColor))
|
|
3248
|
+
throw new Error("Colors must be in hex format (#RRGGBB)");
|
|
3249
|
+
const allEmojiImages = await emojiImageByBrandPromise;
|
|
3250
|
+
const emojiCache = allEmojiImages["apple"] || {};
|
|
3251
|
+
const padding = 20;
|
|
3252
|
+
const availableWidth = width - padding * 2;
|
|
3253
|
+
const tempCanvas = createCanvas(1, 1);
|
|
3254
|
+
const tempCtx = tempCanvas.getContext("2d");
|
|
3255
|
+
if (!tempCtx) throw new Error("Failed to create canvas context");
|
|
3256
|
+
const tokens = text.match(/\S+|\n/g) || [];
|
|
3257
|
+
if (tokens.length === 0)
|
|
3258
|
+
throw new Error("No valid content tokens found in the text");
|
|
3259
|
+
let frames = [];
|
|
3260
|
+
const recalculateSegmentWidths = (segments, fontSize, ctx) => {
|
|
3261
|
+
return segments.map((seg) => {
|
|
3262
|
+
let newWidth = seg.width;
|
|
3263
|
+
switch (seg.type) {
|
|
3264
|
+
case "bold":
|
|
3265
|
+
ctx.font = `bold ${fontSize}px Arial`;
|
|
3266
|
+
newWidth = ctx.measureText(seg.content).width;
|
|
3267
|
+
break;
|
|
3268
|
+
case "italic":
|
|
3269
|
+
ctx.font = `italic ${fontSize}px Arial`;
|
|
3270
|
+
newWidth = ctx.measureText(seg.content).width;
|
|
3271
|
+
break;
|
|
3272
|
+
case "bolditalic":
|
|
3273
|
+
ctx.font = `bold italic ${fontSize}px Arial`;
|
|
3274
|
+
newWidth = ctx.measureText(seg.content).width;
|
|
3275
|
+
break;
|
|
3276
|
+
case "monospace":
|
|
3277
|
+
ctx.font = `${fontSize}px 'Courier New', monospace`;
|
|
3278
|
+
newWidth = ctx.measureText(seg.content).width;
|
|
3279
|
+
break;
|
|
3280
|
+
case "strikethrough":
|
|
3281
|
+
case "text":
|
|
3282
|
+
ctx.font = `${fontSize}px Arial`;
|
|
3283
|
+
newWidth = ctx.measureText(seg.content).width;
|
|
3284
|
+
break;
|
|
3285
|
+
case "emoji":
|
|
3286
|
+
newWidth = fontSize * 1.2;
|
|
3287
|
+
break;
|
|
3288
|
+
}
|
|
3289
|
+
return { ...seg, width: newWidth };
|
|
3290
|
+
});
|
|
3291
|
+
};
|
|
3292
|
+
const renderSegment = async (ctx, segment, x, y, fontSize, lineHeight) => {
|
|
3293
|
+
ctx.fillStyle = highlightWords.includes(segment.content)
|
|
3294
|
+
? "red"
|
|
3295
|
+
: textColor;
|
|
3296
|
+
switch (segment.type) {
|
|
3297
|
+
case "bold":
|
|
3298
|
+
ctx.font = `bold ${fontSize}px Arial`;
|
|
3299
|
+
break;
|
|
3300
|
+
case "italic":
|
|
3301
|
+
ctx.font = `italic ${fontSize}px Arial`;
|
|
3302
|
+
break;
|
|
3303
|
+
case "bolditalic":
|
|
3304
|
+
ctx.font = `bold italic ${fontSize}px Arial`;
|
|
3305
|
+
break;
|
|
3306
|
+
case "monospace":
|
|
3307
|
+
ctx.font = `${fontSize}px 'Courier New', monospace`;
|
|
3308
|
+
break;
|
|
3309
|
+
default:
|
|
3310
|
+
ctx.font = `${fontSize}px Arial`;
|
|
3311
|
+
break;
|
|
3312
|
+
}
|
|
3313
|
+
if (segment.type === "emoji") {
|
|
3314
|
+
const emojiSize = fontSize * 1.2;
|
|
3315
|
+
const emojiY = y + (lineHeight - emojiSize) / 2;
|
|
3316
|
+
if (!emojiCache[segment.content])
|
|
3317
|
+
throw new Error(`Emoji ${segment.content} tidak ditemukan`);
|
|
3318
|
+
const emojiImg = await loadImage(
|
|
3319
|
+
Buffer.from(emojiCache[segment.content], "base64")
|
|
3320
|
+
);
|
|
3321
|
+
ctx.drawImage(emojiImg, x, emojiY, emojiSize, emojiSize);
|
|
3322
|
+
} else {
|
|
3323
|
+
ctx.fillText(segment.content, x, y);
|
|
3324
|
+
if (segment.type === "strikethrough") {
|
|
3325
|
+
ctx.strokeStyle = ctx.fillStyle;
|
|
3326
|
+
ctx.lineWidth = Math.max(1, fontSize / 15);
|
|
3327
|
+
const lineY = y + lineHeight / 2.1;
|
|
3328
|
+
ctx.beginPath();
|
|
3329
|
+
ctx.moveTo(x, lineY);
|
|
3330
|
+
ctx.lineTo(x + segment.width, lineY);
|
|
3331
|
+
ctx.stroke();
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
};
|
|
3335
|
+
for (let i = 1; i <= tokens.length; i++) {
|
|
3336
|
+
const frameTokens = tokens.slice(0, i);
|
|
3337
|
+
const currentText = frameTokens
|
|
3338
|
+
.join(" ")
|
|
3339
|
+
.replace(/ \n /g, "\n")
|
|
3340
|
+
.replace(/\n /g, "\n")
|
|
3341
|
+
.replace(/ \n/g, "\n");
|
|
3342
|
+
if (currentText.trim() === "") continue;
|
|
3343
|
+
let fontSize = 200;
|
|
3344
|
+
let finalLines = [];
|
|
3345
|
+
let lineHeight = 0;
|
|
3346
|
+
const lineHeightMultiplier = 1.3;
|
|
3347
|
+
while (fontSize > 10) {
|
|
3348
|
+
let currentRenderLines = [];
|
|
3349
|
+
const textLines = currentText.split("\n");
|
|
3350
|
+
for (const singleLineText of textLines) {
|
|
3351
|
+
if (singleLineText === "") {
|
|
3352
|
+
currentRenderLines.push([]);
|
|
3353
|
+
continue;
|
|
3354
|
+
}
|
|
3355
|
+
let segments = parseTextToSegments(singleLineText, tempCtx, fontSize);
|
|
3356
|
+
let segmentsForSizing = recalculateSegmentWidths(
|
|
3357
|
+
segments,
|
|
3358
|
+
fontSize,
|
|
3359
|
+
tempCtx
|
|
3360
|
+
);
|
|
3361
|
+
let wrappedLines = rebuildLinesFromSegments(
|
|
3362
|
+
segmentsForSizing,
|
|
3363
|
+
availableWidth,
|
|
3364
|
+
tempCtx,
|
|
3365
|
+
fontSize
|
|
3366
|
+
);
|
|
3367
|
+
currentRenderLines.push(...wrappedLines);
|
|
3368
|
+
}
|
|
3369
|
+
const currentLineHeight = fontSize * lineHeightMultiplier;
|
|
3370
|
+
const totalTextHeight = currentRenderLines.length * currentLineHeight;
|
|
3371
|
+
if (totalTextHeight <= height - padding * 2) {
|
|
3372
|
+
finalLines = currentRenderLines;
|
|
3373
|
+
lineHeight = currentLineHeight;
|
|
3374
|
+
break;
|
|
3375
|
+
}
|
|
3376
|
+
fontSize -= 2;
|
|
3377
|
+
}
|
|
3378
|
+
const canvas = createCanvas(width, height);
|
|
3379
|
+
const ctx = canvas.getContext("2d");
|
|
3380
|
+
if (!ctx) throw new Error("Failed to create canvas context");
|
|
3381
|
+
ctx.fillStyle = bgColor;
|
|
3382
|
+
ctx.fillRect(0, 0, width, height);
|
|
3383
|
+
ctx.textBaseline = "top";
|
|
3384
|
+
const totalTextBlockHeight = finalLines.length * lineHeight;
|
|
3385
|
+
const startY = (height - totalTextBlockHeight) / 2;
|
|
3386
|
+
for (let j = 0; j < finalLines.length; j++) {
|
|
3387
|
+
const line = finalLines[j];
|
|
3388
|
+
const positionY = startY + j * lineHeight;
|
|
3389
|
+
const contentSegments = line.filter((seg) => seg.type !== "whitespace");
|
|
3390
|
+
if (contentSegments.length <= 1) {
|
|
3391
|
+
let positionX = padding;
|
|
3392
|
+
for (const segment of line) {
|
|
3393
|
+
await renderSegment(
|
|
3394
|
+
ctx,
|
|
3395
|
+
segment,
|
|
3396
|
+
positionX,
|
|
3397
|
+
positionY,
|
|
3398
|
+
fontSize,
|
|
3399
|
+
lineHeight
|
|
3400
|
+
);
|
|
3401
|
+
positionX += segment.width;
|
|
3402
|
+
}
|
|
3403
|
+
} else {
|
|
3404
|
+
const totalContentWidth = contentSegments.reduce(
|
|
3405
|
+
(sum, seg) => sum + seg.width,
|
|
3406
|
+
0
|
|
3407
|
+
);
|
|
3408
|
+
const spaceBetween =
|
|
3409
|
+
(availableWidth - totalContentWidth) / (contentSegments.length - 1);
|
|
3410
|
+
let positionX = padding;
|
|
3411
|
+
for (let k = 0; k < contentSegments.length; k++) {
|
|
3412
|
+
const segment = contentSegments[k];
|
|
3413
|
+
await renderSegment(
|
|
3414
|
+
ctx,
|
|
3415
|
+
segment,
|
|
3416
|
+
positionX,
|
|
3417
|
+
positionY,
|
|
3418
|
+
fontSize,
|
|
3419
|
+
lineHeight
|
|
3420
|
+
);
|
|
3421
|
+
positionX += segment.width;
|
|
3422
|
+
if (k < contentSegments.length - 1) {
|
|
3423
|
+
positionX += spaceBetween;
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
const buffer = canvas.toBuffer("image/png");
|
|
3429
|
+
frames.push(buffer);
|
|
3430
|
+
}
|
|
3431
|
+
return frames;
|
|
3432
|
+
} catch (error) {
|
|
3433
|
+
throw error;
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
async function bratHdGenerator(teks, highlightWords = []) {
|
|
3437
|
+
try {
|
|
3438
|
+
if (typeof teks !== "string" || teks.trim().length === 0)
|
|
3439
|
+
throw new Error("Teks tidak boleh kosong.");
|
|
3440
|
+
if (!Array.isArray(highlightWords))
|
|
3441
|
+
throw new TypeError("highlightWords harus berupa array.");
|
|
3442
|
+
const allEmojiImages = await emojiImageByBrandPromise;
|
|
3443
|
+
const emojiCache = allEmojiImages["apple"] || {};
|
|
3444
|
+
let width = 512,
|
|
3445
|
+
height = 512,
|
|
3446
|
+
margin = 8,
|
|
3447
|
+
verticalPadding = 8;
|
|
3448
|
+
const canvas = createCanvas(width, height);
|
|
3449
|
+
const ctx = canvas.getContext("2d");
|
|
3450
|
+
if (!ctx) throw new Error("Gagal membuat konteks kanvas.");
|
|
3451
|
+
ctx.fillStyle = "white";
|
|
3452
|
+
ctx.fillRect(0, 0, width, height);
|
|
3453
|
+
ctx.textAlign = "left";
|
|
3454
|
+
ctx.textBaseline = "top";
|
|
3455
|
+
let fontSize = 200;
|
|
3456
|
+
let lineHeightMultiplier = 1.3;
|
|
3457
|
+
const availableWidth = width - 2 * margin;
|
|
3458
|
+
let finalLines = [];
|
|
3459
|
+
let finalFontSize = 0;
|
|
3460
|
+
let lineHeight = 0;
|
|
3461
|
+
const wordCount = (
|
|
3462
|
+
teks.trim().match(/(\p{L}|\p{N}|\p{Emoji_Presentation})+/gu) || []
|
|
3463
|
+
).length;
|
|
3464
|
+
let lastKnownGoodSolution = null;
|
|
3465
|
+
while (fontSize > 10) {
|
|
3466
|
+
let currentRenderLines = [];
|
|
3467
|
+
const textLines = teks.split("\n");
|
|
3468
|
+
for (const singleLineText of textLines) {
|
|
3469
|
+
if (singleLineText === "") {
|
|
3470
|
+
currentRenderLines.push([]);
|
|
3471
|
+
continue;
|
|
3472
|
+
}
|
|
3473
|
+
let segments = parseTextToSegments(singleLineText, ctx, fontSize);
|
|
3474
|
+
let wrappedLines = rebuildLinesFromSegments(
|
|
3475
|
+
segments,
|
|
3476
|
+
availableWidth,
|
|
3477
|
+
ctx,
|
|
3478
|
+
fontSize
|
|
3479
|
+
);
|
|
3480
|
+
currentRenderLines.push(...wrappedLines);
|
|
3481
|
+
}
|
|
3482
|
+
if (
|
|
3483
|
+
currentRenderLines.length === 1 &&
|
|
3484
|
+
currentRenderLines[0].filter((seg) => seg.type !== "whitespace")
|
|
3485
|
+
.length === 2 &&
|
|
3486
|
+
currentRenderLines[0].some((seg) => seg.type === "text") &&
|
|
3487
|
+
currentRenderLines[0].some((seg) => seg.type === "emoji")
|
|
3488
|
+
) {
|
|
3489
|
+
const textSeg = currentRenderLines[0].find(
|
|
3490
|
+
(seg) => seg.type === "text"
|
|
3491
|
+
);
|
|
3492
|
+
const emojiSeg = currentRenderLines[0].find(
|
|
3493
|
+
(seg) => seg.type === "emoji"
|
|
3494
|
+
);
|
|
3495
|
+
currentRenderLines = [[textSeg], [emojiSeg]];
|
|
3496
|
+
}
|
|
3497
|
+
const currentLineHeight = fontSize * lineHeightMultiplier;
|
|
3498
|
+
const totalTextHeight = currentRenderLines.length * currentLineHeight;
|
|
3499
|
+
if (totalTextHeight <= height - 2 * verticalPadding) {
|
|
3500
|
+
lastKnownGoodSolution = {
|
|
3501
|
+
lines: currentRenderLines,
|
|
3502
|
+
fontSize: fontSize,
|
|
3503
|
+
lineHeight: currentLineHeight,
|
|
3504
|
+
};
|
|
3505
|
+
if (wordCount === 4) {
|
|
3506
|
+
if (currentRenderLines.length === 2) {
|
|
3507
|
+
finalLines = currentRenderLines;
|
|
3508
|
+
finalFontSize = fontSize;
|
|
3509
|
+
lineHeight = currentLineHeight;
|
|
3510
|
+
break;
|
|
3511
|
+
}
|
|
3512
|
+
} else {
|
|
3513
|
+
finalLines = currentRenderLines;
|
|
3514
|
+
finalFontSize = fontSize;
|
|
3515
|
+
lineHeight = currentLineHeight;
|
|
3516
|
+
break;
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
fontSize -= 2;
|
|
3520
|
+
}
|
|
3521
|
+
if (finalLines.length === 0 && lastKnownGoodSolution) {
|
|
3522
|
+
finalLines = lastKnownGoodSolution.lines;
|
|
3523
|
+
finalFontSize = lastKnownGoodSolution.fontSize;
|
|
3524
|
+
lineHeight = lastKnownGoodSolution.lineHeight;
|
|
3525
|
+
}
|
|
3526
|
+
if (
|
|
3527
|
+
finalLines.length === 1 &&
|
|
3528
|
+
finalLines[0].length === 1 &&
|
|
3529
|
+
finalLines[0][0].type === "text"
|
|
3530
|
+
) {
|
|
3531
|
+
const theOnlyWord = finalLines[0][0].content;
|
|
3532
|
+
const heightBasedSize =
|
|
3533
|
+
(height - 2 * verticalPadding) / lineHeightMultiplier;
|
|
3534
|
+
ctx.font = `200px Arial`;
|
|
3535
|
+
const referenceWidth = ctx.measureText(theOnlyWord).width;
|
|
3536
|
+
const widthBasedSize = (availableWidth / referenceWidth) * 200;
|
|
3537
|
+
finalFontSize = Math.floor(Math.min(heightBasedSize, widthBasedSize));
|
|
3538
|
+
lineHeight = finalFontSize * lineHeightMultiplier;
|
|
3539
|
+
}
|
|
3540
|
+
const totalFinalHeight = finalLines.length * lineHeight;
|
|
3541
|
+
let y =
|
|
3542
|
+
finalLines.length === 1
|
|
3543
|
+
? verticalPadding
|
|
3544
|
+
: (height - totalFinalHeight) / 2;
|
|
3545
|
+
const renderSegment = async (segment, x, y) => {
|
|
3546
|
+
ctx.fillStyle = isHighlighted(highlightWords, segment.content)
|
|
3547
|
+
? "red"
|
|
3548
|
+
: "black";
|
|
3549
|
+
switch (segment.type) {
|
|
3550
|
+
case "bold":
|
|
3551
|
+
ctx.font = `bold ${finalFontSize}px Arial`;
|
|
3552
|
+
break;
|
|
3553
|
+
case "italic":
|
|
3554
|
+
ctx.font = `italic ${finalFontSize}px Arial`;
|
|
3555
|
+
break;
|
|
3556
|
+
case "bolditalic":
|
|
3557
|
+
ctx.font = `bold italic ${finalFontSize}px Arial`;
|
|
3558
|
+
break;
|
|
3559
|
+
case "monospace":
|
|
3560
|
+
ctx.font = `${finalFontSize}px 'Courier New', monospace`;
|
|
3561
|
+
break;
|
|
3562
|
+
case "strikethrough":
|
|
3563
|
+
case "text":
|
|
3564
|
+
default:
|
|
3565
|
+
ctx.font = `${finalFontSize}px Arial`;
|
|
3566
|
+
break;
|
|
3567
|
+
}
|
|
3568
|
+
if (segment.type === "emoji") {
|
|
3569
|
+
const emojiSize = finalFontSize * 1.2;
|
|
3570
|
+
const emojiY = y + (lineHeight - emojiSize) / 2;
|
|
3571
|
+
if (!emojiCache[segment.content])
|
|
3572
|
+
throw new Error(`Emoji ${segment.content} tidak ditemukan di cache`);
|
|
3573
|
+
const emojiImg = await loadImage(
|
|
3574
|
+
Buffer.from(emojiCache[segment.content], "base64")
|
|
3575
|
+
);
|
|
3576
|
+
ctx.drawImage(emojiImg, x, emojiY, emojiSize, emojiSize);
|
|
3577
|
+
} else {
|
|
3578
|
+
ctx.fillText(segment.content, x, y);
|
|
3579
|
+
if (segment.type === "strikethrough") {
|
|
3580
|
+
ctx.strokeStyle = ctx.fillStyle;
|
|
3581
|
+
ctx.lineWidth = Math.max(1, finalFontSize / 15);
|
|
3582
|
+
const lineY = y + lineHeight / 2.1;
|
|
3583
|
+
ctx.beginPath();
|
|
3584
|
+
ctx.moveTo(x, lineY);
|
|
3585
|
+
ctx.lineTo(x + segment.width, lineY);
|
|
3586
|
+
ctx.stroke();
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
};
|
|
3590
|
+
for (const line of finalLines) {
|
|
3591
|
+
const contentSegments = line.filter((seg) => seg.type !== "whitespace");
|
|
3592
|
+
if (contentSegments.length <= 1) {
|
|
3593
|
+
let x = margin;
|
|
3594
|
+
for (const segment of line) {
|
|
3595
|
+
await renderSegment(segment, x, y);
|
|
3596
|
+
x += segment.width;
|
|
3597
|
+
}
|
|
3598
|
+
} else {
|
|
3599
|
+
const totalContentWidth = contentSegments.reduce(
|
|
3600
|
+
(sum, seg) => sum + seg.width,
|
|
3601
|
+
0
|
|
3602
|
+
);
|
|
3603
|
+
const spacePerGap =
|
|
3604
|
+
(availableWidth - totalContentWidth) / (contentSegments.length - 1);
|
|
3605
|
+
let currentX = margin;
|
|
3606
|
+
for (let i = 0; i < contentSegments.length; i++) {
|
|
3607
|
+
const segment = contentSegments[i];
|
|
3608
|
+
await renderSegment(segment, currentX, y);
|
|
3609
|
+
currentX += segment.width;
|
|
3610
|
+
if (i < contentSegments.length - 1) {
|
|
3611
|
+
currentX += spacePerGap;
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
y += lineHeight;
|
|
3616
|
+
}
|
|
3617
|
+
const buffer = canvas.toBuffer("image/png");
|
|
3618
|
+
return buffer;
|
|
3619
|
+
} catch (error) {
|
|
3620
|
+
console.error("Terjadi error di bratGenerator:", error);
|
|
3621
|
+
throw error;
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3202
3624
|
|
|
3203
3625
|
module.exports = {
|
|
3204
3626
|
generateQuote,
|
|
@@ -3216,5 +3638,8 @@ module.exports = {
|
|
|
3216
3638
|
generateGoogleLyrics,
|
|
3217
3639
|
bratGenerator,
|
|
3218
3640
|
bratVidGenerator,
|
|
3641
|
+
bratHdGenerator,
|
|
3642
|
+
bratVidHdGenerator,
|
|
3219
3643
|
generateAnimatedBratVid,
|
|
3644
|
+
generateAnimatedBratVidHd,
|
|
3220
3645
|
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
const fs = require("fs-extra");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { createCanvas, loadImage, registerFont } = require("canvas");
|
|
4
|
+
|
|
5
|
+
/* ================= FONT REGISTRATION ================= */
|
|
6
|
+
try {
|
|
7
|
+
registerFont(path.join(process.cwd(), "assets/fonts/Ocr.ttf"), {
|
|
8
|
+
family: "Ocr",
|
|
9
|
+
});
|
|
10
|
+
registerFont(path.join(process.cwd(), "assets/fonts/Sign.ttf"), {
|
|
11
|
+
family: "Sign",
|
|
12
|
+
});
|
|
13
|
+
registerFont(path.join(process.cwd(), "assets/fonts/Frutiger.ttf"), {
|
|
14
|
+
family: "Frutiger",
|
|
15
|
+
});
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.error("⚠️ Gagal mendaftarkan font:", error);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* ================= FONT CONSTANT ================= */
|
|
21
|
+
const FONT_OCR = "Ocr";
|
|
22
|
+
const FONT_NORMAL = "Frutiger";
|
|
23
|
+
|
|
24
|
+
/* ================= DEFAULT DATA ================= */
|
|
25
|
+
const defaultUserData = {
|
|
26
|
+
NIK: "1234567890123456",
|
|
27
|
+
Nama: "YANTO OLI GARDAN",
|
|
28
|
+
"Tempat/Tgl Lahir": "Zimbabwe, 69-69-6969",
|
|
29
|
+
"Jenis Kelamin": "LAKI-LAKI",
|
|
30
|
+
"Gol. Darah": "O",
|
|
31
|
+
Alamat: "JL. Kemana Aja",
|
|
32
|
+
"RT/RW": "001/002",
|
|
33
|
+
"Kel/Desa": "JOLODONG",
|
|
34
|
+
Kecamatan: "KECIMITIN",
|
|
35
|
+
Agama: "KEPO",
|
|
36
|
+
"Status Perkawinan": "SERING KAWIN",
|
|
37
|
+
Pekerjaan: "PENGANGGURAN BANYAK ACARA",
|
|
38
|
+
Kewarganegaraan: "PMO",
|
|
39
|
+
"Berlaku Hingga": "SAMPE MENINGGOY",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const defaultHeaderFooterData = {
|
|
43
|
+
Provinsi: "PROVINSI",
|
|
44
|
+
Kabupaten: "KABUPATEN",
|
|
45
|
+
LokasiTTD: "KONTOLODON",
|
|
46
|
+
TanggalTTD: "12-34-5678",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/* ================= COORDINATES ================= */
|
|
50
|
+
const coordinates = {
|
|
51
|
+
// ✅ HANYA NIK PAKAI OCR
|
|
52
|
+
NIK: { x: 195, y: 139, fontSize: 34, font: FONT_OCR },
|
|
53
|
+
|
|
54
|
+
// ❌ SELAIN NIK → FONT NORMAL
|
|
55
|
+
Nama: { x: 215, y: 174, fontSize: 19 },
|
|
56
|
+
"Tempat/Tgl Lahir": { x: 215, y: 200, fontSize: 19 },
|
|
57
|
+
"Jenis Kelamin": { x: 215, y: 224, fontSize: 19 },
|
|
58
|
+
"Gol. Darah": { x: 520, y: 224, fontSize: 19 },
|
|
59
|
+
Alamat: { x: 215, y: 249, fontSize: 19 },
|
|
60
|
+
"RT/RW": { x: 215, y: 274, fontSize: 19 },
|
|
61
|
+
"Kel/Desa": { x: 215, y: 298, fontSize: 19 },
|
|
62
|
+
Kecamatan: { x: 215, y: 322, fontSize: 19 },
|
|
63
|
+
Agama: { x: 215, y: 348, fontSize: 19 },
|
|
64
|
+
"Status Perkawinan": { x: 215, y: 372, fontSize: 19 },
|
|
65
|
+
Pekerjaan: { x: 215, y: 397, fontSize: 19 },
|
|
66
|
+
Kewarganegaraan: { x: 215, y: 422, fontSize: 19 },
|
|
67
|
+
"Berlaku Hingga": { x: 215, y: 446, fontSize: 19 },
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const headerFooterCoordinates = {
|
|
71
|
+
Provinsi: { y: 55, fontSize: 34, align: "center" },
|
|
72
|
+
Kabupaten: { y: 90, fontSize: 30, align: "center" },
|
|
73
|
+
LokasiTTD: {
|
|
74
|
+
x: 690,
|
|
75
|
+
y: 410,
|
|
76
|
+
fontSize: 19,
|
|
77
|
+
align: "center",
|
|
78
|
+
},
|
|
79
|
+
TanggalTTD: {
|
|
80
|
+
x: 690,
|
|
81
|
+
y: 430,
|
|
82
|
+
fontSize: 16,
|
|
83
|
+
align: "center",
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/* ================= MAIN FUNCTION ================= */
|
|
88
|
+
async function generateCard(options = {}) {
|
|
89
|
+
const {
|
|
90
|
+
userData = {},
|
|
91
|
+
headerFooterData = {},
|
|
92
|
+
profilePicPath,
|
|
93
|
+
signatureText,
|
|
94
|
+
} = options;
|
|
95
|
+
|
|
96
|
+
const finalUserData = { ...defaultUserData, ...userData };
|
|
97
|
+
const finalHeaderFooterData = {
|
|
98
|
+
...defaultHeaderFooterData,
|
|
99
|
+
...headerFooterData,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/* ===== LOAD PROFILE PICTURE ===== */
|
|
103
|
+
let profilePic;
|
|
104
|
+
try {
|
|
105
|
+
if (Buffer.isBuffer(profilePicPath)) {
|
|
106
|
+
profilePic = await loadImage(profilePicPath);
|
|
107
|
+
} else if (
|
|
108
|
+
typeof profilePicPath === "string" &&
|
|
109
|
+
(await fs.pathExists(profilePicPath))
|
|
110
|
+
) {
|
|
111
|
+
profilePic = await loadImage(profilePicPath);
|
|
112
|
+
} else {
|
|
113
|
+
console.error("⚠️ profilePicPath tidak valid");
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.error("⚠️ Gagal load foto profil:", err);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const background = await loadImage(
|
|
123
|
+
path.join(process.cwd(), "assets/ktp/output.png")
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const canvas = createCanvas(background.width, background.height);
|
|
127
|
+
const ctx = canvas.getContext("2d");
|
|
128
|
+
|
|
129
|
+
ctx.textBaseline = "alphabetic";
|
|
130
|
+
ctx.fillStyle = "black";
|
|
131
|
+
|
|
132
|
+
ctx.drawImage(background, 0, 0);
|
|
133
|
+
ctx.drawImage(profilePic, 584, 136, 201, 251);
|
|
134
|
+
|
|
135
|
+
/* ===== DRAW USER DATA ===== */
|
|
136
|
+
for (const [label, value] of Object.entries(finalUserData)) {
|
|
137
|
+
const style = coordinates[label];
|
|
138
|
+
if (!style) continue;
|
|
139
|
+
|
|
140
|
+
const fontFamily = style.font || FONT_NORMAL;
|
|
141
|
+
|
|
142
|
+
ctx.textAlign = "left";
|
|
143
|
+
ctx.font = `${style.fontWeight || "normal"} ${
|
|
144
|
+
style.fontSize || 18
|
|
145
|
+
}px ${fontFamily}`;
|
|
146
|
+
|
|
147
|
+
ctx.lineWidth = 0.6;
|
|
148
|
+
ctx.strokeText(String(value).toUpperCase(), style.x, style.y);
|
|
149
|
+
ctx.fillText(String(value).toUpperCase(), style.x, style.y);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* ===== HEADER & FOOTER ===== */
|
|
153
|
+
for (const [label, value] of Object.entries(finalHeaderFooterData)) {
|
|
154
|
+
const style = headerFooterCoordinates[label];
|
|
155
|
+
if (!style) continue;
|
|
156
|
+
|
|
157
|
+
const xPos = style.x || background.width / 2;
|
|
158
|
+
|
|
159
|
+
ctx.textAlign = style.align || "left";
|
|
160
|
+
ctx.font = `${style.fontWeight || "normal"} ${
|
|
161
|
+
style.fontSize || 18
|
|
162
|
+
}px ${FONT_NORMAL}`;
|
|
163
|
+
|
|
164
|
+
ctx.lineWidth = 0.8;
|
|
165
|
+
ctx.strokeText(String(value).toUpperCase(), xPos, style.y);
|
|
166
|
+
ctx.fillText(String(value).toUpperCase(), xPos, style.y);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* ===== SIGNATURE ===== */
|
|
170
|
+
const signText = signatureText || finalUserData.Nama.split(" ")[0];
|
|
171
|
+
|
|
172
|
+
ctx.textAlign = "center";
|
|
173
|
+
ctx.font = "23px Sign";
|
|
174
|
+
ctx.lineWidth = 1;
|
|
175
|
+
ctx.strokeText(signText.toUpperCase(), 660, 450);
|
|
176
|
+
ctx.fillText(signText.toUpperCase(), 660, 450);
|
|
177
|
+
|
|
178
|
+
return canvas.toBuffer("image/png");
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error("❌ Terjadi kesalahan saat membuat kartu:", error);
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = { generateCard };
|
package/lib/utils.js
CHANGED
|
@@ -1,66 +1,109 @@
|
|
|
1
|
-
const EmojiDbLib = require(
|
|
1
|
+
const EmojiDbLib = require("emoji-db");
|
|
2
2
|
|
|
3
3
|
let emojiDb;
|
|
4
4
|
try {
|
|
5
5
|
emojiDb = new EmojiDbLib({ useDefaultDb: true });
|
|
6
|
-
if (!emojiDb || typeof emojiDb.searchFromText !==
|
|
6
|
+
if (!emojiDb || typeof emojiDb.searchFromText !== "function")
|
|
7
|
+
throw new Error("Gagal menginisialisasi database emoji");
|
|
7
8
|
} catch (error) {
|
|
8
|
-
console.error(
|
|
9
|
+
console.error("Error saat inisialisasi database emoji:", error);
|
|
9
10
|
throw error;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
function parseTextToSegments(text, ctx, fontSize) {
|
|
13
14
|
try {
|
|
14
|
-
if (typeof text !==
|
|
15
|
-
if (!ctx || typeof ctx.measureText !==
|
|
15
|
+
if (typeof text !== "string") text = String(text);
|
|
16
|
+
if (!ctx || typeof ctx.measureText !== "function")
|
|
17
|
+
throw new TypeError("Invalid canvas context");
|
|
16
18
|
const finalSegments = [];
|
|
17
|
-
const rawLines = text.split(
|
|
19
|
+
const rawLines = text.split("\n");
|
|
18
20
|
rawLines.forEach((line, index) => {
|
|
19
|
-
if (line ===
|
|
21
|
+
if (line === "") {
|
|
20
22
|
} else {
|
|
21
23
|
const segmentsInLine = [];
|
|
22
|
-
const emojiMatches = emojiDb.searchFromText({
|
|
24
|
+
const emojiMatches = emojiDb.searchFromText({
|
|
25
|
+
input: line,
|
|
26
|
+
fixCodePoints: true,
|
|
27
|
+
});
|
|
23
28
|
let lastIndex = 0;
|
|
24
29
|
const processChunk = (chunk) => {
|
|
25
30
|
if (!chunk) return;
|
|
26
|
-
const tokenizerRegex =
|
|
31
|
+
const tokenizerRegex =
|
|
32
|
+
/(\*_.*?_\*|_\*.*?\*_)|(\*.*?\*)|(_.*?_)|(~.*?~)|(```.*?```)|(\s+)|([^\s*~_`]+)/g;
|
|
27
33
|
let match;
|
|
28
34
|
while ((match = tokenizerRegex.exec(chunk)) !== null) {
|
|
29
|
-
const [
|
|
35
|
+
const [
|
|
36
|
+
fullMatch,
|
|
37
|
+
boldItalic,
|
|
38
|
+
bold,
|
|
39
|
+
italic,
|
|
40
|
+
strikethrough,
|
|
41
|
+
monospace,
|
|
42
|
+
whitespace,
|
|
43
|
+
textContent,
|
|
44
|
+
] = match;
|
|
30
45
|
if (boldItalic) {
|
|
31
46
|
const content = boldItalic.slice(2, -2);
|
|
32
47
|
ctx.font = `italic bold ${fontSize}px Arial`;
|
|
33
|
-
segmentsInLine.push({
|
|
48
|
+
segmentsInLine.push({
|
|
49
|
+
type: "bolditalic",
|
|
50
|
+
content,
|
|
51
|
+
width: ctx.measureText(content).width,
|
|
52
|
+
});
|
|
34
53
|
} else if (bold) {
|
|
35
54
|
const content = bold.slice(1, -1);
|
|
36
55
|
ctx.font = `bold ${fontSize}px Arial`;
|
|
37
|
-
segmentsInLine.push({
|
|
56
|
+
segmentsInLine.push({
|
|
57
|
+
type: "bold",
|
|
58
|
+
content,
|
|
59
|
+
width: ctx.measureText(content).width,
|
|
60
|
+
});
|
|
38
61
|
} else if (italic) {
|
|
39
62
|
const content = italic.slice(1, -1);
|
|
40
63
|
ctx.font = `italic ${fontSize}px Arial`;
|
|
41
|
-
segmentsInLine.push({
|
|
64
|
+
segmentsInLine.push({
|
|
65
|
+
type: "italic",
|
|
66
|
+
content,
|
|
67
|
+
width: ctx.measureText(content).width,
|
|
68
|
+
});
|
|
42
69
|
} else if (strikethrough) {
|
|
43
70
|
const content = strikethrough.slice(1, -1);
|
|
44
71
|
ctx.font = `${fontSize}px Arial`;
|
|
45
|
-
segmentsInLine.push({
|
|
72
|
+
segmentsInLine.push({
|
|
73
|
+
type: "strikethrough",
|
|
74
|
+
content,
|
|
75
|
+
width: ctx.measureText(content).width,
|
|
76
|
+
});
|
|
46
77
|
} else if (monospace) {
|
|
47
78
|
const content = monospace.slice(3, -3);
|
|
48
79
|
ctx.font = `${fontSize}px 'Courier New', monospace`;
|
|
49
|
-
segmentsInLine.push({
|
|
80
|
+
segmentsInLine.push({
|
|
81
|
+
type: "monospace",
|
|
82
|
+
content,
|
|
83
|
+
width: ctx.measureText(content).width,
|
|
84
|
+
});
|
|
50
85
|
} else if (whitespace) {
|
|
51
86
|
ctx.font = `${fontSize}px Arial`;
|
|
52
|
-
segmentsInLine.push({
|
|
87
|
+
segmentsInLine.push({
|
|
88
|
+
type: "whitespace",
|
|
89
|
+
content: whitespace,
|
|
90
|
+
width: ctx.measureText(whitespace).width,
|
|
91
|
+
});
|
|
53
92
|
} else if (textContent) {
|
|
54
93
|
ctx.font = `${fontSize}px Arial`;
|
|
55
|
-
segmentsInLine.push({
|
|
94
|
+
segmentsInLine.push({
|
|
95
|
+
type: "text",
|
|
96
|
+
content: textContent,
|
|
97
|
+
width: ctx.measureText(textContent).width,
|
|
98
|
+
});
|
|
56
99
|
}
|
|
57
100
|
}
|
|
58
101
|
};
|
|
59
|
-
emojiMatches.forEach(emojiInfo => {
|
|
102
|
+
emojiMatches.forEach((emojiInfo) => {
|
|
60
103
|
const plainText = line.substring(lastIndex, emojiInfo.offset);
|
|
61
104
|
processChunk(plainText);
|
|
62
105
|
segmentsInLine.push({
|
|
63
|
-
type:
|
|
106
|
+
type: "emoji",
|
|
64
107
|
content: emojiInfo.found,
|
|
65
108
|
width: fontSize * 1.2,
|
|
66
109
|
});
|
|
@@ -72,13 +115,13 @@ function parseTextToSegments(text, ctx, fontSize) {
|
|
|
72
115
|
finalSegments.push(...segmentsInLine);
|
|
73
116
|
}
|
|
74
117
|
if (index < rawLines.length - 1) {
|
|
75
|
-
finalSegments.push({ type:
|
|
118
|
+
finalSegments.push({ type: "newline", content: "\n", width: 0 });
|
|
76
119
|
}
|
|
77
120
|
});
|
|
78
121
|
ctx.font = `${fontSize}px Arial`;
|
|
79
122
|
return finalSegments;
|
|
80
123
|
} catch (error) {
|
|
81
|
-
console.error(
|
|
124
|
+
console.error("Error in parseTextToSegments:", error);
|
|
82
125
|
return [];
|
|
83
126
|
}
|
|
84
127
|
}
|
|
@@ -90,50 +133,70 @@ function rebuildLinesFromSegments(segments, maxWidth, ctx, fontSize) {
|
|
|
90
133
|
let currentLineWidth = 0;
|
|
91
134
|
const getFontString = (type, size) => {
|
|
92
135
|
switch (type) {
|
|
93
|
-
case
|
|
94
|
-
|
|
95
|
-
case
|
|
96
|
-
|
|
97
|
-
|
|
136
|
+
case "bold":
|
|
137
|
+
return `bold ${size}px Arial`;
|
|
138
|
+
case "italic":
|
|
139
|
+
return `italic ${size}px Arial`;
|
|
140
|
+
case "bolditalic":
|
|
141
|
+
return `italic bold ${size}px Arial`;
|
|
142
|
+
case "monospace":
|
|
143
|
+
return `${size}px 'Courier New', monospace`;
|
|
144
|
+
default:
|
|
145
|
+
return `${size}px Arial`;
|
|
98
146
|
}
|
|
99
147
|
};
|
|
100
|
-
segments.forEach(segment => {
|
|
101
|
-
if (segment.type ===
|
|
148
|
+
segments.forEach((segment) => {
|
|
149
|
+
if (segment.type === "newline") {
|
|
102
150
|
lines.push(currentLine);
|
|
103
151
|
currentLine = [];
|
|
104
152
|
currentLineWidth = 0;
|
|
105
153
|
return;
|
|
106
154
|
}
|
|
107
|
-
if (segment.width > maxWidth && segment.type !==
|
|
155
|
+
if (segment.width > maxWidth && segment.type !== "emoji") {
|
|
108
156
|
if (currentLine.length > 0) {
|
|
109
157
|
lines.push(currentLine);
|
|
110
158
|
currentLine = [];
|
|
111
159
|
currentLineWidth = 0;
|
|
112
160
|
}
|
|
113
|
-
let tempStr =
|
|
161
|
+
let tempStr = "";
|
|
114
162
|
const originalFont = getFontString(segment.type, fontSize);
|
|
115
163
|
ctx.font = originalFont;
|
|
116
164
|
for (const char of segment.content) {
|
|
117
165
|
const measuredWidth = ctx.measureText(tempStr + char).width;
|
|
118
166
|
if (measuredWidth > maxWidth) {
|
|
119
|
-
lines.push([
|
|
167
|
+
lines.push([
|
|
168
|
+
{
|
|
169
|
+
type: segment.type,
|
|
170
|
+
content: tempStr,
|
|
171
|
+
width: ctx.measureText(tempStr).width,
|
|
172
|
+
},
|
|
173
|
+
]);
|
|
120
174
|
tempStr = char;
|
|
121
175
|
} else {
|
|
122
176
|
tempStr += char;
|
|
123
177
|
}
|
|
124
178
|
}
|
|
125
179
|
if (tempStr) {
|
|
126
|
-
currentLine = [
|
|
180
|
+
currentLine = [
|
|
181
|
+
{
|
|
182
|
+
type: segment.type,
|
|
183
|
+
content: tempStr,
|
|
184
|
+
width: ctx.measureText(tempStr).width,
|
|
185
|
+
},
|
|
186
|
+
];
|
|
127
187
|
currentLineWidth = currentLine[0].width;
|
|
128
188
|
}
|
|
129
189
|
return;
|
|
130
190
|
}
|
|
131
|
-
if (
|
|
191
|
+
if (
|
|
192
|
+
currentLineWidth + segment.width > maxWidth &&
|
|
193
|
+
currentLine.length > 0
|
|
194
|
+
) {
|
|
132
195
|
lines.push(currentLine);
|
|
133
196
|
currentLine = [segment];
|
|
134
197
|
currentLineWidth = segment.width;
|
|
135
198
|
} else {
|
|
136
|
-
if (segment.type ===
|
|
199
|
+
if (segment.type === "whitespace" && currentLine.length === 0) return;
|
|
137
200
|
currentLine.push(segment);
|
|
138
201
|
currentLineWidth += segment.width;
|
|
139
202
|
}
|
|
@@ -143,24 +206,24 @@ function rebuildLinesFromSegments(segments, maxWidth, ctx, fontSize) {
|
|
|
143
206
|
}
|
|
144
207
|
return lines;
|
|
145
208
|
} catch (error) {
|
|
146
|
-
console.error(
|
|
209
|
+
console.error("Error in rebuildLinesFromSegments:", error);
|
|
147
210
|
return [[]];
|
|
148
211
|
}
|
|
149
212
|
}
|
|
150
213
|
|
|
151
214
|
function getContrastColor(hexColor) {
|
|
152
|
-
if (!hexColor || typeof hexColor !==
|
|
153
|
-
const hex = hexColor.replace(
|
|
154
|
-
if (hex.length !== 6) return
|
|
215
|
+
if (!hexColor || typeof hexColor !== "string") return "#FFFFFF";
|
|
216
|
+
const hex = hexColor.replace("#", "");
|
|
217
|
+
if (hex.length !== 6) return "#FFFFFF";
|
|
155
218
|
const r = parseInt(hex.substring(0, 2), 16);
|
|
156
219
|
const g = parseInt(hex.substring(2, 4), 16);
|
|
157
220
|
const b = parseInt(hex.substring(4, 6), 16);
|
|
158
|
-
const luminance =
|
|
159
|
-
return luminance > 140 ?
|
|
160
|
-
}
|
|
221
|
+
const luminance = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
222
|
+
return luminance > 140 ? "#000000" : "#FFFFFF";
|
|
223
|
+
}
|
|
161
224
|
|
|
162
225
|
module.exports = {
|
|
163
226
|
parseTextToSegments,
|
|
164
227
|
rebuildLinesFromSegments,
|
|
165
|
-
getContrastColor
|
|
166
|
-
};
|
|
228
|
+
getContrastColor,
|
|
229
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "medias-fakerator",
|
|
3
|
-
"version": "1.0.0-dra",
|
|
3
|
+
"version": "1.0.0-dra.1",
|
|
4
4
|
"description": "AIO Faker generator by Iruka Devs",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Iruka Devs",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"@ffmpeg-installer/ffmpeg": "1.1.0",
|
|
12
12
|
"canvas": "^3.2.0",
|
|
13
13
|
"emoji-cache": "^2.0.1",
|
|
14
|
-
"emoji-db": "
|
|
14
|
+
"emoji-db": "^16.0.1",
|
|
15
15
|
"fluent-ffmpeg": "^2.1.3",
|
|
16
16
|
"fs-extra": "11.3.2",
|
|
17
17
|
"lru-cache": "^11.1.0",
|