Files
rogerwang2008 319ba1890a feat: 新增 page3 演职员表并拆分多页资源
- 新增 page3.html:无「天」背景的演职员表与火山场景
- 重构为页面专属 CSS/JS 与共用的 text-scene 渲染引擎
- PDF 导出支持 --page 参数
2026-11-11 00:13:14 +08:00

138 lines
3.9 KiB
JavaScript
Raw Permalink 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.
/* 文字画共享渲染引擎
页面脚本调用 TextScene.render({...}) 提供数据即可,示例见 page1.js。
网格坐标:x 0–cols-1,y 0–rows-1;空白格自动填充天空字符。 */
(() => {
const BIRD_WORDS = {
sparrow: '麻雀',
magpie: '喜鹊',
crow: '乌鸦',
};
function render({
cols,
rows,
skyChar = '天',
skyClass = 'sky',
clouds = [],
sun = [],
birds = [],
antennas = [],
buildings = [],
glyphs = [],
title,
container = '.scene',
}) {
if (!cols || !rows) throw new Error('TextScene.render 需要 cols 与 rows');
// skyChar 传空字符串时不铺背景字,空格仍占满整格
const blank = skyChar
? { ch: skyChar, cls: skyClass }
: { ch: ' ', cls: 'void' };
const grid = Array.from({ length: rows }, () =>
Array.from({ length: cols }, () => ({ ...blank }))
);
const put = (x, y, ch, cls) => {
if (x < 0 || x >= cols || y < 0 || y >= rows) {
throw new Error(`元素超出网格范围:(${x}, ${y})`);
}
grid[y][x] = { ch, cls };
};
const putRow = (y, x, n, ch, cls) => {
for (let i = 0; i < n; i++) put(x + i, y, ch, cls);
};
for (const { x, y, tone } of clouds) {
const cls = `cloud cloud-${tone}`;
putRow(y, x + 1, 3, '云', cls);
putRow(y + 1, x, 5, '云', cls);
putRow(y + 2, x + 1, 3, '云', cls);
}
sun.forEach(({ y, x, w }, i) => {
const tone = `sun-t${i + 1}`;
for (let dx = 0; dx < w; dx++) put(x + dx, y, '日', `sun ${tone}`);
});
for (const { x, y, kind } of birds) {
const word = BIRD_WORDS[kind];
if (!word) throw new Error(`未知鸟种:${kind}`);
const [a, b] = [...word];
put(x, y, a, `bird-${kind}`);
put(x + 1, y, b, `bird-${kind}`);
}
for (const { x, y, h, cls = 'ant' } of antennas) {
for (let i = 0; i < h; i++) put(x, y + i, '线', cls);
}
for (const { n, x, y, w, h } of buildings) {
for (let j = 0; j < h; j++) putRow(y + j, x, w, '楼', `bld bld-${n}`);
}
for (const { text, x, y, cls = 'glyph', vertical = false } of glyphs) {
[...text].forEach((ch, i) => {
put(vertical ? x : x + i, vertical ? y + i : y, ch, cls);
});
}
if (title) {
for (let y = title.top; y < title.top + title.height; y++) {
for (let x = title.side; x < cols - title.side; x++) {
grid[y][x] = { ch: ' ', cls: 'cut' };
}
}
}
/* 把同 class 的连续字符合并成一个 span,减少 DOM 节点 */
const html = grid
.map((row) => {
let line = '';
for (let i = 0; i < cols; ) {
let j = i + 1;
while (j < cols && row[j].cls === row[i].cls) j++;
const text = row.slice(i, j).map((cell) => cell.ch).join('');
line += `<span class="${row[i].cls}">${text}</span>`;
i = j;
}
return `<div class="line">${line}</div>`;
})
.join('');
const scene = document.querySelector(container);
if (!scene) throw new Error(`未找到容器:${container}`);
scene.innerHTML = html;
if (!title) return;
const panel = document.createElement('div');
panel.className = 'title-panel';
panel.style.top = `${title.top}em`;
panel.style.left = `${title.side}em`;
panel.style.width = `${cols - title.side * 2}em`;
panel.style.height = `${title.height}em`;
const titleEl = document.createElement('h1');
titleEl.className = 'title';
const { text, emphasis } = title;
if (emphasis && text.includes(emphasis)) {
const [before, after] = text.split(emphasis);
const em = document.createElement('span');
em.className = 'title-em';
em.textContent = emphasis;
titleEl.append(before, em, after);
} else {
titleEl.textContent = text;
}
panel.append(titleEl);
scene.append(panel);
}
window.TextScene = { render };
})();