| import * as PptxModule from "pptxgenjs"; |
| import { cleanHex, normalizeTheme } from "./themes.js"; |
|
|
| export type SlideSpec = { |
| layout: "title" | "section" | "bullets" | "chart_and_insight" | "image_reference"; |
| title: string; |
| subtitle?: string; |
| bullets?: string[]; |
| insights?: string[]; |
| notes?: string; |
| }; |
|
|
| export type PresentationSpec = { |
| title: string; |
| theme: Record<string, unknown>; |
| slides: SlideSpec[]; |
| provenance?: Record<string, unknown>[]; |
| }; |
|
|
| export async function renderPresentation(spec: PresentationSpec): Promise<Buffer> { |
| const PptxGenJS = ((PptxModule as unknown as { default?: unknown }).default ?? PptxModule) as new () => any; |
| const pptx = new PptxGenJS(); |
| pptx.layout = "LAYOUT_WIDE"; |
| pptx.author = "GLM Visual Variable Runtime"; |
| pptx.subject = "Generated from visual evidence"; |
| pptx.title = spec.title; |
|
|
| const theme = normalizeTheme(spec.theme); |
| pptx.defineSlideMaster({ |
| title: "VVR_MASTER", |
| background: { color: cleanHex(theme.background) }, |
| objects: [ |
| { rect: { x: 0, y: 0, w: 13.333, h: 0.12, fill: { color: cleanHex(theme.accent) }, line: { color: cleanHex(theme.accent) } } } |
| ], |
| slideNumber: { x: 12.3, y: 7.15, color: cleanHex(theme.secondary) } |
| }); |
|
|
| const slides = spec.slides.length ? spec.slides : [{ layout: "title" as const, title: spec.title }]; |
| for (const item of slides) { |
| const slide = pptx.addSlide("VVR_MASTER"); |
| slide.addText(item.title, { |
| x: 0.7, |
| y: 0.65, |
| w: 11.9, |
| h: 0.6, |
| fontFace: "Aptos Display", |
| fontSize: item.layout === "title" ? 34 : 26, |
| bold: true, |
| color: cleanHex(theme.primary), |
| margin: 0 |
| }); |
| if (item.subtitle) { |
| slide.addText(item.subtitle, { x: 0.75, y: 1.35, w: 11.7, h: 0.4, fontSize: 16, color: cleanHex(theme.secondary) }); |
| } |
| const bullets = [...(item.bullets ?? []), ...(item.insights ?? [])]; |
| if (bullets.length) { |
| slide.addText(bullets.map((text) => ({ text, options: { bullet: { indent: 14 }, hanging: 4 } })), { |
| x: 0.9, |
| y: 2.0, |
| w: 11.4, |
| h: 4.6, |
| fontSize: 18, |
| color: cleanHex(theme.text_dark), |
| breakLine: false, |
| fit: "shrink" |
| }); |
| } |
| if (item.notes || spec.provenance?.length) { |
| slide.addNotes(`Provenance: ${JSON.stringify(spec.provenance ?? [])}\n${item.notes ?? ""}`); |
| } |
| } |
|
|
| const data = await pptx.write({ outputType: "nodebuffer" }); |
| return Buffer.from(data as ArrayBuffer); |
| } |
|
|