fix: traverse all nodes, use slide container size for canvas, extract slide bg color

This commit is contained in:
李进
2026-07-24 10:07:00 +08:00
parent eec8c8c215
commit 46ecb17956
2 changed files with 365 additions and 47 deletions
+136
View File
@@ -0,0 +1,136 @@
/**
* web-to-pixso JSON → 翻译引擎 Schema 转换器
*
* 把 web-to-pixso Chrome 扩展导出的 JSON 转成 renderer/index.js 能吃的标准格式
*/
const fs = require('fs');
function convert(inputPath, outputPath) {
const raw = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
const nodes = raw.nodes;
const canvas = raw.canvas || {};
// 画布尺寸
const canvasW = canvas.width || 1920;
const canvasH = canvas.height || 1080;
// 收集所有页面(node.children 包含多页时按页面分组)
const pages = [];
function extractPage(node, parentY) {
if (!node || !node.rect) return null;
const rect = node.rect;
const styles = node.styles || {};
const layout = node.layout || {};
const children = node.children || [];
// 跳过不可见节点
if (styles.display === 'none' || styles.visibility === 'hidden') return null;
const elements = [];
// 如果是文字节点
if (node.type === 'text' && node.src) {
elements.push({
type: 'text',
text: node.src,
options: {
x: rect.x / 96,
y: (rect.y - parentY) / 96,
w: rect.w / 96,
h: rect.h / 96,
fontSize: styles.fontSize || 14,
fontFace: (styles.fontFamily || 'Arial').split(',')[0].replace(/['"]/g, '').trim(),
color: (styles.color || '#000000').replace('#', ''),
bold: styles.fontWeight >= 700 || styles.fontWeight === 'bold',
italic: styles.fontStyle === 'italic',
align: styles.textAlign || 'left'
}
});
}
// 如果有背景色或背景图,生成 shape
if (styles.backgroundColor && styles.backgroundColor !== 'rgba(0,0,0,0)' && styles.backgroundColor !== 'transparent') {
elements.push({
type: 'shape',
shapeName: 'rect',
options: {
x: rect.x / 96,
y: (rect.y - parentY) / 96,
w: rect.w / 96,
h: rect.h / 96,
fill: { color: styles.backgroundColor.replace('#', '') }
}
});
}
// 处理子节点
for (const childId of children) {
const childNode = nodes[childId];
if (childNode) {
const childElements = extractPage(childNode, parentY || rect.y);
if (childElements) elements.push(...childElements);
}
}
return elements;
}
// 找顶层容器
const root = nodes.children ? findRoot(nodes) : null;
if (root) {
const pageElements = extractPage(root, 0);
if (pageElements && pageElements.length > 0) {
pages.push({ objects: pageElements });
}
}
// 如果没有分页,把整个 canvas 当一页
if (pages.length === 0) {
const allElements = [];
for (const [id, node] of Object.entries(nodes)) {
if (node.rect && node.type) {
const elems = extractPage(node, 0);
if (elems) allElements.push(...elems);
}
}
pages.push({ objects: allElements });
}
// 输出 Schema
const spec = {
presentation: { layout: 'LAYOUT_16x9' },
slides: pages
};
fs.writeFileSync(outputPath, JSON.stringify(spec, null, 2));
console.log(`Converted: ${inputPath} → ${outputPath}`);
console.log(`Pages: ${pages.length}, Total objects: ${pages.reduce((s,p) => s + p.objects.length, 0)}`);
}
function findRoot(nodes) {
// 找没有 parent 引用的节点作为根
const allIds = new Set(Object.keys(nodes));
const childIds = new Set();
for (const [id, node] of Object.entries(nodes)) {
if (node.children) {
for (const cid of node.children) childIds.add(cid);
}
}
for (const id of allIds) {
if (!childIds.has(id)) return nodes[id];
}
return null;
}
// CLI
const args = process.argv.slice(2);
if (args.length < 1) {
console.error('Usage: node convert-w2p.js <input.json> [output.json]');
process.exit(1);
}
const inputPath = args[0];
const outputPath = args[1] || inputPath.replace('.json', '_schema.json');
convert(inputPath, outputPath);