Files
2026-09-13 15:40:54 +05:30

96 lines
2.8 KiB
JavaScript

const { createCanvas, loadImage, GlobalFonts } = require("@napi-rs/canvas");
const { writeFile } = require("node:fs/promises");
const { parentPort, workerData } = require("node:worker_threads");
function clamp(value, min, max) {
return Math.min(max, Math.max(min, value));
}
function hexToRgba(hex, opacity) {
let value = hex.replace("#", "");
if (value.length === 3) {
value = value
.split("")
.map((char) => char + char)
.join("");
}
let alpha = clamp(opacity, 0, 1);
if (value.length === 8) {
alpha *= parseInt(value.slice(6, 8), 16) / 255;
value = value.slice(0, 6);
}
const r = parseInt(value.slice(0, 2), 16);
const g = parseInt(value.slice(2, 4), 16);
const b = parseInt(value.slice(4, 6), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
function buildCanvasFont(style) {
const italic = style.italic ? "italic " : "";
const weight = style.bold ? "700 " : "400 ";
return `${italic}${weight}${style.fontSize}px "${style.fontFamily}"`;
}
function drawIdText(ctx, text, imageWidth, imageHeight, style) {
const x = (style.xPercent / 100) * imageWidth;
const y = (style.yPercent / 100) * imageHeight;
ctx.font = buildCanvasFont(style);
ctx.textAlign = style.align;
ctx.textBaseline = "alphabetic";
const metrics = ctx.measureText(text);
const ascent = metrics.actualBoundingBoxAscent ?? style.fontSize * 0.8;
const descent = metrics.actualBoundingBoxDescent ?? style.fontSize * 0.2;
const baselineY = y + (ascent - descent) / 2;
ctx.save();
ctx.fillStyle = style.color;
if (style.shadowEnabled) {
ctx.shadowColor = hexToRgba(style.shadowColor, style.shadowOpacity);
ctx.shadowBlur = style.shadowBlur;
ctx.shadowOffsetX = style.shadowOffsetX;
ctx.shadowOffsetY = style.shadowOffsetY;
} else {
ctx.shadowColor = "transparent";
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
}
if (style.outlineEnabled && style.outlineWidth > 0) {
ctx.strokeStyle = hexToRgba(style.outlineColor, style.outlineOpacity);
ctx.lineWidth = style.outlineWidth;
ctx.lineJoin = "round";
ctx.miterLimit = 2;
ctx.strokeText(text, x, baselineY);
ctx.shadowColor = "transparent";
}
ctx.fillText(text, x, baselineY);
ctx.restore();
}
async function run() {
const { imagePath, fonts, style, tasks } = workerData;
for (const font of fonts) {
GlobalFonts.registerFromPath(font.path, font.family);
}
const image = await loadImage(imagePath);
const canvas = createCanvas(image.width, image.height);
const ctx = canvas.getContext("2d");
for (const task of tasks) {
ctx.drawImage(image, 0, 0);
drawIdText(ctx, task.text, image.width, image.height, style);
await writeFile(task.outPath, await canvas.encode("png"));
parentPort?.postMessage({ type: "progress" });
}
}
run().catch((error) => {
throw error;
});