Skip to content

Commit f16cfa5

Browse files
fix(pptx): respect z-order for synthesised charts, shapes, and connectors (#88)
applySynth inserted all synth content at the back of the spTree regardless of the element's deck z, so foreground charts/custGeom SVGs/connectors were buried behind the background cards pptxgenjs wrote on top of them. Tag every emitted pptxgenjs node with its slidewise name, record each synth item's z-anchor, and splice it directly after that node instead of forcing it to the back.
1 parent 247ee75 commit f16cfa5

3 files changed

Lines changed: 304 additions & 58 deletions

File tree

.changeset/fix-synth-zorder.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@textcortex/slidewise": patch
3+
---
4+
5+
fix(pptx): respect z-order for synthesised content (charts, custGeom shapes, connectors)
6+
7+
Synthesised spTree content was always inserted at the back of the slide, so an
8+
in-app chart / custGeom "SVG" / connector with a higher z than its background
9+
card was buried behind that card (and its shadow) and rendered invisible. Each
10+
synth item now anchors directly on top of the pptxgenjs node it sits above
11+
(matched by shape name), preserving the deck's z-order, and only falls to the
12+
back when it is genuinely below every model element.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { describe, it, expect } from "vitest";
2+
import JSZip from "jszip";
3+
import { serializeDeck } from "../index";
4+
import { CURRENT_DECK_VERSION } from "@/lib/schema/migrate";
5+
import type {
6+
Deck,
7+
SlideElement,
8+
ShapeElement,
9+
ChartElement,
10+
ConnectorElement,
11+
} from "@/lib/types";
12+
13+
/**
14+
* Regression: synthesised content (in-app charts, custGeom "svg" shapes,
15+
* connectors) must honour z-order against pptxgenjs-emitted shapes. The bug was
16+
* that ALL synth content was forced to the back of the spTree, so a chart with
17+
* a higher z than its background card got buried behind that card (invisible).
18+
*/
19+
20+
function card(z: number): ShapeElement {
21+
return {
22+
id: "card",
23+
type: "shape",
24+
shape: "rect",
25+
x: 100,
26+
y: 100,
27+
w: 500,
28+
h: 400,
29+
rotation: 0,
30+
z,
31+
fill: "#FFCC00",
32+
};
33+
}
34+
35+
function deckWith(elements: SlideElement[]): Deck {
36+
return {
37+
version: CURRENT_DECK_VERSION,
38+
title: "z",
39+
slides: [{ id: "s1", background: "#FFFFFF", elements }],
40+
};
41+
}
42+
43+
async function slide1(deck: Deck): Promise<string> {
44+
const blob = await serializeDeck(deck);
45+
const zip = await JSZip.loadAsync(await blob.arrayBuffer());
46+
return (await zip.file("ppt/slides/slide1.xml")?.async("string")) ?? "";
47+
}
48+
49+
const chart: ChartElement = {
50+
id: "chart1",
51+
type: "chart",
52+
x: 120,
53+
y: 120,
54+
w: 460,
55+
h: 360,
56+
rotation: 0,
57+
z: 5, // ABOVE the card
58+
kind: "column",
59+
categories: ["A", "B"],
60+
series: [{ name: "S", values: [3, 5] }],
61+
};
62+
63+
describe("synth z-order", () => {
64+
it("places a higher-z synth chart AFTER (on top of) a lower-z card", async () => {
65+
const xml = await slide1(deckWith([card(1), chart]));
66+
const cardPos = xml.indexOf('name="slidewise:card"');
67+
const chartPos = xml.indexOf("<p:graphicFrame");
68+
expect(cardPos).toBeGreaterThanOrEqual(0);
69+
expect(chartPos).toBeGreaterThanOrEqual(0);
70+
expect(chartPos).toBeGreaterThan(cardPos); // chart renders on top
71+
});
72+
73+
it("keeps a lower-z synth backdrop BEHIND a higher-z pptxgenjs card", async () => {
74+
// A custGeom 'svg' backdrop at z=0, card at z=1 on top of it.
75+
const backdrop: ShapeElement = {
76+
id: "bg",
77+
type: "shape",
78+
shape: "rect",
79+
x: 0,
80+
y: 0,
81+
w: 1920,
82+
h: 1080,
83+
rotation: 0,
84+
z: 0,
85+
fill: "#102030",
86+
path: { d: "M0 0 L100 0 L100 100 Z", viewW: 100, viewH: 100 },
87+
};
88+
const xml = await slide1(deckWith([backdrop, card(1)]));
89+
const bgPos = xml.indexOf('name="slidewise:bg"');
90+
const cardPos = xml.indexOf('name="slidewise:card"');
91+
expect(bgPos).toBeGreaterThanOrEqual(0);
92+
expect(cardPos).toBeGreaterThanOrEqual(0);
93+
expect(bgPos).toBeLessThan(cardPos); // backdrop stays behind
94+
});
95+
96+
it("places a higher-z connector AFTER a lower-z card", async () => {
97+
const connector: ConnectorElement = {
98+
id: "cxn",
99+
type: "connector",
100+
x: 100,
101+
y: 100,
102+
w: 300,
103+
h: 200,
104+
rotation: 0,
105+
z: 9,
106+
kind: "straight",
107+
stroke: "#000000",
108+
strokeWidth: 2,
109+
endArrow: "triangle",
110+
};
111+
const xml = await slide1(deckWith([card(1), connector]));
112+
const cardPos = xml.indexOf('name="slidewise:card"');
113+
const cxnPos = xml.indexOf("<p:cxnSp");
114+
expect(cxnPos).toBeGreaterThan(cardPos);
115+
});
116+
});

0 commit comments

Comments
 (0)