Files
pixelheros/docs/软著/gen_docx.mjs
panFD 2dc44b2393 feat(软著): 新增软著登记全套文档与资源整理
1. 新增软著登记所需的源程序生成脚本、docx生成脚本与依赖配置
2. 新增像素大勇者游戏软件V1.0设计说明书与填表指引文档
3. 更新技能配置相关的prefab与meta文件,清理冗余的up状态预制件
4. 修复部分技能buff的颜色与精灵帧引用,更新动画资源引用
5. 新增两个技能图标资源文件
2026-09-14 20:46:31 +08:00

234 lines
8.0 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 软著材料 docx 生成脚本
* 1) 源程序:解析已生成的 txt,按显示宽度折行(每行 ≤84 列),每页 60 行,
* 页眉左侧"软件全称 版本号"、右侧 Word 自动页码域"第 X 页 共 Y 页"。
* 2) 设计说明书:解析 md,生成封面 + 章节正文 docx。
* 用法:node gen_docx.mjs
*/
import fs from 'fs';
import path from 'path';
import {
Document, Packer, Paragraph, TextRun, Header, AlignmentType,
PageNumber, TabStopType, HeadingLevel,
} from 'docx';
const DIR = import.meta.dirname;
const SRC_TXT = path.join(DIR, '像素大勇者游戏软件_V1.0_源程序.txt');
const MANUAL_MD = path.join(DIR, '像素大勇者游戏软件_V1.0_设计说明书.md');
const OUT_SRC = path.join(DIR, '像素大勇者游戏软件_V1.0_源程序.docx');
const OUT_MANUAL = path.join(DIR, '像素大勇者游戏软件_V1.0_设计说明书.docx');
const SOFTWARE = '像素大勇者游戏软件 V1.0';
const LINES_PER_PAGE = 60;
const FOLD_WIDTH = 84; // A4 折行限制:显示列宽(ASCII=1 列,中文=2 列)
// A4 纵向,边距:上下 1.27cm,左右 1.5cm
const PAGE = {
size: { width: 11906, height: 16838 },
margin: { top: 720, bottom: 720, left: 850, right: 850, header: 400 },
};
const TEXT_WIDTH = PAGE.size.width - PAGE.margin.left - PAGE.margin.right;
/** 统一页眉:左侧软件名+版本号,右侧自动页码域 */
function makeHeader() {
return new Header({
children: [
new Paragraph({
tabStops: [{ type: TabStopType.RIGHT, position: TEXT_WIDTH }],
children: [
new TextRun({ text: SOFTWARE, size: 18, font: { ascii: 'Consolas', eastAsia: 'SimSun' } }),
new TextRun({
children: ['\t第 ', PageNumber.CURRENT, ' 页 共 ', PageNumber.TOTAL_PAGES, ' 页'],
size: 18, font: { ascii: 'Consolas', eastAsia: 'SimSun' },
}),
],
}),
],
});
}
// ============================================================
// 一、源程序 docx
// ============================================================
/** 字符显示宽度(ASCII 记 1 列,全角/中文记 2 列) */
const charWidth = (ch) => (ch.charCodeAt(0) > 0xff ? 2 : 1);
/** 超宽行按显示列宽折行,续行缩进 4 空格 */
function foldLine(line, width = FOLD_WIDTH) {
if ([...line].reduce((w, c) => w + charWidth(c), 0) <= width) return [line];
const out = [];
let cur = '', curW = 0;
for (const ch of line) {
const cw = charWidth(ch);
if (curW + cw > width) {
out.push(cur);
cur = ' ' + ch; // 续行缩进
curW = 4 + cw;
} else {
cur += ch;
curW += cw;
}
}
if (cur) out.push(cur);
return out;
}
/** 解析 txt:剔除页眉/分隔线/页间空行,还原连续代码行 */
function parseSourceTxt() {
const raw = fs.readFileSync(SRC_TXT, 'utf8').split('\n');
const codeLines = [];
let count = 0;
for (const line of raw) {
if (line.startsWith('像素大勇者游戏软件')) continue; // 页眉行
if (/^=+$/.test(line.trim()) && line.trim() !== '') continue; // 分隔线
if (count > 0 && count % LINES_PER_PAGE === 0 && line.trim() === '') continue; // 页间空行
codeLines.push(line);
count++;
}
return codeLines;
}
async function buildSourceDocx() {
const codeLines = parseSourceTxt();
// 折行后按每页 50 个显示行分页
const folded = codeLines.flatMap((l) => foldLine(l.replace(/\t/g, ' ')));
const totalPages = Math.ceil(folded.length / LINES_PER_PAGE);
const foldedPages = Math.ceil(codeLines.length / LINES_PER_PAGE); // 折行前理论页数(对照 txt)
const children = folded.map((line, i) =>
new Paragraph({
pageBreakBefore: i > 0 && i % LINES_PER_PAGE === 0,
spacing: { after: 0, line: 230, lineRule: 'auto' },
children: [
new TextRun({
text: line,
size: 19, // 9.5pt
font: { ascii: 'Consolas', eastAsia: 'SimSun' },
}),
],
})
);
const doc = new Document({
sections: [{
properties: { page: PAGE },
headers: { default: makeHeader() },
children,
}],
});
await Packer.toBuffer(doc).then((buf) => fs.writeFileSync(OUT_SRC, buf));
console.log('=== 源程序 docx 生成完成 ===');
console.log(`代码行: ${codeLines.length},折行后显示行: ${folded.length}`);
console.log(`折行前页数(对照txt): ${foldedPages},docx 实际页数: ${totalPages}`);
console.log(`输出: ${OUT_SRC}`);
}
// ============================================================
// 二、设计说明书 docx
// ============================================================
const BODY_FONT = { ascii: 'Times New Roman', eastAsia: 'SimSun' };
const HEAD_FONT = { ascii: 'Arial', eastAsia: 'SimHei' };
/** 解析 **粗体** 行内标记 */
function parseRuns(text, base = {}) {
return text
.split(/(\*\*[^*]+\*\*)/g)
.filter(Boolean)
.map((p) =>
p.startsWith('**')
? new TextRun({ text: p.slice(2, -2), bold: true, font: BODY_FONT, ...base })
: new TextRun({ text: p, font: BODY_FONT, ...base })
);
}
async function buildManualDocx() {
const md = fs.readFileSync(MANUAL_MD, 'utf8').split('\n');
const children = [];
let coverDone = false;
for (const rawLine of md) {
const line = rawLine.trimEnd();
if (!coverDone) {
// 封面区:软件名 / 文档名 / 著作权人 / 日期,居中排布,遇 --- 结束封面
if (line.startsWith('# ')) {
children.push(new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { before: 240, after: 240 },
children: [new TextRun({ text: line.slice(2), bold: true, size: 52, font: HEAD_FONT })],
}));
} else if (/^(著作权人|开发完成日期):/.test(line)) {
children.push(new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { before: 120, after: 120 },
children: [new TextRun({ text: line, size: 24, font: BODY_FONT })],
}));
} else if (line.trim() === '---') {
coverDone = true;
// 封面独立成页
children.push(new Paragraph({ children: [new TextRun({ text: '', size: 2 })], pageBreakBefore: false }));
children.push(new Paragraph({ pageBreakBefore: true, children: [] }));
}
continue;
}
// 正文区
if (line.trim() === '' || line.trim() === '---') continue;
if (line.startsWith('## ')) {
children.push(new Paragraph({
heading: HeadingLevel.HEADING_1,
spacing: { before: 240, after: 120 },
children: [new TextRun({ text: line.slice(3), bold: true, size: 30, font: HEAD_FONT, color: '000000' })],
}));
} else if (line.startsWith('### ')) {
children.push(new Paragraph({
heading: HeadingLevel.HEADING_2,
spacing: { before: 180, after: 100 },
children: [new TextRun({ text: line.slice(4), bold: true, size: 26, font: HEAD_FONT, color: '000000' })],
}));
} else if (line.startsWith('【建议插入截图')) {
children.push(new Paragraph({
spacing: { before: 60, after: 60 },
indent: { left: 420 },
children: [new TextRun({ text: line, italics: true, color: '808080', size: 21, font: BODY_FONT })],
}));
} else if (/^\d+\.\s/.test(line)) {
children.push(new Paragraph({
spacing: { after: 100 },
indent: { left: 420 },
children: parseRuns(line, { size: 21 }),
}));
} else {
children.push(new Paragraph({
spacing: { after: 100, line: 300 },
children: parseRuns(line, { size: 21 }),
}));
}
}
const doc = new Document({
styles: {
default: {
document: { run: { font: BODY_FONT, size: 21 } },
},
},
sections: [{
properties: { page: PAGE },
headers: { default: makeHeader() },
children,
}],
});
await Packer.toBuffer(doc).then((buf) => fs.writeFileSync(OUT_MANUAL, buf));
console.log('=== 设计说明书 docx 生成完成 ===');
console.log(`输出: ${OUT_MANUAL}`);
}
await buildSourceDocx();
await buildManualDocx();