96 lines
2.8 KiB
JavaScript
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;
|
|
});
|