First commit
This commit is contained in:
+162
@@ -0,0 +1,162 @@
|
||||
import { createCanvas, loadImage, registerFont } from "canvas";
|
||||
import sharp from "sharp";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
registerFont(`${import.meta.dirname}/../assets/fonts/MPLUS.ttf`, {
|
||||
family: "M PLUS Rounded 1c",
|
||||
weight: "400",
|
||||
});
|
||||
|
||||
function autoLineBreak(
|
||||
text: string,
|
||||
maxWidth: number,
|
||||
maxHeight: number,
|
||||
font: string,
|
||||
) {
|
||||
const ctx = createCanvas(maxWidth, 100).getContext("2d")!;
|
||||
ctx.font = font;
|
||||
|
||||
const lines: string[] = [];
|
||||
let currentLine = "";
|
||||
const lineHeight = parseInt(font);
|
||||
const maxLines = Math.floor(maxHeight / lineHeight);
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text[i];
|
||||
const testLine = currentLine + char;
|
||||
const width = ctx.measureText(testLine).width;
|
||||
|
||||
if (width > maxWidth) {
|
||||
if (lines.length + 1 === maxLines) {
|
||||
let trimmed = currentLine;
|
||||
while (ctx.measureText(trimmed + "...").width > maxWidth && trimmed.length > 0) {
|
||||
trimmed = trimmed.slice(0, -1);
|
||||
}
|
||||
lines.push(trimmed + "...");
|
||||
return lines.join("\n");
|
||||
} else {
|
||||
lines.push(currentLine);
|
||||
currentLine = char;
|
||||
}
|
||||
|
||||
while (ctx.measureText(currentLine).width > maxWidth && currentLine.length > 1) {
|
||||
const cutPoint = currentLine.length - 1;
|
||||
lines.push(currentLine.slice(0, cutPoint));
|
||||
currentLine = currentLine.slice(cutPoint);
|
||||
}
|
||||
|
||||
} else {
|
||||
currentLine = testLine;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLine) {
|
||||
if (lines.length < maxLines) {
|
||||
lines.push(currentLine);
|
||||
} else if (lines.length === maxLines) {
|
||||
let trimmed = currentLine;
|
||||
while (ctx.measureText(trimmed + "...").width > maxWidth && trimmed.length > 0) {
|
||||
trimmed = trimmed.slice(0, -1);
|
||||
}
|
||||
lines[maxLines - 1] = trimmed + "...";
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function textReplace(text: string, maxWidth: number, font: string, maxHeight: number) {
|
||||
text = text.replaceAll("\n", "")
|
||||
text = text.replaceAll("\r", "");
|
||||
return autoLineBreak(text, maxWidth, maxHeight, font)
|
||||
}
|
||||
|
||||
async function iconReplace(color: boolean, iconURL: string) {
|
||||
let result = "";
|
||||
const bufferReq = await fetch(iconURL, { method: "GET", cache: "no-store" });
|
||||
|
||||
if (bufferReq.status < 200 || bufferReq.status > 299) {
|
||||
return readFileSync(`${import.meta.dirname}/../assets/PersonIcon.txt`, "utf-8");
|
||||
}
|
||||
|
||||
const buffer = await bufferReq.arrayBuffer();
|
||||
|
||||
if (color) {
|
||||
const img = await sharp(Buffer.from(buffer)).resize(512, 512, { fit: "inside" }).png().toBuffer();
|
||||
result = `data:image/png;base64,${img.toString("base64")}`;
|
||||
} else {
|
||||
const img = await sharp(Buffer.from(buffer)).resize(512, 512, { fit: "inside" }).png().grayscale().toBuffer();
|
||||
result = `data:image/png;base64,${img.toString("base64")}`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export default async function MiQ(data: {
|
||||
type: "Buffer" | "Base64Data" | "Base64URL";
|
||||
color: boolean;
|
||||
text: string;
|
||||
iconURL: string;
|
||||
username: string;
|
||||
userid: string;
|
||||
}) {
|
||||
const canvas = createCanvas(1200, 630);
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
// 背景
|
||||
ctx.fillStyle = "black";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// アイコン
|
||||
const iconImg = await loadImage(await iconReplace(data.color, data.iconURL));
|
||||
const iconSize = canvas.height;
|
||||
ctx.drawImage(iconImg, 0, 0, iconSize, iconSize);
|
||||
|
||||
// ユーザー名
|
||||
ctx.font = '38px "M PLUS Rounded 1c"';
|
||||
ctx.fillStyle = "white";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
const x = 910;
|
||||
const y = 520;
|
||||
ctx.fillText(`- ${data.username}`, x, y, canvas.width - iconSize);
|
||||
|
||||
// ユーザーID
|
||||
ctx.font = '28px "M PLUS Rounded 1c"';
|
||||
ctx.fillStyle = "#b4b4b4";
|
||||
ctx.fillText(`@${data.userid}`, x, y + 50, canvas.width - iconSize);
|
||||
|
||||
// 本文
|
||||
const maxWidth = canvas.width - iconSize;
|
||||
const maxHeight = 480 - 19;
|
||||
const lineHeight = 40;
|
||||
ctx.font = `${lineHeight}px "M PLUS Rounded 1c"`;
|
||||
ctx.fillStyle = "white";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "top";
|
||||
|
||||
const wrappedText = textReplace(data.text, maxWidth, ctx.font, maxHeight);
|
||||
const lines = wrappedText.split("\n");
|
||||
lines.forEach((line, index) => {
|
||||
ctx.fillText(line, x, 40 + index * lineHeight);
|
||||
});
|
||||
|
||||
// フェード
|
||||
const fadeColor = "black";
|
||||
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
|
||||
gradient.addColorStop(0, "rgba(0, 0, 0, 0)");
|
||||
gradient.addColorStop(0.5, fadeColor);
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(0, 0, iconSize, canvas.height);
|
||||
|
||||
switch (data.type) {
|
||||
case "Buffer":
|
||||
return canvas.toBuffer();
|
||||
case "Base64Data":
|
||||
return canvas.toDataURL().replace("data:image/png;base64,", "");
|
||||
case "Base64URL":
|
||||
return canvas.toDataURL();
|
||||
default:
|
||||
return new Error("The type property is invalid.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user