* feat(draw-image): add SVG template render engine * feat(draw-image): add opencode plugin and tool tests * test(draw-image): add vitest unit integration e2e suite * chore(draw-image): wire CI dependabot docs and ADR * docs(handoff): set PR number * docs(handoff): fix PR number placeholder in frontmatter * fix(ci): use tempfile and trailing newline in draw-image tests --------- Co-authored-by: opencode-agent <agent@opencode.local>
57 lines
No EOL
1.5 KiB
TypeScript
57 lines
No EOL
1.5 KiB
TypeScript
export type FitMode = "contain" | "cover" | "stretch"
|
|
|
|
export type SlotSpec = {
|
|
name: string
|
|
x: number
|
|
y: number
|
|
w: number
|
|
h: number
|
|
fit: FitMode
|
|
recolor: string
|
|
bg: string
|
|
border: string
|
|
radius: number
|
|
}
|
|
|
|
const SLOT_RE = /<!--\s*slot:\s*([^>]*?)-->/g
|
|
|
|
export function parseSlots(svg: string): SlotSpec[] {
|
|
const slots: SlotSpec[] = []
|
|
let m: RegExpExecArray | null
|
|
while ((m = SLOT_RE.exec(svg)) !== null) {
|
|
const body = m[1].trim()
|
|
const spec = parseSlotBody(body)
|
|
if (spec) slots.push(spec)
|
|
}
|
|
return slots
|
|
}
|
|
|
|
function parseSlotBody(body: string): SlotSpec | null {
|
|
const fields = new Map<string, string>()
|
|
for (const part of body.split(",")) {
|
|
const eq = part.indexOf("=")
|
|
if (eq === -1) continue
|
|
const key = part.slice(0, eq).trim()
|
|
const val = part.slice(eq + 1).trim()
|
|
fields.set(key, val)
|
|
}
|
|
const name = fields.get("name")
|
|
if (!name) return null
|
|
const x = num(fields, "x")
|
|
const y = num(fields, "y")
|
|
const w = num(fields, "w")
|
|
const h = num(fields, "h")
|
|
if ([x, y, w, h].some((v) => Number.isNaN(v))) return null
|
|
const fit = (fields.get("fit") as FitMode) ?? "contain"
|
|
const recolor = fields.get("recolor") ?? "none"
|
|
const bg = fields.get("bg") ?? "none"
|
|
const border = fields.get("border") ?? "none"
|
|
const radius = num(fields, "radius") || 0
|
|
return { name, x, y, w, h, fit, recolor, bg, border, radius }
|
|
}
|
|
|
|
function num(fields: Map<string, string>, key: string): number {
|
|
const v = fields.get(key)
|
|
if (v === undefined) return NaN
|
|
return parseFloat(v)
|
|
} |