@bendyline/squisq-formats 2.1.0 → 2.2.0
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/LICENSE +21 -0
- package/NOTICE.md +20 -0
- package/README.md +1 -1
- package/dist/{chunk-NNHKUXKA.js → chunk-26ISNJ7Y.js} +85 -65
- package/dist/{chunk-NKAJPJ4G.js → chunk-2JJ5RFDZ.js} +0 -1
- package/dist/{chunk-WQSHGBLN.js → chunk-3NKXBZSR.js} +193 -42
- package/dist/{chunk-KURGXM4I.js → chunk-4V3KCHAP.js} +3 -4
- package/dist/{chunk-MLX2BOJC.js → chunk-6RQOV3B3.js} +1 -2
- package/dist/{chunk-EW54IRRS.js → chunk-6S6GU3ZG.js} +5 -6
- package/dist/{chunk-FE6OJV6O.js → chunk-7AWFHP5U.js} +1 -1
- package/dist/{chunk-RFAPOKHJ.js → chunk-AD2WT564.js} +59 -9
- package/dist/{chunk-O3GVVND4.js → chunk-AONELFLA.js} +0 -1
- package/dist/{chunk-SC67HYQJ.js → chunk-EJTNGKEA.js} +5 -8
- package/dist/chunk-GX7RAUME.js +121 -0
- package/dist/{chunk-SSUPBUF5.js → chunk-IIQYS2YH.js} +0 -1
- package/dist/{chunk-RLU7UFYU.js → chunk-IPN56VLW.js} +83 -58
- package/dist/{chunk-DTDF6QDP.js → chunk-JE6LSIHE.js} +81 -20
- package/dist/{chunk-U4MRIFKL.js → chunk-JU2RHXUB.js} +0 -1
- package/dist/{chunk-4VUWTSGM.js → chunk-K6XRMVPW.js} +64 -31
- package/dist/{chunk-ODL3SSPT.js → chunk-KXOZMWBS.js} +0 -1
- package/dist/chunk-OGS5VCGJ.js +446 -0
- package/dist/{chunk-GVS2XXV6.js → chunk-PJXJI2LY.js} +449 -57
- package/dist/{chunk-74GO3FVS.js → chunk-PU7REGWV.js} +5 -8
- package/dist/{chunk-PN52A5AA.js → chunk-SBUW7NHR.js} +0 -1
- package/dist/{chunk-QFLDYKCR.js → chunk-TAAENIRB.js} +5 -8
- package/dist/{chunk-7ARKUCQT.js → chunk-X2DEAXNK.js} +54 -2
- package/dist/container/index.js +1 -2
- package/dist/csv/index.d.ts +27 -2
- package/dist/csv/index.js +1 -2
- package/dist/docx/index.d.ts +5 -1
- package/dist/docx/index.js +9 -11
- package/dist/epub/index.d.ts +2 -0
- package/dist/epub/index.js +5 -6
- package/dist/{export-D2NkylDT.d.ts → export-D9msROJS.d.ts} +18 -6
- package/dist/extract-MN7LA3NL.js +13 -0
- package/dist/html/index.d.ts +11 -4
- package/dist/html/index.js +3 -4
- package/dist/images-ESPQKVTW.js +6 -0
- package/dist/{import-K8mfc0fz.d.ts → import-C3htUTss.d.ts} +5 -1
- package/dist/{import-DTkDxHmZ.d.ts → import-C8whCC7_.d.ts} +6 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +28 -26
- package/dist/infer/index.d.ts +3 -3
- package/dist/infer/index.js +7 -9
- package/dist/{layouts-BHrgZ5FS.d.ts → layouts-CTdPlB-u.d.ts} +1 -1
- package/dist/layouts-DRWZGSPD.js +10 -0
- package/dist/{mapTheme-IR27S6IV.js → mapTheme-4TWH25FT.js} +1 -2
- package/dist/ooxml/index.d.ts +3 -3
- package/dist/ooxml/index.js +14 -13
- package/dist/pdf/index.d.ts +18 -0
- package/dist/pdf/index.js +2 -3
- package/dist/pptx/index.d.ts +4 -4
- package/dist/pptx/index.js +11 -13
- package/dist/{reader-B9L8Ucbj.d.ts → reader-B_m1aKZC.d.ts} +30 -1
- package/dist/registry/index.d.ts +21 -5
- package/dist/registry/index.js +9 -6
- package/dist/{themeReader-DJKErl_j.d.ts → themeReader-DCtwC83Q.d.ts} +1 -1
- package/dist/xlsx/index.d.ts +3 -3
- package/dist/xlsx/index.js +6 -7
- package/package.json +6 -3
- package/dist/chunk-4VUWTSGM.js.map +0 -1
- package/dist/chunk-6M7Z25LA.js +0 -46
- package/dist/chunk-6M7Z25LA.js.map +0 -1
- package/dist/chunk-74GO3FVS.js.map +0 -1
- package/dist/chunk-7ARKUCQT.js.map +0 -1
- package/dist/chunk-DTDF6QDP.js.map +0 -1
- package/dist/chunk-EW54IRRS.js.map +0 -1
- package/dist/chunk-FE6OJV6O.js.map +0 -1
- package/dist/chunk-GVS2XXV6.js.map +0 -1
- package/dist/chunk-KURGXM4I.js.map +0 -1
- package/dist/chunk-MLX2BOJC.js.map +0 -1
- package/dist/chunk-NKAJPJ4G.js.map +0 -1
- package/dist/chunk-NNHKUXKA.js.map +0 -1
- package/dist/chunk-O3GVVND4.js.map +0 -1
- package/dist/chunk-ODL3SSPT.js.map +0 -1
- package/dist/chunk-PN52A5AA.js.map +0 -1
- package/dist/chunk-QFLDYKCR.js.map +0 -1
- package/dist/chunk-RFAPOKHJ.js.map +0 -1
- package/dist/chunk-RLU7UFYU.js.map +0 -1
- package/dist/chunk-SC67HYQJ.js.map +0 -1
- package/dist/chunk-SSUPBUF5.js.map +0 -1
- package/dist/chunk-U4MRIFKL.js.map +0 -1
- package/dist/chunk-UGYF5AZE.js +0 -275
- package/dist/chunk-UGYF5AZE.js.map +0 -1
- package/dist/chunk-WQSHGBLN.js.map +0 -1
- package/dist/chunk-YRT7GQ5Y.js +0 -28
- package/dist/chunk-YRT7GQ5Y.js.map +0 -1
- package/dist/container/index.js.map +0 -1
- package/dist/csv/index.js.map +0 -1
- package/dist/docx/index.js.map +0 -1
- package/dist/epub/index.js.map +0 -1
- package/dist/extract-OJ7ZQV6P.js +0 -15
- package/dist/extract-OJ7ZQV6P.js.map +0 -1
- package/dist/html/index.js.map +0 -1
- package/dist/images-7FBWPKE3.js +0 -7
- package/dist/images-7FBWPKE3.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/infer/index.js.map +0 -1
- package/dist/layouts-5VDIRPIJ.js +0 -12
- package/dist/layouts-5VDIRPIJ.js.map +0 -1
- package/dist/mapTheme-IR27S6IV.js.map +0 -1
- package/dist/ooxml/index.js.map +0 -1
- package/dist/pdf/index.js.map +0 -1
- package/dist/pptx/index.js.map +0 -1
- package/dist/registry/index.js.map +0 -1
- package/dist/xlsx/index.js.map +0 -1
- package/src/__tests__/container.test.ts +0 -230
- package/src/__tests__/convert.test.ts +0 -495
- package/src/__tests__/csvImport.test.ts +0 -84
- package/src/__tests__/docxExport.test.ts +0 -491
- package/src/__tests__/docxImport.test.ts +0 -531
- package/src/__tests__/epub.test.ts +0 -649
- package/src/__tests__/exportThemeReconciliation.test.ts +0 -87
- package/src/__tests__/formatRegistry.test.ts +0 -174
- package/src/__tests__/html.test.ts +0 -439
- package/src/__tests__/htmlImport.test.ts +0 -57
- package/src/__tests__/inferTheme.test.ts +0 -135
- package/src/__tests__/lossyWarnings.test.ts +0 -146
- package/src/__tests__/ooxml.test.ts +0 -271
- package/src/__tests__/ooxmlCancellation.test.ts +0 -113
- package/src/__tests__/ooxmlThemeReader.test.ts +0 -92
- package/src/__tests__/pdfExport.test.ts +0 -322
- package/src/__tests__/pdfImport.test.ts +0 -384
- package/src/__tests__/plainHtml.test.ts +0 -417
- package/src/__tests__/plainHtmlBundle.test.ts +0 -253
- package/src/__tests__/pptxExport.test.ts +0 -138
- package/src/__tests__/pptxImport.test.ts +0 -145
- package/src/__tests__/pptxInferFixtures.ts +0 -314
- package/src/__tests__/pptxLayoutInfer.test.ts +0 -395
- package/src/__tests__/roundTrip.test.ts +0 -201
- package/src/__tests__/roundTripAssets.test.ts +0 -50
- package/src/__tests__/roundTripMatrix.fixtures.ts +0 -86
- package/src/__tests__/roundTripMatrix.helpers.ts +0 -154
- package/src/__tests__/roundTripMatrix.test.ts +0 -142
- package/src/__tests__/sharedContainer.test.ts +0 -41
- package/src/__tests__/sharedImages.test.ts +0 -61
- package/src/__tests__/xlsxExport.test.ts +0 -164
- package/src/__tests__/xlsxImport.test.ts +0 -80
- package/src/__tests__/zipSafety.test.ts +0 -317
- package/src/container/index.ts +0 -94
- package/src/csv/index.ts +0 -188
- package/src/docx/export.ts +0 -1375
- package/src/docx/import.ts +0 -1250
- package/src/docx/index.ts +0 -26
- package/src/docx/styles.ts +0 -145
- package/src/epub/export.ts +0 -968
- package/src/epub/index.ts +0 -20
- package/src/html/docsHtmlBundle.ts +0 -373
- package/src/html/htmlTemplate.ts +0 -385
- package/src/html/imageUtils.ts +0 -61
- package/src/html/import.ts +0 -297
- package/src/html/index.ts +0 -212
- package/src/html/plainHtml.ts +0 -790
- package/src/html/plainHtmlBundle.ts +0 -421
- package/src/index.ts +0 -109
- package/src/infer/extract.ts +0 -127
- package/src/infer/index.ts +0 -199
- package/src/infer/mapTheme.ts +0 -176
- package/src/infer/types.ts +0 -27
- package/src/ooxml/index.ts +0 -111
- package/src/ooxml/namespaces.ts +0 -217
- package/src/ooxml/readUtils.ts +0 -44
- package/src/ooxml/reader.ts +0 -318
- package/src/ooxml/themeReader.ts +0 -197
- package/src/ooxml/types.ts +0 -103
- package/src/ooxml/writer.ts +0 -339
- package/src/ooxml/xmlUtils.ts +0 -123
- package/src/pdf/export.ts +0 -1084
- package/src/pdf/import.ts +0 -1164
- package/src/pdf/index.ts +0 -29
- package/src/pdf/styles.ts +0 -180
- package/src/pptx/export.ts +0 -1184
- package/src/pptx/import.ts +0 -455
- package/src/pptx/index.ts +0 -52
- package/src/pptx/layouts.ts +0 -1222
- package/src/pptx/styles.ts +0 -96
- package/src/pptx/templates.ts +0 -187
- package/src/registry/convert.ts +0 -433
- package/src/registry/defaultFormats.ts +0 -413
- package/src/registry/errors.ts +0 -46
- package/src/registry/index.ts +0 -43
- package/src/registry/registry.ts +0 -48
- package/src/registry/types.ts +0 -170
- package/src/shared/boundedZipArchive.ts +0 -383
- package/src/shared/container.ts +0 -28
- package/src/shared/fidelity.ts +0 -130
- package/src/shared/images.ts +0 -44
- package/src/shared/inlineRuns.ts +0 -99
- package/src/shared/text.ts +0 -41
- package/src/shared/zipEntryCount.ts +0 -151
- package/src/shared/zipLimits.ts +0 -296
- package/src/shared/zipSafety.ts +0 -19
- package/src/xlsx/export.ts +0 -253
- package/src/xlsx/import.ts +0 -160
- package/src/xlsx/index.ts +0 -35
package/src/pptx/import.ts
DELETED
|
@@ -1,455 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PPTX import — PresentationML (.pptx) → MarkdownDocument.
|
|
3
|
-
*
|
|
4
|
-
* Reuses the shared ooxml/ reader. Reads slide order from
|
|
5
|
-
* `ppt/presentation.xml` (`<p:sldIdLst>`), resolves each slide part via
|
|
6
|
-
* relationships, and converts each slide to: an H2 of the title placeholder
|
|
7
|
-
* (or "Slide N"), the remaining text as a bullet list, and any slide tables
|
|
8
|
-
* (`<a:tbl>`) as markdown tables. Text lives in the DrawingML namespace
|
|
9
|
-
* (`a:p` / `a:r` / `a:t`) inside PresentationML shapes (`p:sp`).
|
|
10
|
-
*
|
|
11
|
-
* Theme + layout inference (default ON): the deck's theme part is compiled
|
|
12
|
-
* into a Squisq custom theme and carried in the returned document's
|
|
13
|
-
* frontmatter (`squisq-custom-themes` + `squisq-theme`); slide layouts are
|
|
14
|
-
* classified against the built-in template set, distinctive ones become
|
|
15
|
-
* `squisq-custom-templates` definitions, and each slide's heading is
|
|
16
|
-
* annotated (`{[templateName …]}`) per its layout's verdict. Pass
|
|
17
|
-
* `inferTheme: false` / `inferLayouts: false` for legacy plain imports.
|
|
18
|
-
* Inference never fails an import — extraction errors degrade to plain
|
|
19
|
-
* output with a console warning.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import type {
|
|
23
|
-
HeadingTemplateAnnotation,
|
|
24
|
-
MarkdownBlockNode,
|
|
25
|
-
MarkdownDocument,
|
|
26
|
-
MarkdownImage,
|
|
27
|
-
MarkdownListItem,
|
|
28
|
-
MarkdownTable,
|
|
29
|
-
MarkdownTableCell,
|
|
30
|
-
MarkdownTableRow,
|
|
31
|
-
} from '@bendyline/squisq/markdown';
|
|
32
|
-
import { stringifyMarkdown } from '@bendyline/squisq/markdown';
|
|
33
|
-
import type { CustomTemplateDefinition, Theme } from '@bendyline/squisq/schemas';
|
|
34
|
-
import { getPartBinary, getPartRelationships, getPartXml, openPackage } from '../ooxml/reader.js';
|
|
35
|
-
import type { OoxmlOpenOptions } from '../ooxml/reader.js';
|
|
36
|
-
import type { OoxmlPackage } from '../ooxml/types.js';
|
|
37
|
-
import { NS_DRAWINGML, NS_PML, NS_R } from '../ooxml/namespaces.js';
|
|
38
|
-
import { attrNS, baseDirOf, resolveTarget } from '../ooxml/readUtils.js';
|
|
39
|
-
import type { ContentContainer } from '@bendyline/squisq/storage';
|
|
40
|
-
import { buildContainer } from '../shared/container.js';
|
|
41
|
-
import { extToMime } from '../shared/images.js';
|
|
42
|
-
import type { ExtractedFileTheme } from '../infer/types.js';
|
|
43
|
-
import type { AnalyzedLayout, PptxLayoutInference } from './layouts.js';
|
|
44
|
-
|
|
45
|
-
export interface PptxImportOptions extends OoxmlOpenOptions {
|
|
46
|
-
/**
|
|
47
|
-
* Whether to extract embedded slide images into the document as image nodes
|
|
48
|
-
* (referencing `images/imageN.ext`). When false, pictures are ignored so the
|
|
49
|
-
* markdown never carries dangling image references with no backing container.
|
|
50
|
-
* `pptxToContainer` forces this on. Default: false.
|
|
51
|
-
*/
|
|
52
|
-
extractImages?: boolean;
|
|
53
|
-
/**
|
|
54
|
-
* Infer a Squisq theme from the deck's theme part (colors + fonts) and
|
|
55
|
-
* carry it in the returned document's frontmatter (`squisq-custom-themes`
|
|
56
|
-
* + `squisq-theme`). Default: true — pass false for a legacy import with
|
|
57
|
-
* no frontmatter.
|
|
58
|
-
*/
|
|
59
|
-
inferTheme?: boolean;
|
|
60
|
-
/**
|
|
61
|
-
* Derive custom layout templates from the deck's slide layouts/masters
|
|
62
|
-
* (`squisq-custom-templates` frontmatter) and annotate slide headings
|
|
63
|
-
* with matching built-in or generated templates. Default: true.
|
|
64
|
-
*/
|
|
65
|
-
inferLayouts?: boolean;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Per-import mutable state used to collect embedded images across slides.
|
|
70
|
-
* Mirrors the docx import context's `extractedImages` / `imageCounter`.
|
|
71
|
-
*/
|
|
72
|
-
interface ImportContext {
|
|
73
|
-
pkg: OoxmlPackage;
|
|
74
|
-
extractImages: boolean;
|
|
75
|
-
/** Collected image files: `images/imageN.ext` → { data, mimeType } */
|
|
76
|
-
extractedImages: Map<string, { data: ArrayBuffer; mimeType: string }>;
|
|
77
|
-
imageCounter: number;
|
|
78
|
-
/** Layout analysis when `inferLayouts` is on. */
|
|
79
|
-
inference?: PptxLayoutInference;
|
|
80
|
-
/** Generated custom templates actually referenced by ≥1 slide annotation. */
|
|
81
|
-
usedCustomTemplates: Map<string, CustomTemplateDefinition>;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async function orderedSlidePaths(pkg: OoxmlPackage): Promise<string[]> {
|
|
85
|
-
const pres = await getPartXml(pkg, 'ppt/presentation.xml');
|
|
86
|
-
if (!pres) return [];
|
|
87
|
-
const rels = await getPartRelationships(pkg, 'ppt/presentation.xml');
|
|
88
|
-
const relById = new Map(rels.map((r) => [r.id, r.target]));
|
|
89
|
-
const out: string[] = [];
|
|
90
|
-
const ids = pres.getElementsByTagNameNS(NS_PML, 'sldId');
|
|
91
|
-
for (let i = 0; i < ids.length; i++) {
|
|
92
|
-
const rid = attrNS(ids[i]!, NS_R, 'id', 'r:id');
|
|
93
|
-
const target = rid ? relById.get(rid) : undefined;
|
|
94
|
-
if (target) out.push(resolveTarget('ppt', target));
|
|
95
|
-
}
|
|
96
|
-
return out;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/** Concatenate the DrawingML text runs (`a:t`) inside a paragraph element. */
|
|
100
|
-
function paragraphText(para: Element): string {
|
|
101
|
-
const ts = para.getElementsByTagNameNS(NS_DRAWINGML, 't');
|
|
102
|
-
let s = '';
|
|
103
|
-
for (let i = 0; i < ts.length; i++) s += ts[i]!.textContent ?? '';
|
|
104
|
-
return s.trim();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function isTitleShape(sp: Element): boolean {
|
|
108
|
-
const ph = sp.getElementsByTagNameNS(NS_PML, 'ph');
|
|
109
|
-
if (!ph.length) return false;
|
|
110
|
-
const type = ph[0]!.getAttribute('type');
|
|
111
|
-
return type === 'title' || type === 'ctrTitle';
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function tableToMarkdown(tbl: Element): MarkdownTable {
|
|
115
|
-
const rows: MarkdownTableRow[] = [];
|
|
116
|
-
const trs = tbl.getElementsByTagNameNS(NS_DRAWINGML, 'tr');
|
|
117
|
-
for (let r = 0; r < trs.length; r++) {
|
|
118
|
-
const tcs = trs[r]!.getElementsByTagNameNS(NS_DRAWINGML, 'tc');
|
|
119
|
-
const cells: MarkdownTableCell[] = [];
|
|
120
|
-
for (let c = 0; c < tcs.length; c++) {
|
|
121
|
-
const paras = tcs[c]!.getElementsByTagNameNS(NS_DRAWINGML, 'p');
|
|
122
|
-
const text = Array.from({ length: paras.length }, (_, i) => paragraphText(paras[i]!))
|
|
123
|
-
.filter(Boolean)
|
|
124
|
-
.join(' ');
|
|
125
|
-
cells.push({
|
|
126
|
-
type: 'tableCell',
|
|
127
|
-
...(r === 0 ? { isHeader: true } : {}),
|
|
128
|
-
children: text ? [{ type: 'text', value: text }] : [],
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
rows.push({ type: 'tableRow', children: cells });
|
|
132
|
-
}
|
|
133
|
-
return { type: 'table', children: rows };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Extract every `<p:pic>` picture in a slide as an image node, reading the
|
|
138
|
-
* `<a:blip r:embed>` relationship, resolving it to the media part, and copying
|
|
139
|
-
* the bytes into `ctx.extractedImages` under `images/imageN.ext`.
|
|
140
|
-
*/
|
|
141
|
-
async function extractSlideImages(
|
|
142
|
-
doc: Document,
|
|
143
|
-
slidePath: string,
|
|
144
|
-
ctx: ImportContext,
|
|
145
|
-
): Promise<MarkdownImage[]> {
|
|
146
|
-
const rels = await getPartRelationships(ctx.pkg, slidePath);
|
|
147
|
-
const relById = new Map(rels.map((r) => [r.id, r.target]));
|
|
148
|
-
const baseDir = baseDirOf(slidePath);
|
|
149
|
-
|
|
150
|
-
const images: MarkdownImage[] = [];
|
|
151
|
-
const pics = doc.getElementsByTagNameNS(NS_PML, 'pic');
|
|
152
|
-
for (let i = 0; i < pics.length; i++) {
|
|
153
|
-
const pic = pics[i]!;
|
|
154
|
-
const blips = pic.getElementsByTagNameNS(NS_DRAWINGML, 'blip');
|
|
155
|
-
if (!blips.length) continue;
|
|
156
|
-
const embed = attrNS(blips[0]!, NS_R, 'embed', 'r:embed');
|
|
157
|
-
if (!embed) continue;
|
|
158
|
-
const target = relById.get(embed);
|
|
159
|
-
if (!target) continue;
|
|
160
|
-
|
|
161
|
-
const mediaPath = resolveTarget(baseDir, target);
|
|
162
|
-
const data = await getPartBinary(ctx.pkg, mediaPath);
|
|
163
|
-
if (!data) continue;
|
|
164
|
-
|
|
165
|
-
const dot = mediaPath.lastIndexOf('.');
|
|
166
|
-
const ext = dot !== -1 ? mediaPath.slice(dot).toLowerCase() : '.png';
|
|
167
|
-
const mimeType = extToMime(ext);
|
|
168
|
-
|
|
169
|
-
ctx.imageCounter++;
|
|
170
|
-
const imagePath = `images/image${ctx.imageCounter}${ext}`;
|
|
171
|
-
ctx.extractedImages.set(imagePath, { data, mimeType });
|
|
172
|
-
|
|
173
|
-
// Alt text from the picture's non-visual properties (descr, then name).
|
|
174
|
-
const cNvPrs = pic.getElementsByTagNameNS(NS_PML, 'cNvPr');
|
|
175
|
-
const alt =
|
|
176
|
-
(cNvPrs.length ? cNvPrs[0]!.getAttribute('descr') || cNvPrs[0]!.getAttribute('name') : '') ||
|
|
177
|
-
'Image';
|
|
178
|
-
|
|
179
|
-
images.push({ type: 'image', url: imagePath, alt });
|
|
180
|
-
}
|
|
181
|
-
return images;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ── Layout-verdict annotation building ───────────────────────────────
|
|
185
|
-
|
|
186
|
-
/** One non-title text shape on a slide, with its placeholder identity. */
|
|
187
|
-
interface SlideTextEntry {
|
|
188
|
-
rawType: string;
|
|
189
|
-
idx: number;
|
|
190
|
-
texts: string[];
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/** Annotation params live on a single heading line — flatten whitespace. */
|
|
194
|
-
function cleanParamText(text: string): string {
|
|
195
|
-
return text.replace(/\s+/g, ' ').trim();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Turn a slide's layout verdict into a heading annotation, downgrading to
|
|
200
|
-
* plain when the slide lacks the content the template needs (a bare
|
|
201
|
-
* annotation without its essential params renders an empty card in the
|
|
202
|
-
* player path, which never derives inputs for annotated blocks).
|
|
203
|
-
*/
|
|
204
|
-
function buildSlideAnnotation(
|
|
205
|
-
analyzed: AnalyzedLayout | undefined,
|
|
206
|
-
entries: SlideTextEntry[],
|
|
207
|
-
images: MarkdownImage[],
|
|
208
|
-
ctx: ImportContext,
|
|
209
|
-
): { annotation?: HeadingTemplateAnnotation; omitted: Set<SlideTextEntry> } {
|
|
210
|
-
const omitted = new Set<SlideTextEntry>();
|
|
211
|
-
if (!analyzed) return { omitted };
|
|
212
|
-
const verdict = analyzed.verdict;
|
|
213
|
-
|
|
214
|
-
if (verdict.kind === 'custom') {
|
|
215
|
-
ctx.usedCustomTemplates.set(verdict.def.name, verdict.def);
|
|
216
|
-
return { annotation: { template: verdict.def.name }, omitted };
|
|
217
|
-
}
|
|
218
|
-
if (verdict.kind !== 'builtin') return { omitted };
|
|
219
|
-
|
|
220
|
-
switch (verdict.paramSpec) {
|
|
221
|
-
case 'titleSubtitle': {
|
|
222
|
-
const sub = entries.find((e) => e.rawType === 'subTitle');
|
|
223
|
-
const subtitle = sub ? cleanParamText(sub.texts.join(' ')) : '';
|
|
224
|
-
// The subtitle moves into the card; keep it out of the bullets.
|
|
225
|
-
if (sub && subtitle) omitted.add(sub);
|
|
226
|
-
return {
|
|
227
|
-
annotation: {
|
|
228
|
-
template: verdict.template,
|
|
229
|
-
...(subtitle ? { params: { subtitle } } : {}),
|
|
230
|
-
},
|
|
231
|
-
omitted,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
case 'comparisonPairs': {
|
|
235
|
-
if (!verdict.columns) return { omitted };
|
|
236
|
-
const textAt = (idx: number | undefined): string[] =>
|
|
237
|
-
idx === undefined ? [] : (entries.find((e) => e.idx === idx)?.texts ?? []);
|
|
238
|
-
const side = (idxs: number[]): string => {
|
|
239
|
-
const header = cleanParamText(textAt(idxs[0])[0] ?? '');
|
|
240
|
-
const body = cleanParamText(textAt(idxs[1])[0] ?? '');
|
|
241
|
-
return header ? (body ? `${header}|${body}` : header) : '';
|
|
242
|
-
};
|
|
243
|
-
const left = side(verdict.columns.left);
|
|
244
|
-
const right = side(verdict.columns.right);
|
|
245
|
-
// twoColumn requires both labels — a bare annotation renders nothing.
|
|
246
|
-
if (!left || !right) return { omitted };
|
|
247
|
-
return { annotation: { template: 'twoColumn', params: { left, right } }, omitted };
|
|
248
|
-
}
|
|
249
|
-
case 'featureImage': {
|
|
250
|
-
const image = images[0];
|
|
251
|
-
if (!image) return { omitted };
|
|
252
|
-
const params: Record<string, string> = { imageSrc: image.url };
|
|
253
|
-
if (image.alt && image.alt !== 'Image') params.imageAlt = cleanParamText(image.alt);
|
|
254
|
-
return { annotation: { template: verdict.template, params }, omitted };
|
|
255
|
-
}
|
|
256
|
-
case 'photoGridGate': {
|
|
257
|
-
if (images.length < 2) return { omitted };
|
|
258
|
-
return {
|
|
259
|
-
annotation: {
|
|
260
|
-
template: 'photoGrid',
|
|
261
|
-
params: { images: images.map((i) => i.url).join(',') },
|
|
262
|
-
},
|
|
263
|
-
omitted,
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
default:
|
|
267
|
-
return { annotation: { template: verdict.template }, omitted };
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
async function convertSlide(
|
|
272
|
-
path: string,
|
|
273
|
-
index: number,
|
|
274
|
-
ctx: ImportContext,
|
|
275
|
-
): Promise<MarkdownBlockNode[]> {
|
|
276
|
-
const pkg = ctx.pkg;
|
|
277
|
-
const doc = await getPartXml(pkg, path);
|
|
278
|
-
if (!doc) return [];
|
|
279
|
-
const out: MarkdownBlockNode[] = [];
|
|
280
|
-
|
|
281
|
-
let title = '';
|
|
282
|
-
const entries: SlideTextEntry[] = [];
|
|
283
|
-
const shapes = doc.getElementsByTagNameNS(NS_PML, 'sp');
|
|
284
|
-
for (let s = 0; s < shapes.length; s++) {
|
|
285
|
-
const sp = shapes[s]!;
|
|
286
|
-
const txBody = sp.getElementsByTagNameNS(NS_PML, 'txBody');
|
|
287
|
-
if (!txBody.length) continue;
|
|
288
|
-
const paras = txBody[0]!.getElementsByTagNameNS(NS_DRAWINGML, 'p');
|
|
289
|
-
const texts: string[] = [];
|
|
290
|
-
for (let p = 0; p < paras.length; p++) {
|
|
291
|
-
const t = paragraphText(paras[p]!);
|
|
292
|
-
if (t) texts.push(t);
|
|
293
|
-
}
|
|
294
|
-
if (texts.length === 0) continue;
|
|
295
|
-
if (isTitleShape(sp) && !title) {
|
|
296
|
-
title = texts.join(' ');
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
299
|
-
const phs = sp.getElementsByTagNameNS(NS_PML, 'ph');
|
|
300
|
-
const rawType = phs.length ? (phs[0]!.getAttribute('type') ?? '') : '';
|
|
301
|
-
const idxRaw = phs.length ? phs[0]!.getAttribute('idx') : null;
|
|
302
|
-
const idx = idxRaw ? parseInt(idxRaw, 10) || 0 : 0;
|
|
303
|
-
entries.push({ rawType, idx, texts });
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Images are extracted before annotation building so feature/photoGrid
|
|
307
|
-
// params can reference their container paths; they still land after the
|
|
308
|
-
// bullet list in the output, unchanged.
|
|
309
|
-
const images = ctx.extractImages ? await extractSlideImages(doc, path, ctx) : [];
|
|
310
|
-
|
|
311
|
-
const layoutPath = ctx.inference?.layoutPathBySlide.get(path);
|
|
312
|
-
const analyzed = layoutPath ? ctx.inference?.byLayoutPath.get(layoutPath) : undefined;
|
|
313
|
-
const { annotation, omitted } = buildSlideAnnotation(analyzed, entries, images, ctx);
|
|
314
|
-
|
|
315
|
-
out.push({
|
|
316
|
-
type: 'heading',
|
|
317
|
-
depth: 2,
|
|
318
|
-
children: [{ type: 'text', value: title || `Slide ${index + 1}` }],
|
|
319
|
-
...(annotation ? { templateAnnotation: annotation } : {}),
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
const bullets = entries.filter((e) => !omitted.has(e)).flatMap((e) => e.texts);
|
|
323
|
-
if (bullets.length > 0) {
|
|
324
|
-
const items: MarkdownListItem[] = bullets.map((text) => ({
|
|
325
|
-
type: 'listItem',
|
|
326
|
-
children: [{ type: 'paragraph', children: [{ type: 'text', value: text }] }],
|
|
327
|
-
}));
|
|
328
|
-
out.push({ type: 'list', ordered: false, children: items });
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Embedded pictures land after the bullet list and before any tables.
|
|
332
|
-
for (const image of images) {
|
|
333
|
-
out.push({ type: 'paragraph', children: [image] });
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const tbls = doc.getElementsByTagNameNS(NS_DRAWINGML, 'tbl');
|
|
337
|
-
for (let t = 0; t < tbls.length; t++) out.push(tableToMarkdown(tbls[t]!));
|
|
338
|
-
|
|
339
|
-
return out;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function warnInferenceFailure(step: string, err: unknown): void {
|
|
343
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
344
|
-
console.warn(`pptx import: ${step} failed; importing without it — ${message}`);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
async function importDocument(
|
|
348
|
-
pkg: OoxmlPackage,
|
|
349
|
-
options: PptxImportOptions,
|
|
350
|
-
): Promise<{ doc: MarkdownDocument; ctx: ImportContext }> {
|
|
351
|
-
const ctx: ImportContext = {
|
|
352
|
-
pkg,
|
|
353
|
-
extractImages: options.extractImages ?? false,
|
|
354
|
-
extractedImages: new Map(),
|
|
355
|
-
imageCounter: 0,
|
|
356
|
-
usedCustomTemplates: new Map(),
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
const inferTheme = options.inferTheme !== false;
|
|
360
|
-
const inferLayouts = options.inferLayouts !== false;
|
|
361
|
-
|
|
362
|
-
// All inference modules load lazily so a plain import stays light, and
|
|
363
|
-
// every inference step degrades to a warning rather than failing the import.
|
|
364
|
-
let extraction: ExtractedFileTheme | null = null;
|
|
365
|
-
if (inferTheme || inferLayouts) {
|
|
366
|
-
try {
|
|
367
|
-
const { extractPptxTheme } = await import('../infer/extract.js');
|
|
368
|
-
extraction = await extractPptxTheme(pkg);
|
|
369
|
-
} catch (err: unknown) {
|
|
370
|
-
warnInferenceFailure('theme extraction', err);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
let theme: Theme | undefined;
|
|
375
|
-
if (inferTheme && extraction) {
|
|
376
|
-
try {
|
|
377
|
-
const { compileExtractedTheme } = await import('../infer/mapTheme.js');
|
|
378
|
-
theme = compileExtractedTheme(extraction).theme;
|
|
379
|
-
} catch (err: unknown) {
|
|
380
|
-
warnInferenceFailure('theme compilation', err);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (inferLayouts) {
|
|
385
|
-
try {
|
|
386
|
-
const { analyzePptxLayouts } = await import('./layouts.js');
|
|
387
|
-
const { colorHintsFromExtraction } = await import('../infer/mapTheme.js');
|
|
388
|
-
ctx.inference = await analyzePptxLayouts(pkg, {
|
|
389
|
-
colors: extraction ? colorHintsFromExtraction(extraction) : {},
|
|
390
|
-
});
|
|
391
|
-
} catch (err: unknown) {
|
|
392
|
-
warnInferenceFailure('layout inference', err);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const paths = await orderedSlidePaths(pkg);
|
|
397
|
-
const children: MarkdownBlockNode[] = [];
|
|
398
|
-
for (let i = 0; i < paths.length; i++) {
|
|
399
|
-
children.push(...(await convertSlide(paths[i]!, i, ctx)));
|
|
400
|
-
}
|
|
401
|
-
const doc: MarkdownDocument = { type: 'document', children };
|
|
402
|
-
|
|
403
|
-
const frontmatter: Record<string, unknown> = {};
|
|
404
|
-
if (theme || ctx.usedCustomTemplates.size > 0) {
|
|
405
|
-
const {
|
|
406
|
-
writeCustomThemesToFrontmatter,
|
|
407
|
-
writeCustomTemplatesToFrontmatter,
|
|
408
|
-
FRONTMATTER_CUSTOM_THEMES_KEY,
|
|
409
|
-
FRONTMATTER_CUSTOM_TEMPLATES_KEY,
|
|
410
|
-
} = await import('@bendyline/squisq/doc');
|
|
411
|
-
if (theme) {
|
|
412
|
-
const payload = writeCustomThemesToFrontmatter([theme]);
|
|
413
|
-
if (payload) {
|
|
414
|
-
frontmatter[FRONTMATTER_CUSTOM_THEMES_KEY] = payload;
|
|
415
|
-
// The doc-level selector `resolveThemeForDoc` reads — activates the
|
|
416
|
-
// inferred theme without any global registration.
|
|
417
|
-
frontmatter['squisq-theme'] = theme.id;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
if (ctx.usedCustomTemplates.size > 0) {
|
|
421
|
-
const payload = writeCustomTemplatesToFrontmatter([...ctx.usedCustomTemplates.values()]);
|
|
422
|
-
if (payload) frontmatter[FRONTMATTER_CUSTOM_TEMPLATES_KEY] = payload;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
if (Object.keys(frontmatter).length > 0) doc.frontmatter = frontmatter;
|
|
426
|
-
|
|
427
|
-
return { doc, ctx };
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
export async function pptxToMarkdownDoc(
|
|
431
|
-
data: ArrayBuffer | Blob,
|
|
432
|
-
options: PptxImportOptions = {},
|
|
433
|
-
): Promise<MarkdownDocument> {
|
|
434
|
-
const pkg = await openPackage(data, options);
|
|
435
|
-
const { doc } = await importDocument(pkg, options);
|
|
436
|
-
return doc;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Convert a .pptx file to a ContentContainer with markdown + extracted images.
|
|
441
|
-
*
|
|
442
|
-
* The container holds the primary markdown document plus every embedded slide
|
|
443
|
-
* image under `images/` (e.g. `images/image1.png`). Image extraction is always
|
|
444
|
-
* forced on here so the markdown's image references resolve inside the
|
|
445
|
-
* container. Mirrors `docxToContainer`.
|
|
446
|
-
*/
|
|
447
|
-
export async function pptxToContainer(
|
|
448
|
-
data: ArrayBuffer | Blob,
|
|
449
|
-
options: PptxImportOptions = {},
|
|
450
|
-
): Promise<ContentContainer> {
|
|
451
|
-
const pkg = await openPackage(data, options);
|
|
452
|
-
const { doc, ctx } = await importDocument(pkg, { ...options, extractImages: true });
|
|
453
|
-
|
|
454
|
-
return buildContainer(stringifyMarkdown(doc), ctx.extractedImages);
|
|
455
|
-
}
|
package/src/pptx/index.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @bendyline/squisq-formats PPTX Module
|
|
3
|
-
*
|
|
4
|
-
* PowerPoint .pptx export support using PresentationML (`<p:presentation>`,
|
|
5
|
-
* `<p:sld>`) via the shared ooxml/ infrastructure.
|
|
6
|
-
*
|
|
7
|
-
* Slide segmentation: each H1/H2 heading starts a new slide by default.
|
|
8
|
-
* Inline formatting (bold, italic, code, links) is preserved as DrawingML runs.
|
|
9
|
-
*
|
|
10
|
-
* Includes both export and import paths.
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```ts
|
|
14
|
-
* import { markdownDocToPptx } from '@bendyline/squisq-formats/pptx';
|
|
15
|
-
* ```
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
// Export
|
|
19
|
-
export { markdownDocToPptx, docToPptx } from './export.js';
|
|
20
|
-
export type { PptxExportOptions } from './export.js';
|
|
21
|
-
|
|
22
|
-
// Import
|
|
23
|
-
import { markdownToDoc } from '@bendyline/squisq/doc';
|
|
24
|
-
import type { Doc } from '@bendyline/squisq/schemas';
|
|
25
|
-
import { type PptxImportOptions, pptxToMarkdownDoc } from './import.js';
|
|
26
|
-
|
|
27
|
-
export type { PptxImportOptions } from './import.js';
|
|
28
|
-
export { pptxToMarkdownDoc, pptxToContainer } from './import.js';
|
|
29
|
-
|
|
30
|
-
// Layout inference (used by the PPTX importer and the theme dialog)
|
|
31
|
-
export { analyzePptxLayouts, inspectPptxLayouts } from './layouts.js';
|
|
32
|
-
export type {
|
|
33
|
-
AnalyzedLayout,
|
|
34
|
-
AnalyzePptxLayoutsOptions,
|
|
35
|
-
ExtractedPlaceholder,
|
|
36
|
-
ExtractedSlideLayout,
|
|
37
|
-
InspectPptxLayoutsOptions,
|
|
38
|
-
LayoutVerdict,
|
|
39
|
-
PptxColorHints,
|
|
40
|
-
PptxLayoutInference,
|
|
41
|
-
PptxLayoutSummary,
|
|
42
|
-
} from './layouts.js';
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Convert a .pptx file to a squisq Doc (via the markdown model).
|
|
46
|
-
*/
|
|
47
|
-
export async function pptxToDoc(
|
|
48
|
-
data: ArrayBuffer | Blob,
|
|
49
|
-
options?: PptxImportOptions,
|
|
50
|
-
): Promise<Doc> {
|
|
51
|
-
return markdownToDoc(await pptxToMarkdownDoc(data, options));
|
|
52
|
-
}
|