@bendyline/squisq-formats 1.3.0 → 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/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 -15
- package/dist/html/index.js +26 -165
- 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 +2 -2
- 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 -317
- 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__/plainHtml.test.d.ts +0 -5
- package/dist/__tests__/plainHtml.test.d.ts.map +0 -1
- package/dist/__tests__/plainHtml.test.js +0 -327
- package/dist/__tests__/plainHtml.test.js.map +0 -1
- package/dist/__tests__/plainHtmlBundle.test.d.ts +0 -11
- package/dist/__tests__/plainHtmlBundle.test.d.ts.map +0 -1
- package/dist/__tests__/plainHtmlBundle.test.js +0 -210
- package/dist/__tests__/plainHtmlBundle.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/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 -985
- 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/docsHtmlBundle.d.ts +0 -53
- package/dist/html/docsHtmlBundle.d.ts.map +0 -1
- package/dist/html/docsHtmlBundle.js +0 -299
- package/dist/html/docsHtmlBundle.js.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 -282
- 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/html/plainHtml.d.ts +0 -79
- package/dist/html/plainHtml.d.ts.map +0 -1
- package/dist/html/plainHtml.js +0 -614
- package/dist/html/plainHtml.js.map +0 -1
- package/dist/html/plainHtmlBundle.d.ts +0 -93
- package/dist/html/plainHtmlBundle.d.ts.map +0 -1
- package/dist/html/plainHtmlBundle.js +0 -357
- package/dist/html/plainHtmlBundle.js.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 -724
- 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
package/dist/pptx/export.js
DELETED
|
@@ -1,724 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PPTX Export
|
|
3
|
-
*
|
|
4
|
-
* Converts a squisq MarkdownDocument (or Doc) into a .pptx file
|
|
5
|
-
* by generating PresentationML + DrawingML XML and assembling the
|
|
6
|
-
* OOXML package.
|
|
7
|
-
*
|
|
8
|
-
* Slide segmentation: each H1 or H2 heading starts a new slide.
|
|
9
|
-
* The heading becomes the slide title; content below becomes the body.
|
|
10
|
-
*
|
|
11
|
-
* Supports:
|
|
12
|
-
* - Theme-based styling (background, text colors, fonts from squisq themes)
|
|
13
|
-
* - Image embedding (pass images as ArrayBuffers via options)
|
|
14
|
-
* - Inline formatting (bold, italic, strikethrough, code, links)
|
|
15
|
-
* - Lists, code blocks, blockquotes, tables, math
|
|
16
|
-
*
|
|
17
|
-
* No third-party pptx library — all XML is generated directly using
|
|
18
|
-
* the shared ooxml/ infrastructure.
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* import { parseMarkdown } from '@bendyline/squisq/markdown';
|
|
23
|
-
* import { markdownDocToPptx } from '@bendyline/squisq-formats/pptx';
|
|
24
|
-
*
|
|
25
|
-
* const md = parseMarkdown('# Slide 1\n\nHello world\n\n## Slide 2\n\nMore content');
|
|
26
|
-
* const blob = await markdownDocToPptx(md, { themeId: 'documentary' });
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
import { resolveTheme, resolveFontFamily } from '@bendyline/squisq/schemas';
|
|
30
|
-
import { docToMarkdown } from '@bendyline/squisq/doc';
|
|
31
|
-
import { readFrontmatterThemeId } from '@bendyline/squisq/markdown';
|
|
32
|
-
import { createPackage } from '../ooxml/writer.js';
|
|
33
|
-
import { xmlDeclaration, escapeXml } from '../ooxml/xmlUtils.js';
|
|
34
|
-
import { NS_PML, NS_DRAWINGML, NS_R, REL_OFFICE_DOCUMENT, REL_SLIDE, REL_SLIDE_LAYOUT, REL_SLIDE_MASTER, REL_THEME, REL_HYPERLINK, REL_IMAGE, CONTENT_TYPE_PPTX_PRESENTATION, CONTENT_TYPE_PPTX_SLIDE, CONTENT_TYPE_PPTX_SLIDE_LAYOUT, CONTENT_TYPE_PPTX_SLIDE_MASTER, CONTENT_TYPE_PPTX_THEME, } from '../ooxml/namespaces.js';
|
|
35
|
-
import { DEFAULT_FONT, DEFAULT_TITLE_FONT, DEFAULT_CODE_FONT, DEFAULT_TITLE_SIZE, DEFAULT_CODE_SIZE, HYPERLINK_COLOR, TITLE_LEFT, TITLE_TOP, TITLE_WIDTH, TITLE_HEIGHT, BODY_LEFT, BODY_TOP, BODY_WIDTH, BODY_HEIGHT, } from './styles.js';
|
|
36
|
-
import { buildPresentationXml, buildSlideMasterXml, buildSlideLayoutXml, buildThemeXml, } from './templates.js';
|
|
37
|
-
/**
|
|
38
|
-
* Convert a MarkdownDocument to a .pptx ArrayBuffer.
|
|
39
|
-
*/
|
|
40
|
-
export async function markdownDocToPptx(doc, options = {}) {
|
|
41
|
-
// Resolve theme from options or frontmatter. The frontmatter lookup
|
|
42
|
-
// accepts the editor's canonical `squisq-theme` key as well as the
|
|
43
|
-
// shorter legacy `themeId` / `theme` aliases — see
|
|
44
|
-
// `readFrontmatterThemeId` for the precedence.
|
|
45
|
-
const themeId = options.themeId ?? readFrontmatterThemeId(doc.frontmatter);
|
|
46
|
-
const style = resolveSlideStyle(themeId, options);
|
|
47
|
-
const slides = segmentIntoSlides(doc.children, options.slideBreak ?? 'h2');
|
|
48
|
-
// Ensure at least one slide
|
|
49
|
-
if (slides.length === 0) {
|
|
50
|
-
slides.push({ bodyNodes: [] });
|
|
51
|
-
}
|
|
52
|
-
const slideXmls = [];
|
|
53
|
-
const slideContexts = [];
|
|
54
|
-
for (let i = 0; i < slides.length; i++) {
|
|
55
|
-
const ctx = new SlideContext(style, options.images, i);
|
|
56
|
-
const xml = buildSlideXml(slides[i], ctx);
|
|
57
|
-
slideXmls.push(xml);
|
|
58
|
-
slideContexts.push(ctx);
|
|
59
|
-
}
|
|
60
|
-
return buildPptxPackage(slideXmls, slideContexts, options);
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Convert a squisq Doc to a .pptx ArrayBuffer.
|
|
64
|
-
*
|
|
65
|
-
* Convenience wrapper: Doc -> MarkdownDocument -> PPTX.
|
|
66
|
-
*/
|
|
67
|
-
export async function docToPptx(doc, options = {}) {
|
|
68
|
-
const markdownDoc = docToMarkdown(doc);
|
|
69
|
-
// Carry themeId from Doc if not set in options
|
|
70
|
-
if (!options.themeId && doc.themeId) {
|
|
71
|
-
options = { ...options, themeId: doc.themeId };
|
|
72
|
-
}
|
|
73
|
-
return markdownDocToPptx(markdownDoc, options);
|
|
74
|
-
}
|
|
75
|
-
function resolveSlideStyle(themeId, options) {
|
|
76
|
-
if (!themeId) {
|
|
77
|
-
return {
|
|
78
|
-
background: 'FFFFFF',
|
|
79
|
-
text: '333333',
|
|
80
|
-
titleColor: '333333',
|
|
81
|
-
mutedColor: '666666',
|
|
82
|
-
titleFont: options.defaultFont ?? DEFAULT_TITLE_FONT,
|
|
83
|
-
bodyFont: options.defaultFont ?? DEFAULT_FONT,
|
|
84
|
-
codeFont: DEFAULT_CODE_FONT,
|
|
85
|
-
codeColor: '333333',
|
|
86
|
-
hasTheme: false,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
const theme = resolveTheme(themeId);
|
|
90
|
-
const c = theme.colors;
|
|
91
|
-
return {
|
|
92
|
-
background: stripHash(c.background),
|
|
93
|
-
text: stripHash(c.text),
|
|
94
|
-
titleColor: stripHash(c.highlight || c.secondary || c.text),
|
|
95
|
-
mutedColor: stripHash(c.textMuted || c.text),
|
|
96
|
-
titleFont: resolveFontFamily(theme.typography?.titleFont, DEFAULT_TITLE_FONT),
|
|
97
|
-
bodyFont: resolveFontFamily(theme.typography?.bodyFont, options.defaultFont || DEFAULT_FONT),
|
|
98
|
-
codeFont: resolveFontFamily(theme.typography?.monoFont, DEFAULT_CODE_FONT),
|
|
99
|
-
codeColor: stripHash(c.textMuted || c.text),
|
|
100
|
-
hasTheme: true,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
function stripHash(color) {
|
|
104
|
-
return color.startsWith('#') ? color.slice(1) : color;
|
|
105
|
-
}
|
|
106
|
-
function segmentIntoSlides(children, slideBreak) {
|
|
107
|
-
const maxDepth = slideBreak === 'h1' ? 1 : slideBreak === 'h2' ? 2 : 6;
|
|
108
|
-
const slides = [];
|
|
109
|
-
let current;
|
|
110
|
-
for (const node of children) {
|
|
111
|
-
if (node.type === 'heading' && node.depth <= maxDepth) {
|
|
112
|
-
if (current)
|
|
113
|
-
slides.push(current);
|
|
114
|
-
current = {
|
|
115
|
-
title: extractPlainText(node.children),
|
|
116
|
-
titleDepth: node.depth,
|
|
117
|
-
bodyNodes: [],
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
if (!current) {
|
|
122
|
-
current = { bodyNodes: [] };
|
|
123
|
-
}
|
|
124
|
-
current.bodyNodes.push(node);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
if (current)
|
|
128
|
-
slides.push(current);
|
|
129
|
-
return slides;
|
|
130
|
-
}
|
|
131
|
-
function extractPlainText(nodes) {
|
|
132
|
-
const parts = [];
|
|
133
|
-
for (const node of nodes) {
|
|
134
|
-
switch (node.type) {
|
|
135
|
-
case 'text':
|
|
136
|
-
parts.push(node.value);
|
|
137
|
-
break;
|
|
138
|
-
case 'inlineCode':
|
|
139
|
-
parts.push(node.value);
|
|
140
|
-
break;
|
|
141
|
-
case 'strong':
|
|
142
|
-
case 'emphasis':
|
|
143
|
-
case 'delete':
|
|
144
|
-
parts.push(extractPlainText(node.children));
|
|
145
|
-
break;
|
|
146
|
-
case 'link':
|
|
147
|
-
parts.push(extractPlainText(node.children));
|
|
148
|
-
break;
|
|
149
|
-
case 'image':
|
|
150
|
-
parts.push(node.alt ?? '');
|
|
151
|
-
break;
|
|
152
|
-
case 'inlineMath':
|
|
153
|
-
parts.push(node.value);
|
|
154
|
-
break;
|
|
155
|
-
default:
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return parts.join('');
|
|
160
|
-
}
|
|
161
|
-
class SlideContext {
|
|
162
|
-
constructor(style, images, slideIndex) {
|
|
163
|
-
this.nextRelId = 1;
|
|
164
|
-
this.relationships = [];
|
|
165
|
-
this.embeddedImages = [];
|
|
166
|
-
this.nextShapeId = 4; // 1=group, 2=title, 3=body
|
|
167
|
-
this.style = style;
|
|
168
|
-
this.images = images;
|
|
169
|
-
this.slideIndex = slideIndex;
|
|
170
|
-
}
|
|
171
|
-
allocRelId() {
|
|
172
|
-
return `rId${this.nextRelId++ + 1}`;
|
|
173
|
-
}
|
|
174
|
-
allocShapeId() {
|
|
175
|
-
return this.nextShapeId++;
|
|
176
|
-
}
|
|
177
|
-
addHyperlink(url) {
|
|
178
|
-
const id = this.allocRelId();
|
|
179
|
-
this.relationships.push({
|
|
180
|
-
id,
|
|
181
|
-
type: REL_HYPERLINK,
|
|
182
|
-
target: url,
|
|
183
|
-
targetMode: 'External',
|
|
184
|
-
});
|
|
185
|
-
return id;
|
|
186
|
-
}
|
|
187
|
-
/** Register an embedded image and return its relationship ID */
|
|
188
|
-
addImage(src, data, alt) {
|
|
189
|
-
const relId = this.allocRelId();
|
|
190
|
-
const ext = inferExtension(src);
|
|
191
|
-
const contentType = inferMimeType(ext);
|
|
192
|
-
const imgIndex = this.embeddedImages.length + 1;
|
|
193
|
-
const mediaPath = `ppt/media/image_s${this.slideIndex}_${imgIndex}.${ext}`;
|
|
194
|
-
this.embeddedImages.push({ relId, mediaPath, data, contentType, alt });
|
|
195
|
-
this.relationships.push({
|
|
196
|
-
id: relId,
|
|
197
|
-
type: REL_IMAGE,
|
|
198
|
-
target: `../media/image_s${this.slideIndex}_${imgIndex}.${ext}`,
|
|
199
|
-
});
|
|
200
|
-
return relId;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
// ============================================
|
|
204
|
-
// Slide XML Generation
|
|
205
|
-
// ============================================
|
|
206
|
-
function buildSlideXml(slide, ctx) {
|
|
207
|
-
const shapes = [];
|
|
208
|
-
shapes.push(`<p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>` + `<p:grpSpPr/>`);
|
|
209
|
-
if (slide.title) {
|
|
210
|
-
shapes.push(buildTitleShape(slide.title, ctx.style));
|
|
211
|
-
}
|
|
212
|
-
// Convert body blocks and collect any image-only paragraphs
|
|
213
|
-
const bodyParas = convertBodyBlocks(slide.bodyNodes, ctx);
|
|
214
|
-
const hasImages = ctx.embeddedImages.length > 0;
|
|
215
|
-
const hasText = bodyParas.length > 0;
|
|
216
|
-
if (hasText && hasImages) {
|
|
217
|
-
// Split: text in top half of body area, images below
|
|
218
|
-
const textHeight = Math.round(BODY_HEIGHT * 0.45);
|
|
219
|
-
const imgTop = BODY_TOP + textHeight + 91440; // 0.1" gap
|
|
220
|
-
const imgHeight = BODY_HEIGHT - textHeight - 91440;
|
|
221
|
-
shapes.push(buildBodyShapeCustom(bodyParas, BODY_TOP, textHeight, ctx.style));
|
|
222
|
-
shapes.push(buildImageShape(ctx.embeddedImages[0], ctx.allocShapeId(), BODY_LEFT, imgTop, BODY_WIDTH, imgHeight));
|
|
223
|
-
}
|
|
224
|
-
else if (hasImages && !hasText) {
|
|
225
|
-
// Full content area for image
|
|
226
|
-
shapes.push(buildImageShape(ctx.embeddedImages[0], ctx.allocShapeId(), BODY_LEFT, BODY_TOP, BODY_WIDTH, BODY_HEIGHT));
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
// Text only (or empty)
|
|
230
|
-
shapes.push(buildBodyShapeCustom(bodyParas || `<a:p><a:endParaRPr lang="en-US"/></a:p>`, BODY_TOP, BODY_HEIGHT, ctx.style));
|
|
231
|
-
}
|
|
232
|
-
// Additional images beyond the first get placed as extra shapes
|
|
233
|
-
for (let i = 1; i < ctx.embeddedImages.length; i++) {
|
|
234
|
-
shapes.push(buildImageShape(ctx.embeddedImages[i], ctx.allocShapeId(), BODY_LEFT, BODY_TOP, BODY_WIDTH, BODY_HEIGHT));
|
|
235
|
-
}
|
|
236
|
-
// Build background
|
|
237
|
-
const bgXml = ctx.style.hasTheme
|
|
238
|
-
? `<p:bg><p:bgPr><a:solidFill><a:srgbClr val="${ctx.style.background}"/></a:solidFill><a:effectLst/></p:bgPr></p:bg>`
|
|
239
|
-
: '';
|
|
240
|
-
return (xmlDeclaration() +
|
|
241
|
-
`<p:sld xmlns:a="${NS_DRAWINGML}" xmlns:r="${NS_R}" xmlns:p="${NS_PML}">` +
|
|
242
|
-
`<p:cSld>` +
|
|
243
|
-
bgXml +
|
|
244
|
-
`<p:spTree>` +
|
|
245
|
-
shapes.join('') +
|
|
246
|
-
`</p:spTree>` +
|
|
247
|
-
`</p:cSld>` +
|
|
248
|
-
`</p:sld>`);
|
|
249
|
-
}
|
|
250
|
-
function buildTitleShape(title, style) {
|
|
251
|
-
return (`<p:sp>` +
|
|
252
|
-
`<p:nvSpPr>` +
|
|
253
|
-
`<p:cNvPr id="2" name="Title"/>` +
|
|
254
|
-
`<p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr>` +
|
|
255
|
-
`<p:nvPr><p:ph type="title"/></p:nvPr>` +
|
|
256
|
-
`</p:nvSpPr>` +
|
|
257
|
-
`<p:spPr>` +
|
|
258
|
-
`<a:xfrm><a:off x="${TITLE_LEFT}" y="${TITLE_TOP}"/>` +
|
|
259
|
-
`<a:ext cx="${TITLE_WIDTH}" cy="${TITLE_HEIGHT}"/></a:xfrm>` +
|
|
260
|
-
`</p:spPr>` +
|
|
261
|
-
`<p:txBody>` +
|
|
262
|
-
`<a:bodyPr/>` +
|
|
263
|
-
`<a:lstStyle/>` +
|
|
264
|
-
`<a:p>` +
|
|
265
|
-
`<a:r>` +
|
|
266
|
-
`<a:rPr lang="en-US" sz="${DEFAULT_TITLE_SIZE}" dirty="0">` +
|
|
267
|
-
`<a:solidFill><a:srgbClr val="${style.titleColor}"/></a:solidFill>` +
|
|
268
|
-
`<a:latin typeface="${escapeXml(style.titleFont)}"/>` +
|
|
269
|
-
`</a:rPr>` +
|
|
270
|
-
`<a:t>${escapeXml(title)}</a:t>` +
|
|
271
|
-
`</a:r>` +
|
|
272
|
-
`</a:p>` +
|
|
273
|
-
`</p:txBody>` +
|
|
274
|
-
`</p:sp>`);
|
|
275
|
-
}
|
|
276
|
-
function buildBodyShapeCustom(bodyContent, top, height, style) {
|
|
277
|
-
// Set default text color for the body via defRPr
|
|
278
|
-
const defRPr = style.hasTheme
|
|
279
|
-
? `<a:defRPr><a:solidFill><a:srgbClr val="${style.text}"/></a:solidFill><a:latin typeface="${escapeXml(style.bodyFont)}"/></a:defRPr>`
|
|
280
|
-
: '';
|
|
281
|
-
return (`<p:sp>` +
|
|
282
|
-
`<p:nvSpPr>` +
|
|
283
|
-
`<p:cNvPr id="3" name="Content"/>` +
|
|
284
|
-
`<p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr>` +
|
|
285
|
-
`<p:nvPr><p:ph idx="1"/></p:nvPr>` +
|
|
286
|
-
`</p:nvSpPr>` +
|
|
287
|
-
`<p:spPr>` +
|
|
288
|
-
`<a:xfrm><a:off x="${BODY_LEFT}" y="${top}"/>` +
|
|
289
|
-
`<a:ext cx="${BODY_WIDTH}" cy="${height}"/></a:xfrm>` +
|
|
290
|
-
`</p:spPr>` +
|
|
291
|
-
`<p:txBody>` +
|
|
292
|
-
`<a:bodyPr/>` +
|
|
293
|
-
`<a:lstStyle>` +
|
|
294
|
-
`<a:lvl1pPr>${defRPr}</a:lvl1pPr>` +
|
|
295
|
-
`</a:lstStyle>` +
|
|
296
|
-
bodyContent +
|
|
297
|
-
`</p:txBody>` +
|
|
298
|
-
`</p:sp>`);
|
|
299
|
-
}
|
|
300
|
-
function buildImageShape(img, shapeId, left, top, maxWidth, maxHeight) {
|
|
301
|
-
return (`<p:pic>` +
|
|
302
|
-
`<p:nvPicPr>` +
|
|
303
|
-
`<p:cNvPr id="${shapeId}" name="${escapeXml(img.alt || 'Picture')}"/>` +
|
|
304
|
-
`<p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr>` +
|
|
305
|
-
`<p:nvPr/>` +
|
|
306
|
-
`</p:nvPicPr>` +
|
|
307
|
-
`<p:blipFill>` +
|
|
308
|
-
`<a:blip r:embed="${img.relId}"/>` +
|
|
309
|
-
`<a:stretch><a:fillRect/></a:stretch>` +
|
|
310
|
-
`</p:blipFill>` +
|
|
311
|
-
`<p:spPr>` +
|
|
312
|
-
`<a:xfrm><a:off x="${left}" y="${top}"/>` +
|
|
313
|
-
`<a:ext cx="${maxWidth}" cy="${maxHeight}"/></a:xfrm>` +
|
|
314
|
-
`<a:prstGeom prst="rect"><a:avLst/></a:prstGeom>` +
|
|
315
|
-
`</p:spPr>` +
|
|
316
|
-
`</p:pic>`);
|
|
317
|
-
}
|
|
318
|
-
// ============================================
|
|
319
|
-
// Block Conversion (Markdown -> DrawingML paragraphs)
|
|
320
|
-
// ============================================
|
|
321
|
-
function convertBodyBlocks(nodes, ctx) {
|
|
322
|
-
const parts = [];
|
|
323
|
-
for (const node of nodes) {
|
|
324
|
-
parts.push(convertBlock(node, ctx, 0));
|
|
325
|
-
}
|
|
326
|
-
return parts.join('');
|
|
327
|
-
}
|
|
328
|
-
function convertBlock(node, ctx, listDepth) {
|
|
329
|
-
switch (node.type) {
|
|
330
|
-
case 'heading':
|
|
331
|
-
return convertHeading(node, ctx);
|
|
332
|
-
case 'paragraph':
|
|
333
|
-
return convertParagraph(node, ctx);
|
|
334
|
-
case 'blockquote':
|
|
335
|
-
return convertBlockquote(node, ctx);
|
|
336
|
-
case 'list':
|
|
337
|
-
return convertList(node, ctx, listDepth);
|
|
338
|
-
case 'code':
|
|
339
|
-
return convertCodeBlock(node, ctx);
|
|
340
|
-
case 'table':
|
|
341
|
-
return convertTable(node, ctx);
|
|
342
|
-
case 'thematicBreak':
|
|
343
|
-
return convertThematicBreak(ctx);
|
|
344
|
-
case 'htmlBlock':
|
|
345
|
-
return convertHtmlBlock(node, ctx);
|
|
346
|
-
case 'math':
|
|
347
|
-
return convertMathBlock(node);
|
|
348
|
-
default:
|
|
349
|
-
return '';
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
function convertHeading(node, ctx) {
|
|
353
|
-
const size = headingSizeForDepth(node.depth);
|
|
354
|
-
const runs = convertInlines(node.children, ctx, { bold: true });
|
|
355
|
-
return (`<a:p>` +
|
|
356
|
-
`<a:pPr>` +
|
|
357
|
-
`<a:spcBef><a:spcPts val="600"/></a:spcBef>` +
|
|
358
|
-
`<a:defRPr sz="${size}" b="1">` +
|
|
359
|
-
`<a:solidFill><a:srgbClr val="${ctx.style.titleColor}"/></a:solidFill>` +
|
|
360
|
-
`<a:latin typeface="${escapeXml(ctx.style.titleFont)}"/>` +
|
|
361
|
-
`</a:defRPr>` +
|
|
362
|
-
`</a:pPr>` +
|
|
363
|
-
runs +
|
|
364
|
-
`</a:p>`);
|
|
365
|
-
}
|
|
366
|
-
function headingSizeForDepth(depth) {
|
|
367
|
-
const sizes = { 1: 3200, 2: 2800, 3: 2400, 4: 2000, 5: 1800, 6: 1800 };
|
|
368
|
-
return sizes[depth] ?? 1800;
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* Check if a paragraph contains only a single image (no surrounding text).
|
|
372
|
-
*/
|
|
373
|
-
function isImageOnlyParagraph(node) {
|
|
374
|
-
if (node.children.length === 1 && node.children[0].type === 'image') {
|
|
375
|
-
return node.children[0];
|
|
376
|
-
}
|
|
377
|
-
return null;
|
|
378
|
-
}
|
|
379
|
-
function convertParagraph(node, ctx) {
|
|
380
|
-
// If paragraph is just an image and we have the data, embed it as a shape
|
|
381
|
-
const soloImage = isImageOnlyParagraph(node);
|
|
382
|
-
if (soloImage && ctx.images) {
|
|
383
|
-
const data = ctx.images.get(soloImage.url);
|
|
384
|
-
if (data) {
|
|
385
|
-
ctx.addImage(soloImage.url, data, soloImage.alt || '');
|
|
386
|
-
return ''; // Image will be rendered as a p:pic shape, not inline text
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
const runs = convertInlines(node.children, ctx);
|
|
390
|
-
return `<a:p>${runs}</a:p>`;
|
|
391
|
-
}
|
|
392
|
-
function convertBlockquote(node, ctx) {
|
|
393
|
-
const parts = [];
|
|
394
|
-
for (const child of node.children) {
|
|
395
|
-
if (child.type === 'paragraph') {
|
|
396
|
-
const runs = convertInlines(child.children, ctx, { italic: true });
|
|
397
|
-
parts.push(`<a:p>` +
|
|
398
|
-
`<a:pPr marL="457200">` +
|
|
399
|
-
`<a:spcBef><a:spcPts val="200"/></a:spcBef>` +
|
|
400
|
-
`</a:pPr>` +
|
|
401
|
-
runs +
|
|
402
|
-
`</a:p>`);
|
|
403
|
-
}
|
|
404
|
-
else {
|
|
405
|
-
parts.push(convertBlock(child, ctx, 0));
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
return parts.join('');
|
|
409
|
-
}
|
|
410
|
-
function convertList(node, ctx, depth) {
|
|
411
|
-
const parts = [];
|
|
412
|
-
for (const item of node.children) {
|
|
413
|
-
parts.push(convertListItem(item, ctx, node.ordered ?? false, depth));
|
|
414
|
-
}
|
|
415
|
-
return parts.join('');
|
|
416
|
-
}
|
|
417
|
-
function convertListItem(item, ctx, ordered, depth) {
|
|
418
|
-
const parts = [];
|
|
419
|
-
for (const child of item.children) {
|
|
420
|
-
if (child.type === 'paragraph') {
|
|
421
|
-
const runs = convertInlines(child.children, ctx);
|
|
422
|
-
const bullet = ordered ? `<a:buAutoNum type="arabicPeriod"/>` : `<a:buChar char="\u2022"/>`;
|
|
423
|
-
const indent = 457200 * (depth + 1);
|
|
424
|
-
parts.push(`<a:p>` +
|
|
425
|
-
`<a:pPr lvl="${depth}" marL="${indent}" indent="-228600">` +
|
|
426
|
-
bullet +
|
|
427
|
-
`</a:pPr>` +
|
|
428
|
-
runs +
|
|
429
|
-
`</a:p>`);
|
|
430
|
-
}
|
|
431
|
-
else if (child.type === 'list') {
|
|
432
|
-
parts.push(convertList(child, ctx, depth + 1));
|
|
433
|
-
}
|
|
434
|
-
else {
|
|
435
|
-
parts.push(convertBlock(child, ctx, depth));
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
return parts.join('');
|
|
439
|
-
}
|
|
440
|
-
function convertCodeBlock(node, ctx) {
|
|
441
|
-
const lines = node.value.split('\n');
|
|
442
|
-
const parts = [];
|
|
443
|
-
for (const line of lines) {
|
|
444
|
-
parts.push(`<a:p>` +
|
|
445
|
-
`<a:r>` +
|
|
446
|
-
`<a:rPr lang="en-US" sz="${DEFAULT_CODE_SIZE}" dirty="0">` +
|
|
447
|
-
`<a:latin typeface="${escapeXml(ctx.style.codeFont)}"/>` +
|
|
448
|
-
`<a:solidFill><a:srgbClr val="${ctx.style.codeColor}"/></a:solidFill>` +
|
|
449
|
-
`</a:rPr>` +
|
|
450
|
-
`<a:t>${escapeXml(line || ' ')}</a:t>` +
|
|
451
|
-
`</a:r>` +
|
|
452
|
-
`</a:p>`);
|
|
453
|
-
}
|
|
454
|
-
return parts.join('');
|
|
455
|
-
}
|
|
456
|
-
function convertTable(node, ctx) {
|
|
457
|
-
const parts = [];
|
|
458
|
-
for (let ri = 0; ri < node.children.length; ri++) {
|
|
459
|
-
const row = node.children[ri];
|
|
460
|
-
parts.push(convertTableRow(row, ctx, ri === 0));
|
|
461
|
-
}
|
|
462
|
-
return parts.join('');
|
|
463
|
-
}
|
|
464
|
-
function convertTableRow(row, ctx, isHeader) {
|
|
465
|
-
const cells = [];
|
|
466
|
-
for (const cell of row.children) {
|
|
467
|
-
cells.push(extractPlainText(cell.children));
|
|
468
|
-
}
|
|
469
|
-
const text = cells.join(' | ');
|
|
470
|
-
const format = isHeader ? { bold: true } : {};
|
|
471
|
-
return `<a:p>${makeRun(text, format, ctx.style)}</a:p>`;
|
|
472
|
-
}
|
|
473
|
-
function convertThematicBreak(ctx) {
|
|
474
|
-
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>`;
|
|
475
|
-
}
|
|
476
|
-
function convertHtmlBlock(node, ctx) {
|
|
477
|
-
const text = stripHtmlTags(node.rawHtml).trim();
|
|
478
|
-
if (!text)
|
|
479
|
-
return '';
|
|
480
|
-
return `<a:p>${makeRun(text, {}, ctx.style)}</a:p>`;
|
|
481
|
-
}
|
|
482
|
-
function convertMathBlock(node) {
|
|
483
|
-
return (`<a:p>` +
|
|
484
|
-
`<a:pPr algn="ctr"/>` +
|
|
485
|
-
`<a:r>` +
|
|
486
|
-
`<a:rPr lang="en-US" i="1">` +
|
|
487
|
-
`<a:latin typeface="Cambria Math"/>` +
|
|
488
|
-
`</a:rPr>` +
|
|
489
|
-
`<a:t>${escapeXml(node.value)}</a:t>` +
|
|
490
|
-
`</a:r>` +
|
|
491
|
-
`</a:p>`);
|
|
492
|
-
}
|
|
493
|
-
function convertInlines(nodes, ctx, format = {}) {
|
|
494
|
-
const parts = [];
|
|
495
|
-
for (const node of nodes) {
|
|
496
|
-
parts.push(convertInline(node, ctx, format));
|
|
497
|
-
}
|
|
498
|
-
return parts.join('');
|
|
499
|
-
}
|
|
500
|
-
function convertInline(node, ctx, format) {
|
|
501
|
-
switch (node.type) {
|
|
502
|
-
case 'text':
|
|
503
|
-
return makeRun(node.value, format, ctx.style);
|
|
504
|
-
case 'strong':
|
|
505
|
-
return convertInlines(node.children, ctx, { ...format, bold: true });
|
|
506
|
-
case 'emphasis':
|
|
507
|
-
return convertInlines(node.children, ctx, { ...format, italic: true });
|
|
508
|
-
case 'delete':
|
|
509
|
-
return convertInlines(node.children, ctx, { ...format, strike: true });
|
|
510
|
-
case 'inlineCode':
|
|
511
|
-
return makeRun(node.value, { ...format, code: true }, ctx.style);
|
|
512
|
-
case 'link':
|
|
513
|
-
return convertLink(node, ctx, format);
|
|
514
|
-
case 'image':
|
|
515
|
-
return convertImage(node, format, ctx);
|
|
516
|
-
case 'break':
|
|
517
|
-
return `<a:br/>`;
|
|
518
|
-
case 'htmlInline':
|
|
519
|
-
return makeRun(stripHtmlTags(node.rawHtml), format, ctx.style);
|
|
520
|
-
case 'inlineMath':
|
|
521
|
-
return makeRun(node.value, { ...format, code: true }, ctx.style);
|
|
522
|
-
default:
|
|
523
|
-
return '';
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
function makeRun(text, format, style) {
|
|
527
|
-
if (!text)
|
|
528
|
-
return '';
|
|
529
|
-
const rPrParts = [`lang="en-US"`, `dirty="0"`];
|
|
530
|
-
if (format.bold)
|
|
531
|
-
rPrParts.push(`b="1"`);
|
|
532
|
-
if (format.italic)
|
|
533
|
-
rPrParts.push(`i="1"`);
|
|
534
|
-
if (format.strike)
|
|
535
|
-
rPrParts.push(`strike="sngStrike"`);
|
|
536
|
-
let innerParts = '';
|
|
537
|
-
if (format.code) {
|
|
538
|
-
innerParts += `<a:latin typeface="${escapeXml(style.codeFont)}"/>`;
|
|
539
|
-
innerParts += `<a:solidFill><a:srgbClr val="${style.codeColor}"/></a:solidFill>`;
|
|
540
|
-
rPrParts.push(`sz="${DEFAULT_CODE_SIZE}"`);
|
|
541
|
-
}
|
|
542
|
-
else if (style.hasTheme) {
|
|
543
|
-
innerParts += `<a:solidFill><a:srgbClr val="${style.text}"/></a:solidFill>`;
|
|
544
|
-
}
|
|
545
|
-
return (`<a:r>` +
|
|
546
|
-
`<a:rPr ${rPrParts.join(' ')}>${innerParts}</a:rPr>` +
|
|
547
|
-
`<a:t>${escapeXml(text)}</a:t>` +
|
|
548
|
-
`</a:r>`);
|
|
549
|
-
}
|
|
550
|
-
function convertLink(node, ctx, format) {
|
|
551
|
-
const rId = ctx.addHyperlink(node.url);
|
|
552
|
-
const parts = [];
|
|
553
|
-
for (const child of node.children) {
|
|
554
|
-
if (child.type === 'text') {
|
|
555
|
-
parts.push(makeHyperlinkRun(child.value, rId, format));
|
|
556
|
-
}
|
|
557
|
-
else {
|
|
558
|
-
parts.push(convertInline(child, ctx, format));
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
return parts.join('');
|
|
562
|
-
}
|
|
563
|
-
function makeHyperlinkRun(text, rId, format) {
|
|
564
|
-
if (!text)
|
|
565
|
-
return '';
|
|
566
|
-
const rPrParts = [`lang="en-US"`, `dirty="0"`];
|
|
567
|
-
if (format.bold)
|
|
568
|
-
rPrParts.push(`b="1"`);
|
|
569
|
-
if (format.italic)
|
|
570
|
-
rPrParts.push(`i="1"`);
|
|
571
|
-
return (`<a:r>` +
|
|
572
|
-
`<a:rPr ${rPrParts.join(' ')}>` +
|
|
573
|
-
`<a:solidFill><a:srgbClr val="${HYPERLINK_COLOR}"/></a:solidFill>` +
|
|
574
|
-
`<a:hlinkClick r:id="${rId}"/>` +
|
|
575
|
-
`</a:rPr>` +
|
|
576
|
-
`<a:t>${escapeXml(text)}</a:t>` +
|
|
577
|
-
`</a:r>`);
|
|
578
|
-
}
|
|
579
|
-
function convertImage(node, format, ctx) {
|
|
580
|
-
// For inline images (not image-only paragraphs), try to embed
|
|
581
|
-
if (ctx.images) {
|
|
582
|
-
const data = ctx.images.get(node.url);
|
|
583
|
-
if (data) {
|
|
584
|
-
// Register for rendering as a shape; return empty text
|
|
585
|
-
ctx.addImage(node.url, data, node.alt || '');
|
|
586
|
-
return '';
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
// Fallback: placeholder text
|
|
590
|
-
const alt = node.alt || node.url;
|
|
591
|
-
return makeRun(`[Image: ${alt}]`, { ...format, italic: true }, ctx.style);
|
|
592
|
-
}
|
|
593
|
-
// ============================================
|
|
594
|
-
// Package Assembly
|
|
595
|
-
// ============================================
|
|
596
|
-
async function buildPptxPackage(slideXmls, slideContexts, options) {
|
|
597
|
-
const pkg = createPackage();
|
|
598
|
-
const slideCount = slideXmls.length;
|
|
599
|
-
const slideMasterRelId = 'rId100';
|
|
600
|
-
const themeRelId = 'rId101';
|
|
601
|
-
const slideRelIds = [];
|
|
602
|
-
for (let i = 0; i < slideCount; i++) {
|
|
603
|
-
slideRelIds.push(`rId${i + 1}`);
|
|
604
|
-
}
|
|
605
|
-
// --- ppt/presentation.xml ---
|
|
606
|
-
const presentationXml = buildPresentationXml(slideCount, slideRelIds, slideMasterRelId, themeRelId);
|
|
607
|
-
pkg.addPart('ppt/presentation.xml', presentationXml, CONTENT_TYPE_PPTX_PRESENTATION);
|
|
608
|
-
// --- Slides ---
|
|
609
|
-
for (let i = 0; i < slideCount; i++) {
|
|
610
|
-
const slidePath = `ppt/slides/slide${i + 1}.xml`;
|
|
611
|
-
pkg.addPart(slidePath, slideXmls[i], CONTENT_TYPE_PPTX_SLIDE);
|
|
612
|
-
pkg.addRelationship(slidePath, {
|
|
613
|
-
id: 'rId1',
|
|
614
|
-
type: REL_SLIDE_LAYOUT,
|
|
615
|
-
target: '../slideLayouts/slideLayout1.xml',
|
|
616
|
-
});
|
|
617
|
-
// Per-slide relationships (hyperlinks + images)
|
|
618
|
-
const ctx = slideContexts[i];
|
|
619
|
-
for (const rel of ctx.relationships) {
|
|
620
|
-
pkg.addRelationship(slidePath, {
|
|
621
|
-
id: rel.id,
|
|
622
|
-
type: rel.type,
|
|
623
|
-
target: rel.target,
|
|
624
|
-
...(rel.targetMode ? { targetMode: rel.targetMode } : {}),
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
// Embed image binary parts
|
|
628
|
-
for (const img of ctx.embeddedImages) {
|
|
629
|
-
pkg.addBinaryPart(img.mediaPath, img.data, img.contentType);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
// --- Slide layout ---
|
|
633
|
-
const layoutMasterRelId = 'rId1';
|
|
634
|
-
const slideLayoutXml = buildSlideLayoutXml(layoutMasterRelId);
|
|
635
|
-
pkg.addPart('ppt/slideLayouts/slideLayout1.xml', slideLayoutXml, CONTENT_TYPE_PPTX_SLIDE_LAYOUT);
|
|
636
|
-
pkg.addRelationship('ppt/slideLayouts/slideLayout1.xml', {
|
|
637
|
-
id: layoutMasterRelId,
|
|
638
|
-
type: REL_SLIDE_MASTER,
|
|
639
|
-
target: '../slideMasters/slideMaster1.xml',
|
|
640
|
-
});
|
|
641
|
-
// --- Slide master ---
|
|
642
|
-
const masterLayoutRelId = 'rId1';
|
|
643
|
-
const masterThemeRelId = 'rId2';
|
|
644
|
-
const slideMasterXml = buildSlideMasterXml(masterLayoutRelId);
|
|
645
|
-
pkg.addPart('ppt/slideMasters/slideMaster1.xml', slideMasterXml, CONTENT_TYPE_PPTX_SLIDE_MASTER);
|
|
646
|
-
pkg.addRelationship('ppt/slideMasters/slideMaster1.xml', {
|
|
647
|
-
id: masterLayoutRelId,
|
|
648
|
-
type: REL_SLIDE_LAYOUT,
|
|
649
|
-
target: '../slideLayouts/slideLayout1.xml',
|
|
650
|
-
});
|
|
651
|
-
pkg.addRelationship('ppt/slideMasters/slideMaster1.xml', {
|
|
652
|
-
id: masterThemeRelId,
|
|
653
|
-
type: REL_THEME,
|
|
654
|
-
target: '../theme/theme1.xml',
|
|
655
|
-
});
|
|
656
|
-
// --- Theme ---
|
|
657
|
-
const themeXml = buildThemeXml();
|
|
658
|
-
pkg.addPart('ppt/theme/theme1.xml', themeXml, CONTENT_TYPE_PPTX_THEME);
|
|
659
|
-
// --- Root relationship ---
|
|
660
|
-
pkg.addRelationship('', {
|
|
661
|
-
id: 'rId1',
|
|
662
|
-
type: REL_OFFICE_DOCUMENT,
|
|
663
|
-
target: 'ppt/presentation.xml',
|
|
664
|
-
});
|
|
665
|
-
// --- Presentation relationships ---
|
|
666
|
-
for (let i = 0; i < slideCount; i++) {
|
|
667
|
-
pkg.addRelationship('ppt/presentation.xml', {
|
|
668
|
-
id: slideRelIds[i],
|
|
669
|
-
type: REL_SLIDE,
|
|
670
|
-
target: `slides/slide${i + 1}.xml`,
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
pkg.addRelationship('ppt/presentation.xml', {
|
|
674
|
-
id: slideMasterRelId,
|
|
675
|
-
type: REL_SLIDE_MASTER,
|
|
676
|
-
target: 'slideMasters/slideMaster1.xml',
|
|
677
|
-
});
|
|
678
|
-
pkg.addRelationship('ppt/presentation.xml', {
|
|
679
|
-
id: themeRelId,
|
|
680
|
-
type: REL_THEME,
|
|
681
|
-
target: 'theme/theme1.xml',
|
|
682
|
-
});
|
|
683
|
-
// --- Core properties ---
|
|
684
|
-
if (options.title || options.author || options.description) {
|
|
685
|
-
pkg.setCoreProperties({
|
|
686
|
-
title: options.title,
|
|
687
|
-
creator: options.author,
|
|
688
|
-
description: options.description,
|
|
689
|
-
created: new Date().toISOString(),
|
|
690
|
-
modified: new Date().toISOString(),
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
return pkg.toArrayBuffer();
|
|
694
|
-
}
|
|
695
|
-
// ============================================
|
|
696
|
-
// Helpers
|
|
697
|
-
// ============================================
|
|
698
|
-
function stripHtmlTags(html) {
|
|
699
|
-
return html.replace(/<[^>]*>/g, '');
|
|
700
|
-
}
|
|
701
|
-
function inferExtension(path) {
|
|
702
|
-
const dot = path.lastIndexOf('.');
|
|
703
|
-
if (dot === -1)
|
|
704
|
-
return 'png';
|
|
705
|
-
const ext = path
|
|
706
|
-
.substring(dot + 1)
|
|
707
|
-
.toLowerCase()
|
|
708
|
-
.split('?')[0];
|
|
709
|
-
return ext || 'png';
|
|
710
|
-
}
|
|
711
|
-
function inferMimeType(ext) {
|
|
712
|
-
const mimeTypes = {
|
|
713
|
-
jpg: 'image/jpeg',
|
|
714
|
-
jpeg: 'image/jpeg',
|
|
715
|
-
png: 'image/png',
|
|
716
|
-
gif: 'image/gif',
|
|
717
|
-
webp: 'image/webp',
|
|
718
|
-
svg: 'image/svg+xml',
|
|
719
|
-
bmp: 'image/bmp',
|
|
720
|
-
avif: 'image/avif',
|
|
721
|
-
};
|
|
722
|
-
return mimeTypes[ext] || 'image/png';
|
|
723
|
-
}
|
|
724
|
-
//# sourceMappingURL=export.js.map
|