feat: 图片自动下载 + 重叠去重 bug 修复
- IMG 标签自动从 src URL 下载图片并嵌入 PPT - URL 拼接用 origin 而非完整 pageUrl(修复 404) - collectSlideChildren 补全 backgroundImages 字段传递 - 重叠去重 key 加 type,避免不同类型节点被误删 - 三个 PPT 都验证通过
This commit is contained in:
+59
-5
@@ -261,7 +261,7 @@ function collectObjects(nodeId, ctx, processed) {
|
|||||||
* @param {Object} input web-to-pixso 格式的 JSON 对象
|
* @param {Object} input web-to-pixso 格式的 JSON 对象
|
||||||
* @returns {Object} html2pptx Schema JSON 对象
|
* @returns {Object} html2pptx Schema JSON 对象
|
||||||
*/
|
*/
|
||||||
function convert(input) {
|
async function convert(input) {
|
||||||
if (!input || !input.nodes) {
|
if (!input || !input.nodes) {
|
||||||
throw new Error('无效输入:缺少 "nodes" 字段');
|
throw new Error('无效输入:缺少 "nodes" 字段');
|
||||||
}
|
}
|
||||||
@@ -272,6 +272,57 @@ function convert(input) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const canvas = input.canvas || {};
|
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×1080(16:9 常见尺寸)
|
// 如果没有 canvas 信息则默认 1920×1080(16:9 常见尺寸)
|
||||||
const canvasWidth = canvas.width || 1920;
|
const canvasWidth = canvas.width || 1920;
|
||||||
const canvasHeight = canvas.height || 1080;
|
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 },
|
rect: { x: nr.x - slideX, y: nr.y - slideY, w: rw, h: rh },
|
||||||
styles: node.styles || {}, src: node.src || node.text || '',
|
styles: node.styles || {}, src: node.src || node.text || '',
|
||||||
layerGroup: node.layerGroup || '',
|
layerGroup: node.layerGroup || '',
|
||||||
|
backgroundImages: node.backgroundImages,
|
||||||
parentW: parentW || rw,
|
parentW: parentW || rw,
|
||||||
_depth: depth
|
_depth: depth
|
||||||
});
|
});
|
||||||
@@ -578,15 +630,15 @@ function convert(input) {
|
|||||||
return za - zb;
|
return za - zb;
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===== 同坐标完全重叠去重:保留后者(高层) =====
|
// ===== 同坐标完全重叠去重:保留后者(高层),仅同类型 =====
|
||||||
var seen = {};
|
var seen = {};
|
||||||
var overlapRemove = new Set();
|
var overlapRemove = new Set();
|
||||||
for (var i = sg.children.length - 1; i >= 0; i--) {
|
for (var i = sg.children.length - 1; i >= 0; i--) {
|
||||||
var ci = sg.children[i];
|
var ci = sg.children[i];
|
||||||
if (!ci.rect) continue;
|
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]) {
|
if (seen[key]) {
|
||||||
overlapRemove.add(ci.id); // 后出现的(更高层)保留,前面的移除
|
overlapRemove.add(ci.id);
|
||||||
} else {
|
} else {
|
||||||
seen[key] = true;
|
seen[key] = true;
|
||||||
}
|
}
|
||||||
@@ -881,6 +933,7 @@ function convert(input) {
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
|
(async function() {
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
if (args.length < 1) {
|
if (args.length < 1) {
|
||||||
@@ -912,7 +965,7 @@ if (require.main === module) {
|
|||||||
|
|
||||||
// 执行转换并写入
|
// 执行转换并写入
|
||||||
try {
|
try {
|
||||||
const result = convert(inputJson);
|
const result = await convert(inputJson);
|
||||||
console.log('result slides:', result.slides.length, 'first objects:', result.slides[0]?.objects?.length);
|
console.log('result slides:', result.slides.length, 'first objects:', result.slides[0]?.objects?.length);
|
||||||
|
|
||||||
// 渲染 PPTX(支持自定义布局)
|
// 渲染 PPTX(支持自定义布局)
|
||||||
@@ -975,6 +1028,7 @@ if (require.main === module) {
|
|||||||
console.error('❌ 转换失败:', err.message);
|
console.error('❌ 转换失败:', err.message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { convert };
|
module.exports = { convert };
|
||||||
|
|||||||
Reference in New Issue
Block a user