@bendyline/squisq-formats 1.2.3 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-33YRFXZZ.js +1187 -0
- package/dist/chunk-33YRFXZZ.js.map +1 -0
- package/dist/{chunk-EUQQYBZZ.js → chunk-ERZ627GR.js} +4 -4
- package/dist/chunk-ERZ627GR.js.map +1 -0
- package/dist/{chunk-MEZF76JA.js → chunk-UDS45KUJ.js} +68 -25
- package/dist/chunk-UDS45KUJ.js.map +1 -0
- package/dist/{chunk-NGWHV77G.js → chunk-VN2KEOYB.js} +7 -6
- package/dist/chunk-VN2KEOYB.js.map +1 -0
- package/dist/container/index.d.ts +7 -5
- package/dist/container/index.js +39 -70
- package/dist/container/index.js.map +1 -1
- package/dist/docx/index.d.ts +128 -15
- package/dist/docx/index.js +17 -22
- package/dist/docx/index.js.map +1 -1
- package/dist/epub/index.d.ts +66 -11
- package/dist/epub/index.js +10 -18
- package/dist/epub/index.js.map +1 -1
- package/dist/html/index.d.ts +278 -9
- package/dist/html/index.js +26 -137
- package/dist/html/index.js.map +1 -1
- package/dist/index.d.ts +9 -38
- package/dist/index.js +62 -37
- package/dist/index.js.map +1 -1
- package/dist/ooxml/index.d.ts +353 -14
- package/dist/ooxml/index.js +116 -22
- package/dist/ooxml/index.js.map +1 -1
- package/dist/pdf/index.d.ts +143 -17
- package/dist/pdf/index.js +16 -25
- package/dist/pdf/index.js.map +1 -1
- package/dist/pptx/index.d.ts +83 -8
- package/dist/pptx/index.js +14 -34
- package/dist/pptx/index.js.map +1 -1
- package/dist/xlsx/index.d.ts +12 -9
- package/dist/xlsx/index.js +12 -44
- package/dist/xlsx/index.js.map +1 -1
- package/package.json +3 -2
- package/src/__tests__/html.test.ts +82 -4
- package/src/__tests__/pdfExport.test.ts +1 -1
- package/src/__tests__/plainHtml.test.ts +372 -0
- package/src/__tests__/plainHtmlBundle.test.ts +235 -0
- package/src/docx/export.ts +163 -22
- package/src/epub/export.ts +3 -3
- package/src/html/docsHtmlBundle.ts +369 -0
- package/src/html/htmlTemplate.ts +40 -0
- package/src/html/index.ts +32 -3
- package/src/html/plainHtml.ts +736 -0
- package/src/html/plainHtmlBundle.ts +419 -0
- package/src/pptx/export.ts +10 -9
- package/dist/__tests__/container.test.d.ts +0 -8
- package/dist/__tests__/container.test.d.ts.map +0 -1
- package/dist/__tests__/container.test.js +0 -147
- package/dist/__tests__/container.test.js.map +0 -1
- package/dist/__tests__/docxExport.test.d.ts +0 -8
- package/dist/__tests__/docxExport.test.d.ts.map +0 -1
- package/dist/__tests__/docxExport.test.js +0 -383
- package/dist/__tests__/docxExport.test.js.map +0 -1
- package/dist/__tests__/docxImport.test.d.ts +0 -8
- package/dist/__tests__/docxImport.test.d.ts.map +0 -1
- package/dist/__tests__/docxImport.test.js +0 -309
- package/dist/__tests__/docxImport.test.js.map +0 -1
- package/dist/__tests__/epub.test.d.ts +0 -8
- package/dist/__tests__/epub.test.d.ts.map +0 -1
- package/dist/__tests__/epub.test.js +0 -472
- package/dist/__tests__/epub.test.js.map +0 -1
- package/dist/__tests__/html.test.d.ts +0 -8
- package/dist/__tests__/html.test.d.ts.map +0 -1
- package/dist/__tests__/html.test.js +0 -243
- package/dist/__tests__/html.test.js.map +0 -1
- package/dist/__tests__/ooxml.test.d.ts +0 -5
- package/dist/__tests__/ooxml.test.d.ts.map +0 -1
- package/dist/__tests__/ooxml.test.js +0 -152
- package/dist/__tests__/ooxml.test.js.map +0 -1
- package/dist/__tests__/pdfExport.test.d.ts +0 -8
- package/dist/__tests__/pdfExport.test.d.ts.map +0 -1
- package/dist/__tests__/pdfExport.test.js +0 -275
- package/dist/__tests__/pdfExport.test.js.map +0 -1
- package/dist/__tests__/pdfImport.test.d.ts +0 -11
- package/dist/__tests__/pdfImport.test.d.ts.map +0 -1
- package/dist/__tests__/pdfImport.test.js +0 -254
- package/dist/__tests__/pdfImport.test.js.map +0 -1
- package/dist/__tests__/roundTrip.test.d.ts +0 -7
- package/dist/__tests__/roundTrip.test.d.ts.map +0 -1
- package/dist/__tests__/roundTrip.test.js +0 -173
- package/dist/__tests__/roundTrip.test.js.map +0 -1
- package/dist/chunk-EUQQYBZZ.js.map +0 -1
- package/dist/chunk-MEZF76JA.js.map +0 -1
- package/dist/chunk-NGWHV77G.js.map +0 -1
- package/dist/chunk-UM5V2XZG.js +0 -242
- package/dist/chunk-UM5V2XZG.js.map +0 -1
- package/dist/container/index.d.ts.map +0 -1
- package/dist/docx/export.d.ts +0 -69
- package/dist/docx/export.d.ts.map +0 -1
- package/dist/docx/export.js +0 -854
- package/dist/docx/export.js.map +0 -1
- package/dist/docx/import.d.ts +0 -65
- package/dist/docx/import.d.ts.map +0 -1
- package/dist/docx/import.js +0 -779
- package/dist/docx/import.js.map +0 -1
- package/dist/docx/index.d.ts.map +0 -1
- package/dist/docx/styles.d.ts +0 -68
- package/dist/docx/styles.d.ts.map +0 -1
- package/dist/docx/styles.js +0 -125
- package/dist/docx/styles.js.map +0 -1
- package/dist/epub/export.d.ts +0 -72
- package/dist/epub/export.d.ts.map +0 -1
- package/dist/epub/export.js +0 -698
- package/dist/epub/export.js.map +0 -1
- package/dist/epub/index.d.ts.map +0 -1
- package/dist/html/htmlTemplate.d.ts +0 -70
- package/dist/html/htmlTemplate.d.ts.map +0 -1
- package/dist/html/htmlTemplate.js +0 -239
- package/dist/html/htmlTemplate.js.map +0 -1
- package/dist/html/imageUtils.d.ts +0 -29
- package/dist/html/imageUtils.d.ts.map +0 -1
- package/dist/html/imageUtils.js +0 -63
- package/dist/html/imageUtils.js.map +0 -1
- package/dist/html/index.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/ooxml/index.d.ts.map +0 -1
- package/dist/ooxml/namespaces.d.ts +0 -77
- package/dist/ooxml/namespaces.d.ts.map +0 -1
- package/dist/ooxml/namespaces.js +0 -104
- package/dist/ooxml/namespaces.js.map +0 -1
- package/dist/ooxml/reader.d.ts +0 -52
- package/dist/ooxml/reader.d.ts.map +0 -1
- package/dist/ooxml/reader.js +0 -190
- package/dist/ooxml/reader.js.map +0 -1
- package/dist/ooxml/types.d.ts +0 -82
- package/dist/ooxml/types.d.ts.map +0 -1
- package/dist/ooxml/types.js +0 -9
- package/dist/ooxml/types.js.map +0 -1
- package/dist/ooxml/writer.d.ts +0 -71
- package/dist/ooxml/writer.d.ts.map +0 -1
- package/dist/ooxml/writer.js +0 -200
- package/dist/ooxml/writer.js.map +0 -1
- package/dist/ooxml/xmlUtils.d.ts +0 -76
- package/dist/ooxml/xmlUtils.d.ts.map +0 -1
- package/dist/ooxml/xmlUtils.js +0 -108
- package/dist/ooxml/xmlUtils.js.map +0 -1
- package/dist/pdf/export.d.ts +0 -56
- package/dist/pdf/export.d.ts.map +0 -1
- package/dist/pdf/export.js +0 -768
- package/dist/pdf/export.js.map +0 -1
- package/dist/pdf/import.d.ts +0 -94
- package/dist/pdf/import.d.ts.map +0 -1
- package/dist/pdf/import.js +0 -796
- package/dist/pdf/import.js.map +0 -1
- package/dist/pdf/index.d.ts.map +0 -1
- package/dist/pdf/styles.d.ts +0 -142
- package/dist/pdf/styles.d.ts.map +0 -1
- package/dist/pdf/styles.js +0 -145
- package/dist/pdf/styles.js.map +0 -1
- package/dist/pptx/export.d.ts +0 -75
- package/dist/pptx/export.d.ts.map +0 -1
- package/dist/pptx/export.js +0 -722
- package/dist/pptx/export.js.map +0 -1
- package/dist/pptx/index.d.ts.map +0 -1
- package/dist/pptx/styles.d.ts +0 -54
- package/dist/pptx/styles.d.ts.map +0 -1
- package/dist/pptx/styles.js +0 -73
- package/dist/pptx/styles.js.map +0 -1
- package/dist/pptx/templates.d.ts +0 -24
- package/dist/pptx/templates.d.ts.map +0 -1
- package/dist/pptx/templates.js +0 -158
- package/dist/pptx/templates.js.map +0 -1
- package/dist/xlsx/index.d.ts.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/pptx/export.ts","../src/pptx/styles.ts","../src/pptx/templates.ts","../src/pptx/index.ts"],"sourcesContent":["/**\n * PPTX Export\n *\n * Converts a squisq MarkdownDocument (or Doc) into a .pptx file\n * by generating PresentationML + DrawingML XML and assembling the\n * OOXML package.\n *\n * Slide segmentation: each H1 or H2 heading starts a new slide.\n * The heading becomes the slide title; content below becomes the body.\n *\n * Supports:\n * - Theme-based styling (background, text colors, fonts from squisq themes)\n * - Image embedding (pass images as ArrayBuffers via options)\n * - Inline formatting (bold, italic, strikethrough, code, links)\n * - Lists, code blocks, blockquotes, tables, math\n *\n * No third-party pptx library — all XML is generated directly using\n * the shared ooxml/ infrastructure.\n *\n * @example\n * ```ts\n * import { parseMarkdown } from '@bendyline/squisq/markdown';\n * import { markdownDocToPptx } from '@bendyline/squisq-formats/pptx';\n *\n * const md = parseMarkdown('# Slide 1\\n\\nHello world\\n\\n## Slide 2\\n\\nMore content');\n * const blob = await markdownDocToPptx(md, { themeId: 'documentary' });\n * ```\n */\n\nimport type { Doc } from '@bendyline/squisq/schemas';\nimport { resolveTheme, resolveFontFamily } from '@bendyline/squisq/schemas';\nimport type { Theme } from '@bendyline/squisq/schemas';\nimport { docToMarkdown } from '@bendyline/squisq/doc';\nimport type {\n MarkdownDocument,\n MarkdownBlockNode,\n MarkdownInlineNode,\n MarkdownHeading,\n MarkdownParagraph,\n MarkdownBlockquote,\n MarkdownList,\n MarkdownListItem,\n MarkdownCodeBlock,\n MarkdownTable,\n MarkdownTableRow,\n MarkdownHtmlBlock,\n MarkdownMathBlock,\n MarkdownLink,\n MarkdownImage,\n} from '@bendyline/squisq/markdown';\nimport { readFrontmatterThemeId } from '@bendyline/squisq/markdown';\n\nimport { createPackage } from '../ooxml/writer.js';\nimport { xmlDeclaration, escapeXml } from '../ooxml/xmlUtils.js';\nimport {\n NS_PML,\n NS_DRAWINGML,\n NS_R,\n REL_OFFICE_DOCUMENT,\n REL_SLIDE,\n REL_SLIDE_LAYOUT,\n REL_SLIDE_MASTER,\n REL_THEME,\n REL_HYPERLINK,\n REL_IMAGE,\n CONTENT_TYPE_PPTX_PRESENTATION,\n CONTENT_TYPE_PPTX_SLIDE,\n CONTENT_TYPE_PPTX_SLIDE_LAYOUT,\n CONTENT_TYPE_PPTX_SLIDE_MASTER,\n CONTENT_TYPE_PPTX_THEME,\n} from '../ooxml/namespaces.js';\nimport {\n DEFAULT_FONT,\n DEFAULT_TITLE_FONT,\n DEFAULT_CODE_FONT,\n DEFAULT_TITLE_SIZE,\n DEFAULT_CODE_SIZE,\n HYPERLINK_COLOR,\n TITLE_LEFT,\n TITLE_TOP,\n TITLE_WIDTH,\n TITLE_HEIGHT,\n BODY_LEFT,\n BODY_TOP,\n BODY_WIDTH,\n BODY_HEIGHT,\n} from './styles.js';\nimport {\n buildPresentationXml,\n buildSlideMasterXml,\n buildSlideLayoutXml,\n buildThemeXml,\n} from './templates.js';\n\n// ============================================\n// Public API\n// ============================================\n\n/**\n * Options for PPTX export.\n */\nexport interface PptxExportOptions {\n /** Presentation title (appears in core properties) */\n title?: string;\n /** Presentation author */\n author?: string;\n /** Presentation description */\n description?: string;\n /**\n * Which heading depth triggers a new slide.\n * - `'h1'` — only H1 headings start slides\n * - `'h2'` — H1 and H2 headings start slides (default)\n * - `'heading'` — any heading (H1–H6) starts a slide\n */\n slideBreak?: 'h1' | 'h2' | 'heading';\n /** Default body font family. Default: \"Calibri\" */\n defaultFont?: string;\n /** Default body font size in points. Default: 18 */\n defaultFontSize?: number;\n /**\n * Squisq theme ID to apply (e.g., 'documentary', 'cinematic', 'bold').\n * Controls slide background, text colors, and fonts.\n * Falls back to the document's frontmatter `themeId` if not set here.\n */\n themeId?: string;\n /**\n * Image data keyed by the path/URL used in markdown.\n * When provided, images are embedded as picture shapes instead of\n * showing `[Image: alt]` placeholders.\n */\n images?: Map<string, ArrayBuffer>;\n}\n\n/**\n * Convert a MarkdownDocument to a .pptx ArrayBuffer.\n */\nexport async function markdownDocToPptx(\n doc: MarkdownDocument,\n options: PptxExportOptions = {},\n): Promise<ArrayBuffer> {\n // Resolve theme from options or frontmatter. The frontmatter lookup\n // accepts the editor's canonical `squisq-theme` key as well as the\n // shorter legacy `themeId` / `theme` aliases — see\n // `readFrontmatterThemeId` for the precedence.\n const themeId = options.themeId ?? readFrontmatterThemeId(doc.frontmatter);\n const style = resolveSlideStyle(themeId, options);\n\n const slides = segmentIntoSlides(doc.children, options.slideBreak ?? 'h2');\n\n // Ensure at least one slide\n if (slides.length === 0) {\n slides.push({ bodyNodes: [] });\n }\n\n const slideXmls: string[] = [];\n const slideContexts: SlideContext[] = [];\n\n for (let i = 0; i < slides.length; i++) {\n const ctx = new SlideContext(style, options.images, i);\n const xml = buildSlideXml(slides[i], ctx);\n slideXmls.push(xml);\n slideContexts.push(ctx);\n }\n\n return buildPptxPackage(slideXmls, slideContexts, options);\n}\n\n/**\n * Convert a squisq Doc to a .pptx ArrayBuffer.\n *\n * Convenience wrapper: Doc -> MarkdownDocument -> PPTX.\n */\nexport async function docToPptx(doc: Doc, options: PptxExportOptions = {}): Promise<ArrayBuffer> {\n const markdownDoc = docToMarkdown(doc);\n // Carry themeId from Doc if not set in options\n if (!options.themeId && doc.themeId) {\n options = { ...options, themeId: doc.themeId };\n }\n return markdownDocToPptx(markdownDoc, options);\n}\n\n// ============================================\n// Resolved Style\n// ============================================\n\n/** Resolved visual style for slides, derived from a Theme or defaults. */\ninterface SlideStyle {\n background: string; // hex without #\n text: string;\n titleColor: string;\n mutedColor: string;\n titleFont: string;\n bodyFont: string;\n codeFont: string;\n codeColor: string;\n hasTheme: boolean;\n}\n\nfunction resolveSlideStyle(themeId: string | undefined, options: PptxExportOptions): SlideStyle {\n if (!themeId) {\n return {\n background: 'FFFFFF',\n text: '333333',\n titleColor: '333333',\n mutedColor: '666666',\n titleFont: options.defaultFont ?? DEFAULT_TITLE_FONT,\n bodyFont: options.defaultFont ?? DEFAULT_FONT,\n codeFont: DEFAULT_CODE_FONT,\n codeColor: '333333',\n hasTheme: false,\n };\n }\n\n const theme: Theme = resolveTheme(themeId);\n const c = theme.colors;\n\n return {\n background: stripHash(c.background),\n text: stripHash(c.text),\n titleColor: stripHash(c.highlight || c.secondary || c.text),\n mutedColor: stripHash(c.textMuted || c.text),\n titleFont: resolveFontFamily(theme.typography?.titleFont, DEFAULT_TITLE_FONT),\n bodyFont: resolveFontFamily(theme.typography?.bodyFont, options.defaultFont || DEFAULT_FONT),\n codeFont: resolveFontFamily(theme.typography?.monoFont, DEFAULT_CODE_FONT),\n codeColor: stripHash(c.textMuted || c.text),\n hasTheme: true,\n };\n}\n\nfunction stripHash(color: string): string {\n return color.startsWith('#') ? color.slice(1) : color;\n}\n\n// ============================================\n// Slide Segmentation\n// ============================================\n\ninterface SlideData {\n title?: string;\n titleDepth?: number;\n bodyNodes: MarkdownBlockNode[];\n}\n\nfunction segmentIntoSlides(\n children: MarkdownBlockNode[],\n slideBreak: 'h1' | 'h2' | 'heading',\n): SlideData[] {\n const maxDepth = slideBreak === 'h1' ? 1 : slideBreak === 'h2' ? 2 : 6;\n const slides: SlideData[] = [];\n let current: SlideData | undefined;\n\n for (const node of children) {\n if (node.type === 'heading' && node.depth <= maxDepth) {\n if (current) slides.push(current);\n current = {\n title: extractPlainText(node.children),\n titleDepth: node.depth,\n bodyNodes: [],\n };\n } else {\n if (!current) {\n current = { bodyNodes: [] };\n }\n current.bodyNodes.push(node);\n }\n }\n\n if (current) slides.push(current);\n return slides;\n}\n\nfunction extractPlainText(nodes: MarkdownInlineNode[]): string {\n const parts: string[] = [];\n for (const node of nodes) {\n switch (node.type) {\n case 'text':\n parts.push(node.value);\n break;\n case 'inlineCode':\n parts.push(node.value);\n break;\n case 'strong':\n case 'emphasis':\n case 'delete':\n parts.push(extractPlainText(node.children));\n break;\n case 'link':\n parts.push(extractPlainText(node.children));\n break;\n case 'image':\n parts.push(node.alt ?? '');\n break;\n case 'inlineMath':\n parts.push(node.value);\n break;\n default:\n break;\n }\n }\n return parts.join('');\n}\n\n// ============================================\n// Slide Context\n// ============================================\n\ninterface EmbeddedImage {\n relId: string;\n mediaPath: string; // e.g. ppt/media/image_s0_1.png\n data: ArrayBuffer;\n contentType: string;\n alt: string;\n}\n\nclass SlideContext {\n private nextRelId = 1;\n\n readonly relationships: Array<{\n id: string;\n type: string;\n target: string;\n targetMode?: 'External';\n }> = [];\n\n readonly style: SlideStyle;\n readonly images: Map<string, ArrayBuffer> | undefined;\n readonly slideIndex: number;\n readonly embeddedImages: EmbeddedImage[] = [];\n private nextShapeId = 4; // 1=group, 2=title, 3=body\n\n constructor(style: SlideStyle, images: Map<string, ArrayBuffer> | undefined, slideIndex: number) {\n this.style = style;\n this.images = images;\n this.slideIndex = slideIndex;\n }\n\n allocRelId(): string {\n return `rId${this.nextRelId++ + 1}`;\n }\n\n allocShapeId(): number {\n return this.nextShapeId++;\n }\n\n addHyperlink(url: string): string {\n const id = this.allocRelId();\n this.relationships.push({\n id,\n type: REL_HYPERLINK,\n target: url,\n targetMode: 'External',\n });\n return id;\n }\n\n /** Register an embedded image and return its relationship ID */\n addImage(src: string, data: ArrayBuffer, alt: string): string {\n const relId = this.allocRelId();\n const ext = inferExtension(src);\n const contentType = inferMimeType(ext);\n const imgIndex = this.embeddedImages.length + 1;\n const mediaPath = `ppt/media/image_s${this.slideIndex}_${imgIndex}.${ext}`;\n\n this.embeddedImages.push({ relId, mediaPath, data, contentType, alt });\n this.relationships.push({\n id: relId,\n type: REL_IMAGE,\n target: `../media/image_s${this.slideIndex}_${imgIndex}.${ext}`,\n });\n\n return relId;\n }\n}\n\n// ============================================\n// Slide XML Generation\n// ============================================\n\nfunction buildSlideXml(slide: SlideData, ctx: SlideContext): string {\n const shapes: string[] = [];\n\n shapes.push(\n `<p:nvGrpSpPr><p:cNvPr id=\"1\" name=\"\"/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>` + `<p:grpSpPr/>`,\n );\n\n if (slide.title) {\n shapes.push(buildTitleShape(slide.title, ctx.style));\n }\n\n // Convert body blocks and collect any image-only paragraphs\n const bodyParas = convertBodyBlocks(slide.bodyNodes, ctx);\n const hasImages = ctx.embeddedImages.length > 0;\n const hasText = bodyParas.length > 0;\n\n if (hasText && hasImages) {\n // Split: text in top half of body area, images below\n const textHeight = Math.round(BODY_HEIGHT * 0.45);\n const imgTop = BODY_TOP + textHeight + 91440; // 0.1\" gap\n const imgHeight = BODY_HEIGHT - textHeight - 91440;\n shapes.push(buildBodyShapeCustom(bodyParas, BODY_TOP, textHeight, ctx.style));\n shapes.push(\n buildImageShape(\n ctx.embeddedImages[0],\n ctx.allocShapeId(),\n BODY_LEFT,\n imgTop,\n BODY_WIDTH,\n imgHeight,\n ),\n );\n } else if (hasImages && !hasText) {\n // Full content area for image\n shapes.push(\n buildImageShape(\n ctx.embeddedImages[0],\n ctx.allocShapeId(),\n BODY_LEFT,\n BODY_TOP,\n BODY_WIDTH,\n BODY_HEIGHT,\n ),\n );\n } else {\n // Text only (or empty)\n shapes.push(\n buildBodyShapeCustom(\n bodyParas || `<a:p><a:endParaRPr lang=\"en-US\"/></a:p>`,\n BODY_TOP,\n BODY_HEIGHT,\n ctx.style,\n ),\n );\n }\n\n // Additional images beyond the first get placed as extra shapes\n for (let i = 1; i < ctx.embeddedImages.length; i++) {\n shapes.push(\n buildImageShape(\n ctx.embeddedImages[i],\n ctx.allocShapeId(),\n BODY_LEFT,\n BODY_TOP,\n BODY_WIDTH,\n BODY_HEIGHT,\n ),\n );\n }\n\n // Build background\n const bgXml = ctx.style.hasTheme\n ? `<p:bg><p:bgPr><a:solidFill><a:srgbClr val=\"${ctx.style.background}\"/></a:solidFill><a:effectLst/></p:bgPr></p:bg>`\n : '';\n\n return (\n xmlDeclaration() +\n `<p:sld xmlns:a=\"${NS_DRAWINGML}\" xmlns:r=\"${NS_R}\" xmlns:p=\"${NS_PML}\">` +\n `<p:cSld>` +\n bgXml +\n `<p:spTree>` +\n shapes.join('') +\n `</p:spTree>` +\n `</p:cSld>` +\n `</p:sld>`\n );\n}\n\nfunction buildTitleShape(title: string, style: SlideStyle): string {\n return (\n `<p:sp>` +\n `<p:nvSpPr>` +\n `<p:cNvPr id=\"2\" name=\"Title\"/>` +\n `<p:cNvSpPr><a:spLocks noGrp=\"1\"/></p:cNvSpPr>` +\n `<p:nvPr><p:ph type=\"title\"/></p:nvPr>` +\n `</p:nvSpPr>` +\n `<p:spPr>` +\n `<a:xfrm><a:off x=\"${TITLE_LEFT}\" y=\"${TITLE_TOP}\"/>` +\n `<a:ext cx=\"${TITLE_WIDTH}\" cy=\"${TITLE_HEIGHT}\"/></a:xfrm>` +\n `</p:spPr>` +\n `<p:txBody>` +\n `<a:bodyPr/>` +\n `<a:lstStyle/>` +\n `<a:p>` +\n `<a:r>` +\n `<a:rPr lang=\"en-US\" sz=\"${DEFAULT_TITLE_SIZE}\" dirty=\"0\">` +\n `<a:solidFill><a:srgbClr val=\"${style.titleColor}\"/></a:solidFill>` +\n `<a:latin typeface=\"${escapeXml(style.titleFont)}\"/>` +\n `</a:rPr>` +\n `<a:t>${escapeXml(title)}</a:t>` +\n `</a:r>` +\n `</a:p>` +\n `</p:txBody>` +\n `</p:sp>`\n );\n}\n\nfunction buildBodyShapeCustom(\n bodyContent: string,\n top: number,\n height: number,\n style: SlideStyle,\n): string {\n // Set default text color for the body via defRPr\n const defRPr = style.hasTheme\n ? `<a:defRPr><a:solidFill><a:srgbClr val=\"${style.text}\"/></a:solidFill><a:latin typeface=\"${escapeXml(style.bodyFont)}\"/></a:defRPr>`\n : '';\n\n return (\n `<p:sp>` +\n `<p:nvSpPr>` +\n `<p:cNvPr id=\"3\" name=\"Content\"/>` +\n `<p:cNvSpPr><a:spLocks noGrp=\"1\"/></p:cNvSpPr>` +\n `<p:nvPr><p:ph idx=\"1\"/></p:nvPr>` +\n `</p:nvSpPr>` +\n `<p:spPr>` +\n `<a:xfrm><a:off x=\"${BODY_LEFT}\" y=\"${top}\"/>` +\n `<a:ext cx=\"${BODY_WIDTH}\" cy=\"${height}\"/></a:xfrm>` +\n `</p:spPr>` +\n `<p:txBody>` +\n `<a:bodyPr/>` +\n `<a:lstStyle>` +\n `<a:lvl1pPr>${defRPr}</a:lvl1pPr>` +\n `</a:lstStyle>` +\n bodyContent +\n `</p:txBody>` +\n `</p:sp>`\n );\n}\n\nfunction buildImageShape(\n img: EmbeddedImage,\n shapeId: number,\n left: number,\n top: number,\n maxWidth: number,\n maxHeight: number,\n): string {\n return (\n `<p:pic>` +\n `<p:nvPicPr>` +\n `<p:cNvPr id=\"${shapeId}\" name=\"${escapeXml(img.alt || 'Picture')}\"/>` +\n `<p:cNvPicPr><a:picLocks noChangeAspect=\"1\"/></p:cNvPicPr>` +\n `<p:nvPr/>` +\n `</p:nvPicPr>` +\n `<p:blipFill>` +\n `<a:blip r:embed=\"${img.relId}\"/>` +\n `<a:stretch><a:fillRect/></a:stretch>` +\n `</p:blipFill>` +\n `<p:spPr>` +\n `<a:xfrm><a:off x=\"${left}\" y=\"${top}\"/>` +\n `<a:ext cx=\"${maxWidth}\" cy=\"${maxHeight}\"/></a:xfrm>` +\n `<a:prstGeom prst=\"rect\"><a:avLst/></a:prstGeom>` +\n `</p:spPr>` +\n `</p:pic>`\n );\n}\n\n// ============================================\n// Block Conversion (Markdown -> DrawingML paragraphs)\n// ============================================\n\nfunction convertBodyBlocks(nodes: MarkdownBlockNode[], ctx: SlideContext): string {\n const parts: string[] = [];\n for (const node of nodes) {\n parts.push(convertBlock(node, ctx, 0));\n }\n return parts.join('');\n}\n\nfunction convertBlock(node: MarkdownBlockNode, ctx: SlideContext, listDepth: number): string {\n switch (node.type) {\n case 'heading':\n return convertHeading(node, ctx);\n case 'paragraph':\n return convertParagraph(node, ctx);\n case 'blockquote':\n return convertBlockquote(node, ctx);\n case 'list':\n return convertList(node, ctx, listDepth);\n case 'code':\n return convertCodeBlock(node, ctx);\n case 'table':\n return convertTable(node, ctx);\n case 'thematicBreak':\n return convertThematicBreak(ctx);\n case 'htmlBlock':\n return convertHtmlBlock(node, ctx);\n case 'math':\n return convertMathBlock(node);\n default:\n return '';\n }\n}\n\nfunction convertHeading(node: MarkdownHeading, ctx: SlideContext): string {\n const size = headingSizeForDepth(node.depth);\n const runs = convertInlines(node.children, ctx, { bold: true });\n return (\n `<a:p>` +\n `<a:pPr>` +\n `<a:spcBef><a:spcPts val=\"600\"/></a:spcBef>` +\n `<a:defRPr sz=\"${size}\" b=\"1\">` +\n `<a:solidFill><a:srgbClr val=\"${ctx.style.titleColor}\"/></a:solidFill>` +\n `<a:latin typeface=\"${escapeXml(ctx.style.titleFont)}\"/>` +\n `</a:defRPr>` +\n `</a:pPr>` +\n runs +\n `</a:p>`\n );\n}\n\nfunction headingSizeForDepth(depth: number): number {\n const sizes: Record<number, number> = { 1: 3200, 2: 2800, 3: 2400, 4: 2000, 5: 1800, 6: 1800 };\n return sizes[depth] ?? 1800;\n}\n\n/**\n * Check if a paragraph contains only a single image (no surrounding text).\n */\nfunction isImageOnlyParagraph(node: MarkdownParagraph): MarkdownImage | null {\n if (node.children.length === 1 && node.children[0].type === 'image') {\n return node.children[0];\n }\n return null;\n}\n\nfunction convertParagraph(node: MarkdownParagraph, ctx: SlideContext): string {\n // If paragraph is just an image and we have the data, embed it as a shape\n const soloImage = isImageOnlyParagraph(node);\n if (soloImage && ctx.images) {\n const data = ctx.images.get(soloImage.url);\n if (data) {\n ctx.addImage(soloImage.url, data, soloImage.alt || '');\n return ''; // Image will be rendered as a p:pic shape, not inline text\n }\n }\n\n const runs = convertInlines(node.children, ctx);\n return `<a:p>${runs}</a:p>`;\n}\n\nfunction convertBlockquote(node: MarkdownBlockquote, ctx: SlideContext): string {\n const parts: string[] = [];\n for (const child of node.children) {\n if (child.type === 'paragraph') {\n const runs = convertInlines(child.children, ctx, { italic: true });\n parts.push(\n `<a:p>` +\n `<a:pPr marL=\"457200\">` +\n `<a:spcBef><a:spcPts val=\"200\"/></a:spcBef>` +\n `</a:pPr>` +\n runs +\n `</a:p>`,\n );\n } else {\n parts.push(convertBlock(child, ctx, 0));\n }\n }\n return parts.join('');\n}\n\nfunction convertList(node: MarkdownList, ctx: SlideContext, depth: number): string {\n const parts: string[] = [];\n for (const item of node.children) {\n parts.push(convertListItem(item, ctx, node.ordered ?? false, depth));\n }\n return parts.join('');\n}\n\nfunction convertListItem(\n item: MarkdownListItem,\n ctx: SlideContext,\n ordered: boolean,\n depth: number,\n): string {\n const parts: string[] = [];\n for (const child of item.children) {\n if (child.type === 'paragraph') {\n const runs = convertInlines(child.children, ctx);\n const bullet = ordered ? `<a:buAutoNum type=\"arabicPeriod\"/>` : `<a:buChar char=\"\\u2022\"/>`;\n const indent = 457200 * (depth + 1);\n parts.push(\n `<a:p>` +\n `<a:pPr lvl=\"${depth}\" marL=\"${indent}\" indent=\"-228600\">` +\n bullet +\n `</a:pPr>` +\n runs +\n `</a:p>`,\n );\n } else if (child.type === 'list') {\n parts.push(convertList(child, ctx, depth + 1));\n } else {\n parts.push(convertBlock(child, ctx, depth));\n }\n }\n return parts.join('');\n}\n\nfunction convertCodeBlock(node: MarkdownCodeBlock, ctx: SlideContext): string {\n const lines = node.value.split('\\n');\n const parts: string[] = [];\n for (const line of lines) {\n parts.push(\n `<a:p>` +\n `<a:r>` +\n `<a:rPr lang=\"en-US\" sz=\"${DEFAULT_CODE_SIZE}\" dirty=\"0\">` +\n `<a:latin typeface=\"${escapeXml(ctx.style.codeFont)}\"/>` +\n `<a:solidFill><a:srgbClr val=\"${ctx.style.codeColor}\"/></a:solidFill>` +\n `</a:rPr>` +\n `<a:t>${escapeXml(line || ' ')}</a:t>` +\n `</a:r>` +\n `</a:p>`,\n );\n }\n return parts.join('');\n}\n\nfunction convertTable(node: MarkdownTable, ctx: SlideContext): string {\n const parts: string[] = [];\n for (let ri = 0; ri < node.children.length; ri++) {\n const row = node.children[ri];\n parts.push(convertTableRow(row, ctx, ri === 0));\n }\n return parts.join('');\n}\n\nfunction convertTableRow(row: MarkdownTableRow, ctx: SlideContext, isHeader: boolean): string {\n const cells: string[] = [];\n for (const cell of row.children) {\n cells.push(extractPlainText(cell.children));\n }\n const text = cells.join(' | ');\n const format: InlineFormat = isHeader ? { bold: true } : {};\n return `<a:p>${makeRun(text, format, ctx.style)}</a:p>`;\n}\n\nfunction convertThematicBreak(ctx: SlideContext): string {\n return `<a:p><a:pPr><a:spcAft><a:spcPts val=\"1200\"/></a:spcAft></a:pPr><a:r><a:rPr lang=\"en-US\" sz=\"400\"><a:solidFill><a:srgbClr val=\"${ctx.style.mutedColor}\"/></a:solidFill></a:rPr><a:t>———</a:t></a:r></a:p>`;\n}\n\nfunction convertHtmlBlock(node: MarkdownHtmlBlock, ctx: SlideContext): string {\n const text = stripHtmlTags(node.rawHtml).trim();\n if (!text) return '';\n return `<a:p>${makeRun(text, {}, ctx.style)}</a:p>`;\n}\n\nfunction convertMathBlock(node: MarkdownMathBlock): string {\n return (\n `<a:p>` +\n `<a:pPr algn=\"ctr\"/>` +\n `<a:r>` +\n `<a:rPr lang=\"en-US\" i=\"1\">` +\n `<a:latin typeface=\"Cambria Math\"/>` +\n `</a:rPr>` +\n `<a:t>${escapeXml(node.value)}</a:t>` +\n `</a:r>` +\n `</a:p>`\n );\n}\n\n// ============================================\n// Inline Conversion (DrawingML runs)\n// ============================================\n\ninterface InlineFormat {\n bold?: boolean;\n italic?: boolean;\n strike?: boolean;\n code?: boolean;\n}\n\nfunction convertInlines(\n nodes: MarkdownInlineNode[],\n ctx: SlideContext,\n format: InlineFormat = {},\n): string {\n const parts: string[] = [];\n for (const node of nodes) {\n parts.push(convertInline(node, ctx, format));\n }\n return parts.join('');\n}\n\nfunction convertInline(node: MarkdownInlineNode, ctx: SlideContext, format: InlineFormat): string {\n switch (node.type) {\n case 'text':\n return makeRun(node.value, format, ctx.style);\n case 'strong':\n return convertInlines(node.children, ctx, { ...format, bold: true });\n case 'emphasis':\n return convertInlines(node.children, ctx, { ...format, italic: true });\n case 'delete':\n return convertInlines(node.children, ctx, { ...format, strike: true });\n case 'inlineCode':\n return makeRun(node.value, { ...format, code: true }, ctx.style);\n case 'link':\n return convertLink(node, ctx, format);\n case 'image':\n return convertImage(node, format, ctx);\n case 'break':\n return `<a:br/>`;\n case 'htmlInline':\n return makeRun(stripHtmlTags(node.rawHtml), format, ctx.style);\n case 'inlineMath':\n return makeRun(node.value, { ...format, code: true }, ctx.style);\n default:\n return '';\n }\n}\n\nfunction makeRun(text: string, format: InlineFormat, style: SlideStyle): string {\n if (!text) return '';\n\n const rPrParts: string[] = [`lang=\"en-US\"`, `dirty=\"0\"`];\n\n if (format.bold) rPrParts.push(`b=\"1\"`);\n if (format.italic) rPrParts.push(`i=\"1\"`);\n if (format.strike) rPrParts.push(`strike=\"sngStrike\"`);\n\n let innerParts = '';\n\n if (format.code) {\n innerParts += `<a:latin typeface=\"${escapeXml(style.codeFont)}\"/>`;\n innerParts += `<a:solidFill><a:srgbClr val=\"${style.codeColor}\"/></a:solidFill>`;\n rPrParts.push(`sz=\"${DEFAULT_CODE_SIZE}\"`);\n } else if (style.hasTheme) {\n innerParts += `<a:solidFill><a:srgbClr val=\"${style.text}\"/></a:solidFill>`;\n }\n\n return (\n `<a:r>` +\n `<a:rPr ${rPrParts.join(' ')}>${innerParts}</a:rPr>` +\n `<a:t>${escapeXml(text)}</a:t>` +\n `</a:r>`\n );\n}\n\nfunction convertLink(node: MarkdownLink, ctx: SlideContext, format: InlineFormat): string {\n const rId = ctx.addHyperlink(node.url);\n const parts: string[] = [];\n\n for (const child of node.children) {\n if (child.type === 'text') {\n parts.push(makeHyperlinkRun(child.value, rId, format));\n } else {\n parts.push(convertInline(child, ctx, format));\n }\n }\n\n return parts.join('');\n}\n\nfunction makeHyperlinkRun(text: string, rId: string, format: InlineFormat): string {\n if (!text) return '';\n\n const rPrParts: string[] = [`lang=\"en-US\"`, `dirty=\"0\"`];\n if (format.bold) rPrParts.push(`b=\"1\"`);\n if (format.italic) rPrParts.push(`i=\"1\"`);\n\n return (\n `<a:r>` +\n `<a:rPr ${rPrParts.join(' ')}>` +\n `<a:solidFill><a:srgbClr val=\"${HYPERLINK_COLOR}\"/></a:solidFill>` +\n `<a:hlinkClick r:id=\"${rId}\"/>` +\n `</a:rPr>` +\n `<a:t>${escapeXml(text)}</a:t>` +\n `</a:r>`\n );\n}\n\nfunction convertImage(node: MarkdownImage, format: InlineFormat, ctx: SlideContext): string {\n // For inline images (not image-only paragraphs), try to embed\n if (ctx.images) {\n const data = ctx.images.get(node.url);\n if (data) {\n // Register for rendering as a shape; return empty text\n ctx.addImage(node.url, data, node.alt || '');\n return '';\n }\n }\n // Fallback: placeholder text\n const alt = node.alt || node.url;\n return makeRun(`[Image: ${alt}]`, { ...format, italic: true }, ctx.style);\n}\n\n// ============================================\n// Package Assembly\n// ============================================\n\nasync function buildPptxPackage(\n slideXmls: string[],\n slideContexts: SlideContext[],\n options: PptxExportOptions,\n): Promise<ArrayBuffer> {\n const pkg = createPackage();\n const slideCount = slideXmls.length;\n\n const slideMasterRelId = 'rId100';\n const themeRelId = 'rId101';\n const slideRelIds: string[] = [];\n\n for (let i = 0; i < slideCount; i++) {\n slideRelIds.push(`rId${i + 1}`);\n }\n\n // --- ppt/presentation.xml ---\n const presentationXml = buildPresentationXml(\n slideCount,\n slideRelIds,\n slideMasterRelId,\n themeRelId,\n );\n pkg.addPart('ppt/presentation.xml', presentationXml, CONTENT_TYPE_PPTX_PRESENTATION);\n\n // --- Slides ---\n for (let i = 0; i < slideCount; i++) {\n const slidePath = `ppt/slides/slide${i + 1}.xml`;\n pkg.addPart(slidePath, slideXmls[i], CONTENT_TYPE_PPTX_SLIDE);\n\n pkg.addRelationship(slidePath, {\n id: 'rId1',\n type: REL_SLIDE_LAYOUT,\n target: '../slideLayouts/slideLayout1.xml',\n });\n\n // Per-slide relationships (hyperlinks + images)\n const ctx = slideContexts[i];\n for (const rel of ctx.relationships) {\n pkg.addRelationship(slidePath, {\n id: rel.id,\n type: rel.type,\n target: rel.target,\n ...(rel.targetMode ? { targetMode: rel.targetMode } : {}),\n });\n }\n\n // Embed image binary parts\n for (const img of ctx.embeddedImages) {\n pkg.addBinaryPart(img.mediaPath, img.data, img.contentType);\n }\n }\n\n // --- Slide layout ---\n const layoutMasterRelId = 'rId1';\n const slideLayoutXml = buildSlideLayoutXml(layoutMasterRelId);\n pkg.addPart('ppt/slideLayouts/slideLayout1.xml', slideLayoutXml, CONTENT_TYPE_PPTX_SLIDE_LAYOUT);\n pkg.addRelationship('ppt/slideLayouts/slideLayout1.xml', {\n id: layoutMasterRelId,\n type: REL_SLIDE_MASTER,\n target: '../slideMasters/slideMaster1.xml',\n });\n\n // --- Slide master ---\n const masterLayoutRelId = 'rId1';\n const masterThemeRelId = 'rId2';\n const slideMasterXml = buildSlideMasterXml(masterLayoutRelId);\n pkg.addPart('ppt/slideMasters/slideMaster1.xml', slideMasterXml, CONTENT_TYPE_PPTX_SLIDE_MASTER);\n pkg.addRelationship('ppt/slideMasters/slideMaster1.xml', {\n id: masterLayoutRelId,\n type: REL_SLIDE_LAYOUT,\n target: '../slideLayouts/slideLayout1.xml',\n });\n pkg.addRelationship('ppt/slideMasters/slideMaster1.xml', {\n id: masterThemeRelId,\n type: REL_THEME,\n target: '../theme/theme1.xml',\n });\n\n // --- Theme ---\n const themeXml = buildThemeXml();\n pkg.addPart('ppt/theme/theme1.xml', themeXml, CONTENT_TYPE_PPTX_THEME);\n\n // --- Root relationship ---\n pkg.addRelationship('', {\n id: 'rId1',\n type: REL_OFFICE_DOCUMENT,\n target: 'ppt/presentation.xml',\n });\n\n // --- Presentation relationships ---\n for (let i = 0; i < slideCount; i++) {\n pkg.addRelationship('ppt/presentation.xml', {\n id: slideRelIds[i],\n type: REL_SLIDE,\n target: `slides/slide${i + 1}.xml`,\n });\n }\n pkg.addRelationship('ppt/presentation.xml', {\n id: slideMasterRelId,\n type: REL_SLIDE_MASTER,\n target: 'slideMasters/slideMaster1.xml',\n });\n pkg.addRelationship('ppt/presentation.xml', {\n id: themeRelId,\n type: REL_THEME,\n target: 'theme/theme1.xml',\n });\n\n // --- Core properties ---\n if (options.title || options.author || options.description) {\n pkg.setCoreProperties({\n title: options.title,\n creator: options.author,\n description: options.description,\n created: new Date().toISOString(),\n modified: new Date().toISOString(),\n });\n }\n\n return pkg.toArrayBuffer();\n}\n\n// ============================================\n// Helpers\n// ============================================\n\nfunction stripHtmlTags(html: string): string {\n return html.replace(/<[^>]*>/g, '');\n}\n\nfunction inferExtension(path: string): string {\n const dot = path.lastIndexOf('.');\n if (dot === -1) return 'png';\n const ext = path\n .substring(dot + 1)\n .toLowerCase()\n .split('?')[0];\n return ext || 'png';\n}\n\nfunction inferMimeType(ext: string): string {\n const mimeTypes: Record<string, string> = {\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n png: 'image/png',\n gif: 'image/gif',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n bmp: 'image/bmp',\n avif: 'image/avif',\n };\n return mimeTypes[ext] || 'image/png';\n}\n","/**\n * PPTX Constants & Style Definitions\n *\n * Shared constants for PPTX export: slide dimensions, text box positioning,\n * font sizes, and unit conversion helpers. All dimensions are in EMU\n * (English Metric Units) unless otherwise noted.\n *\n * 1 inch = 914400 EMU\n * 1 point = 12700 EMU\n * Font sizes in PPTX are in hundredths of a point (centi-points).\n */\n\n// ============================================\n// Slide Dimensions (EMU)\n// ============================================\n\n/** Widescreen slide width: 10 inches */\nexport const SLIDE_WIDTH = 9144000;\n\n/** Widescreen slide height: 7.5 inches */\nexport const SLIDE_HEIGHT = 6858000;\n\n// ============================================\n// Title Shape Positioning (EMU)\n// ============================================\n\n/** Title shape left offset: 0.5 inches */\nexport const TITLE_LEFT = 457200;\n\n/** Title shape top offset: 0.3 inches */\nexport const TITLE_TOP = 274638;\n\n/** Title shape width: 9 inches */\nexport const TITLE_WIDTH = 8229600;\n\n/** Title shape height: 1.25 inches */\nexport const TITLE_HEIGHT = 1143000;\n\n// ============================================\n// Body Shape Positioning (EMU)\n// ============================================\n\n/** Body shape left offset: 0.5 inches */\nexport const BODY_LEFT = 457200;\n\n/** Body shape top offset: 1.75 inches */\nexport const BODY_TOP = 1600200;\n\n/** Body shape width: 9 inches */\nexport const BODY_WIDTH = 8229600;\n\n/** Body shape height: 4.95 inches */\nexport const BODY_HEIGHT = 4525963;\n\n// ============================================\n// Default Fonts & Sizes\n// ============================================\n\n/** Default font for body text */\nexport const DEFAULT_FONT = 'Calibri';\n\n/** Default font for titles */\nexport const DEFAULT_TITLE_FONT = 'Calibri Light';\n\n/** Default font for code blocks */\nexport const DEFAULT_CODE_FONT = 'Consolas';\n\n/** Default title font size in centi-points (44pt) */\nexport const DEFAULT_TITLE_SIZE = 4400;\n\n/** Default body font size in centi-points (18pt) */\nexport const DEFAULT_BODY_SIZE = 1800;\n\n/** Default code font size in centi-points (14pt) */\nexport const DEFAULT_CODE_SIZE = 1400;\n\n/** Hyperlink color (standard blue) */\nexport const HYPERLINK_COLOR = '0563C1';\n\n// ============================================\n// Unit Helpers\n// ============================================\n\n/**\n * Convert inches to EMUs.\n */\nexport function inchesToEmu(inches: number): number {\n return Math.round(inches * 914400);\n}\n\n/**\n * Convert points to centi-points (PPTX font size unit).\n */\nexport function pointsToCentiPoints(points: number): number {\n return Math.round(points * 100);\n}\n","/**\n * PPTX Boilerplate Templates\n *\n * Minimal but valid XML templates for the required PPTX support parts:\n * slide master, slide layout, and theme. These are semi-static — they\n * don't change per-document but are required for PowerPoint to open the file.\n */\n\nimport { xmlDeclaration } from '../ooxml/xmlUtils.js';\nimport { NS_PML, NS_DRAWINGML, NS_R } from '../ooxml/namespaces.js';\nimport {\n SLIDE_WIDTH,\n SLIDE_HEIGHT,\n TITLE_LEFT,\n TITLE_TOP,\n TITLE_WIDTH,\n TITLE_HEIGHT,\n BODY_LEFT,\n BODY_TOP,\n BODY_WIDTH,\n BODY_HEIGHT,\n} from './styles.js';\n\n/**\n * Build presentation.xml — the root document listing all slides.\n */\nexport function buildPresentationXml(\n slideCount: number,\n slideRelIds: string[],\n slideMasterRelId: string,\n _themeRelId: string,\n): string {\n const sldIdList: string[] = [];\n for (let i = 0; i < slideCount; i++) {\n // Slide IDs start at 256 (PowerPoint convention)\n sldIdList.push(`<p:sldId id=\"${256 + i}\" r:id=\"${slideRelIds[i]}\"/>`);\n }\n\n return (\n xmlDeclaration() +\n `<p:presentation xmlns:a=\"${NS_DRAWINGML}\" xmlns:r=\"${NS_R}\" xmlns:p=\"${NS_PML}\"` +\n ` saveSubsetFonts=\"1\">` +\n `<p:sldMasterIdLst>` +\n `<p:sldMasterId id=\"2147483648\" r:id=\"${slideMasterRelId}\"/>` +\n `</p:sldMasterIdLst>` +\n `<p:sldIdLst>${sldIdList.join('')}</p:sldIdLst>` +\n `<p:sldSz cx=\"${SLIDE_WIDTH}\" cy=\"${SLIDE_HEIGHT}\" type=\"screen4x3\"/>` +\n `<p:notesSz cx=\"${SLIDE_HEIGHT}\" cy=\"${SLIDE_WIDTH}\"/>` +\n `</p:presentation>`\n );\n}\n\n/**\n * Build a minimal slide master (slideMaster1.xml).\n */\nexport function buildSlideMasterXml(layoutRelId: string): string {\n return (\n xmlDeclaration() +\n `<p:sldMaster xmlns:a=\"${NS_DRAWINGML}\" xmlns:r=\"${NS_R}\" xmlns:p=\"${NS_PML}\">` +\n `<p:cSld>` +\n `<p:bg>` +\n `<p:bgRef idx=\"1001\">` +\n `<a:schemeClr val=\"bg1\"/>` +\n `</p:bgRef>` +\n `</p:bg>` +\n `<p:spTree>` +\n `<p:nvGrpSpPr><p:cNvPr id=\"1\" name=\"\"/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>` +\n `<p:grpSpPr/>` +\n `</p:spTree>` +\n `</p:cSld>` +\n `<p:clrMap bg1=\"lt1\" tx1=\"dk1\" bg2=\"lt2\" tx2=\"dk2\"` +\n ` accent1=\"accent1\" accent2=\"accent2\" accent3=\"accent3\" accent4=\"accent4\"` +\n ` accent5=\"accent5\" accent6=\"accent6\" hlink=\"hlink\" folHlink=\"folHlink\"/>` +\n `<p:sldLayoutIdLst>` +\n `<p:sldLayoutId id=\"2147483649\" r:id=\"${layoutRelId}\"/>` +\n `</p:sldLayoutIdLst>` +\n `</p:sldMaster>`\n );\n}\n\n/**\n * Build a minimal slide layout (slideLayout1.xml) — title + content.\n */\nexport function buildSlideLayoutXml(_masterRelId: string): string {\n return (\n xmlDeclaration() +\n `<p:sldLayout xmlns:a=\"${NS_DRAWINGML}\" xmlns:r=\"${NS_R}\" xmlns:p=\"${NS_PML}\"` +\n ` type=\"obj\" preserve=\"1\">` +\n `<p:cSld name=\"Title and Content\">` +\n `<p:spTree>` +\n `<p:nvGrpSpPr><p:cNvPr id=\"1\" name=\"\"/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>` +\n `<p:grpSpPr/>` +\n // Title placeholder\n `<p:sp>` +\n `<p:nvSpPr>` +\n `<p:cNvPr id=\"2\" name=\"Title 1\"/>` +\n `<p:cNvSpPr><a:spLocks noGrp=\"1\"/></p:cNvSpPr>` +\n `<p:nvPr><p:ph type=\"title\"/></p:nvPr>` +\n `</p:nvSpPr>` +\n `<p:spPr>` +\n `<a:xfrm><a:off x=\"${TITLE_LEFT}\" y=\"${TITLE_TOP}\"/>` +\n `<a:ext cx=\"${TITLE_WIDTH}\" cy=\"${TITLE_HEIGHT}\"/></a:xfrm>` +\n `</p:spPr>` +\n `<p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:endParaRPr lang=\"en-US\"/></a:p></p:txBody>` +\n `</p:sp>` +\n // Content placeholder\n `<p:sp>` +\n `<p:nvSpPr>` +\n `<p:cNvPr id=\"3\" name=\"Content 2\"/>` +\n `<p:cNvSpPr><a:spLocks noGrp=\"1\"/></p:cNvSpPr>` +\n `<p:nvPr><p:ph idx=\"1\"/></p:nvPr>` +\n `</p:nvSpPr>` +\n `<p:spPr>` +\n `<a:xfrm><a:off x=\"${BODY_LEFT}\" y=\"${BODY_TOP}\"/>` +\n `<a:ext cx=\"${BODY_WIDTH}\" cy=\"${BODY_HEIGHT}\"/></a:xfrm>` +\n `</p:spPr>` +\n `<p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:endParaRPr lang=\"en-US\"/></a:p></p:txBody>` +\n `</p:sp>` +\n `</p:spTree>` +\n `</p:cSld>` +\n `</p:sldLayout>`\n );\n}\n\n/**\n * Build a minimal DrawingML theme (theme1.xml).\n */\nexport function buildThemeXml(): string {\n return (\n xmlDeclaration() +\n `<a:theme xmlns:a=\"${NS_DRAWINGML}\" name=\"Office Theme\">` +\n `<a:themeElements>` +\n // Color scheme\n `<a:clrScheme name=\"Office\">` +\n `<a:dk1><a:sysClr val=\"windowText\" lastClr=\"000000\"/></a:dk1>` +\n `<a:lt1><a:sysClr val=\"window\" lastClr=\"FFFFFF\"/></a:lt1>` +\n `<a:dk2><a:srgbClr val=\"44546A\"/></a:dk2>` +\n `<a:lt2><a:srgbClr val=\"E7E6E6\"/></a:lt2>` +\n `<a:accent1><a:srgbClr val=\"4472C4\"/></a:accent1>` +\n `<a:accent2><a:srgbClr val=\"ED7D31\"/></a:accent2>` +\n `<a:accent3><a:srgbClr val=\"A5A5A5\"/></a:accent3>` +\n `<a:accent4><a:srgbClr val=\"FFC000\"/></a:accent4>` +\n `<a:accent5><a:srgbClr val=\"5B9BD5\"/></a:accent5>` +\n `<a:accent6><a:srgbClr val=\"70AD47\"/></a:accent6>` +\n `<a:hlink><a:srgbClr val=\"0563C1\"/></a:hlink>` +\n `<a:folHlink><a:srgbClr val=\"954F72\"/></a:folHlink>` +\n `</a:clrScheme>` +\n // Font scheme\n `<a:fontScheme name=\"Office\">` +\n `<a:majorFont>` +\n `<a:latin typeface=\"Calibri Light\"/>` +\n `<a:ea typeface=\"\"/>` +\n `<a:cs typeface=\"\"/>` +\n `</a:majorFont>` +\n `<a:minorFont>` +\n `<a:latin typeface=\"Calibri\"/>` +\n `<a:ea typeface=\"\"/>` +\n `<a:cs typeface=\"\"/>` +\n `</a:minorFont>` +\n `</a:fontScheme>` +\n // Format scheme (minimal)\n `<a:fmtScheme name=\"Office\">` +\n `<a:fillStyleLst>` +\n `<a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill>` +\n `<a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill>` +\n `<a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill>` +\n `</a:fillStyleLst>` +\n `<a:lnStyleLst>` +\n `<a:ln w=\"6350\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill></a:ln>` +\n `<a:ln w=\"6350\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill></a:ln>` +\n `<a:ln w=\"6350\"><a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill></a:ln>` +\n `</a:lnStyleLst>` +\n `<a:effectStyleLst>` +\n `<a:effectStyle><a:effectLst/></a:effectStyle>` +\n `<a:effectStyle><a:effectLst/></a:effectStyle>` +\n `<a:effectStyle><a:effectLst/></a:effectStyle>` +\n `</a:effectStyleLst>` +\n `<a:bgFillStyleLst>` +\n `<a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill>` +\n `<a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill>` +\n `<a:solidFill><a:schemeClr val=\"phClr\"/></a:solidFill>` +\n `</a:bgFillStyleLst>` +\n `</a:fmtScheme>` +\n `</a:themeElements>` +\n `</a:theme>`\n );\n}\n","/**\n * @bendyline/squisq-formats PPTX Module\n *\n * PowerPoint .pptx export support using PresentationML (`<p:presentation>`,\n * `<p:sld>`) via the shared ooxml/ infrastructure.\n *\n * Slide segmentation: each H1/H2 heading starts a new slide by default.\n * Inline formatting (bold, italic, code, links) is preserved as DrawingML runs.\n *\n * Import is not yet implemented.\n *\n * @example\n * ```ts\n * import { markdownDocToPptx } from '@bendyline/squisq-formats/pptx';\n * ```\n */\n\n// Export\nexport { markdownDocToPptx, docToPptx } from './export.js';\nexport type { PptxExportOptions } from './export.js';\n\n// Import (stubs — not yet implemented)\nimport type { MarkdownDocument } from '@bendyline/squisq/markdown';\nimport type { Doc } from '@bendyline/squisq/schemas';\n\n/**\n * Options for PPTX import (placeholder).\n */\nexport interface PptxImportOptions {\n /** Whether to extract embedded images as data URIs */\n extractImages?: boolean;\n}\n\n/**\n * Convert a .pptx file to a MarkdownDocument.\n *\n * @throws Error — PPTX import is not yet implemented\n */\nexport async function pptxToMarkdownDoc(\n _data: ArrayBuffer | Blob,\n _options?: PptxImportOptions,\n): Promise<MarkdownDocument> {\n throw new Error('PPTX import is not yet implemented');\n}\n\n/**\n * Convert a .pptx file to a squisq Doc.\n *\n * @throws Error — PPTX import is not yet implemented\n */\nexport async function pptxToDoc(\n _data: ArrayBuffer | Blob,\n _options?: PptxImportOptions,\n): Promise<Doc> {\n throw new Error('PPTX import is not yet implemented');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA8BA,SAAS,cAAc,yBAAyB;AAEhD,SAAS,qBAAqB;AAkB9B,SAAS,8BAA8B;;;ACjChC,IAAM,cAAc;AAGpB,IAAM,eAAe;AAOrB,IAAM,aAAa;AAGnB,IAAM,YAAY;AAGlB,IAAM,cAAc;AAGpB,IAAM,eAAe;AAOrB,IAAM,YAAY;AAGlB,IAAM,WAAW;AAGjB,IAAM,aAAa;AAGnB,IAAM,cAAc;AAOpB,IAAM,eAAe;AAGrB,IAAM,qBAAqB;AAG3B,IAAM,oBAAoB;AAG1B,IAAM,qBAAqB;AAM3B,IAAM,oBAAoB;AAG1B,IAAM,kBAAkB;;;ACnDxB,SAAS,qBACd,YACA,aACA,kBACA,aACQ;AACR,QAAM,YAAsB,CAAC;AAC7B,WAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AAEnC,cAAU,KAAK,gBAAgB,MAAM,CAAC,WAAW,YAAY,CAAC,CAAC,KAAK;AAAA,EACtE;AAEA,SACE,eAAe,IACf,4BAA4B,YAAY,cAAc,IAAI,cAAc,MAAM,gFAGtC,gBAAgB,qCAEzC,UAAU,KAAK,EAAE,CAAC,6BACjB,WAAW,SAAS,YAAY,sCAC9B,YAAY,SAAS,WAAW;AAGtD;AAKO,SAAS,oBAAoB,aAA6B;AAC/D,SACE,eAAe,IACf,yBAAyB,YAAY,cAAc,IAAI,cAAc,MAAM,8bAgBnC,WAAW;AAIvD;AAKO,SAAS,oBAAoB,cAA8B;AAChE,SACE,eAAe,IACf,yBAAyB,YAAY,cAAc,IAAI,cAAc,MAAM,uUActD,UAAU,QAAQ,SAAS,iBAClC,WAAW,SAAS,YAAY,uRAYzB,SAAS,QAAQ,QAAQ,iBAChC,UAAU,SAAS,WAAW;AAQhD;AAKO,SAAS,gBAAwB;AACtC,SACE,eAAe,IACf,qBAAqB,YAAY;AAwDrC;;;AFlDA,eAAsB,kBACpB,KACA,UAA6B,CAAC,GACR;AAKtB,QAAM,UAAU,QAAQ,WAAW,uBAAuB,IAAI,WAAW;AACzE,QAAM,QAAQ,kBAAkB,SAAS,OAAO;AAEhD,QAAM,SAAS,kBAAkB,IAAI,UAAU,QAAQ,cAAc,IAAI;AAGzE,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC;AAAA,EAC/B;AAEA,QAAM,YAAsB,CAAC;AAC7B,QAAM,gBAAgC,CAAC;AAEvC,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,MAAM,IAAI,aAAa,OAAO,QAAQ,QAAQ,CAAC;AACrD,UAAM,MAAM,cAAc,OAAO,CAAC,GAAG,GAAG;AACxC,cAAU,KAAK,GAAG;AAClB,kBAAc,KAAK,GAAG;AAAA,EACxB;AAEA,SAAO,iBAAiB,WAAW,eAAe,OAAO;AAC3D;AAOA,eAAsB,UAAU,KAAU,UAA6B,CAAC,GAAyB;AAC/F,QAAM,cAAc,cAAc,GAAG;AAErC,MAAI,CAAC,QAAQ,WAAW,IAAI,SAAS;AACnC,cAAU,EAAE,GAAG,SAAS,SAAS,IAAI,QAAQ;AAAA,EAC/C;AACA,SAAO,kBAAkB,aAAa,OAAO;AAC/C;AAmBA,SAAS,kBAAkB,SAA6B,SAAwC;AAC9F,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,MACL,YAAY;AAAA,MACZ,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,WAAW,QAAQ,eAAe;AAAA,MAClC,UAAU,QAAQ,eAAe;AAAA,MACjC,UAAU;AAAA,MACV,WAAW;AAAA,MACX,UAAU;AAAA,IACZ;AAAA,EACF;AAEA,QAAM,QAAe,aAAa,OAAO;AACzC,QAAM,IAAI,MAAM;AAEhB,SAAO;AAAA,IACL,YAAY,UAAU,EAAE,UAAU;AAAA,IAClC,MAAM,UAAU,EAAE,IAAI;AAAA,IACtB,YAAY,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,IAAI;AAAA,IAC1D,YAAY,UAAU,EAAE,aAAa,EAAE,IAAI;AAAA,IAC3C,WAAW,kBAAkB,MAAM,YAAY,WAAW,kBAAkB;AAAA,IAC5E,UAAU,kBAAkB,MAAM,YAAY,UAAU,QAAQ,eAAe,YAAY;AAAA,IAC3F,UAAU,kBAAkB,MAAM,YAAY,UAAU,iBAAiB;AAAA,IACzE,WAAW,UAAU,EAAE,aAAa,EAAE,IAAI;AAAA,IAC1C,UAAU;AAAA,EACZ;AACF;AAEA,SAAS,UAAU,OAAuB;AACxC,SAAO,MAAM,WAAW,GAAG,IAAI,MAAM,MAAM,CAAC,IAAI;AAClD;AAYA,SAAS,kBACP,UACA,YACa;AACb,QAAM,WAAW,eAAe,OAAO,IAAI,eAAe,OAAO,IAAI;AACrE,QAAM,SAAsB,CAAC;AAC7B,MAAI;AAEJ,aAAW,QAAQ,UAAU;AAC3B,QAAI,KAAK,SAAS,aAAa,KAAK,SAAS,UAAU;AACrD,UAAI,QAAS,QAAO,KAAK,OAAO;AAChC,gBAAU;AAAA,QACR,OAAO,iBAAiB,KAAK,QAAQ;AAAA,QACrC,YAAY,KAAK;AAAA,QACjB,WAAW,CAAC;AAAA,MACd;AAAA,IACF,OAAO;AACL,UAAI,CAAC,SAAS;AACZ,kBAAU,EAAE,WAAW,CAAC,EAAE;AAAA,MAC5B;AACA,cAAQ,UAAU,KAAK,IAAI;AAAA,IAC7B;AAAA,EACF;AAEA,MAAI,QAAS,QAAO,KAAK,OAAO;AAChC,SAAO;AACT;AAEA,SAAS,iBAAiB,OAAqC;AAC7D,QAAM,QAAkB,CAAC;AACzB,aAAW,QAAQ,OAAO;AACxB,YAAQ,KAAK,MAAM;AAAA,MACjB,KAAK;AACH,cAAM,KAAK,KAAK,KAAK;AACrB;AAAA,MACF,KAAK;AACH,cAAM,KAAK,KAAK,KAAK;AACrB;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AACH,cAAM,KAAK,iBAAiB,KAAK,QAAQ,CAAC;AAC1C;AAAA,MACF,KAAK;AACH,cAAM,KAAK,iBAAiB,KAAK,QAAQ,CAAC;AAC1C;AAAA,MACF,KAAK;AACH,cAAM,KAAK,KAAK,OAAO,EAAE;AACzB;AAAA,MACF,KAAK;AACH,cAAM,KAAK,KAAK,KAAK;AACrB;AAAA,MACF;AACE;AAAA,IACJ;AAAA,EACF;AACA,SAAO,MAAM,KAAK,EAAE;AACtB;AAcA,IAAM,eAAN,MAAmB;AAAA;AAAA,EAgBjB,YAAY,OAAmB,QAA8C,YAAoB;AAfjG,SAAQ,YAAY;AAEpB,SAAS,gBAKJ,CAAC;AAKN,SAAS,iBAAkC,CAAC;AAC5C,SAAQ,cAAc;AAGpB,SAAK,QAAQ;AACb,SAAK,SAAS;AACd,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,aAAqB;AACnB,WAAO,MAAM,KAAK,cAAc,CAAC;AAAA,EACnC;AAAA,EAEA,eAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,aAAa,KAAqB;AAChC,UAAM,KAAK,KAAK,WAAW;AAC3B,SAAK,cAAc,KAAK;AAAA,MACtB;AAAA,MACA,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,YAAY;AAAA,IACd,CAAC;AACD,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,SAAS,KAAa,MAAmB,KAAqB;AAC5D,UAAM,QAAQ,KAAK,WAAW;AAC9B,UAAM,MAAM,eAAe,GAAG;AAC9B,UAAM,cAAc,cAAc,GAAG;AACrC,UAAM,WAAW,KAAK,eAAe,SAAS;AAC9C,UAAM,YAAY,oBAAoB,KAAK,UAAU,IAAI,QAAQ,IAAI,GAAG;AAExE,SAAK,eAAe,KAAK,EAAE,OAAO,WAAW,MAAM,aAAa,IAAI,CAAC;AACrE,SAAK,cAAc,KAAK;AAAA,MACtB,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,QAAQ,mBAAmB,KAAK,UAAU,IAAI,QAAQ,IAAI,GAAG;AAAA,IAC/D,CAAC;AAED,WAAO;AAAA,EACT;AACF;AAMA,SAAS,cAAc,OAAkB,KAA2B;AAClE,QAAM,SAAmB,CAAC;AAE1B,SAAO;AAAA,IACL;AAAA,EACF;AAEA,MAAI,MAAM,OAAO;AACf,WAAO,KAAK,gBAAgB,MAAM,OAAO,IAAI,KAAK,CAAC;AAAA,EACrD;AAGA,QAAM,YAAY,kBAAkB,MAAM,WAAW,GAAG;AACxD,QAAM,YAAY,IAAI,eAAe,SAAS;AAC9C,QAAM,UAAU,UAAU,SAAS;AAEnC,MAAI,WAAW,WAAW;AAExB,UAAM,aAAa,KAAK,MAAM,cAAc,IAAI;AAChD,UAAM,SAAS,WAAW,aAAa;AACvC,UAAM,YAAY,cAAc,aAAa;AAC7C,WAAO,KAAK,qBAAqB,WAAW,UAAU,YAAY,IAAI,KAAK,CAAC;AAC5E,WAAO;AAAA,MACL;AAAA,QACE,IAAI,eAAe,CAAC;AAAA,QACpB,IAAI,aAAa;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF,WAAW,aAAa,CAAC,SAAS;AAEhC,WAAO;AAAA,MACL;AAAA,QACE,IAAI,eAAe,CAAC;AAAA,QACpB,IAAI,aAAa;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF,OAAO;AAEL,WAAO;AAAA,MACL;AAAA,QACE,aAAa;AAAA,QACb;AAAA,QACA;AAAA,QACA,IAAI;AAAA,MACN;AAAA,IACF;AAAA,EACF;AAGA,WAAS,IAAI,GAAG,IAAI,IAAI,eAAe,QAAQ,KAAK;AAClD,WAAO;AAAA,MACL;AAAA,QACE,IAAI,eAAe,CAAC;AAAA,QACpB,IAAI,aAAa;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,QAAM,QAAQ,IAAI,MAAM,WACpB,8CAA8C,IAAI,MAAM,UAAU,oDAClE;AAEJ,SACE,eAAe,IACf,mBAAmB,YAAY,cAAc,IAAI,cAAc,MAAM,eAErE,QACA,eACA,OAAO,KAAK,EAAE,IACd;AAIJ;AAEA,SAAS,gBAAgB,OAAe,OAA2B;AACjE,SACE,wKAOqB,UAAU,QAAQ,SAAS,iBAClC,WAAW,SAAS,YAAY,4FAOnB,kBAAkB,4CACb,MAAM,UAAU,uCAC1B,UAAU,MAAM,SAAS,CAAC,mBAExC,UAAU,KAAK,CAAC;AAM5B;AAEA,SAAS,qBACP,aACA,KACA,QACA,OACQ;AAER,QAAM,SAAS,MAAM,WACjB,0CAA0C,MAAM,IAAI,uCAAuC,UAAU,MAAM,QAAQ,CAAC,mBACpH;AAEJ,SACE,qKAOqB,SAAS,QAAQ,GAAG,iBAC3B,UAAU,SAAS,MAAM,oEAKzB,MAAM,8BAEpB,cACA;AAGJ;AAEA,SAAS,gBACP,KACA,SACA,MACA,KACA,UACA,WACQ;AACR,SACE,kCAEgB,OAAO,WAAW,UAAU,IAAI,OAAO,SAAS,CAAC,iHAK7C,IAAI,KAAK,iFAIR,IAAI,QAAQ,GAAG,iBACtB,QAAQ,SAAS,SAAS;AAK5C;AAMA,SAAS,kBAAkB,OAA4B,KAA2B;AAChF,QAAM,QAAkB,CAAC;AACzB,aAAW,QAAQ,OAAO;AACxB,UAAM,KAAK,aAAa,MAAM,KAAK,CAAC,CAAC;AAAA,EACvC;AACA,SAAO,MAAM,KAAK,EAAE;AACtB;AAEA,SAAS,aAAa,MAAyB,KAAmB,WAA2B;AAC3F,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AACH,aAAO,eAAe,MAAM,GAAG;AAAA,IACjC,KAAK;AACH,aAAO,iBAAiB,MAAM,GAAG;AAAA,IACnC,KAAK;AACH,aAAO,kBAAkB,MAAM,GAAG;AAAA,IACpC,KAAK;AACH,aAAO,YAAY,MAAM,KAAK,SAAS;AAAA,IACzC,KAAK;AACH,aAAO,iBAAiB,MAAM,GAAG;AAAA,IACnC,KAAK;AACH,aAAO,aAAa,MAAM,GAAG;AAAA,IAC/B,KAAK;AACH,aAAO,qBAAqB,GAAG;AAAA,IACjC,KAAK;AACH,aAAO,iBAAiB,MAAM,GAAG;AAAA,IACnC,KAAK;AACH,aAAO,iBAAiB,IAAI;AAAA,IAC9B;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,eAAe,MAAuB,KAA2B;AACxE,QAAM,OAAO,oBAAoB,KAAK,KAAK;AAC3C,QAAM,OAAO,eAAe,KAAK,UAAU,KAAK,EAAE,MAAM,KAAK,CAAC;AAC9D,SACE,uEAGiB,IAAI,wCACW,IAAI,MAAM,UAAU,uCAC9B,UAAU,IAAI,MAAM,SAAS,CAAC,2BAGpD,OACA;AAEJ;AAEA,SAAS,oBAAoB,OAAuB;AAClD,QAAM,QAAgC,EAAE,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,KAAM,GAAG,MAAM,GAAG,KAAK;AAC7F,SAAO,MAAM,KAAK,KAAK;AACzB;AAKA,SAAS,qBAAqB,MAA+C;AAC3E,MAAI,KAAK,SAAS,WAAW,KAAK,KAAK,SAAS,CAAC,EAAE,SAAS,SAAS;AACnE,WAAO,KAAK,SAAS,CAAC;AAAA,EACxB;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,MAAyB,KAA2B;AAE5E,QAAM,YAAY,qBAAqB,IAAI;AAC3C,MAAI,aAAa,IAAI,QAAQ;AAC3B,UAAM,OAAO,IAAI,OAAO,IAAI,UAAU,GAAG;AACzC,QAAI,MAAM;AACR,UAAI,SAAS,UAAU,KAAK,MAAM,UAAU,OAAO,EAAE;AACrD,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,OAAO,eAAe,KAAK,UAAU,GAAG;AAC9C,SAAO,QAAQ,IAAI;AACrB;AAEA,SAAS,kBAAkB,MAA0B,KAA2B;AAC9E,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,KAAK,UAAU;AACjC,QAAI,MAAM,SAAS,aAAa;AAC9B,YAAM,OAAO,eAAe,MAAM,UAAU,KAAK,EAAE,QAAQ,KAAK,CAAC;AACjE,YAAM;AAAA,QACJ,iFAIE,OACA;AAAA,MACJ;AAAA,IACF,OAAO;AACL,YAAM,KAAK,aAAa,OAAO,KAAK,CAAC,CAAC;AAAA,IACxC;AAAA,EACF;AACA,SAAO,MAAM,KAAK,EAAE;AACtB;AAEA,SAAS,YAAY,MAAoB,KAAmB,OAAuB;AACjF,QAAM,QAAkB,CAAC;AACzB,aAAW,QAAQ,KAAK,UAAU;AAChC,UAAM,KAAK,gBAAgB,MAAM,KAAK,KAAK,WAAW,OAAO,KAAK,CAAC;AAAA,EACrE;AACA,SAAO,MAAM,KAAK,EAAE;AACtB;AAEA,SAAS,gBACP,MACA,KACA,SACA,OACQ;AACR,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,KAAK,UAAU;AACjC,QAAI,MAAM,SAAS,aAAa;AAC9B,YAAM,OAAO,eAAe,MAAM,UAAU,GAAG;AAC/C,YAAM,SAAS,UAAU,uCAAuC;AAChE,YAAM,SAAS,UAAU,QAAQ;AACjC,YAAM;AAAA,QACJ,oBACiB,KAAK,WAAW,MAAM,wBACrC,SACA,aACA,OACA;AAAA,MACJ;AAAA,IACF,WAAW,MAAM,SAAS,QAAQ;AAChC,YAAM,KAAK,YAAY,OAAO,KAAK,QAAQ,CAAC,CAAC;AAAA,IAC/C,OAAO;AACL,YAAM,KAAK,aAAa,OAAO,KAAK,KAAK,CAAC;AAAA,IAC5C;AAAA,EACF;AACA,SAAO,MAAM,KAAK,EAAE;AACtB;AAEA,SAAS,iBAAiB,MAAyB,KAA2B;AAC5E,QAAM,QAAQ,KAAK,MAAM,MAAM,IAAI;AACnC,QAAM,QAAkB,CAAC;AACzB,aAAW,QAAQ,OAAO;AACxB,UAAM;AAAA,MACJ,qCAE6B,iBAAiB,kCACtB,UAAU,IAAI,MAAM,QAAQ,CAAC,mCACnB,IAAI,MAAM,SAAS,iCAE3C,UAAU,QAAQ,GAAG,CAAC;AAAA,IAGlC;AAAA,EACF;AACA,SAAO,MAAM,KAAK,EAAE;AACtB;AAEA,SAAS,aAAa,MAAqB,KAA2B;AACpE,QAAM,QAAkB,CAAC;AACzB,WAAS,KAAK,GAAG,KAAK,KAAK,SAAS,QAAQ,MAAM;AAChD,UAAM,MAAM,KAAK,SAAS,EAAE;AAC5B,UAAM,KAAK,gBAAgB,KAAK,KAAK,OAAO,CAAC,CAAC;AAAA,EAChD;AACA,SAAO,MAAM,KAAK,EAAE;AACtB;AAEA,SAAS,gBAAgB,KAAuB,KAAmB,UAA2B;AAC5F,QAAM,QAAkB,CAAC;AACzB,aAAW,QAAQ,IAAI,UAAU;AAC/B,UAAM,KAAK,iBAAiB,KAAK,QAAQ,CAAC;AAAA,EAC5C;AACA,QAAM,OAAO,MAAM,KAAK,OAAO;AAC/B,QAAM,SAAuB,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;AAC1D,SAAO,QAAQ,QAAQ,MAAM,QAAQ,IAAI,KAAK,CAAC;AACjD;AAEA,SAAS,qBAAqB,KAA2B;AACvD,SAAO,iIAAiI,IAAI,MAAM,UAAU;AAC9J;AAEA,SAAS,iBAAiB,MAAyB,KAA2B;AAC5E,QAAM,OAAO,cAAc,KAAK,OAAO,EAAE,KAAK;AAC9C,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,QAAQ,QAAQ,MAAM,CAAC,GAAG,IAAI,KAAK,CAAC;AAC7C;AAEA,SAAS,iBAAiB,MAAiC;AACzD,SACE,yGAMQ,UAAU,KAAK,KAAK,CAAC;AAIjC;AAaA,SAAS,eACP,OACA,KACA,SAAuB,CAAC,GAChB;AACR,QAAM,QAAkB,CAAC;AACzB,aAAW,QAAQ,OAAO;AACxB,UAAM,KAAK,cAAc,MAAM,KAAK,MAAM,CAAC;AAAA,EAC7C;AACA,SAAO,MAAM,KAAK,EAAE;AACtB;AAEA,SAAS,cAAc,MAA0B,KAAmB,QAA8B;AAChG,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AACH,aAAO,QAAQ,KAAK,OAAO,QAAQ,IAAI,KAAK;AAAA,IAC9C,KAAK;AACH,aAAO,eAAe,KAAK,UAAU,KAAK,EAAE,GAAG,QAAQ,MAAM,KAAK,CAAC;AAAA,IACrE,KAAK;AACH,aAAO,eAAe,KAAK,UAAU,KAAK,EAAE,GAAG,QAAQ,QAAQ,KAAK,CAAC;AAAA,IACvE,KAAK;AACH,aAAO,eAAe,KAAK,UAAU,KAAK,EAAE,GAAG,QAAQ,QAAQ,KAAK,CAAC;AAAA,IACvE,KAAK;AACH,aAAO,QAAQ,KAAK,OAAO,EAAE,GAAG,QAAQ,MAAM,KAAK,GAAG,IAAI,KAAK;AAAA,IACjE,KAAK;AACH,aAAO,YAAY,MAAM,KAAK,MAAM;AAAA,IACtC,KAAK;AACH,aAAO,aAAa,MAAM,QAAQ,GAAG;AAAA,IACvC,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,QAAQ,cAAc,KAAK,OAAO,GAAG,QAAQ,IAAI,KAAK;AAAA,IAC/D,KAAK;AACH,aAAO,QAAQ,KAAK,OAAO,EAAE,GAAG,QAAQ,MAAM,KAAK,GAAG,IAAI,KAAK;AAAA,IACjE;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,QAAQ,MAAc,QAAsB,OAA2B;AAC9E,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,WAAqB,CAAC,gBAAgB,WAAW;AAEvD,MAAI,OAAO,KAAM,UAAS,KAAK,OAAO;AACtC,MAAI,OAAO,OAAQ,UAAS,KAAK,OAAO;AACxC,MAAI,OAAO,OAAQ,UAAS,KAAK,oBAAoB;AAErD,MAAI,aAAa;AAEjB,MAAI,OAAO,MAAM;AACf,kBAAc,sBAAsB,UAAU,MAAM,QAAQ,CAAC;AAC7D,kBAAc,gCAAgC,MAAM,SAAS;AAC7D,aAAS,KAAK,OAAO,iBAAiB,GAAG;AAAA,EAC3C,WAAW,MAAM,UAAU;AACzB,kBAAc,gCAAgC,MAAM,IAAI;AAAA,EAC1D;AAEA,SACE,eACU,SAAS,KAAK,GAAG,CAAC,IAAI,UAAU,gBAClC,UAAU,IAAI,CAAC;AAG3B;AAEA,SAAS,YAAY,MAAoB,KAAmB,QAA8B;AACxF,QAAM,MAAM,IAAI,aAAa,KAAK,GAAG;AACrC,QAAM,QAAkB,CAAC;AAEzB,aAAW,SAAS,KAAK,UAAU;AACjC,QAAI,MAAM,SAAS,QAAQ;AACzB,YAAM,KAAK,iBAAiB,MAAM,OAAO,KAAK,MAAM,CAAC;AAAA,IACvD,OAAO;AACL,YAAM,KAAK,cAAc,OAAO,KAAK,MAAM,CAAC;AAAA,IAC9C;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,EAAE;AACtB;AAEA,SAAS,iBAAiB,MAAc,KAAa,QAA8B;AACjF,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,WAAqB,CAAC,gBAAgB,WAAW;AACvD,MAAI,OAAO,KAAM,UAAS,KAAK,OAAO;AACtC,MAAI,OAAO,OAAQ,UAAS,KAAK,OAAO;AAExC,SACE,eACU,SAAS,KAAK,GAAG,CAAC,iCACI,eAAe,wCACxB,GAAG,mBAElB,UAAU,IAAI,CAAC;AAG3B;AAEA,SAAS,aAAa,MAAqB,QAAsB,KAA2B;AAE1F,MAAI,IAAI,QAAQ;AACd,UAAM,OAAO,IAAI,OAAO,IAAI,KAAK,GAAG;AACpC,QAAI,MAAM;AAER,UAAI,SAAS,KAAK,KAAK,MAAM,KAAK,OAAO,EAAE;AAC3C,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,MAAM,KAAK,OAAO,KAAK;AAC7B,SAAO,QAAQ,WAAW,GAAG,KAAK,EAAE,GAAG,QAAQ,QAAQ,KAAK,GAAG,IAAI,KAAK;AAC1E;AAMA,eAAe,iBACb,WACA,eACA,SACsB;AACtB,QAAM,MAAM,cAAc;AAC1B,QAAM,aAAa,UAAU;AAE7B,QAAM,mBAAmB;AACzB,QAAM,aAAa;AACnB,QAAM,cAAwB,CAAC;AAE/B,WAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,gBAAY,KAAK,MAAM,IAAI,CAAC,EAAE;AAAA,EAChC;AAGA,QAAM,kBAAkB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,QAAQ,wBAAwB,iBAAiB,8BAA8B;AAGnF,WAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,UAAM,YAAY,mBAAmB,IAAI,CAAC;AAC1C,QAAI,QAAQ,WAAW,UAAU,CAAC,GAAG,uBAAuB;AAE5D,QAAI,gBAAgB,WAAW;AAAA,MAC7B,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,QAAQ;AAAA,IACV,CAAC;AAGD,UAAM,MAAM,cAAc,CAAC;AAC3B,eAAW,OAAO,IAAI,eAAe;AACnC,UAAI,gBAAgB,WAAW;AAAA,QAC7B,IAAI,IAAI;AAAA,QACR,MAAM,IAAI;AAAA,QACV,QAAQ,IAAI;AAAA,QACZ,GAAI,IAAI,aAAa,EAAE,YAAY,IAAI,WAAW,IAAI,CAAC;AAAA,MACzD,CAAC;AAAA,IACH;AAGA,eAAW,OAAO,IAAI,gBAAgB;AACpC,UAAI,cAAc,IAAI,WAAW,IAAI,MAAM,IAAI,WAAW;AAAA,IAC5D;AAAA,EACF;AAGA,QAAM,oBAAoB;AAC1B,QAAM,iBAAiB,oBAAoB,iBAAiB;AAC5D,MAAI,QAAQ,qCAAqC,gBAAgB,8BAA8B;AAC/F,MAAI,gBAAgB,qCAAqC;AAAA,IACvD,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,EACV,CAAC;AAGD,QAAM,oBAAoB;AAC1B,QAAM,mBAAmB;AACzB,QAAM,iBAAiB,oBAAoB,iBAAiB;AAC5D,MAAI,QAAQ,qCAAqC,gBAAgB,8BAA8B;AAC/F,MAAI,gBAAgB,qCAAqC;AAAA,IACvD,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,EACV,CAAC;AACD,MAAI,gBAAgB,qCAAqC;AAAA,IACvD,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,EACV,CAAC;AAGD,QAAM,WAAW,cAAc;AAC/B,MAAI,QAAQ,wBAAwB,UAAU,uBAAuB;AAGrE,MAAI,gBAAgB,IAAI;AAAA,IACtB,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,EACV,CAAC;AAGD,WAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,QAAI,gBAAgB,wBAAwB;AAAA,MAC1C,IAAI,YAAY,CAAC;AAAA,MACjB,MAAM;AAAA,MACN,QAAQ,eAAe,IAAI,CAAC;AAAA,IAC9B,CAAC;AAAA,EACH;AACA,MAAI,gBAAgB,wBAAwB;AAAA,IAC1C,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,EACV,CAAC;AACD,MAAI,gBAAgB,wBAAwB;AAAA,IAC1C,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,QAAQ;AAAA,EACV,CAAC;AAGD,MAAI,QAAQ,SAAS,QAAQ,UAAU,QAAQ,aAAa;AAC1D,QAAI,kBAAkB;AAAA,MACpB,OAAO,QAAQ;AAAA,MACf,SAAS,QAAQ;AAAA,MACjB,aAAa,QAAQ;AAAA,MACrB,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,MAChC,WAAU,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnC,CAAC;AAAA,EACH;AAEA,SAAO,IAAI,cAAc;AAC3B;AAMA,SAAS,cAAc,MAAsB;AAC3C,SAAO,KAAK,QAAQ,YAAY,EAAE;AACpC;AAEA,SAAS,eAAe,MAAsB;AAC5C,QAAM,MAAM,KAAK,YAAY,GAAG;AAChC,MAAI,QAAQ,GAAI,QAAO;AACvB,QAAM,MAAM,KACT,UAAU,MAAM,CAAC,EACjB,YAAY,EACZ,MAAM,GAAG,EAAE,CAAC;AACf,SAAO,OAAO;AAChB;AAEA,SAAS,cAAc,KAAqB;AAC1C,QAAM,YAAoC;AAAA,IACxC,KAAK;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AAAA,IACL,MAAM;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AAAA,IACL,MAAM;AAAA,EACR;AACA,SAAO,UAAU,GAAG,KAAK;AAC3B;;;AG3+BA,eAAsB,kBACpB,OACA,UAC2B;AAC3B,QAAM,IAAI,MAAM,oCAAoC;AACtD;AAOA,eAAsB,UACpB,OACA,UACc;AACd,QAAM,IAAI,MAAM,oCAAoC;AACtD;","names":[]}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ContentContainer, MemoryContentContainer } from '@bendyline/squisq/storage';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Container ZIP serialization — convert between ContentContainer and ZIP archives.
|
|
3
5
|
*
|
|
@@ -10,8 +12,7 @@
|
|
|
10
12
|
* audio/narration.mp3
|
|
11
13
|
* timing.json
|
|
12
14
|
*/
|
|
13
|
-
|
|
14
|
-
import { MemoryContentContainer } from '@bendyline/squisq/storage';
|
|
15
|
+
|
|
15
16
|
/**
|
|
16
17
|
* Serialize a ContentContainer to a ZIP blob.
|
|
17
18
|
*
|
|
@@ -21,7 +22,7 @@ import { MemoryContentContainer } from '@bendyline/squisq/storage';
|
|
|
21
22
|
* @param container — The container to serialize
|
|
22
23
|
* @returns A Blob containing the ZIP archive
|
|
23
24
|
*/
|
|
24
|
-
|
|
25
|
+
declare function containerToZip(container: ContentContainer): Promise<Blob>;
|
|
25
26
|
/**
|
|
26
27
|
* Deserialize a ZIP archive into a MemoryContentContainer.
|
|
27
28
|
*
|
|
@@ -32,5 +33,6 @@ export declare function containerToZip(container: ContentContainer): Promise<Blo
|
|
|
32
33
|
* @param zipData — The ZIP archive as ArrayBuffer, Uint8Array, or Blob
|
|
33
34
|
* @returns A MemoryContentContainer populated with the ZIP's contents
|
|
34
35
|
*/
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
declare function zipToContainer(zipData: ArrayBuffer | Uint8Array | Blob): Promise<MemoryContentContainer>;
|
|
37
|
+
|
|
38
|
+
export { containerToZip, zipToContainer };
|
package/dist/container/index.js
CHANGED
|
@@ -1,74 +1,43 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* timing.json
|
|
12
|
-
*/
|
|
13
|
-
import JSZip from 'jszip';
|
|
14
|
-
import { MemoryContentContainer } from '@bendyline/squisq/storage';
|
|
15
|
-
/**
|
|
16
|
-
* Serialize a ContentContainer to a ZIP blob.
|
|
17
|
-
*
|
|
18
|
-
* All files in the container are written to the ZIP archive preserving
|
|
19
|
-
* their path structure. The resulting blob can be saved as a .zip file.
|
|
20
|
-
*
|
|
21
|
-
* @param container — The container to serialize
|
|
22
|
-
* @returns A Blob containing the ZIP archive
|
|
23
|
-
*/
|
|
24
|
-
export async function containerToZip(container) {
|
|
25
|
-
const zip = new JSZip();
|
|
26
|
-
const entries = await container.listFiles();
|
|
27
|
-
for (const entry of entries) {
|
|
28
|
-
const data = await container.readFile(entry.path);
|
|
29
|
-
if (data) {
|
|
30
|
-
zip.file(entry.path, new Uint8Array(data));
|
|
31
|
-
}
|
|
1
|
+
// src/container/index.ts
|
|
2
|
+
import JSZip from "jszip";
|
|
3
|
+
import { MemoryContentContainer } from "@bendyline/squisq/storage";
|
|
4
|
+
async function containerToZip(container) {
|
|
5
|
+
const zip = new JSZip();
|
|
6
|
+
const entries = await container.listFiles();
|
|
7
|
+
for (const entry of entries) {
|
|
8
|
+
const data = await container.readFile(entry.path);
|
|
9
|
+
if (data) {
|
|
10
|
+
zip.file(entry.path, new Uint8Array(data));
|
|
32
11
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
12
|
+
}
|
|
13
|
+
return zip.generateAsync({
|
|
14
|
+
type: "blob",
|
|
15
|
+
compression: "DEFLATE",
|
|
16
|
+
compressionOptions: { level: 6 }
|
|
17
|
+
});
|
|
38
18
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const entryPath = relativePath.startsWith('/') ? relativePath.slice(1) : relativePath;
|
|
59
|
-
if (!entryPath)
|
|
60
|
-
return;
|
|
61
|
-
// Path-traversal protection: reject absolute paths, backslashes, and .. segments
|
|
62
|
-
if (entryPath.startsWith('/') ||
|
|
63
|
-
entryPath.includes('\\') ||
|
|
64
|
-
entryPath.split('/').some((seg) => seg === '..')) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
filePromises.push(zipEntry.async('arraybuffer').then((data) => {
|
|
68
|
-
return container.writeFile(entryPath, data);
|
|
69
|
-
}));
|
|
70
|
-
});
|
|
71
|
-
await Promise.all(filePromises);
|
|
72
|
-
return container;
|
|
19
|
+
async function zipToContainer(zipData) {
|
|
20
|
+
const zip = await JSZip.loadAsync(zipData);
|
|
21
|
+
const container = new MemoryContentContainer();
|
|
22
|
+
const filePromises = [];
|
|
23
|
+
zip.forEach((relativePath, zipEntry) => {
|
|
24
|
+
if (zipEntry.dir) return;
|
|
25
|
+
const entryPath = relativePath.startsWith("/") ? relativePath.slice(1) : relativePath;
|
|
26
|
+
if (!entryPath) return;
|
|
27
|
+
if (entryPath.startsWith("/") || entryPath.includes("\\") || entryPath.split("/").some((seg) => seg === "..")) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
filePromises.push(
|
|
31
|
+
zipEntry.async("arraybuffer").then((data) => {
|
|
32
|
+
return container.writeFile(entryPath, data);
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
await Promise.all(filePromises);
|
|
37
|
+
return container;
|
|
73
38
|
}
|
|
39
|
+
export {
|
|
40
|
+
containerToZip,
|
|
41
|
+
zipToContainer
|
|
42
|
+
};
|
|
74
43
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"sources":["../../src/container/index.ts"],"sourcesContent":["/**\n * Container ZIP serialization — convert between ContentContainer and ZIP archives.\n *\n * Uses JSZip (already a formats dependency) to serialize a ContentContainer to\n * a ZIP blob and to deserialize a ZIP blob into a MemoryContentContainer.\n *\n * ZIP structure mirrors the container's flat path hierarchy directly:\n * index.md\n * images/hero.jpg\n * audio/narration.mp3\n * timing.json\n */\n\nimport JSZip from 'jszip';\nimport type { ContentContainer } from '@bendyline/squisq/storage';\nimport { MemoryContentContainer } from '@bendyline/squisq/storage';\n\n/**\n * Serialize a ContentContainer to a ZIP blob.\n *\n * All files in the container are written to the ZIP archive preserving\n * their path structure. The resulting blob can be saved as a .zip file.\n *\n * @param container — The container to serialize\n * @returns A Blob containing the ZIP archive\n */\nexport async function containerToZip(container: ContentContainer): Promise<Blob> {\n const zip = new JSZip();\n const entries = await container.listFiles();\n\n for (const entry of entries) {\n const data = await container.readFile(entry.path);\n if (data) {\n zip.file(entry.path, new Uint8Array(data));\n }\n }\n\n return zip.generateAsync({\n type: 'blob',\n compression: 'DEFLATE',\n compressionOptions: { level: 6 },\n });\n}\n\n/**\n * Deserialize a ZIP archive into a MemoryContentContainer.\n *\n * Reads all files from the ZIP and writes them into a new MemoryContentContainer.\n * Directory entries are skipped. The resulting container can be used immediately\n * for rendering, editing, or saving to persistent storage.\n *\n * @param zipData — The ZIP archive as ArrayBuffer, Uint8Array, or Blob\n * @returns A MemoryContentContainer populated with the ZIP's contents\n */\nexport async function zipToContainer(\n zipData: ArrayBuffer | Uint8Array | Blob,\n): Promise<MemoryContentContainer> {\n const zip = await JSZip.loadAsync(zipData);\n const container = new MemoryContentContainer();\n\n const filePromises: Promise<void>[] = [];\n\n zip.forEach((relativePath, zipEntry) => {\n // Skip directories\n if (zipEntry.dir) return;\n\n // Strip leading slash if present\n const entryPath = relativePath.startsWith('/') ? relativePath.slice(1) : relativePath;\n if (!entryPath) return;\n\n // Path-traversal protection: reject absolute paths, backslashes, and .. segments\n if (\n entryPath.startsWith('/') ||\n entryPath.includes('\\\\') ||\n entryPath.split('/').some((seg) => seg === '..')\n ) {\n return;\n }\n\n filePromises.push(\n zipEntry.async('arraybuffer').then((data) => {\n return container.writeFile(entryPath, data);\n }),\n );\n });\n\n await Promise.all(filePromises);\n return container;\n}\n"],"mappings":";AAaA,OAAO,WAAW;AAElB,SAAS,8BAA8B;AAWvC,eAAsB,eAAe,WAA4C;AAC/E,QAAM,MAAM,IAAI,MAAM;AACtB,QAAM,UAAU,MAAM,UAAU,UAAU;AAE1C,aAAW,SAAS,SAAS;AAC3B,UAAM,OAAO,MAAM,UAAU,SAAS,MAAM,IAAI;AAChD,QAAI,MAAM;AACR,UAAI,KAAK,MAAM,MAAM,IAAI,WAAW,IAAI,CAAC;AAAA,IAC3C;AAAA,EACF;AAEA,SAAO,IAAI,cAAc;AAAA,IACvB,MAAM;AAAA,IACN,aAAa;AAAA,IACb,oBAAoB,EAAE,OAAO,EAAE;AAAA,EACjC,CAAC;AACH;AAYA,eAAsB,eACpB,SACiC;AACjC,QAAM,MAAM,MAAM,MAAM,UAAU,OAAO;AACzC,QAAM,YAAY,IAAI,uBAAuB;AAE7C,QAAM,eAAgC,CAAC;AAEvC,MAAI,QAAQ,CAAC,cAAc,aAAa;AAEtC,QAAI,SAAS,IAAK;AAGlB,UAAM,YAAY,aAAa,WAAW,GAAG,IAAI,aAAa,MAAM,CAAC,IAAI;AACzE,QAAI,CAAC,UAAW;AAGhB,QACE,UAAU,WAAW,GAAG,KACxB,UAAU,SAAS,IAAI,KACvB,UAAU,MAAM,GAAG,EAAE,KAAK,CAAC,QAAQ,QAAQ,IAAI,GAC/C;AACA;AAAA,IACF;AAEA,iBAAa;AAAA,MACX,SAAS,MAAM,aAAa,EAAE,KAAK,CAAC,SAAS;AAC3C,eAAO,UAAU,UAAU,WAAW,IAAI;AAAA,MAC5C,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AAED,QAAM,QAAQ,IAAI,YAAY;AAC9B,SAAO;AACT;","names":[]}
|
package/dist/docx/index.d.ts
CHANGED
|
@@ -1,23 +1,136 @@
|
|
|
1
|
+
import { Doc } from '@bendyline/squisq/schemas';
|
|
2
|
+
import { MarkdownDocument } from '@bendyline/squisq/markdown';
|
|
3
|
+
import { ContentContainer } from '@bendyline/squisq/storage';
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
|
-
*
|
|
6
|
+
* DOCX Export
|
|
3
7
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
8
|
+
* Converts a squisq MarkdownDocument (or Doc) into a .docx file
|
|
9
|
+
* by generating WordprocessingML XML and assembling the OOXML package.
|
|
6
10
|
*
|
|
7
|
-
*
|
|
11
|
+
* No third-party docx library — all XML is generated directly using
|
|
12
|
+
* the shared ooxml/ infrastructure of this package.
|
|
8
13
|
*
|
|
9
14
|
* @example
|
|
10
15
|
* ```ts
|
|
11
|
-
* import {
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
16
|
+
* import { parseMarkdown } from '@bendyline/squisq/markdown';
|
|
17
|
+
* import { markdownDocToDocx } from '@bendyline/squisq-formats/docx';
|
|
18
|
+
*
|
|
19
|
+
* const md = parseMarkdown('# Hello\n\nWorld **bold** text');
|
|
20
|
+
* const blob = await markdownDocToDocx(md);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Options for DOCX export.
|
|
26
|
+
*/
|
|
27
|
+
interface DocxExportOptions {
|
|
28
|
+
/** Document title (appears in core properties) */
|
|
29
|
+
title?: string;
|
|
30
|
+
/** Document author */
|
|
31
|
+
author?: string;
|
|
32
|
+
/** Document description */
|
|
33
|
+
description?: string;
|
|
34
|
+
/** Default body font family. Default: "Calibri" */
|
|
35
|
+
defaultFont?: string;
|
|
36
|
+
/** Default body font size in points. Default: 11 */
|
|
37
|
+
defaultFontSize?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Squisq theme ID to apply (e.g., 'documentary', 'cinematic').
|
|
40
|
+
* When set, overrides fonts with the theme's typography and applies
|
|
41
|
+
* the theme's primary color to headings.
|
|
42
|
+
*/
|
|
43
|
+
themeId?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Pre-resolved image data keyed by image URL/path as it appears in the
|
|
46
|
+
* markdown source. When provided, images are embedded in the .docx file
|
|
47
|
+
* as binary parts instead of emitting placeholder text.
|
|
48
|
+
*/
|
|
49
|
+
images?: Map<string, {
|
|
50
|
+
data: ArrayBuffer | Uint8Array;
|
|
51
|
+
contentType: string;
|
|
52
|
+
}>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Convert a MarkdownDocument to a .docx Blob.
|
|
56
|
+
*
|
|
57
|
+
* @param doc - The parsed markdown document
|
|
58
|
+
* @param options - Export options
|
|
59
|
+
* @returns An ArrayBuffer containing the .docx file
|
|
60
|
+
*/
|
|
61
|
+
declare function markdownDocToDocx(doc: MarkdownDocument, options?: DocxExportOptions): Promise<ArrayBuffer>;
|
|
62
|
+
/**
|
|
63
|
+
* Convert a squisq Doc to a .docx Blob.
|
|
64
|
+
*
|
|
65
|
+
* Convenience wrapper that converts Doc → MarkdownDocument → DOCX.
|
|
66
|
+
*
|
|
67
|
+
* @param doc - The squisq Doc
|
|
68
|
+
* @param options - Export options
|
|
69
|
+
* @returns An ArrayBuffer containing the .docx file
|
|
70
|
+
*/
|
|
71
|
+
declare function docToDocx(doc: Doc, options?: DocxExportOptions): Promise<ArrayBuffer>;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* DOCX Import
|
|
75
|
+
*
|
|
76
|
+
* Parses a .docx file (Office Open XML WordprocessingML) and converts
|
|
77
|
+
* its content into a squisq MarkdownDocument (or Doc).
|
|
78
|
+
*
|
|
79
|
+
* Uses JSZip + DOMParser to read the archive and parse the XML — no
|
|
80
|
+
* third-party docx library. Handles headings, paragraphs, inline
|
|
81
|
+
* formatting (bold, italic, strikethrough), hyperlinks, lists, tables,
|
|
82
|
+
* blockquotes, code blocks, images, and footnotes.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* import { docxToMarkdownDoc } from '@bendyline/squisq-formats/docx';
|
|
87
|
+
*
|
|
88
|
+
* const response = await fetch('document.docx');
|
|
89
|
+
* const data = await response.arrayBuffer();
|
|
90
|
+
* const doc = await docxToMarkdownDoc(data);
|
|
17
91
|
* ```
|
|
18
92
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Options for DOCX import.
|
|
96
|
+
*/
|
|
97
|
+
interface DocxImportOptions {
|
|
98
|
+
/**
|
|
99
|
+
* Whether to extract embedded images as base64 data URIs.
|
|
100
|
+
* When false, images are represented as `[Image]` placeholders.
|
|
101
|
+
* Default: false
|
|
102
|
+
*/
|
|
103
|
+
extractImages?: boolean;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Convert a .docx file to a MarkdownDocument.
|
|
107
|
+
*
|
|
108
|
+
* @param data - The raw .docx file as ArrayBuffer or Blob
|
|
109
|
+
* @param options - Import options
|
|
110
|
+
* @returns A MarkdownDocument representing the document content
|
|
111
|
+
*/
|
|
112
|
+
declare function docxToMarkdownDoc(data: ArrayBuffer | Blob, options?: DocxImportOptions): Promise<MarkdownDocument>;
|
|
113
|
+
/**
|
|
114
|
+
* Convert a .docx file to a squisq Doc.
|
|
115
|
+
*
|
|
116
|
+
* Convenience wrapper: DOCX → MarkdownDocument → Doc.
|
|
117
|
+
*
|
|
118
|
+
* @param data - The raw .docx file as ArrayBuffer or Blob
|
|
119
|
+
* @param options - Import options
|
|
120
|
+
* @returns A squisq Doc
|
|
121
|
+
*/
|
|
122
|
+
declare function docxToDoc(data: ArrayBuffer | Blob, options?: DocxImportOptions): Promise<Doc>;
|
|
123
|
+
/**
|
|
124
|
+
* Convert a .docx file to a ContentContainer with markdown + extracted images.
|
|
125
|
+
*
|
|
126
|
+
* The container will contain:
|
|
127
|
+
* - The primary markdown document (index.md)
|
|
128
|
+
* - Any embedded images under images/ (e.g., images/image1.png)
|
|
129
|
+
*
|
|
130
|
+
* @param data - The raw .docx file as ArrayBuffer or Blob
|
|
131
|
+
* @param options - Import options
|
|
132
|
+
* @returns A ContentContainer with the document and its media
|
|
133
|
+
*/
|
|
134
|
+
declare function docxToContainer(data: ArrayBuffer | Blob, options?: DocxImportOptions): Promise<ContentContainer>;
|
|
135
|
+
|
|
136
|
+
export { type DocxExportOptions, type DocxImportOptions, docToDocx, docxToContainer, docxToDoc, docxToMarkdownDoc, markdownDocToDocx };
|
package/dist/docx/index.js
CHANGED
|
@@ -1,23 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*/
|
|
19
|
-
// Export
|
|
20
|
-
export { markdownDocToDocx, docToDocx } from './export.js';
|
|
21
|
-
// Import
|
|
22
|
-
export { docxToMarkdownDoc, docxToDoc, docxToContainer } from './import.js';
|
|
1
|
+
import {
|
|
2
|
+
docToDocx,
|
|
3
|
+
docxToContainer,
|
|
4
|
+
docxToDoc,
|
|
5
|
+
docxToMarkdownDoc,
|
|
6
|
+
markdownDocToDocx
|
|
7
|
+
} from "../chunk-UDS45KUJ.js";
|
|
8
|
+
import "../chunk-YN5HFCEW.js";
|
|
9
|
+
import "../chunk-7DEUGIOO.js";
|
|
10
|
+
import "../chunk-U4MRIFKL.js";
|
|
11
|
+
export {
|
|
12
|
+
docToDocx,
|
|
13
|
+
docxToContainer,
|
|
14
|
+
docxToDoc,
|
|
15
|
+
docxToMarkdownDoc,
|
|
16
|
+
markdownDocToDocx
|
|
17
|
+
};
|
|
23
18
|
//# sourceMappingURL=index.js.map
|
package/dist/docx/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/epub/index.d.ts
CHANGED
|
@@ -1,20 +1,75 @@
|
|
|
1
|
+
import { AudioSegment, Doc } from '@bendyline/squisq/schemas';
|
|
2
|
+
import { MarkdownDocument } from '@bendyline/squisq/markdown';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
|
-
*
|
|
5
|
+
* EPUB 3 Export
|
|
3
6
|
*
|
|
4
|
-
*
|
|
7
|
+
* Converts a MarkdownDocument (or Doc) to an EPUB 3 file (.epub).
|
|
5
8
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
+
* An EPUB is a ZIP archive containing XHTML chapter files, images,
|
|
10
|
+
* a package manifest (content.opf), and a navigation document (toc.xhtml).
|
|
11
|
+
* Content is split into chapters at H1/H2 heading boundaries.
|
|
12
|
+
*
|
|
13
|
+
* Uses JSZip for packaging (already a dependency), escapeXml from the
|
|
14
|
+
* OOXML utils, and image utilities from the HTML exporter.
|
|
9
15
|
*
|
|
10
16
|
* @example
|
|
11
17
|
* ```ts
|
|
12
|
-
* import { markdownDocToEpub
|
|
18
|
+
* import { markdownDocToEpub } from '@bendyline/squisq-formats/epub';
|
|
13
19
|
*
|
|
14
|
-
* const epub = await markdownDocToEpub(markdownDoc, {
|
|
15
|
-
*
|
|
20
|
+
* const epub = await markdownDocToEpub(markdownDoc, {
|
|
21
|
+
* title: 'My Book',
|
|
22
|
+
* author: 'Jane Doe',
|
|
23
|
+
* });
|
|
16
24
|
* ```
|
|
17
25
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
|
|
27
|
+
interface EpubExportOptions {
|
|
28
|
+
/** Book title (default: 'Untitled') */
|
|
29
|
+
title?: string;
|
|
30
|
+
/** Author name */
|
|
31
|
+
author?: string;
|
|
32
|
+
/** Book description / summary */
|
|
33
|
+
description?: string;
|
|
34
|
+
/** BCP-47 language code (default: 'en') */
|
|
35
|
+
language?: string;
|
|
36
|
+
/** Publisher name */
|
|
37
|
+
publisher?: string;
|
|
38
|
+
/** Squisq theme ID for CSS styling */
|
|
39
|
+
themeId?: string;
|
|
40
|
+
/** Pre-resolved image data keyed by relative path as it appears in the markdown */
|
|
41
|
+
images?: Map<string, ArrayBuffer>;
|
|
42
|
+
/** Cover image data (JPEG or PNG) */
|
|
43
|
+
coverImage?: ArrayBuffer;
|
|
44
|
+
/**
|
|
45
|
+
* Audio narration data keyed by segment src/name.
|
|
46
|
+
* When provided alongside audioSegments, EPUB 3 Media Overlays (SMIL)
|
|
47
|
+
* are generated for synchronized audio playback.
|
|
48
|
+
*/
|
|
49
|
+
audio?: Map<string, ArrayBuffer>;
|
|
50
|
+
/**
|
|
51
|
+
* Audio segment metadata (from Doc.audio.segments).
|
|
52
|
+
* Required together with `audio` to generate Media Overlays.
|
|
53
|
+
* Each segment's duration and startTime are used to build SMIL timing.
|
|
54
|
+
*/
|
|
55
|
+
audioSegments?: AudioSegment[];
|
|
56
|
+
/** Total document duration in seconds (used for Media Overlay metadata) */
|
|
57
|
+
totalDuration?: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Convert a MarkdownDocument to an EPUB 3 file.
|
|
61
|
+
*
|
|
62
|
+
* Chapters are split at H1/H2 heading boundaries. All referenced images
|
|
63
|
+
* (provided via `options.images`) are embedded in the archive.
|
|
64
|
+
*/
|
|
65
|
+
declare function markdownDocToEpub(doc: MarkdownDocument, options?: EpubExportOptions): Promise<ArrayBuffer>;
|
|
66
|
+
/**
|
|
67
|
+
* Convert a squisq Doc to an EPUB 3 file.
|
|
68
|
+
*
|
|
69
|
+
* Convenience wrapper: Doc → MarkdownDocument → EPUB.
|
|
70
|
+
* When the Doc has audio segments and `options.audio` is provided,
|
|
71
|
+
* EPUB 3 Media Overlays are generated for narrated playback.
|
|
72
|
+
*/
|
|
73
|
+
declare function docToEpub(doc: Doc, options?: EpubExportOptions): Promise<ArrayBuffer>;
|
|
74
|
+
|
|
75
|
+
export { type EpubExportOptions, docToEpub, markdownDocToEpub };
|
package/dist/epub/index.js
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* ```ts
|
|
12
|
-
* import { markdownDocToEpub, docToEpub } from '@bendyline/squisq-formats/epub';
|
|
13
|
-
*
|
|
14
|
-
* const epub = await markdownDocToEpub(markdownDoc, { title: 'My Book' });
|
|
15
|
-
* const epub2 = await docToEpub(doc, { author: 'Jane Doe' });
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
export { markdownDocToEpub, docToEpub } from './export.js';
|
|
1
|
+
import {
|
|
2
|
+
docToEpub,
|
|
3
|
+
markdownDocToEpub
|
|
4
|
+
} from "../chunk-ERZ627GR.js";
|
|
5
|
+
import "../chunk-U4MRIFKL.js";
|
|
6
|
+
import "../chunk-A3FHLTY5.js";
|
|
7
|
+
export {
|
|
8
|
+
docToEpub,
|
|
9
|
+
markdownDocToEpub
|
|
10
|
+
};
|
|
19
11
|
//# sourceMappingURL=index.js.map
|