koishi-plugin-p5-advance-letter-generator
Version:
434 lines (373 loc) • 17.2 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name2 in all)
__defProp(target, name2, { get: all[name2], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
Config: () => Config,
apply: () => apply,
inject: () => inject,
name: () => name,
usage: () => usage
});
module.exports = __toCommonJS(src_exports);
var import_koishi = require("koishi");
var path = __toESM(require("path"));
var inject = {
required: ["canvas", "puppeteer"]
};
var name = "p5-advance-letter-generator";
var usage = `## 使用
1. 启动 \`puppeteer\` 服务。️
2. 设置指令别名。
## 特性
- 文本参数是必需的,可用 \`/\` 换行。
- 可用以下选项调整生成:
- \`-w [canvasWidth:number]\`:画布宽度,默认为 1770 像素。
- \`--height [canvasHeight:number]\`:画布高度,默认为 1300 像素。
- 示例:
- 生成一张 1920px * 1080px 的 p5 预告信图片:
\`\`\`
p5advanceLetter.generateAdvanceLetter -w 1920 --height 1080 尊敬的金城润矢先生:/扭曲事实沉溺于金钱利益之人 ,/您的种种恶行,我等已全然知晓/那个扭曲的欲望/就由我等来收下!/心之怪盗团-Joker敬上
\`\`\`
## QQ 群
- 956758505`;
var Config = import_koishi.Schema.object({
imageType: import_koishi.Schema.union(["png", "jpeg", "webp"]).default("png").description(`生成的图片类型。`)
});
function apply(ctx, config) {
const filePath = path.join(__dirname, "emptyHtml.html").replace(/\\/g, "/");
const pageGotoFilePath = "file://" + filePath;
ctx.command("p5advanceLetter", "查看p5预告信生成帮助").action(async ({ session }) => {
await session.execute(`p5advanceLetter -h`);
});
ctx.command("p5advanceLetter.生成预告信 [text:text]", "生成p5预告信").option("canvasWidth", "-w [canvasWidth:number] 画布宽度", { fallback: 1770 }).option("canvasHeight", "--height [canvasHeight:number] 画布高度", { fallback: 1300 }).action(async ({ session, options }, text) => {
if (!text) {
await session.send("请给我要生成的语句呀,不然佐仓双叶来了都没办法生成!");
return;
}
const { canvasWidth, canvasHeight } = options;
text = text.replace(/\/+/g, "\\n").replace(/\n/g, "\\n");
const result = await generateAdvanceLetterImage(text, canvasWidth, canvasHeight);
await session.send(import_koishi.h.image(result, `image/${config.imageType}`));
});
ctx.command("p5advanceLetter.生成UI [text:text]", "生成p5UI").option("canvasWidth", "-w [canvasWidth:number] 画布宽度", { fallback: 1770 }).option("canvasHeight", "--height [canvasHeight:number] 画布高度", { fallback: 1300 }).action(async ({ session, options }, text) => {
if (!text) {
await session.send("请给我要生成的语句呀,不然佐仓双叶来了都没办法生成!");
return;
}
const { canvasWidth, canvasHeight } = options;
text = text.replace(/\/+/g, "\\n").replace(/\n/g, "\\n");
const result = await generateUIImage(text, canvasWidth, canvasHeight);
await session.send(import_koishi.h.image(result, `image/${config.imageType}`));
});
async function generateAdvanceLetterImage(text, canvasWidth, canvasHeight) {
const html = `<html>
<html lang="zh">
<head>
<title>generateAdvanceLetterImage</title>
<meta charSet="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
@font-face {
font-family: '微软雅黑';
src: local('微软雅黑'), url('./assets/fonts/msyh.woff2') format('truetype');
}
@font-face {
font-family: '微软雅黑 Bold';
src: local('微软雅黑 Bold'), url('./assets/fonts/msyhbd.ttc') format('truetype');
}
@font-face {
font-family: '黑体';
src: local('黑体'), url('./assets/fonts/simhei.ttf') format('truetype');
}
@font-face {
font-family: '新宋体';
src: local('新宋体'), url('./assets/fonts/simsun.ttc') format('truetype');
}
@font-face {
font-family: '华文琥珀';
src: local('华文琥珀'), url('./assets/fonts/STHUPO.TTF') format('truetype');
}
body {
font-family: '微软雅黑', '微软雅黑 Bold', '黑体', '新宋体', '华文琥珀';
}
canvas {
border: 1px solid black;
font-family: '微软雅黑', '微软雅黑 Bold', '黑体', '新宋体', '华文琥珀';
}
</style>
</head>
<body>
<h1 style="font-family: '微软雅黑'; font-weight: normal; font-style: normal;">微软雅黑</h1>
<h1 style="font-family: '微软雅黑 Bold'; font-weight: normal; font-style: normal;">微软雅黑 Bold</h1>
<h1 style="font-family: '黑体'; font-weight: normal; font-style: normal;">黑体</h1>
<h1 style="font-family: '新宋体'; font-weight: normal; font-style: normal;">新宋体</h1>
<h1 style="font-family: '华文琥珀'; font-weight: normal; font-style: normal;">华文琥珀</h1>
<canvas id="myCanvas" width="${canvasWidth}" height="${canvasHeight}"></canvas>
<script>
window.onload = () => {
const fonts = ['微软雅黑', '微软雅黑 Bold', '黑体', '新宋体', '华文琥珀'];
const colors = ['white', 'black', 'gray', 'red'];
const bgColors = {
white: ['black', 'gray'],
black: ['white', 'gray'],
gray: ['white', 'black'],
red: ['white'],
};
const canvas = document.getElementById('myCanvas');
canvas.width = ${canvasWidth};
canvas.height = ${canvasHeight};
const context = canvas.getContext('2d');
const text = '${text}';
const img = new Image();
img.src = './assets/background.png';
img.onload = () => {
context.drawImage(img, 0, 0, ${canvasWidth}, ${canvasHeight});
const sentences = text.split('\\n');
let textHeight = 0;
let maxCharHeight = 0;
const sentencesCount = sentences.length;
for (const sentence of sentences) { // for 0
const trimmedSentence = sentence.trim();
const textFont = randomChoice(fonts);
const textSize = Math.min(1770 / trimmedSentence.length, 1300 / (sentencesCount + 6));
context.font = \`\${textSize}px "\${textFont}"\`;
const metrics = context.measureText(trimmedSentence);
const charHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;
maxCharHeight = Math.max(maxCharHeight, charHeight);
textHeight += maxCharHeight + 20;
} // for 0
let y = (${canvasHeight} - textHeight) / 2;
for (const sentence of sentences) { // for 1
const trimmedSentence = sentence.trim();
const textSize = Math.min(1770 / (trimmedSentence.length + 6), 1300 / (sentencesCount + 6));
const textFont = randomChoice(fonts);
context.font = \`\${textSize}px "\${textFont}"\`;
const metrics = context.measureText(trimmedSentence);
const sentenceWidth = metrics.width;
let x = (${canvasWidth} - sentenceWidth) / 2 - 170;
for (let i = 0; i < trimmedSentence.length; i++) { // for 2
const char = trimmedSentence[i];
const charFont = randomChoice(fonts);
// 模拟随机字符大小
const randomArray = new Uint8Array(1);
crypto.getRandomValues(randomArray);
const charSize = Math.floor(textSize + (randomArray[0] / 255) * 20);
context.font = \`\${charSize}px "\${textFont}"\`;
const color = randomChoice(colors);
const bgColor = randomChoice(bgColors[color]);
const charMetrics = context.measureText(char);
const charWidth = charMetrics.width;
const charHeight = charMetrics.fontBoundingBoxAscent + charMetrics.fontBoundingBoxDescent;
const centerX = x + charWidth / 2;
const centerY = y + charHeight / 2;
const randomAngle = (Math.random() * 10) - 5;
const angle = randomAngle * Math.PI / 180;
context.translate(centerX, centerY);
context.rotate(angle);
context.translate(-centerX, -centerY);
context.fillStyle = bgColor;
context.fillRect(x, y, charWidth, charHeight + 10);
context.fillStyle = color;
context.fillText(char, x, y + charMetrics.fontBoundingBoxAscent);
x += charWidth + 20;
context.resetTransform();
} // for 2
y += maxCharHeight + 20;
} // for 1
} // img.onload
} // window.onload
function randomChoice(array) {
const randomIndex = Math.floor(Math.random() * array.length);
return array[randomIndex];
}
</script>
</body>
</html>
`;
const browser = ctx.puppeteer.browser;
const context = await browser.createBrowserContext();
const page = await context.newPage();
await page.setViewport({ width: canvasWidth, height: canvasHeight });
await page.goto(pageGotoFilePath);
await page.setContent(import_koishi.h.unescape(html), { waitUntil: "load" });
const canvas = await page.$("canvas#myCanvas");
const buffer = await canvas.screenshot({ type: config.imageType });
await page.close();
await context.close();
return buffer;
}
__name(generateAdvanceLetterImage, "generateAdvanceLetterImage");
async function generateUIImage(text, canvasWidth, canvasHeight) {
const html = `<html>
<html lang="zh">
<head>
<title>generateUIImage</title>
<style>
@font-face {
font-family: '微软雅黑 Bold';
src: local('微软雅黑 Bold'), url('./assets/fonts/msyhbd.ttc') format('truetype');
}
body {
font-family: '微软雅黑 Bold';
}
canvas {
border: 1px solid black;
font-family: '微软雅黑 Bold';
}
</style>
</head>
<body>
<h1 style="font-family: '微软雅黑 Bold'; font-weight: normal; font-style: normal;">微软雅黑 Bold</h1>
<canvas id="myCanvas" width="${canvasWidth}" height="${canvasHeight}"></canvas>
<script>
function randomChoice(array) {
const randomIndex = Math.floor(Math.random() * array.length);
return array[randomIndex];
}
const text = '${text}';
// 设置字体列表
const fonts = ['微软雅黑 Bold'];
// 设置颜色列表
const colors = ['white', 'red'];
// 设置背景颜色列表
const bgColors = {white: ['black', 'red'], red: ['black']};
const canvas = document.getElementById('myCanvas');
canvas.width = ${canvasWidth};
canvas.height = ${canvasHeight};
const context = canvas.getContext('2d');
const img = new Image();
img.src = './assets/background.png';
img.onload = function () {
context.drawImage(img, 0, 0, ${canvasWidth}, ${canvasHeight});
// 分割文本为多个句子
const sentences = text.split('\\n');
// 计算文本总高度和最大字符高度
let textHeight = 0;
let maxCharHeight = 0;
const sentencesCount = sentences.length;
for (const sentence of sentences) {
const trimmedSentence = sentence.trim();
const textSize = Math.min(1770 / trimmedSentence.length, 1300 / (sentencesCount + 6));
context.font = \`\${textSize}px "\${textSize}"\`;
const metrics = context.measureText(trimmedSentence);
const charHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent + 20;
maxCharHeight = Math.max(maxCharHeight, charHeight);
textHeight += maxCharHeight + 20;
}
// 计算文本起始位置
let y = (${canvasHeight} - textHeight) / 2;
// 遍历每个句子
for (const sentence of sentences) {
// 计算句子宽度
const trimmedSentence = sentence.trim();
const textSize = Math.min(1770 / (trimmedSentence.length + 6), 1300 / (sentencesCount + 6));
const textFont = randomChoice(fonts);
context.font = \`\${textSize}px "\${textFont}"\`;
const metrics = context.measureText(trimmedSentence);
const sentenceWidth = metrics.width;
// 计算句子起始位置
let x = (${canvasWidth} - sentenceWidth) / 2 - 170;
// 遍历句子中的每个字符
for (let i = 0; i < trimmedSentence.length; i++) {
const char = trimmedSentence[i];
// 随机选择字体和大小
const charFont = randomChoice(fonts);
// 模拟随机字符大小
const randomArray = new Uint8Array(1);
crypto.getRandomValues(randomArray);
const charSize = Math.floor(textSize + (randomArray[0] / 255) * 20);
context.font = \`\${charSize}px "\${charFont}"\`;
// 随机选择颜色
const color = randomChoice(colors);
// 根据颜色选择背景颜色
const bgColor = randomChoice(bgColors[color]);
// 获取字符大小
const charMetrics = context.measureText(char);
// console.log(charMetrics)
const charWidth = charMetrics.width;
const charHeight = charMetrics.fontBoundingBoxAscent + charMetrics.fontBoundingBoxDescent;
// 计算字符中心点坐标
const centerX = x + charWidth / 2;
const centerY = y + charHeight / 2;
// 随机旋转角度
const randomAngle = (Math.random() * 10) - 5;
const angle = randomAngle * Math.PI / 180;
// 移动画布原点到字符中心点
context.translate(centerX, centerY);
// 进行旋转
context.rotate(angle);
// 移动画布原点回初始位置
context.translate(-centerX, -centerY);
// 绘制背景色块
context.fillStyle = bgColor;
context.fillRect(x, y, charWidth + 7, charHeight + 7);
// 绘制白色边框
context.strokeStyle = 'white';
context.lineWidth = 7;
context.strokeRect(x, y, charWidth, charHeight);
// 绘制字符
context.fillStyle = color;
context.fillText(char, x + 7, y + 7 + charMetrics.fontBoundingBoxAscent);
// 更新x坐标
x += charWidth + 20;
// 恢复旋转
context.resetTransform();
}
// 更新y坐标以换行
y += maxCharHeight + 20;
}
};
</script>
</body>
</html>
`;
const browser = ctx.puppeteer.browser;
const context = await browser.createBrowserContext();
const page = await context.newPage();
await page.setViewport({ width: canvasWidth, height: canvasHeight });
await page.goto(pageGotoFilePath);
await page.setContent(import_koishi.h.unescape(html), { waitUntil: "load" });
const canvas = await page.$("canvas#myCanvas");
const buffer = await canvas.screenshot({ type: config.imageType });
await page.close();
await context.close();
return buffer;
}
__name(generateUIImage, "generateUIImage");
}
__name(apply, "apply");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Config,
apply,
inject,
name,
usage
});