chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Slide layout parser — extracts color map override, background,
|
||||
* and placeholder shapes from a p:sldLayout XML.
|
||||
*/
|
||||
|
||||
import { emuToPx } from '../parser/units'
|
||||
import type { SafeXmlNode } from '../parser/xml-parser'
|
||||
import { isPlaceholder, parseAllAttributes } from './xml-helpers'
|
||||
|
||||
interface PlaceholderXfrm {
|
||||
position: { x: number; y: number }
|
||||
size: { w: number; h: number }
|
||||
}
|
||||
|
||||
export interface PlaceholderEntry {
|
||||
node: SafeXmlNode
|
||||
/** When placeholder is inside a group, position/size in slide space (px). */
|
||||
absoluteXfrm?: PlaceholderXfrm
|
||||
}
|
||||
|
||||
export interface LayoutData {
|
||||
colorMapOverride?: Map<string, string>
|
||||
background?: SafeXmlNode
|
||||
placeholders: PlaceholderEntry[]
|
||||
spTree: SafeXmlNode
|
||||
rels: Map<string, import('../parser/rel-parser').RelEntry>
|
||||
/** When false, shapes from the slide master should NOT be rendered on this layout. */
|
||||
showMasterSp: boolean
|
||||
}
|
||||
|
||||
function getShapeXfrmInEmu(
|
||||
node: SafeXmlNode
|
||||
): { offX: number; offY: number; cx: number; cy: number } | null {
|
||||
const spPr = node.child('spPr')
|
||||
if (!spPr.exists()) return null
|
||||
const xfrm = spPr.child('xfrm')
|
||||
if (!xfrm.exists()) return null
|
||||
const off = xfrm.child('off')
|
||||
const ext = xfrm.child('ext')
|
||||
const offX = off.numAttr('x') ?? 0
|
||||
const offY = off.numAttr('y') ?? 0
|
||||
const cx = ext.numAttr('cx') ?? 0
|
||||
const cy = ext.numAttr('cy') ?? 0
|
||||
return { offX, offY, cx, cy }
|
||||
}
|
||||
|
||||
function getGroupXfrmInEmu(grpSp: SafeXmlNode): {
|
||||
offX: number
|
||||
offY: number
|
||||
cx: number
|
||||
cy: number
|
||||
chOffX: number
|
||||
chOffY: number
|
||||
chExtCx: number
|
||||
chExtCy: number
|
||||
} | null {
|
||||
const grpSpPr = grpSp.child('grpSpPr')
|
||||
if (!grpSpPr.exists()) return null
|
||||
const xfrm = grpSpPr.child('xfrm')
|
||||
if (!xfrm.exists()) return null
|
||||
const off = xfrm.child('off')
|
||||
const ext = xfrm.child('ext')
|
||||
const chOff = xfrm.child('chOff')
|
||||
const chExt = xfrm.child('chExt')
|
||||
const offX = off.numAttr('x') ?? 0
|
||||
const offY = off.numAttr('y') ?? 0
|
||||
const cx = ext.numAttr('cx') ?? 0
|
||||
const cy = ext.numAttr('cy') ?? 0
|
||||
// OOXML: when chOff/chExt omitted, child box equals group box (chOff=0,0 and chExt=ext)
|
||||
const chOffX = chOff.exists() ? (chOff.numAttr('x') ?? 0) : 0
|
||||
const chOffY = chOff.exists() ? (chOff.numAttr('y') ?? 0) : 0
|
||||
const chExtCx = chExt.exists() ? (chExt.numAttr('cx') ?? cx) : cx
|
||||
const chExtCy = chExt.exists() ? (chExt.numAttr('cy') ?? cy) : cy
|
||||
return {
|
||||
offX,
|
||||
offY,
|
||||
cx,
|
||||
cy,
|
||||
chOffX,
|
||||
chOffY,
|
||||
chExtCx: chExtCx > 0 ? chExtCx : 1,
|
||||
chExtCy: chExtCy > 0 ? chExtCy : 1,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively collect placeholders; when inside a group, compute position/size in slide space.
|
||||
*/
|
||||
function extractPlaceholdersRecursive(
|
||||
spTree: SafeXmlNode,
|
||||
groupTransform: { offX: number; offY: number; scaleX: number; scaleY: number } | null
|
||||
): PlaceholderEntry[] {
|
||||
const out: PlaceholderEntry[] = []
|
||||
for (const child of spTree.allChildren()) {
|
||||
if (child.localName === 'grpSp') {
|
||||
const gx = getGroupXfrmInEmu(child)
|
||||
if (gx && gx.chExtCx > 0 && gx.chExtCy > 0) {
|
||||
const scaleX = gx.cx / gx.chExtCx
|
||||
const scaleY = gx.cy / gx.chExtCy
|
||||
const baseOffX = gx.offX - gx.chOffX * scaleX
|
||||
const baseOffY = gx.offY - gx.chOffY * scaleY
|
||||
const nextTransform = groupTransform
|
||||
? {
|
||||
offX: groupTransform.offX + baseOffX * groupTransform.scaleX,
|
||||
offY: groupTransform.offY + baseOffY * groupTransform.scaleY,
|
||||
scaleX: groupTransform.scaleX * scaleX,
|
||||
scaleY: groupTransform.scaleY * scaleY,
|
||||
}
|
||||
: { offX: baseOffX, offY: baseOffY, scaleX, scaleY }
|
||||
const nested = extractPlaceholdersRecursive(child, nextTransform)
|
||||
out.push(...nested)
|
||||
} else {
|
||||
out.push(...extractPlaceholdersRecursive(child, groupTransform))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (!isPlaceholder(child)) continue
|
||||
const sx = getShapeXfrmInEmu(child)
|
||||
if (!sx) {
|
||||
out.push({ node: child })
|
||||
continue
|
||||
}
|
||||
if (groupTransform) {
|
||||
const absOffX = groupTransform.offX + sx.offX * groupTransform.scaleX
|
||||
const absOffY = groupTransform.offY + sx.offY * groupTransform.scaleY
|
||||
const absCx = sx.cx * groupTransform.scaleX
|
||||
const absCy = sx.cy * groupTransform.scaleY
|
||||
out.push({
|
||||
node: child,
|
||||
absoluteXfrm: {
|
||||
position: { x: emuToPx(absOffX), y: emuToPx(absOffY) },
|
||||
size: { w: emuToPx(absCx), h: emuToPx(absCy) },
|
||||
},
|
||||
})
|
||||
} else {
|
||||
out.push({
|
||||
node: child,
|
||||
absoluteXfrm: {
|
||||
position: { x: emuToPx(sx.offX), y: emuToPx(sx.offY) },
|
||||
size: { w: emuToPx(sx.cx), h: emuToPx(sx.cy) },
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a slide layout XML root (`p:sldLayout`) into LayoutData.
|
||||
*/
|
||||
export function parseLayout(root: SafeXmlNode): LayoutData {
|
||||
const cSld = root.child('cSld')
|
||||
|
||||
// --- Background ---
|
||||
const bg = cSld.child('bg')
|
||||
const background = bg.exists() ? bg : undefined
|
||||
|
||||
// --- Shape tree ---
|
||||
const spTree = cSld.child('spTree')
|
||||
|
||||
// --- Color map override ---
|
||||
let colorMapOverride: Map<string, string> | undefined
|
||||
const clrMapOvr = root.child('clrMapOvr')
|
||||
if (clrMapOvr.exists()) {
|
||||
const overrideMapping = clrMapOvr.child('overrideClrMapping')
|
||||
if (overrideMapping.exists()) {
|
||||
colorMapOverride = parseAllAttributes(overrideMapping)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Placeholders (recursive so we find title/body inside grpSp; resolve position in slide space) ---
|
||||
const placeholders = extractPlaceholdersRecursive(spTree, null)
|
||||
|
||||
// --- showMasterSp: if "0", master shapes should not be rendered for this layout ---
|
||||
const showMasterSpAttr = root.attr('showMasterSp')
|
||||
const showMasterSp = showMasterSpAttr !== '0'
|
||||
|
||||
return {
|
||||
colorMapOverride,
|
||||
background,
|
||||
placeholders,
|
||||
spTree,
|
||||
rels: new Map(), // populated later by buildPresentation
|
||||
showMasterSp,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user