Skip to content

Commit a5ac4ca

Browse files
committed
address comments
1 parent eb3f7f5 commit a5ac4ca

4 files changed

Lines changed: 39 additions & 128 deletions

File tree

apps/sim/lib/pptx-renderer/model/layout.ts

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { emuToPx } from '../parser/units'
77
import type { SafeXmlNode } from '../parser/xml-parser'
8+
import { isPlaceholder, parseAllAttributes } from './xml-helpers'
89

910
export interface PlaceholderXfrm {
1011
position: { x: number; y: number }
@@ -27,23 +28,6 @@ export interface LayoutData {
2728
showMasterSp: boolean
2829
}
2930

30-
/**
31-
* Check whether a shape node contains a placeholder definition.
32-
*/
33-
function isPlaceholder(node: SafeXmlNode): boolean {
34-
const nvSpPr = node.child('nvSpPr')
35-
if (nvSpPr.exists()) {
36-
const nvPr = nvSpPr.child('nvPr')
37-
if (nvPr.child('ph').exists()) return true
38-
}
39-
const nvPicPr = node.child('nvPicPr')
40-
if (nvPicPr.exists()) {
41-
const nvPr = nvPicPr.child('nvPr')
42-
if (nvPr.child('ph').exists()) return true
43-
}
44-
return false
45-
}
46-
4731
function getShapeXfrmInEmu(
4832
node: SafeXmlNode
4933
): { offX: number; offY: number; cx: number; cy: number } | null {
@@ -161,21 +145,6 @@ function extractPlaceholdersRecursive(
161145
return out
162146
}
163147

164-
/**
165-
* Parse all attributes of a node into a Map<string, string>.
166-
*/
167-
function parseAllAttributes(node: SafeXmlNode): Map<string, string> {
168-
const result = new Map<string, string>()
169-
const el = node.element
170-
if (!el) return result
171-
const attrs = el.attributes
172-
for (let i = 0; i < attrs.length; i++) {
173-
const attr = attrs[i]
174-
result.set(attr.localName, attr.value)
175-
}
176-
return result
177-
}
178-
179148
/**
180149
* Parse a slide layout XML root (`p:sldLayout`) into LayoutData.
181150
*/

apps/sim/lib/pptx-renderer/model/master.ts

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import type { SafeXmlNode } from '../parser/xml-parser'
7+
import { isPlaceholder, parseAllAttributes } from './xml-helpers'
78

89
export interface MasterData {
910
colorMap: Map<string, string>
@@ -19,24 +20,6 @@ export interface MasterData {
1920
rels: Map<string, import('../parser/rel-parser').RelEntry>
2021
}
2122

22-
/**
23-
* Check whether a shape node contains a placeholder definition.
24-
* Looks for `p:nvSpPr > p:nvPr > p:ph` or `p:nvPicPr > p:nvPr > p:ph`.
25-
*/
26-
function isPlaceholder(node: SafeXmlNode): boolean {
27-
const nvSpPr = node.child('nvSpPr')
28-
if (nvSpPr.exists()) {
29-
const nvPr = nvSpPr.child('nvPr')
30-
if (nvPr.child('ph').exists()) return true
31-
}
32-
const nvPicPr = node.child('nvPicPr')
33-
if (nvPicPr.exists()) {
34-
const nvPr = nvPicPr.child('nvPr')
35-
if (nvPr.child('ph').exists()) return true
36-
}
37-
return false
38-
}
39-
4023
/**
4124
* Extract placeholder shape nodes from an spTree node.
4225
* A shape is considered a placeholder if it has a `p:ph` element in its nvPr.
@@ -52,22 +35,6 @@ function extractPlaceholders(spTree: SafeXmlNode): SafeXmlNode[] {
5235
return placeholders
5336
}
5437

55-
/**
56-
* Parse all attributes of a node into a Map<string, string>.
57-
* Used for clrMap where every attribute is a color mapping entry.
58-
*/
59-
function parseAllAttributes(node: SafeXmlNode): Map<string, string> {
60-
const result = new Map<string, string>()
61-
const el = node.element
62-
if (!el) return result
63-
const attrs = el.attributes
64-
for (let i = 0; i < attrs.length; i++) {
65-
const attr = attrs[i]
66-
result.set(attr.localName, attr.value)
67-
}
68-
return result
69-
}
70-
7138
/**
7239
* Parse a slide master XML root (`p:sldMaster`) into MasterData.
7340
*/

apps/sim/lib/pptx-renderer/model/slide.ts

Lines changed: 4 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55

66
import { type RelEntry, resolveRelTarget } from '../parser/rel-parser'
7-
import { emuToPx } from '../parser/units'
87
import { parseXml, type SafeXmlNode } from '../parser/xml-parser'
98
import { parseBaseProps } from './nodes/base-node'
109
import { type ChartNodeData, parseChartNode } from './nodes/chart-node'
@@ -178,28 +177,10 @@ function parseDiagramFrame(
178177
return undefined
179178
}
180179

181-
/**
182-
* Read xfrm off/ext from a shape-like node (dsp:sp uses dsp:spPr > a:xfrm).
183-
*/
184-
function readShapeBounds(node: SafeXmlNode): { x: number; y: number; w: number; h: number } | null {
185-
const spPr = node.child('spPr')
186-
if (!spPr.exists()) return null
187-
const xfrm = spPr.child('xfrm')
188-
if (!xfrm.exists()) return null
189-
const off = xfrm.child('off')
190-
const ext = xfrm.child('ext')
191-
const x = emuToPx(off.numAttr('x') ?? 0)
192-
const y = emuToPx(off.numAttr('y') ?? 0)
193-
const w = emuToPx(ext.numAttr('cx') ?? 0)
194-
const h = emuToPx(ext.numAttr('cy') ?? 0)
195-
return { x, y, w, h }
196-
}
197-
198180
/**
199181
* Build a GroupNodeData from a diagram drawing XML string.
200182
* Diagram drawings use dsp: namespace (drawingml 2008); structure is dsp:drawing > dsp:spTree > dsp:sp.
201-
* Diagram shapes use their own coordinate space; we compute childOffset/childExtent from
202-
* the actual bounding box of all shapes so remapping preserves layout and spacing.
183+
* Diagram shapes are positioned in the graphicFrame's own coordinate space.
203184
*/
204185
function buildDiagramGroup(
205186
base: ReturnType<typeof parseBaseProps>,
@@ -218,64 +199,25 @@ function buildDiagramGroup(
218199
}
219200

220201
const CHILD_TAGS = new Set(['sp', 'pic', 'grpSp', 'graphicFrame', 'cxnSp'])
221-
// Circular presets need isotropic scaling; tree/org-chart style diagrams should keep native axis scaling.
222-
const CIRCULAR_PRESETS = new Set(['pie', 'arc', 'blockArc', 'donut', 'circularArrow'])
223202
const children: SafeXmlNode[] = []
224-
let minX = Number.POSITIVE_INFINITY
225-
let minY = Number.POSITIVE_INFINITY
226-
let maxRight = Number.NEGATIVE_INFINITY
227-
let maxBottom = Number.NEGATIVE_INFINITY
228-
let hasCircularPreset = false
229203

230204
for (const child of spTree.allChildren()) {
231205
if (CHILD_TAGS.has(child.localName)) {
232206
children.push(child)
233-
const prst = child.child('spPr').child('prstGeom').attr('prst')
234-
if (prst && CIRCULAR_PRESETS.has(prst)) hasCircularPreset = true
235-
const b = readShapeBounds(child)
236-
if (b) {
237-
minX = Math.min(minX, b.x)
238-
minY = Math.min(minY, b.y)
239-
maxRight = Math.max(maxRight, b.x + b.w)
240-
maxBottom = Math.max(maxBottom, b.y + b.h)
241-
}
242207
}
243208
}
244209

245-
const hasBounds =
246-
minX !== Number.POSITIVE_INFINITY &&
247-
minY !== Number.POSITIVE_INFINITY &&
248-
maxRight !== Number.NEGATIVE_INFINITY &&
249-
maxBottom !== Number.NEGATIVE_INFINITY
250-
251-
// Check if shapes extend significantly beyond the diagram frame (negative offsets or huge extents).
252-
// When decorative shapes (e.g. blockArc) have large negative coordinates, including them in
253-
// the bounding box distorts the layout. Fall back to frame-based coordinates in that case.
254-
const bboxSpansNegative = hasBounds && (minX < 0 || minY < 0)
255-
const bboxMuchLargerThanFrame =
256-
hasBounds && (maxRight - minX > base.size.w * 2 || maxBottom - minY > base.size.h * 2)
257-
const useFrameCoords = bboxSpansNegative || bboxMuchLargerThanFrame
258-
259210
// Use the graphicFrame's own dimensions as the child coordinate space.
260211
// Diagram shapes are positioned in the frame's coordinate space (EMU converted to px).
261212
// Using frame dimensions gives a 1:1 scale, preserving original positions and sizes.
262213
// This avoids enlarging shapes when the bounding box is smaller than the frame.
263-
let extentW = Math.max(1, base.size.w)
264-
let extentH = Math.max(1, base.size.h)
265-
let offX = 0
266-
let offY = 0
267-
268-
if (!hasBounds) {
269-
extentW = Math.max(1, base.size.w)
270-
extentH = Math.max(1, base.size.h)
271-
offX = 0
272-
offY = 0
273-
}
214+
const extentW = Math.max(1, base.size.w)
215+
const extentH = Math.max(1, base.size.h)
274216

275217
return {
276218
...base,
277219
nodeType: 'group',
278-
childOffset: { x: offX, y: offY },
220+
childOffset: { x: 0, y: 0 },
279221
childExtent: { w: extentW, h: extentH },
280222
children,
281223
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { SafeXmlNode } from '../parser/xml-parser'
2+
3+
/**
4+
* Check whether a shape-like node contains a placeholder definition.
5+
*/
6+
export function isPlaceholder(node: SafeXmlNode): boolean {
7+
const nvSpPr = node.child('nvSpPr')
8+
if (nvSpPr.exists()) {
9+
const nvPr = nvSpPr.child('nvPr')
10+
if (nvPr.child('ph').exists()) return true
11+
}
12+
const nvPicPr = node.child('nvPicPr')
13+
if (nvPicPr.exists()) {
14+
const nvPr = nvPicPr.child('nvPr')
15+
if (nvPr.child('ph').exists()) return true
16+
}
17+
return false
18+
}
19+
20+
/**
21+
* Parse all attributes of a node into a local-name keyed map.
22+
*/
23+
export function parseAllAttributes(node: SafeXmlNode): Map<string, string> {
24+
const result = new Map<string, string>()
25+
const el = node.element
26+
if (!el) return result
27+
const attrs = el.attributes
28+
for (let i = 0; i < attrs.length; i++) {
29+
const attr = attrs[i]
30+
result.set(attr.localName, attr.value)
31+
}
32+
return result
33+
}

0 commit comments

Comments
 (0)