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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,178 @@
/**
* Parse OOXML custom geometry (a:custGeom) into SVG path strings.
*/
import type { SafeXmlNode } from '../parser/xml-parser'
function inferPathExtent(pathNode: SafeXmlNode): { w: number; h: number } {
let maxX = 0
let maxY = 0
for (const cmd of pathNode.allChildren()) {
if (cmd.localName === 'moveTo' || cmd.localName === 'lnTo') {
const pt = cmd.child('pt')
maxX = Math.max(maxX, pt.numAttr('x') ?? 0)
maxY = Math.max(maxY, pt.numAttr('y') ?? 0)
continue
}
if (cmd.localName === 'cubicBezTo' || cmd.localName === 'quadBezTo') {
for (const pt of cmd.children('pt')) {
maxX = Math.max(maxX, pt.numAttr('x') ?? 0)
maxY = Math.max(maxY, pt.numAttr('y') ?? 0)
}
continue
}
if (cmd.localName === 'arcTo') {
maxX = Math.max(maxX, cmd.numAttr('wR') ?? 0)
maxY = Math.max(maxY, cmd.numAttr('hR') ?? 0)
}
}
return {
w: Math.max(1, maxX),
h: Math.max(1, maxY),
}
}
/**
* Render a custom geometry element to an SVG path d-attribute string.
*
* @param custGeom - SafeXmlNode wrapping the `a:custGeom` element
* @param width - Target width in pixels
* @param height - Target height in pixels
* @returns SVG path d-attribute string
*/
export function renderCustomGeometry(
custGeom: SafeXmlNode,
width: number,
height: number,
sourceExtent?: { w: number; h: number }
): string {
const pathLst = custGeom.child('pathLst')
if (!pathLst.exists()) return ''
const paths = pathLst.children('path')
const segments: string[] = []
for (const pathNode of paths) {
const fallbackExtent = inferPathExtent(pathNode)
const pathW = pathNode.numAttr('w') ?? sourceExtent?.w ?? fallbackExtent.w
const pathH = pathNode.numAttr('h') ?? sourceExtent?.h ?? fallbackExtent.h
const scaleX = pathW > 0 ? width / pathW : 1
const scaleY = pathH > 0 ? height / pathH : 1
// Track current position for arcTo calculations
let curX = 0
let curY = 0
const commands = pathNode.allChildren()
for (const cmd of commands) {
switch (cmd.localName) {
case 'moveTo': {
const pt = cmd.child('pt')
const x = (pt.numAttr('x') ?? 0) * scaleX
const y = (pt.numAttr('y') ?? 0) * scaleY
segments.push(`M${x},${y}`)
curX = x
curY = y
break
}
case 'lnTo': {
const pt = cmd.child('pt')
const x = (pt.numAttr('x') ?? 0) * scaleX
const y = (pt.numAttr('y') ?? 0) * scaleY
segments.push(`L${x},${y}`)
curX = x
curY = y
break
}
case 'cubicBezTo': {
const pts = cmd.children('pt')
if (pts.length >= 3) {
const x1 = (pts[0].numAttr('x') ?? 0) * scaleX
const y1 = (pts[0].numAttr('y') ?? 0) * scaleY
const x2 = (pts[1].numAttr('x') ?? 0) * scaleX
const y2 = (pts[1].numAttr('y') ?? 0) * scaleY
const x3 = (pts[2].numAttr('x') ?? 0) * scaleX
const y3 = (pts[2].numAttr('y') ?? 0) * scaleY
segments.push(`C${x1},${y1} ${x2},${y2} ${x3},${y3}`)
curX = x3
curY = y3
}
break
}
case 'quadBezTo': {
const pts = cmd.children('pt')
if (pts.length >= 2) {
const x1 = (pts[0].numAttr('x') ?? 0) * scaleX
const y1 = (pts[0].numAttr('y') ?? 0) * scaleY
const x2 = (pts[1].numAttr('x') ?? 0) * scaleX
const y2 = (pts[1].numAttr('y') ?? 0) * scaleY
segments.push(`Q${x1},${y1} ${x2},${y2}`)
curX = x2
curY = y2
}
break
}
case 'arcTo': {
const wRRaw = cmd.numAttr('wR') ?? 0
const hRRaw = cmd.numAttr('hR') ?? 0
const wR = wRRaw * scaleX
const hR = hRRaw * scaleY
const stAngRaw = cmd.numAttr('stAng') ?? 0
const swAngRaw = cmd.numAttr('swAng') ?? 0
// OOXML angles are in 60000ths of a degree
const stAng = stAngRaw / 60000
const swAng = swAngRaw / 60000
if (wR === 0 || hR === 0 || swAng === 0) {
// Degenerate arc, skip
break
}
// OOXML arcTo angles are visual (geometric ray) angles in path coordinate space.
// Convert to parametric using UNSCALED radii before computing positions.
const stVisRad = (stAng * Math.PI) / 180
const stAngRad = Math.atan2(wRRaw * Math.sin(stVisRad), hRRaw * Math.cos(stVisRad))
const endVisRad = ((stAng + swAng) * Math.PI) / 180
const endAngRad = Math.atan2(wRRaw * Math.sin(endVisRad), hRRaw * Math.cos(endVisRad))
// Compute center and endpoint in unscaled path space, then scale
const curXU = curX / scaleX
const curYU = curY / scaleY
const cx = curXU - wRRaw * Math.cos(stAngRad)
const cy = curYU - hRRaw * Math.sin(stAngRad)
const endX = (cx + wRRaw * Math.cos(endAngRad)) * scaleX
const endY = (cy + hRRaw * Math.sin(endAngRad)) * scaleY
// SVG arc flags
const largeArc = Math.abs(swAng) > 180 ? 1 : 0
const sweep = swAng > 0 ? 1 : 0
segments.push(`A${wR},${hR} 0 ${largeArc},${sweep} ${endX},${endY}`)
curX = endX
curY = endY
break
}
case 'close': {
segments.push('Z')
break
}
default:
// Unknown command, skip
break
}
}
}
return segments.join(' ')
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,44 @@
/**
* Convert OOXML arc specification to SVG path arc command.
* Based on PPTXjs shapeArc() implementation.
*
* @param cx - Center X coordinate
* @param cy - Center Y coordinate
* @param rx - Horizontal radius
* @param ry - Vertical radius
* @param startAngle - Start angle in degrees
* @param endAngle - End angle in degrees
* @param isClose - Whether to close the path with Z
* @returns SVG path string for the arc
*/
export function shapeArc(
cx: number,
cy: number,
rx: number,
ry: number,
startAngle: number,
endAngle: number,
isClose: boolean
): string {
const startRad = (startAngle * Math.PI) / 180
const endRad = (endAngle * Math.PI) / 180
const x1 = cx + rx * Math.cos(startRad)
const y1 = cy + ry * Math.sin(startRad)
const x2 = cx + rx * Math.cos(endRad)
const y2 = cy + ry * Math.sin(endRad)
// OOXML convention: always sweep clockwise from startAngle to endAngle.
// Compute the clockwise sweep in degrees, handling angle wrapping.
let sweepDeg = (((endAngle - startAngle) % 360) + 360) % 360
if (sweepDeg === 0 && startAngle !== endAngle) sweepDeg = 360
const largeArc = sweepDeg > 180 ? 1 : 0
const sweep = 1 // always clockwise
let d = `M${x1},${y1} A${rx},${ry} 0 ${largeArc},${sweep} ${x2},${y2}`
if (isClose) {
d += ' Z'
}
return d
}