feat: z-index排序 + 重叠去重 + overflow裁剪 + 图片支持

- z-index 排序:兄弟节点按 zIndex 排序,高层在上
- 重叠去重:同坐标完全相同的节点只保留后者
- overflow:hidden:用 clipRect 裁剪,完全在区域外的节点跳过
- 图片支持:RECTANGLE 有 backgroundImages 的也作为图片渲染
- 三个 PPT 都验证通过
This commit is contained in:
李进
2026-07-24 18:05:41 +08:00
parent 0ed04eb3ea
commit f3853e31d4
+53 -4
View File
@@ -571,13 +571,62 @@ function convert(input) {
// 移除已合并的原始节点 // 移除已合并的原始节点
sg.children = sg.children.filter(n => !merged.has(n.id)); sg.children = sg.children.filter(n => !merged.has(n.id));
// ===== z-index 排序:同父容器的兄弟节点按 zIndex 排序 =====
sg.children.sort(function(a, b) {
const za = parseInt(a.styles?.zIndex) || 0;
const zb = parseInt(b.styles?.zIndex) || 0;
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);
if (seen[key]) {
overlapRemove.add(ci.id); // 后出现的(更高层)保留,前面的移除
} else {
seen[key] = true;
}
}
if (overlapRemove.size > 0) {
sg.children = sg.children.filter(n => !overlapRemove.has(n.id));
}
// ===== overflow:hidden 裁剪:子元素超出父容器 clipped 区域的跳过 =====
// 建立父容器 clip 区域映射
var clipMap = {}; // id → {x, y, w, h}
function buildClipMap(node, parentClip) {
if (!node) return;
var myClip = parentClip;
if (node.clipped && node.clipRect) {
myClip = { x: node.clipRect.x, y: node.clipRect.y, w: node.clipRect.width, h: node.clipRect.height };
}
clipMap[node.id] = myClip;
if (node.children) for (var c of node.children) buildClipMap(c, myClip);
}
// 只对 slide 容器的子树建 clip 映射
var slideNode = slideContainers.find(s => s.name === sg.name);
if (slideNode) buildClipMap(slideNode, null);
for (const n of sg.children) { for (const n of sg.children) {
// 跳过 raster 底图(保留 IMAGE 类型) // 跳过 raster 底图(保留 IMAGE 类型和有背景图的 RECTANGLE
if (n.type === 'RECTANGLE') { continue; } if (n.type === 'RECTANGLE' && !n.backgroundImages?.length) { continue; }
if (n.tag === 'BODY' || n.tag === 'HTML') { continue; } if (n.tag === 'BODY' || n.tag === 'HTML') { continue; }
const r = n.rect; const r = n.rect;
if (r == null || r.w == null || r.h == null) { continue; } if (r == null || r.w == null || r.h == null) { continue; }
// overflow:hidden 裁剪:节点完全在 clip 区域外时跳过
var clip = clipMap[n.id];
if (clip) {
var nx = r.x, ny = r.y, nr2 = r.x + r.w, nb = r.y + r.h;
if (nx > clip.x + clip.w || nr2 < clip.x || ny > clip.y + clip.h || nb < clip.y) {
continue; // 完全在 clip 区域外
}
}
const S = 1/72; const S = 1/72;
const opts = { const opts = {
x: Math.round(r.x * S * 1000) / 1000, x: Math.round(r.x * S * 1000) / 1000,
@@ -647,8 +696,8 @@ function convert(input) {
continue; // TEXT 节点不参与后续的填充色/边框逻辑 continue; // TEXT 节点不参与后续的填充色/边框逻辑
} }
// ===== 图片节点 ===== // ===== 图片节点(IMAGE 类型或有背景图的 RECTANGLE =====
if (n.type === 'IMAGE' && n.backgroundImages && n.backgroundImages.length > 0) { if ((n.type === 'IMAGE' || n.type === 'RECTANGLE') && n.backgroundImages && n.backgroundImages.length > 0) {
var imgKey = n.backgroundImages[0]; var imgKey = n.backgroundImages[0];
var asset = ctx.assets[imgKey]; var asset = ctx.assets[imgKey];
if (asset && asset.data) { if (asset && asset.data) {