import { createCanvas, loadImage, registerFont } from "canvas"; import { writeFileSync } from "fs"; import sharp from "sharp"; import { MiQOptions } from "./miq"; function maxLengthCut( text: string, maxLength: number, ) { if (text.length > maxLength) { text = text.substring(0, maxLength) + "..."; } return text; } function autoLineBreak( text: string, maxWidth: number, font: string = "48px Noto Sans JP" ): string { const ctx = createCanvas(maxWidth, 100).getContext("2d"); ctx.font = font; const lines: string[] = []; let currentLine = ""; 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) { 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) lines.push(currentLine); return lines.join("\n"); } function textReplace( text: string, maxWidth: number ) { text = text.replaceAll("\n", ""); text = maxLengthCut(text, 100); text = autoLineBreak(text, maxWidth); return text; } async function iconReplace( color: boolean, iconURL: string, ) { let result = ""; const buffer = await(await fetch(iconURL, { method: "GET", cache: "no-store", })).arrayBuffer(); if (color) { const img = await sharp(Buffer.from(buffer)) .png() .toBuffer(); result = `data:image/png;base64,${img.toString("base64")}`; } else { const img = await sharp(Buffer.from(buffer)) .png() .grayscale() .toBuffer(); result = `data:image/png;base64,${img.toString("base64")}`; } return result; } /** * A function to generate * Make it a quote on Node.js. */ export default async function MiQ({ type, color, text, iconURL, userName, userID, }: MiQOptions) { // フォント読み込み registerFont("miq/fonts/NotoSansJP.ttf", { family: "Noto Sans JP" }); // 初期化 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( color, iconURL, )); const iconSize = canvas.height; ctx.drawImage(iconImg, 0, 0, iconSize, iconSize); // ユーザー名描画 ctx.font = "38px Noto Sans JP"; ctx.fillStyle = "white"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; let x = 910; let y = 480; ctx.fillText(`- ${userName}`, x, y, canvas.width-x); // ユーザーID描画 ctx.font = "28px Noto Sans JP"; ctx.fillStyle = "#3c3c3c"; ctx.fillText(`@${userID}`, x, y+50, canvas.width-x); // 本文描画 const maxWidth = canvas.width - iconSize; text = textReplace(text, maxWidth); ctx.font = "48px Noto Sans JP"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillStyle = "white"; ctx.fillText(text, x, 80); // フェード描画 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); // 返答 if (type === "Buffer") { return canvas.toBuffer(); } else if (type === "Base64Data") { return canvas.toDataURL() .replace("data:image/png;base64,", ""); } else if (type === "Base64URL") { return canvas.toDataURL(); } else { return "Error: The type property is invalid."; } } /*(async () => { writeFileSync( "miq/a.png", await MiQ({ type: "Buffer", color: false, text: "テスト", iconURL: "https://media.uwuzu.net/uwuzu-net/files/3ov7dghkn7.webp", userName: "Last2014", userID: "last2014", }), "utf-8" ); })()*/