@bendyline/squisq-formats 2.3.1 → 2.3.3
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/README.md +6 -1
- package/dist/{chunk-IPN56VLW.js → chunk-C22NYDN2.js} +529 -11
- package/dist/{chunk-LBHZSY7N.js → chunk-YLDQWCHS.js} +10 -0
- package/dist/{import-C3htUTss.d.ts → import-BhBGQqnz.d.ts} +10 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/pptx/index.d.ts +2 -2
- package/dist/pptx/index.js +1 -1
- package/dist/registry/index.d.ts +10 -1
- package/dist/registry/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -136,6 +136,7 @@ Chapters split at H1/H2 boundaries. Providing `audio` + `audioSegments` generate
|
|
|
136
136
|
|
|
137
137
|
```ts
|
|
138
138
|
import {
|
|
139
|
+
docToPptx,
|
|
139
140
|
markdownDocToPptx,
|
|
140
141
|
pptxToMarkdownDoc,
|
|
141
142
|
pptxToContainer,
|
|
@@ -145,6 +146,10 @@ import {
|
|
|
145
146
|
// slideBreak: 'h1' | 'h2' (default — H1 and H2 both break) | 'heading'
|
|
146
147
|
const pptxBytes = await markdownDocToPptx(markdownDoc, { slideBreak: 'h2', images });
|
|
147
148
|
|
|
149
|
+
// Export: player-ready Doc -> 16:9 PPTX using the slideshow's canonical
|
|
150
|
+
// block expansion and materialized visual layers (managed cover included by default)
|
|
151
|
+
const visualDeck = await docToPptx(slideshowDoc, { images });
|
|
152
|
+
|
|
148
153
|
// Import: PPTX → MarkdownDocument (add { extractImages: true } to reference slide images)
|
|
149
154
|
const imported = await pptxToMarkdownDoc(pptxBuffer);
|
|
150
155
|
|
|
@@ -152,7 +157,7 @@ const imported = await pptxToMarkdownDoc(pptxBuffer);
|
|
|
152
157
|
const container = await pptxToContainer(pptxBuffer);
|
|
153
158
|
```
|
|
154
159
|
|
|
155
|
-
**Fidelity:**
|
|
160
|
+
**Fidelity:** `markdownDocToPptx` preserves inline formatting as DrawingML runs. `docToPptx` treats a template-backed/player-ready `Doc` as a visual deck: it uses the same renderable-block flattening, timing expansion, managed-cover policy, theme, transitions, and materialized layer stack as slideshow view, so the PPTX has the same slide count and a matching 16:9 canvas. Text, shapes, tables, paths, and supplied images become editable native PowerPoint objects; browser-only map/video/Mermaid layers degrade to a static image when supplied or a labeled text fallback. Import reads slide order from `ppt/presentation.xml` and converts each slide's title (→ H2), body text (→ bullet list), and tables. **Slide-image extraction (v1.5):** import can now extract slide-level `<p:pic>` bitmaps into `images/` — `pptxToContainer` returns a container with those files and forces `extractImages: true`, while `pptxToMarkdownDoc` leaves it off by default. Honest limit: only slide-level `<p:pic>` bitmaps are extracted — layout/master images, charts, SmartArt, and picture-fills are **not**.
|
|
156
161
|
|
|
157
162
|
### CSV
|
|
158
163
|
|
|
@@ -54,12 +54,22 @@ import {
|
|
|
54
54
|
} from "./chunk-AONELFLA.js";
|
|
55
55
|
|
|
56
56
|
// src/pptx/export.ts
|
|
57
|
-
import { resolveFontFamily } from "@bendyline/squisq/schemas";
|
|
58
|
-
import {
|
|
57
|
+
import { resolveFontFamily, VIEWPORT_PRESETS } from "@bendyline/squisq/schemas";
|
|
58
|
+
import {
|
|
59
|
+
createTemplateContext,
|
|
60
|
+
docToMarkdown,
|
|
61
|
+
expandCoverBlock,
|
|
62
|
+
expandDocBlocks,
|
|
63
|
+
flattenRenderableBlocks,
|
|
64
|
+
isTemplateBlock,
|
|
65
|
+
resolvePersistentLayers,
|
|
66
|
+
resolveThemeForDoc
|
|
67
|
+
} from "@bendyline/squisq/doc";
|
|
68
|
+
import { stripIconMarkers } from "@bendyline/squisq/icon-marker";
|
|
59
69
|
import { readFrontmatterThemeId } from "@bendyline/squisq/markdown";
|
|
60
70
|
|
|
61
71
|
// src/pptx/styles.ts
|
|
62
|
-
var SLIDE_WIDTH =
|
|
72
|
+
var SLIDE_WIDTH = 12192e3;
|
|
63
73
|
var SLIDE_HEIGHT = 6858e3;
|
|
64
74
|
var TITLE_LEFT = 457200;
|
|
65
75
|
var TITLE_TOP = 274638;
|
|
@@ -82,7 +92,7 @@ function buildPresentationXml(slideCount, slideRelIds, slideMasterRelId, _themeR
|
|
|
82
92
|
for (let i = 0; i < slideCount; i++) {
|
|
83
93
|
sldIdList.push(`<p:sldId id="${256 + i}" r:id="${slideRelIds[i]}"/>`);
|
|
84
94
|
}
|
|
85
|
-
return xmlDeclaration() + `<p:presentation xmlns:a="${NS_DRAWINGML}" xmlns:r="${NS_R}" xmlns:p="${NS_PML}" saveSubsetFonts="1"><p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="${slideMasterRelId}"/></p:sldMasterIdLst><p:sldIdLst>${sldIdList.join("")}</p:sldIdLst><p:sldSz cx="${SLIDE_WIDTH}" cy="${SLIDE_HEIGHT}" type="
|
|
95
|
+
return xmlDeclaration() + `<p:presentation xmlns:a="${NS_DRAWINGML}" xmlns:r="${NS_R}" xmlns:p="${NS_PML}" saveSubsetFonts="1"><p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="${slideMasterRelId}"/></p:sldMasterIdLst><p:sldIdLst>${sldIdList.join("")}</p:sldIdLst><p:sldSz cx="${SLIDE_WIDTH}" cy="${SLIDE_HEIGHT}" type="screen16x9"/><p:notesSz cx="${SLIDE_HEIGHT}" cy="${SLIDE_WIDTH}"/></p:presentation>`;
|
|
86
96
|
}
|
|
87
97
|
function buildSlideMasterXml(layoutRelId) {
|
|
88
98
|
return xmlDeclaration() + `<p:sldMaster xmlns:a="${NS_DRAWINGML}" xmlns:r="${NS_R}" xmlns:p="${NS_PML}"><p:cSld><p:bg><p:bgRef idx="1001"><a:schemeClr val="bg1"/></p:bgRef></p:bg><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/></p:spTree></p:cSld><p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/><p:sldLayoutIdLst><p:sldLayoutId id="2147483649" r:id="${layoutRelId}"/></p:sldLayoutIdLst></p:sldMaster>`;
|
|
@@ -121,11 +131,69 @@ async function markdownDocToPptx(doc, options = {}) {
|
|
|
121
131
|
return buildPptxPackage(slideXmls, slideContexts, options);
|
|
122
132
|
}
|
|
123
133
|
async function docToPptx(doc, options = {}) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
134
|
+
options.signal?.throwIfAborted();
|
|
135
|
+
const effectiveOptions = !options.themeId && doc.themeId ? { ...options, themeId: doc.themeId } : options;
|
|
136
|
+
const flatBlocks = flattenRenderableBlocks(doc.blocks);
|
|
137
|
+
if (!flatBlocks.some(isTemplateBlock)) {
|
|
138
|
+
return markdownDocToPptx(docToMarkdown(doc), effectiveOptions);
|
|
139
|
+
}
|
|
140
|
+
const theme = resolveThemeForDoc(doc, effectiveOptions.themeId, effectiveOptions.themeRegistry);
|
|
141
|
+
const style = slideStyleFromTheme(theme, effectiveOptions);
|
|
142
|
+
const audioSegments = doc.audio?.segments?.map((segment) => ({
|
|
143
|
+
startTime: segment.startTime,
|
|
144
|
+
duration: segment.duration
|
|
145
|
+
}));
|
|
146
|
+
const persistentLayers = resolvePersistentLayers(
|
|
147
|
+
{ persistentLayers: doc.persistentLayers },
|
|
148
|
+
theme
|
|
149
|
+
);
|
|
150
|
+
const expanded = expandDocBlocks(flatBlocks, {
|
|
151
|
+
audioSegments,
|
|
152
|
+
viewport: VIEWPORT_PRESETS.landscape,
|
|
153
|
+
persistentLayers,
|
|
154
|
+
theme,
|
|
155
|
+
customTemplates: doc.customTemplates
|
|
156
|
+
});
|
|
157
|
+
const slideBlocks = [];
|
|
158
|
+
if (shouldIncludeCoverSlide(doc, effectiveOptions) && doc.startBlock) {
|
|
159
|
+
const coverContext = createTemplateContext(
|
|
160
|
+
theme,
|
|
161
|
+
0,
|
|
162
|
+
Math.max(1, expanded.length + 1),
|
|
163
|
+
VIEWPORT_PRESETS.landscape
|
|
164
|
+
);
|
|
165
|
+
slideBlocks.push({
|
|
166
|
+
id: "cover-block",
|
|
167
|
+
startTime: -1,
|
|
168
|
+
duration: 0,
|
|
169
|
+
audioSegment: -1,
|
|
170
|
+
layers: expandCoverBlock(doc.startBlock, coverContext)
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
slideBlocks.push(...expanded);
|
|
174
|
+
if (slideBlocks.length === 0) {
|
|
175
|
+
slideBlocks.push({ id: "empty-slide", startTime: 0, duration: 0, audioSegment: 0, layers: [] });
|
|
127
176
|
}
|
|
128
|
-
|
|
177
|
+
const slideXmls = [];
|
|
178
|
+
const slideContexts = [];
|
|
179
|
+
for (let index = 0; index < slideBlocks.length; index++) {
|
|
180
|
+
if ((index & 31) === 0) effectiveOptions.signal?.throwIfAborted();
|
|
181
|
+
const ctx = new SlideContext(
|
|
182
|
+
style,
|
|
183
|
+
effectiveOptions.images,
|
|
184
|
+
index,
|
|
185
|
+
effectiveOptions.allowRelativeHyperlinks ?? false,
|
|
186
|
+
effectiveOptions.signal
|
|
187
|
+
);
|
|
188
|
+
slideXmls.push(buildLayerSlideXml(slideBlocks[index], ctx));
|
|
189
|
+
slideContexts.push(ctx);
|
|
190
|
+
}
|
|
191
|
+
return buildPptxPackage(slideXmls, slideContexts, effectiveOptions);
|
|
192
|
+
}
|
|
193
|
+
function officeTypeface(family, fallback) {
|
|
194
|
+
const resolved = resolveFontFamily(family, fallback);
|
|
195
|
+
const first = resolved.split(",")[0]?.trim().replace(/^['"]|['"]$/g, "");
|
|
196
|
+
return first || fallback;
|
|
129
197
|
}
|
|
130
198
|
function resolveSlideStyle(themeId, options, doc) {
|
|
131
199
|
if (!themeId) {
|
|
@@ -142,19 +210,37 @@ function resolveSlideStyle(themeId, options, doc) {
|
|
|
142
210
|
};
|
|
143
211
|
}
|
|
144
212
|
const theme = resolveThemeForDoc(doc, themeId, options.themeRegistry);
|
|
213
|
+
return slideStyleFromTheme(theme, options);
|
|
214
|
+
}
|
|
215
|
+
function slideStyleFromTheme(theme, options) {
|
|
145
216
|
const c = theme.colors;
|
|
146
217
|
return {
|
|
147
218
|
background: toOoxmlHex(c.background, "FFFFFF"),
|
|
148
219
|
text: toOoxmlHex(c.text, "333333"),
|
|
149
220
|
titleColor: toOoxmlHex(c.highlight || c.secondary || c.text, "333333"),
|
|
150
221
|
mutedColor: toOoxmlHex(c.textMuted || c.text, "666666"),
|
|
151
|
-
titleFont:
|
|
152
|
-
bodyFont:
|
|
153
|
-
codeFont:
|
|
222
|
+
titleFont: officeTypeface(theme.typography?.titleFont, DEFAULT_TITLE_FONT),
|
|
223
|
+
bodyFont: officeTypeface(theme.typography?.bodyFont, options.defaultFont || DEFAULT_FONT),
|
|
224
|
+
codeFont: officeTypeface(theme.typography?.monoFont, DEFAULT_CODE_FONT),
|
|
154
225
|
codeColor: toOoxmlHex(c.textMuted || c.text, "333333"),
|
|
155
226
|
hasTheme: true
|
|
156
227
|
};
|
|
157
228
|
}
|
|
229
|
+
function shouldIncludeCoverSlide(doc, options) {
|
|
230
|
+
if (options.includeCoverSlide !== void 0) return options.includeCoverSlide;
|
|
231
|
+
const frontmatterValue = doc.frontmatter?.["squisq-cover-slide"] ?? doc.frontmatter?.["cover-slide"];
|
|
232
|
+
if (typeof frontmatterValue === "boolean") return frontmatterValue;
|
|
233
|
+
if (typeof frontmatterValue === "string") {
|
|
234
|
+
const normalized = frontmatterValue.trim().toLowerCase();
|
|
235
|
+
if (normalized === "false" || normalized === "no" || normalized === "off" || normalized === "0") {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
if (normalized === "true" || normalized === "yes" || normalized === "on" || normalized === "1") {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
158
244
|
function segmentIntoSlides(children, slideBreak) {
|
|
159
245
|
const maxDepth = slideBreak === "h1" ? 1 : slideBreak === "h2" ? 2 : 6;
|
|
160
246
|
const slides = [];
|
|
@@ -301,6 +387,438 @@ function buildSlideXml(slide, ctx) {
|
|
|
301
387
|
const extensionAttrs = transitionXml.includes("p14:") ? ` xmlns:mc="${NS_MC}" xmlns:p14="${NS_PML_2010}" mc:Ignorable="p14"` : "";
|
|
302
388
|
return xmlDeclaration() + `<p:sld xmlns:a="${NS_DRAWINGML}" xmlns:r="${NS_R}" xmlns:p="${NS_PML}"${extensionAttrs}><p:cSld>` + bgXml + `<p:spTree>` + shapes.join("") + `</p:spTree></p:cSld>` + transitionXml + `</p:sld>`;
|
|
303
389
|
}
|
|
390
|
+
function buildLayerSlideXml(block, ctx) {
|
|
391
|
+
const shapes = [
|
|
392
|
+
`<p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/>`
|
|
393
|
+
];
|
|
394
|
+
const textLayerY = (block.layers ?? []).filter((layer) => layer.type === "text").map((layer) => resolvePositionValue(layer.position.y, 1080));
|
|
395
|
+
for (let index = 0; index < (block.layers?.length ?? 0); index++) {
|
|
396
|
+
if ((index & 31) === 0) ctx.signal?.throwIfAborted();
|
|
397
|
+
const layer = block.layers[index];
|
|
398
|
+
const currentY = layer.type === "text" ? resolvePositionValue(layer.position.y, 1080) : void 0;
|
|
399
|
+
const nextTextY = currentY === void 0 ? void 0 : textLayerY.filter((candidate) => candidate > currentY + 1).sort((a, b) => a - b)[0];
|
|
400
|
+
shapes.push(
|
|
401
|
+
...convertLayerToShapes(
|
|
402
|
+
layer,
|
|
403
|
+
ctx,
|
|
404
|
+
nextTextY === void 0 || currentY === void 0 ? void 0 : Math.max(currentY + 1, nextTextY - 8)
|
|
405
|
+
)
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
const bgXml = ctx.style.hasTheme ? `<p:bg><p:bgPr><a:solidFill><a:srgbClr val="${ctx.style.background}"/></a:solidFill><a:effectLst/></p:bgPr></p:bg>` : "";
|
|
409
|
+
const transitionXml = buildTransitionXml(block.transition);
|
|
410
|
+
const extensionAttrs = transitionXml.includes("p14:") ? ` xmlns:mc="${NS_MC}" xmlns:p14="${NS_PML_2010}" mc:Ignorable="p14"` : "";
|
|
411
|
+
return xmlDeclaration() + `<p:sld xmlns:a="${NS_DRAWINGML}" xmlns:r="${NS_R}" xmlns:p="${NS_PML}"${extensionAttrs}><p:cSld>${bgXml}<p:spTree>${shapes.join("")}</p:spTree></p:cSld>` + transitionXml + `</p:sld>`;
|
|
412
|
+
}
|
|
413
|
+
function convertLayerToShapes(layer, ctx, textBottom) {
|
|
414
|
+
switch (layer.type) {
|
|
415
|
+
case "text":
|
|
416
|
+
return [buildLayerTextShape(layer, ctx.allocShapeId(), textBottom)];
|
|
417
|
+
case "shape":
|
|
418
|
+
return [buildLayerShape(layer, ctx.allocShapeId())];
|
|
419
|
+
case "image": {
|
|
420
|
+
const data = ctx.images?.get(layer.content.src);
|
|
421
|
+
if (data) {
|
|
422
|
+
const image = ctx.addImage(layer.content.src, data, layer.content.alt);
|
|
423
|
+
const rect = toEmuRect(resolveLayerRect(layer.position, 1920, 1080));
|
|
424
|
+
const embedded = ctx.embeddedImages.find((candidate) => candidate.relId === image);
|
|
425
|
+
return [
|
|
426
|
+
buildImageShape(embedded, ctx.allocShapeId(), rect.x, rect.y, rect.width, rect.height)
|
|
427
|
+
];
|
|
428
|
+
}
|
|
429
|
+
return [
|
|
430
|
+
buildLayerTextShape(
|
|
431
|
+
textLayerForPlaceholder(
|
|
432
|
+
layer.position,
|
|
433
|
+
`[Image: ${layer.content.alt || layer.content.src}]`,
|
|
434
|
+
ctx
|
|
435
|
+
),
|
|
436
|
+
ctx.allocShapeId()
|
|
437
|
+
)
|
|
438
|
+
];
|
|
439
|
+
}
|
|
440
|
+
case "table":
|
|
441
|
+
return buildTableLayerShapes(layer, ctx);
|
|
442
|
+
case "tree":
|
|
443
|
+
return [
|
|
444
|
+
buildLayerTextShape(
|
|
445
|
+
{
|
|
446
|
+
...layer,
|
|
447
|
+
type: "text",
|
|
448
|
+
content: {
|
|
449
|
+
text: treeItemsToText(layer.content.items),
|
|
450
|
+
style: {
|
|
451
|
+
fontSize: layer.content.style.fontSize,
|
|
452
|
+
fontFamily: layer.content.style.fontFamily,
|
|
453
|
+
color: layer.content.style.rowColor,
|
|
454
|
+
lineHeight: 1.35
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
ctx.allocShapeId()
|
|
459
|
+
)
|
|
460
|
+
];
|
|
461
|
+
case "path":
|
|
462
|
+
return buildPathLayerShapes(layer, ctx);
|
|
463
|
+
case "map": {
|
|
464
|
+
const staticSrc = layer.content.staticSrc;
|
|
465
|
+
const data = staticSrc ? ctx.images?.get(staticSrc) : void 0;
|
|
466
|
+
if (staticSrc && data) {
|
|
467
|
+
const relId = ctx.addImage(staticSrc, data, "Map");
|
|
468
|
+
const embedded = ctx.embeddedImages.find((candidate) => candidate.relId === relId);
|
|
469
|
+
const rect = toEmuRect(resolveLayerRect(layer.position, 1920, 1080));
|
|
470
|
+
return [
|
|
471
|
+
buildImageShape(embedded, ctx.allocShapeId(), rect.x, rect.y, rect.width, rect.height)
|
|
472
|
+
];
|
|
473
|
+
}
|
|
474
|
+
return [
|
|
475
|
+
buildLayerTextShape(
|
|
476
|
+
textLayerForPlaceholder(
|
|
477
|
+
layer.position,
|
|
478
|
+
`Map: ${layer.content.center.lat.toFixed(4)}, ${layer.content.center.lng.toFixed(4)}`,
|
|
479
|
+
ctx
|
|
480
|
+
),
|
|
481
|
+
ctx.allocShapeId()
|
|
482
|
+
)
|
|
483
|
+
];
|
|
484
|
+
}
|
|
485
|
+
case "video": {
|
|
486
|
+
const poster = layer.content.posterSrc;
|
|
487
|
+
const data = poster ? ctx.images?.get(poster) : void 0;
|
|
488
|
+
if (poster && data) {
|
|
489
|
+
const relId = ctx.addImage(poster, data, layer.content.alt);
|
|
490
|
+
const embedded = ctx.embeddedImages.find((candidate) => candidate.relId === relId);
|
|
491
|
+
const rect = toEmuRect(resolveLayerRect(layer.position, 1920, 1080));
|
|
492
|
+
return [
|
|
493
|
+
buildImageShape(embedded, ctx.allocShapeId(), rect.x, rect.y, rect.width, rect.height)
|
|
494
|
+
];
|
|
495
|
+
}
|
|
496
|
+
return [
|
|
497
|
+
buildLayerTextShape(
|
|
498
|
+
textLayerForPlaceholder(
|
|
499
|
+
layer.position,
|
|
500
|
+
`[Video: ${layer.content.alt || layer.content.src}]`,
|
|
501
|
+
ctx
|
|
502
|
+
),
|
|
503
|
+
ctx.allocShapeId()
|
|
504
|
+
)
|
|
505
|
+
];
|
|
506
|
+
}
|
|
507
|
+
case "mermaid":
|
|
508
|
+
return [
|
|
509
|
+
buildLayerTextShape(
|
|
510
|
+
textLayerForPlaceholder(layer.position, layer.content.source, ctx),
|
|
511
|
+
ctx.allocShapeId()
|
|
512
|
+
)
|
|
513
|
+
];
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function resolvePositionValue(value, dimension) {
|
|
517
|
+
if (typeof value === "number") return value;
|
|
518
|
+
if (typeof value === "string") {
|
|
519
|
+
const trimmed = value.trim();
|
|
520
|
+
if (trimmed.endsWith("%")) {
|
|
521
|
+
const percent = Number.parseFloat(trimmed);
|
|
522
|
+
if (Number.isFinite(percent)) return percent / 100 * dimension;
|
|
523
|
+
}
|
|
524
|
+
const numeric = Number.parseFloat(trimmed);
|
|
525
|
+
if (Number.isFinite(numeric)) return numeric;
|
|
526
|
+
}
|
|
527
|
+
return 0;
|
|
528
|
+
}
|
|
529
|
+
function resolveLayerRect(position, defaultWidth, defaultHeight) {
|
|
530
|
+
const width = Math.max(
|
|
531
|
+
1,
|
|
532
|
+
position.width != null ? resolvePositionValue(position.width, 1920) : defaultWidth
|
|
533
|
+
);
|
|
534
|
+
const height = Math.max(
|
|
535
|
+
1,
|
|
536
|
+
position.height != null ? resolvePositionValue(position.height, 1080) : defaultHeight
|
|
537
|
+
);
|
|
538
|
+
let x = resolvePositionValue(position.x, 1920);
|
|
539
|
+
let y = resolvePositionValue(position.y, 1080);
|
|
540
|
+
const anchor = position.anchor ?? "top-left";
|
|
541
|
+
if (anchor === "center") {
|
|
542
|
+
x -= width / 2;
|
|
543
|
+
y -= height / 2;
|
|
544
|
+
} else {
|
|
545
|
+
if (anchor.endsWith("right")) x -= width;
|
|
546
|
+
if (anchor.startsWith("bottom")) y -= height;
|
|
547
|
+
}
|
|
548
|
+
const clippedX = Math.min(1919, Math.max(0, x));
|
|
549
|
+
const clippedY = Math.min(1079, Math.max(0, y));
|
|
550
|
+
const clippedWidth = Math.max(1, Math.min(width - (clippedX - x), 1920 - clippedX));
|
|
551
|
+
const clippedHeight = Math.max(1, Math.min(height - (clippedY - y), 1080 - clippedY));
|
|
552
|
+
return { x: clippedX, y: clippedY, width: clippedWidth, height: clippedHeight };
|
|
553
|
+
}
|
|
554
|
+
function toEmuRect(rect) {
|
|
555
|
+
return {
|
|
556
|
+
x: Math.round(rect.x / 1920 * SLIDE_WIDTH),
|
|
557
|
+
y: Math.round(rect.y / 1080 * SLIDE_HEIGHT),
|
|
558
|
+
width: Math.max(1, Math.round(rect.width / 1920 * SLIDE_WIDTH)),
|
|
559
|
+
height: Math.max(1, Math.round(rect.height / 1080 * SLIDE_HEIGHT))
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function buildLayerTextShape(layer, shapeId, maxBottom) {
|
|
563
|
+
const text = stripIconMarkers(layer.content.text ?? "");
|
|
564
|
+
const style = layer.content.style;
|
|
565
|
+
const padding = style.padding ?? 0;
|
|
566
|
+
const rawLines = text.split("\n");
|
|
567
|
+
const estimatedWidth = Math.min(
|
|
568
|
+
1920,
|
|
569
|
+
Math.max(1, Math.max(...rawLines.map((line) => line.length), 1) * style.fontSize * 0.55)
|
|
570
|
+
);
|
|
571
|
+
const boxWidth = layer.position.width != null ? resolvePositionValue(layer.position.width, 1920) : estimatedWidth;
|
|
572
|
+
const fittedFontSize = rawLines.length === 1 && style.fontSize >= 80 && text.length > 0 ? Math.min(style.fontSize, boxWidth / (text.length * 0.78)) : style.fontSize;
|
|
573
|
+
const charsPerLine = Math.max(1, Math.floor(boxWidth / Math.max(1, fittedFontSize * 0.58)));
|
|
574
|
+
const wrappedLineCount = Math.max(
|
|
575
|
+
1,
|
|
576
|
+
rawLines.reduce((count, line) => count + Math.max(1, Math.ceil(line.length / charsPerLine)), 0)
|
|
577
|
+
);
|
|
578
|
+
const officeFontMetricSlack = fittedFontSize * 0.8;
|
|
579
|
+
const estimatedHeight = Math.max(
|
|
580
|
+
fittedFontSize * (style.lineHeight ?? 1.4) * wrappedLineCount + officeFontMetricSlack + padding * 2,
|
|
581
|
+
fittedFontSize + officeFontMetricSlack + padding * 2
|
|
582
|
+
);
|
|
583
|
+
const resolvedRect = resolveLayerRect(layer.position, boxWidth, estimatedHeight);
|
|
584
|
+
const safeBottom = Math.min(1060, maxBottom ?? 1060);
|
|
585
|
+
const virtualRect = {
|
|
586
|
+
...resolvedRect,
|
|
587
|
+
y: Math.min(resolvedRect.y, safeBottom - 1),
|
|
588
|
+
height: Math.max(1, Math.min(resolvedRect.height, safeBottom - resolvedRect.y))
|
|
589
|
+
};
|
|
590
|
+
const heightScale = Math.min(1, virtualRect.height / estimatedHeight);
|
|
591
|
+
const renderedFontSize = Math.max(1, fittedFontSize * heightScale * (heightScale < 1 ? 0.9 : 1));
|
|
592
|
+
const renderedStyle = renderedFontSize === style.fontSize ? style : { ...style, fontSize: renderedFontSize };
|
|
593
|
+
const rect = toEmuRect(virtualRect);
|
|
594
|
+
const align = style.textAlign === "center" ? "ctr" : style.textAlign === "right" ? "r" : "l";
|
|
595
|
+
const vertical = style.verticalAlign === "bottom" ? "b" : style.verticalAlign === "middle" ? "ctr" : "t";
|
|
596
|
+
const inset = Math.max(0, Math.round(padding / 1920 * SLIDE_WIDTH));
|
|
597
|
+
const bodyPr = `<a:bodyPr wrap="square" anchor="${vertical}" vertOverflow="clip" horzOverflow="clip" lIns="${inset}" rIns="${inset}" tIns="${inset}" bIns="${inset}"><a:noAutofit/></a:bodyPr>`;
|
|
598
|
+
const paragraphs = text.split("\n").map((line) => buildLayerTextParagraph(line, renderedStyle, align)).join("");
|
|
599
|
+
return `<p:sp><p:nvSpPr><p:cNvPr id="${shapeId}" name="${escapeXml(layer.id)}"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="${rect.x}" y="${rect.y}"/><a:ext cx="${rect.width}" cy="${rect.height}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom>` + fillXml(style.background, style.backgroundOpacity, style.backgroundGradient) + lineXml(style.borderColor, style.borderWidth, style.borderStyle) + `</p:spPr><p:txBody>${bodyPr}<a:lstStyle/>${paragraphs}</p:txBody></p:sp>`;
|
|
600
|
+
}
|
|
601
|
+
function buildLayerTextParagraph(text, style, align) {
|
|
602
|
+
const size = Math.max(100, Math.round(style.fontSize * 75));
|
|
603
|
+
const color = parseCssColor(style.color, "000000");
|
|
604
|
+
const typeface = officeTypeface(style.fontFamily, DEFAULT_FONT);
|
|
605
|
+
const runProperties = [
|
|
606
|
+
`lang="en-US"`,
|
|
607
|
+
`dirty="0"`,
|
|
608
|
+
`sz="${size}"`,
|
|
609
|
+
...style.fontWeight === "bold" ? ['b="1"'] : [],
|
|
610
|
+
...style.fontStyle === "italic" ? ['i="1"'] : []
|
|
611
|
+
].join(" ");
|
|
612
|
+
const lineSpacing = Math.max(1e4, Math.round((style.lineHeight ?? 1.4) * 1e5));
|
|
613
|
+
return `<a:p><a:pPr algn="${align}"><a:lnSpc><a:spcPct val="${lineSpacing}"/></a:lnSpc></a:pPr><a:r><a:rPr ${runProperties}>${solidColorXml(color)}<a:latin typeface="${escapeXml(typeface)}"/></a:rPr><a:t>${escapeXml(text || " ")}</a:t></a:r></a:p>`;
|
|
614
|
+
}
|
|
615
|
+
function buildLayerShape(layer, shapeId) {
|
|
616
|
+
const rect = toEmuRect(resolveLayerRect(layer.position, 100, 100));
|
|
617
|
+
const geometry = layer.content.shape === "circle" ? "ellipse" : layer.content.shape === "line" ? "line" : layer.content.borderRadius ? "roundRect" : "rect";
|
|
618
|
+
const fill = layer.content.shape === "line" ? "<a:noFill/>" : fillXml(layer.content.fill, layer.content.fillOpacity, layer.content.gradient);
|
|
619
|
+
const stroke = lineXml(
|
|
620
|
+
layer.content.stroke ?? (layer.content.shape === "line" ? "#ffffff" : void 0),
|
|
621
|
+
layer.content.strokeWidth ?? (layer.content.shape === "line" ? 2 : void 0),
|
|
622
|
+
layer.content.borderStyle
|
|
623
|
+
);
|
|
624
|
+
return `<p:sp><p:nvSpPr><p:cNvPr id="${shapeId}" name="${escapeXml(layer.id)}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="${rect.x}" y="${rect.y}"/><a:ext cx="${rect.width}" cy="${rect.height}"/></a:xfrm><a:prstGeom prst="${geometry}"><a:avLst/></a:prstGeom>` + fill + stroke + `</p:spPr></p:sp>`;
|
|
625
|
+
}
|
|
626
|
+
function buildTableLayerShapes(layer, ctx) {
|
|
627
|
+
const rows = [layer.content.headers, ...layer.content.rows];
|
|
628
|
+
const columnCount = Math.max(1, ...rows.map((row) => row.length));
|
|
629
|
+
const rowCount = Math.max(1, rows.length);
|
|
630
|
+
const rect = resolveLayerRect(layer.position, 1920, 1080);
|
|
631
|
+
const cellWidth = rect.width / columnCount;
|
|
632
|
+
const cellHeight = rect.height / rowCount;
|
|
633
|
+
const shapes = [];
|
|
634
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
|
635
|
+
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
|
|
636
|
+
const isHeader = rowIndex === 0;
|
|
637
|
+
const text = rows[rowIndex]?.[columnIndex] ?? "";
|
|
638
|
+
shapes.push(
|
|
639
|
+
buildLayerTextShape(
|
|
640
|
+
{
|
|
641
|
+
id: `${layer.id}-${rowIndex}-${columnIndex}`,
|
|
642
|
+
type: "text",
|
|
643
|
+
position: {
|
|
644
|
+
x: rect.x + columnIndex * cellWidth,
|
|
645
|
+
y: rect.y + rowIndex * cellHeight,
|
|
646
|
+
width: cellWidth,
|
|
647
|
+
height: cellHeight
|
|
648
|
+
},
|
|
649
|
+
content: {
|
|
650
|
+
text,
|
|
651
|
+
style: {
|
|
652
|
+
fontSize: layer.content.style.fontSize,
|
|
653
|
+
fontFamily: isHeader ? layer.content.style.headerFontFamily ?? layer.content.style.fontFamily : layer.content.style.fontFamily,
|
|
654
|
+
fontWeight: isHeader ? "bold" : "normal",
|
|
655
|
+
color: isHeader ? layer.content.style.headerColor : layer.content.style.cellColor,
|
|
656
|
+
background: isHeader ? layer.content.style.headerBackground : layer.content.style.cellBackground,
|
|
657
|
+
borderColor: layer.content.style.borderColor,
|
|
658
|
+
borderWidth: 1,
|
|
659
|
+
padding: Math.max(4, layer.content.style.fontSize * 0.25),
|
|
660
|
+
verticalAlign: "middle",
|
|
661
|
+
textAlign: layer.content.align?.[columnIndex] ?? "left",
|
|
662
|
+
lineHeight: 1.2
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
ctx.allocShapeId()
|
|
667
|
+
)
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return shapes;
|
|
672
|
+
}
|
|
673
|
+
function treeItemsToText(items, depth = 0) {
|
|
674
|
+
const lines = [];
|
|
675
|
+
for (const item of items) {
|
|
676
|
+
lines.push(
|
|
677
|
+
`${" ".repeat(depth)}${item.isDir ? "\u25BE " : "\u2022 "}${item.label}${item.comment ? ` \u2014 ${item.comment}` : ""}`
|
|
678
|
+
);
|
|
679
|
+
if (item.children.length > 0) lines.push(treeItemsToText(item.children, depth + 1));
|
|
680
|
+
}
|
|
681
|
+
return lines.filter(Boolean).join("\n");
|
|
682
|
+
}
|
|
683
|
+
function buildPathLayerShapes(layer, ctx) {
|
|
684
|
+
if (layer.content.shapeKind) {
|
|
685
|
+
const shape = {
|
|
686
|
+
id: layer.id,
|
|
687
|
+
type: "shape",
|
|
688
|
+
position: layer.position,
|
|
689
|
+
content: {
|
|
690
|
+
shape: layer.content.shapeKind === "circle" ? "circle" : "rect",
|
|
691
|
+
fill: layer.content.fill,
|
|
692
|
+
fillOpacity: layer.content.fillOpacity,
|
|
693
|
+
gradient: layer.content.gradient,
|
|
694
|
+
stroke: layer.content.stroke,
|
|
695
|
+
strokeWidth: layer.content.strokeWidth,
|
|
696
|
+
borderStyle: layer.content.borderStyle
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
return [buildLayerShape(shape, ctx.allocShapeId())];
|
|
700
|
+
}
|
|
701
|
+
const numbers = layer.content.d.match(/-?\d+(?:\.\d+)?/g)?.map(Number);
|
|
702
|
+
if (!numbers || numbers.length < 4) return [];
|
|
703
|
+
const x1 = numbers[0];
|
|
704
|
+
const y1 = numbers[1];
|
|
705
|
+
const x2 = numbers[numbers.length - 2];
|
|
706
|
+
const y2 = numbers[numbers.length - 1];
|
|
707
|
+
const rect = toEmuRect({
|
|
708
|
+
x: Math.min(x1, x2),
|
|
709
|
+
y: Math.min(y1, y2),
|
|
710
|
+
width: Math.max(1, Math.abs(x2 - x1)),
|
|
711
|
+
height: Math.max(1, Math.abs(y2 - y1))
|
|
712
|
+
});
|
|
713
|
+
const flipH = x2 < x1 ? ' flipH="1"' : "";
|
|
714
|
+
const flipV = y2 < y1 ? ' flipV="1"' : "";
|
|
715
|
+
const stroke = parseCssColor(layer.content.stroke, "FFFFFF");
|
|
716
|
+
const width = Math.max(1, Math.round((layer.content.strokeWidth ?? 2) * 9525));
|
|
717
|
+
const head = markerXml("headEnd", layer.content.startMarker);
|
|
718
|
+
const tail = markerXml("tailEnd", layer.content.endMarker);
|
|
719
|
+
const dash = dashXml(layer.content.borderStyle, layer.content.dasharray);
|
|
720
|
+
const shapeId = ctx.allocShapeId();
|
|
721
|
+
return [
|
|
722
|
+
`<p:sp><p:nvSpPr><p:cNvPr id="${shapeId}" name="${escapeXml(layer.id)}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm${flipH}${flipV}><a:off x="${rect.x}" y="${rect.y}"/><a:ext cx="${rect.width}" cy="${rect.height}"/></a:xfrm><a:prstGeom prst="line"><a:avLst/></a:prstGeom><a:noFill/><a:ln w="${width}">${solidColorXml(stroke)}${dash}${head}${tail}</a:ln></p:spPr></p:sp>`
|
|
723
|
+
];
|
|
724
|
+
}
|
|
725
|
+
function textLayerForPlaceholder(position, text, ctx) {
|
|
726
|
+
return {
|
|
727
|
+
id: `placeholder-${ctx.allocShapeId()}`,
|
|
728
|
+
type: "text",
|
|
729
|
+
position,
|
|
730
|
+
content: {
|
|
731
|
+
text,
|
|
732
|
+
style: {
|
|
733
|
+
fontSize: 28,
|
|
734
|
+
fontFamily: ctx.style.bodyFont,
|
|
735
|
+
fontStyle: "italic",
|
|
736
|
+
color: `#${ctx.style.mutedColor}`,
|
|
737
|
+
textAlign: "center",
|
|
738
|
+
verticalAlign: "middle",
|
|
739
|
+
padding: 12
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
function parseCssColor(color, fallback) {
|
|
745
|
+
if (!color) return { hex: toOoxmlHex(fallback), alpha: 1e5 };
|
|
746
|
+
const trimmed = color.trim();
|
|
747
|
+
if (trimmed === "transparent" || trimmed === "none") return { hex: "000000", alpha: 0 };
|
|
748
|
+
const hexMatch = /^#?([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(trimmed);
|
|
749
|
+
if (hexMatch) {
|
|
750
|
+
const raw = hexMatch[1];
|
|
751
|
+
const expanded = raw.length === 3 ? raw.split("").map((char) => char + char).join("") : raw;
|
|
752
|
+
const alpha = expanded.length === 8 ? Math.round(Number.parseInt(expanded.slice(6, 8), 16) / 255 * 1e5) : 1e5;
|
|
753
|
+
return { hex: expanded.slice(0, 6).toUpperCase(), alpha };
|
|
754
|
+
}
|
|
755
|
+
const rgbMatch = /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*([\d.]+))?\s*\)$/i.exec(trimmed);
|
|
756
|
+
if (rgbMatch) {
|
|
757
|
+
const channel = (value) => Math.max(0, Math.min(255, Math.round(Number(value)))).toString(16).padStart(2, "0");
|
|
758
|
+
const alpha = rgbMatch[4] ? Math.round(Math.max(0, Math.min(1, Number(rgbMatch[4]))) * 1e5) : 1e5;
|
|
759
|
+
return {
|
|
760
|
+
hex: `${channel(rgbMatch[1])}${channel(rgbMatch[2])}${channel(rgbMatch[3])}`.toUpperCase(),
|
|
761
|
+
alpha
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
const named = {
|
|
765
|
+
black: "000000",
|
|
766
|
+
white: "FFFFFF",
|
|
767
|
+
red: "FF0000",
|
|
768
|
+
blue: "0000FF",
|
|
769
|
+
green: "008000",
|
|
770
|
+
gray: "808080",
|
|
771
|
+
grey: "808080"
|
|
772
|
+
};
|
|
773
|
+
return { hex: named[trimmed.toLowerCase()] ?? toOoxmlHex(fallback), alpha: 1e5 };
|
|
774
|
+
}
|
|
775
|
+
function solidColorXml(color) {
|
|
776
|
+
const alpha = color.alpha < 1e5 ? `<a:alpha val="${color.alpha}"/>` : "";
|
|
777
|
+
return `<a:solidFill><a:srgbClr val="${color.hex}">${alpha}</a:srgbClr></a:solidFill>`;
|
|
778
|
+
}
|
|
779
|
+
function fillXml(fill, opacity, gradient) {
|
|
780
|
+
if (gradient) return gradientFillXml(gradient.from, gradient.to, gradient.angle ?? 0, opacity);
|
|
781
|
+
if (!fill || fill === "none" || fill === "transparent") return "<a:noFill/>";
|
|
782
|
+
if (fill.includes("gradient(")) {
|
|
783
|
+
const colors = fill.match(/#[0-9a-f]{3,8}|rgba?\([^)]*\)/gi) ?? [];
|
|
784
|
+
if (colors.length >= 2) {
|
|
785
|
+
const angle = Number.parseFloat(
|
|
786
|
+
/(?:linear-gradient\()\s*([\d.-]+)deg/i.exec(fill)?.[1] ?? "0"
|
|
787
|
+
);
|
|
788
|
+
return gradientFillXml(colors[0], colors[colors.length - 1], angle, opacity);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
const color = parseCssColor(fill, "000000");
|
|
792
|
+
if (opacity !== void 0)
|
|
793
|
+
color.alpha = Math.round(color.alpha * Math.max(0, Math.min(1, opacity)));
|
|
794
|
+
return solidColorXml(color);
|
|
795
|
+
}
|
|
796
|
+
function gradientFillXml(from, to, angle, opacity) {
|
|
797
|
+
const first = parseCssColor(from, "000000");
|
|
798
|
+
const second = parseCssColor(to, first.hex);
|
|
799
|
+
if (opacity !== void 0) {
|
|
800
|
+
const factor = Math.max(0, Math.min(1, opacity));
|
|
801
|
+
first.alpha = Math.round(first.alpha * factor);
|
|
802
|
+
second.alpha = Math.round(second.alpha * factor);
|
|
803
|
+
}
|
|
804
|
+
const ooxmlAngle = Math.round(((90 - angle) % 360 + 360) % 360 * 6e4);
|
|
805
|
+
return `<a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:srgbClr val="${first.hex}">${first.alpha < 1e5 ? `<a:alpha val="${first.alpha}"/>` : ""}</a:srgbClr></a:gs><a:gs pos="100000"><a:srgbClr val="${second.hex}">${second.alpha < 1e5 ? `<a:alpha val="${second.alpha}"/>` : ""}</a:srgbClr></a:gs></a:gsLst><a:lin ang="${ooxmlAngle}" scaled="1"/></a:gradFill>`;
|
|
806
|
+
}
|
|
807
|
+
function lineXml(color, widthPx, borderStyle) {
|
|
808
|
+
if (!color || !widthPx || widthPx <= 0) return "<a:ln><a:noFill/></a:ln>";
|
|
809
|
+
const width = Math.max(1, Math.round(widthPx * 9525));
|
|
810
|
+
return `<a:ln w="${width}">${solidColorXml(parseCssColor(color, "000000"))}${dashXml(borderStyle)}</a:ln>`;
|
|
811
|
+
}
|
|
812
|
+
function dashXml(style, dasharray) {
|
|
813
|
+
if (style === "dashed" || dasharray && dasharray !== "none") return '<a:prstDash val="dash"/>';
|
|
814
|
+
if (style === "dotted") return '<a:prstDash val="dot"/>';
|
|
815
|
+
return "";
|
|
816
|
+
}
|
|
817
|
+
function markerXml(tag, marker) {
|
|
818
|
+
if (!marker || marker === "none") return "";
|
|
819
|
+
const type = marker === "arrow" ? "triangle" : marker === "open" ? "arrow" : marker === "diamond" ? "diamond" : marker === "circle" ? "oval" : "square";
|
|
820
|
+
return `<a:${tag} type="${type}" w="med" len="med"/>`;
|
|
821
|
+
}
|
|
304
822
|
function buildTransitionXml(transition) {
|
|
305
823
|
if (!transition || transition.type === "cut") return "";
|
|
306
824
|
const child = buildTransitionChildXml(transition);
|
|
@@ -295,6 +295,7 @@ function pptxImportOptionsFrom(options) {
|
|
|
295
295
|
function defaultFormats() {
|
|
296
296
|
const md = {
|
|
297
297
|
id: "md",
|
|
298
|
+
templateAnnotationHandling: "preserved",
|
|
298
299
|
label: "Markdown",
|
|
299
300
|
mimeType: MIME.md,
|
|
300
301
|
extensions: [".md", ".markdown"],
|
|
@@ -320,6 +321,7 @@ function defaultFormats() {
|
|
|
320
321
|
};
|
|
321
322
|
const docx = {
|
|
322
323
|
id: "docx",
|
|
324
|
+
templateAnnotationHandling: "ignored",
|
|
323
325
|
label: "Word (DOCX)",
|
|
324
326
|
mimeType: MIME.docx,
|
|
325
327
|
extensions: [".docx"],
|
|
@@ -349,6 +351,7 @@ function defaultFormats() {
|
|
|
349
351
|
};
|
|
350
352
|
const pdf = {
|
|
351
353
|
id: "pdf",
|
|
354
|
+
templateAnnotationHandling: "ignored",
|
|
352
355
|
label: "PDF",
|
|
353
356
|
mimeType: MIME.pdf,
|
|
354
357
|
extensions: [".pdf"],
|
|
@@ -380,6 +383,7 @@ function defaultFormats() {
|
|
|
380
383
|
};
|
|
381
384
|
const pptx = {
|
|
382
385
|
id: "pptx",
|
|
386
|
+
templateAnnotationHandling: "rendered",
|
|
383
387
|
label: "PowerPoint (PPTX)",
|
|
384
388
|
mimeType: MIME.pptx,
|
|
385
389
|
extensions: [".pptx"],
|
|
@@ -409,6 +413,7 @@ function defaultFormats() {
|
|
|
409
413
|
};
|
|
410
414
|
const xlsx = {
|
|
411
415
|
id: "xlsx",
|
|
416
|
+
templateAnnotationHandling: "ignored",
|
|
412
417
|
label: "Excel (XLSX)",
|
|
413
418
|
mimeType: MIME.xlsx,
|
|
414
419
|
extensions: [".xlsx"],
|
|
@@ -432,6 +437,7 @@ function defaultFormats() {
|
|
|
432
437
|
};
|
|
433
438
|
const csv = {
|
|
434
439
|
id: "csv",
|
|
440
|
+
templateAnnotationHandling: "ignored",
|
|
435
441
|
label: "CSV",
|
|
436
442
|
mimeType: MIME.csv,
|
|
437
443
|
extensions: [".csv"],
|
|
@@ -456,6 +462,7 @@ function defaultFormats() {
|
|
|
456
462
|
};
|
|
457
463
|
const html = {
|
|
458
464
|
id: "html",
|
|
465
|
+
templateAnnotationHandling: "rendered",
|
|
459
466
|
label: "HTML (single file)",
|
|
460
467
|
mimeType: MIME.html,
|
|
461
468
|
extensions: [".html", ".htm"],
|
|
@@ -485,6 +492,7 @@ function defaultFormats() {
|
|
|
485
492
|
};
|
|
486
493
|
const htmlzip = {
|
|
487
494
|
id: "htmlzip",
|
|
495
|
+
templateAnnotationHandling: "rendered",
|
|
488
496
|
label: "HTML (ZIP archive)",
|
|
489
497
|
mimeType: MIME.zip,
|
|
490
498
|
extensions: [".html.zip"],
|
|
@@ -508,6 +516,7 @@ function defaultFormats() {
|
|
|
508
516
|
};
|
|
509
517
|
const epub = {
|
|
510
518
|
id: "epub",
|
|
519
|
+
templateAnnotationHandling: "ignored",
|
|
511
520
|
label: "EPUB",
|
|
512
521
|
mimeType: MIME.epub,
|
|
513
522
|
extensions: [".epub"],
|
|
@@ -529,6 +538,7 @@ function defaultFormats() {
|
|
|
529
538
|
};
|
|
530
539
|
const dbk = {
|
|
531
540
|
id: "dbk",
|
|
541
|
+
templateAnnotationHandling: "preserved",
|
|
532
542
|
label: "Squisq container (DBK)",
|
|
533
543
|
mimeType: MIME.zip,
|
|
534
544
|
extensions: [".dbk", ".zip"],
|
|
@@ -71,6 +71,12 @@ interface PptxExportOptions {
|
|
|
71
71
|
images?: Map<string, ArrayBuffer>;
|
|
72
72
|
/** Permit carefully validated relative hyperlink targets. Default: false. */
|
|
73
73
|
allowRelativeHyperlinks?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Include the Doc's managed cover (`startBlock`) as slide 1. Defaults to
|
|
76
|
+
* the document's `squisq-cover-slide` / `cover-slide` setting, then true.
|
|
77
|
+
* MarkdownDocument export is unaffected because it has no managed cover.
|
|
78
|
+
*/
|
|
79
|
+
includeCoverSlide?: boolean;
|
|
74
80
|
}
|
|
75
81
|
/**
|
|
76
82
|
* Convert a MarkdownDocument to a .pptx ArrayBuffer.
|
|
@@ -79,7 +85,10 @@ declare function markdownDocToPptx(doc: MarkdownDocument, options?: PptxExportOp
|
|
|
79
85
|
/**
|
|
80
86
|
* Convert a squisq Doc to a .pptx ArrayBuffer.
|
|
81
87
|
*
|
|
82
|
-
*
|
|
88
|
+
* Template-backed Docs are exported through the same canonical slideshow
|
|
89
|
+
* expansion as the player, then their materialized visual layers are mapped
|
|
90
|
+
* to native DrawingML shapes. Plain, non-template Docs retain the semantic
|
|
91
|
+
* Markdown export path for backward compatibility.
|
|
83
92
|
*/
|
|
84
93
|
declare function docToPptx(doc: Doc, options?: PptxExportOptions): Promise<ArrayBuffer>;
|
|
85
94
|
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export { ExtractedFileTheme, InferSourceFormat, InferThemeOptions, InferredFileT
|
|
|
9
9
|
export { BUILTIN_FORMAT_IDS, BuiltinFormatOptions, ConversionError, ConversionErrorCode, ConversionErrorOptions, ConversionLimits, ConversionResult, ConvertOptions, ConvertSource, DEFAULT_CONVERSION_LIMITS, DbkFormatOptions, FormatDefinition, FormatId, FormatRegistry, MarkdownFormatOptions, NormalizedInput, PreparedConversion, PreparedExportOptions, convert, createRegistry, defaultFormats, defaultRegistry, prepareConversion, resolveConversionLimits } from './registry/index.js';
|
|
10
10
|
export { Z as ZipSafetyError, a as ZipSafetyErrorCode, b as ZipSafetyErrorOptions, c as ZipSafetyLimits } from './zipLimits-BOKCB7qk.js';
|
|
11
11
|
export { H as HtmlExportOptions, a as HtmlImportOptions, c as collectImagePaths, h as htmlToMarkdown, b as htmlToMarkdownDoc, d as htmlToMarkdownDocSync } from './import-C8whCC7_.js';
|
|
12
|
-
export { P as PptxExportOptions, a as PptxImportOptions, d as docToPptx, m as markdownDocToPptx, p as pptxToMarkdownDoc } from './import-
|
|
12
|
+
export { P as PptxExportOptions, a as PptxImportOptions, d as docToPptx, m as markdownDocToPptx, p as pptxToMarkdownDoc } from './import-BhBGQqnz.js';
|
|
13
13
|
export { X as XlsxExportOptions, a as XlsxImportOptions, d as docToXlsx, m as markdownDocToXlsx, x as xlsxToMarkdownDoc } from './export-D9msROJS.js';
|
|
14
14
|
import '@bendyline/squisq/schemas';
|
|
15
15
|
import '@bendyline/squisq/markdown';
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
defaultRegistry,
|
|
12
12
|
prepareConversion,
|
|
13
13
|
resolveConversionLimits
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-YLDQWCHS.js";
|
|
15
15
|
import {
|
|
16
16
|
inferThemeFromFile
|
|
17
17
|
} from "./chunk-6S6GU3ZG.js";
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
markdownDocToPptx,
|
|
34
34
|
pptxToDoc,
|
|
35
35
|
pptxToMarkdownDoc
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-C22NYDN2.js";
|
|
37
37
|
import "./chunk-TAAENIRB.js";
|
|
38
38
|
import "./chunk-GX7RAUME.js";
|
|
39
39
|
import {
|
package/dist/pptx/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as PptxImportOptions } from '../import-
|
|
2
|
-
export { P as PptxExportOptions, d as docToPptx, m as markdownDocToPptx, b as pptxToContainer, p as pptxToMarkdownDoc } from '../import-
|
|
1
|
+
import { a as PptxImportOptions } from '../import-BhBGQqnz.js';
|
|
2
|
+
export { P as PptxExportOptions, d as docToPptx, m as markdownDocToPptx, b as pptxToContainer, p as pptxToMarkdownDoc } from '../import-BhBGQqnz.js';
|
|
3
3
|
import { Doc } from '@bendyline/squisq/schemas';
|
|
4
4
|
export { A as AnalyzePptxLayoutsOptions, a as AnalyzedLayout, E as ExtractedPlaceholder, b as ExtractedSlideLayout, I as InspectPptxLayoutsOptions, L as LayoutVerdict, P as PptxColorHints, c as PptxLayoutInference, d as PptxLayoutSummary, e as analyzePptxLayouts, i as inspectPptxLayouts } from '../layouts-CTdPlB-u.js';
|
|
5
5
|
import '@bendyline/squisq/markdown';
|
package/dist/pptx/index.js
CHANGED
package/dist/registry/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { TransformStyleInput, TransformStyleRegistry } from '@bendyline/squisq/t
|
|
|
3
3
|
import { ParseOptions, StringifyOptions, MarkdownDocument } from '@bendyline/squisq/markdown';
|
|
4
4
|
import { ContentContainer } from '@bendyline/squisq/storage';
|
|
5
5
|
import { DocxImportOptions, DocxExportOptions } from '../docx/index.js';
|
|
6
|
-
import { a as PptxImportOptions, P as PptxExportOptions } from '../import-
|
|
6
|
+
import { a as PptxImportOptions, P as PptxExportOptions } from '../import-BhBGQqnz.js';
|
|
7
7
|
import { a as XlsxImportOptions, X as XlsxExportOptions } from '../export-D9msROJS.js';
|
|
8
8
|
import { CsvImportOptions, CsvExportOptions } from '../csv/index.js';
|
|
9
9
|
import { PdfImportOptions, PdfExportOptions } from '../pdf/index.js';
|
|
@@ -123,12 +123,21 @@ type PreparedExportOptions = Pick<ConvertOptions, 'signal' | 'title' | 'resolveP
|
|
|
123
123
|
interface PreparedConversion {
|
|
124
124
|
convert(to: FormatId, options?: PreparedExportOptions): Promise<ConversionResult>;
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* How a format's exporter treats Squisq template annotations: `rendered`
|
|
128
|
+
* materializes template visuals into the output, `preserved` keeps annotations
|
|
129
|
+
* intact for round-tripping, and `ignored` flattens blocks to semantic content
|
|
130
|
+
* so annotations have no effect on the exported result.
|
|
131
|
+
*/
|
|
132
|
+
type TemplateAnnotationHandling = 'rendered' | 'preserved' | 'ignored';
|
|
126
133
|
/** Describes how a single format imports to / exports from the squisq model. */
|
|
127
134
|
interface FormatDefinition {
|
|
128
135
|
id: FormatId;
|
|
129
136
|
label: string;
|
|
130
137
|
mimeType: string;
|
|
131
138
|
extensions: readonly string[];
|
|
139
|
+
/** Exporter treatment of template annotations. Absent means unspecified. */
|
|
140
|
+
templateAnnotationHandling?: TemplateAnnotationHandling;
|
|
132
141
|
/** Import raw bytes to a MarkdownDocument. */
|
|
133
142
|
importDoc?(data: ArrayBuffer, options: ConvertOptions): Promise<MarkdownDocument>;
|
|
134
143
|
/** Import raw bytes to a ContentContainer (markdown + extracted media). */
|
package/dist/registry/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bendyline/squisq-formats",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.3",
|
|
4
4
|
"description": "Document format converters — DOCX, PDF, OOXML import/export",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bendyline",
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"dependencies": {
|
|
108
108
|
"@pdf-lib/upng": "1.0.1",
|
|
109
109
|
"@xmldom/xmldom": "0.9.10",
|
|
110
|
-
"@bendyline/squisq": "2.3.
|
|
110
|
+
"@bendyline/squisq": "2.3.3",
|
|
111
111
|
"jszip": "3.10.1",
|
|
112
112
|
"pdf-lib": "1.17.1",
|
|
113
113
|
"pdfjs-dist": "4.10.38"
|