feat: 图片自动下载 + 重叠去重 bug 修复

- IMG 标签自动从 src URL 下载图片并嵌入 PPT
- URL 拼接用 origin 而非完整 pageUrl(修复 404)
- collectSlideChildren 补全 backgroundImages 字段传递
- 重叠去重 key 加 type,避免不同类型节点被误删
- 三个 PPT 都验证通过
This commit is contained in:
李进
2026-07-24 18:25:01 +08:00
parent f3853e31d4
commit 8ab3f479a7
+59 -5
View File
@@ -261,7 +261,7 @@ function collectObjects(nodeId, ctx, processed) {
* @param {Object} input web-to-pixso 格式的 JSON 对象
* @returns {Object} html2pptx Schema JSON 对象
*/
function convert(input) {
async function convert(input) {
if (!input || !input.nodes) {
throw new Error('无效输入:缺少 "nodes" 字段');
}
@@ -272,6 +272,57 @@ function convert(input) {
}
const canvas = input.canvas || {};
// ===== 图片下载:RECTANGLE+IMG 标签且无 backgroundImages 时,从 src URL 下载 =====
const pageUrl = input.source?.url || '';
const https = require('https');
const http = require('http');
function downloadImage(url) {
return new Promise(function(resolve) {
try {
var mod = url.startsWith('https') ? https : http;
mod.get(url, { timeout: 5000 }, function(res) {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
downloadImage(res.headers.location).then(resolve);
return;
}
var chunks = [];
res.on('data', function(c) { chunks.push(c); });
res.on('end', function() {
var buf = Buffer.concat(chunks);
var b64 = 'data:image/png;base64,' + buf.toString('base64');
resolve(b64);
});
res.on('error', function() { resolve(null); });
}).on('error', function() { resolve(null); });
} catch (e) { resolve(null); }
});
}
async function fetchNodeImages(node) {
if (!node) return;
if (node.tag === 'IMG' && node.attributes?.src && (!node.backgroundImages || node.backgroundImages.length === 0)) {
var src = node.attributes.src;
// 拼接完整 URL:相对路径基于 origin,绝对路径基于 origin
var origin = pageUrl.replace(/^(https?:\/\/[^\/]+).*/, '$1');
if (src.startsWith('/')) src = origin + src;
else if (!src.startsWith('http')) src = pageUrl.replace(/\/[^\/]*$/, '/') + src;
console.log('📥 下载图片: ' + src);
var data = await downloadImage(src);
if (data) {
if (!input.assets) input.assets = {};
var key = 'img-' + (node.id || Math.random().toString(36).slice(2));
input.assets[key] = { data: data };
node.backgroundImages = [key];
node.type = 'IMAGE'; // 标记为 IMAGE 类型
}
}
if (node.children) for (var c of node.children) await fetchNodeImages(c);
}
// 异步下载所有图片
await fetchNodeImages(inputRoot);
// 如果没有 canvas 信息则默认 1920×108016:9 常见尺寸)
const canvasWidth = canvas.width || 1920;
const canvasHeight = canvas.height || 1080;
@@ -293,6 +344,7 @@ function convert(input) {
rect: { x: nr.x - slideX, y: nr.y - slideY, w: rw, h: rh },
styles: node.styles || {}, src: node.src || node.text || '',
layerGroup: node.layerGroup || '',
backgroundImages: node.backgroundImages,
parentW: parentW || rw,
_depth: depth
});
@@ -578,15 +630,15 @@ function convert(input) {
return za - zb;
});
// ===== 同坐标完全重叠去重:保留后者(高层) =====
// ===== 同坐标完全重叠去重:保留后者(高层),仅同类型 =====
var seen = {};
var overlapRemove = new Set();
for (var i = sg.children.length - 1; i >= 0; i--) {
var ci = sg.children[i];
if (!ci.rect) continue;
var key = Math.round(ci.rect.x) + ',' + Math.round(ci.rect.y) + ',' + Math.round(ci.rect.w) + ',' + Math.round(ci.rect.h);
var key = ci.type + ',' + Math.round(ci.rect.x) + ',' + Math.round(ci.rect.y) + ',' + Math.round(ci.rect.w) + ',' + Math.round(ci.rect.h);
if (seen[key]) {
overlapRemove.add(ci.id); // 后出现的(更高层)保留,前面的移除
overlapRemove.add(ci.id);
} else {
seen[key] = true;
}
@@ -881,6 +933,7 @@ function convert(input) {
// ============================================================
if (require.main === module) {
(async function() {
const args = process.argv.slice(2);
if (args.length < 1) {
@@ -912,7 +965,7 @@ if (require.main === module) {
// 执行转换并写入
try {
const result = convert(inputJson);
const result = await convert(inputJson);
console.log('result slides:', result.slides.length, 'first objects:', result.slides[0]?.objects?.length);
// 渲染 PPTX(支持自定义布局)
@@ -975,6 +1028,7 @@ if (require.main === module) {
console.error('❌ 转换失败:', err.message);
process.exit(1);
}
})();
}
module.exports = { convert };