@opengeni/artifact-tool 0.2.2
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 +190 -0
- package/README.md +104 -0
- package/dist/bounded-zip.d.ts +28 -0
- package/dist/chunk-4BSENMAE.js +39 -0
- package/dist/chunk-4BSENMAE.js.map +1 -0
- package/dist/chunk-4FRFNNBM.js +276 -0
- package/dist/chunk-4FRFNNBM.js.map +1 -0
- package/dist/chunk-5XIA6KHZ.js +49 -0
- package/dist/chunk-5XIA6KHZ.js.map +1 -0
- package/dist/chunk-66CT2ZRP.js +2229 -0
- package/dist/chunk-66CT2ZRP.js.map +1 -0
- package/dist/chunk-72SYDU4O.js +319 -0
- package/dist/chunk-72SYDU4O.js.map +1 -0
- package/dist/chunk-B2ZJRZDA.js +279 -0
- package/dist/chunk-B2ZJRZDA.js.map +1 -0
- package/dist/chunk-DCTLTNPW.js +2458 -0
- package/dist/chunk-DCTLTNPW.js.map +1 -0
- package/dist/chunk-FGZM4FQ6.js +473 -0
- package/dist/chunk-FGZM4FQ6.js.map +1 -0
- package/dist/chunk-HTJBLBDE.js +591 -0
- package/dist/chunk-HTJBLBDE.js.map +1 -0
- package/dist/chunk-IYFXZQ34.js +5306 -0
- package/dist/chunk-IYFXZQ34.js.map +1 -0
- package/dist/chunk-J6N7IR44.js +172 -0
- package/dist/chunk-J6N7IR44.js.map +1 -0
- package/dist/chunk-N2IHNWBS.js +1002 -0
- package/dist/chunk-N2IHNWBS.js.map +1 -0
- package/dist/chunk-NAG44K67.js +549 -0
- package/dist/chunk-NAG44K67.js.map +1 -0
- package/dist/chunk-O24KTEGP.js +33 -0
- package/dist/chunk-O24KTEGP.js.map +1 -0
- package/dist/chunk-PI2AZVGK.js +2074 -0
- package/dist/chunk-PI2AZVGK.js.map +1 -0
- package/dist/chunk-Q2FFHEY7.js +40 -0
- package/dist/chunk-Q2FFHEY7.js.map +1 -0
- package/dist/chunk-RBGQADXA.js +57 -0
- package/dist/chunk-RBGQADXA.js.map +1 -0
- package/dist/chunk-U5G2BKM2.js +613 -0
- package/dist/chunk-U5G2BKM2.js.map +1 -0
- package/dist/chunk-VOJHMSM3.js +39 -0
- package/dist/chunk-VOJHMSM3.js.map +1 -0
- package/dist/chunk-VUNV25KB.js +16 -0
- package/dist/chunk-VUNV25KB.js.map +1 -0
- package/dist/chunk-XIFUNVBO.js +101 -0
- package/dist/chunk-XIFUNVBO.js.map +1 -0
- package/dist/chunk-ZCJYRJGB.js +489 -0
- package/dist/chunk-ZCJYRJGB.js.map +1 -0
- package/dist/chunk-ZZXPEE4S.js +2260 -0
- package/dist/chunk-ZZXPEE4S.js.map +1 -0
- package/dist/document-docx-api.d.ts +49 -0
- package/dist/document-docx-codec.d.ts +9 -0
- package/dist/document-docx-codec.js +4765 -0
- package/dist/document-docx-codec.js.map +1 -0
- package/dist/document-docx-import.d.ts +261 -0
- package/dist/document-docx-packer.d.ts +18 -0
- package/dist/document-docx-state.d.ts +12 -0
- package/dist/document-render.d.ts +3 -0
- package/dist/document-render.js +492 -0
- package/dist/document-render.js.map +1 -0
- package/dist/document.d.ts +432 -0
- package/dist/document.js +37 -0
- package/dist/document.js.map +1 -0
- package/dist/errors.d.ts +23 -0
- package/dist/file-blob.d.ts +11 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +186 -0
- package/dist/index.js.map +1 -0
- package/dist/inspection.d.ts +12 -0
- package/dist/kernel.d.ts +101 -0
- package/dist/lib-GQLPACFK.js +9746 -0
- package/dist/lib-GQLPACFK.js.map +1 -0
- package/dist/materializer-cli-entry.d.ts +2 -0
- package/dist/materializer-cli-entry.js +29 -0
- package/dist/materializer-cli-entry.js.map +1 -0
- package/dist/materializer-cli.d.ts +24 -0
- package/dist/materializer-cli.js +24 -0
- package/dist/materializer-cli.js.map +1 -0
- package/dist/native-raster.d.ts +10 -0
- package/dist/native.d.ts +129 -0
- package/dist/native.js +31 -0
- package/dist/native.js.map +1 -0
- package/dist/office-import.d.ts +48 -0
- package/dist/office-import.js +154 -0
- package/dist/office-import.js.map +1 -0
- package/dist/presentation-pptx-api.d.ts +60 -0
- package/dist/presentation-pptx-import.d.ts +5 -0
- package/dist/presentation-pptx-state-digest-765CZZED.js +10 -0
- package/dist/presentation-pptx-state-digest-765CZZED.js.map +1 -0
- package/dist/presentation-pptx-state-digest.d.ts +3 -0
- package/dist/presentation-pptx-state.d.ts +4 -0
- package/dist/presentation-pptx-xml.d.ts +29 -0
- package/dist/presentation-pptx.d.ts +5 -0
- package/dist/presentation-pptx.js +2365 -0
- package/dist/presentation-pptx.js.map +1 -0
- package/dist/presentation-render.d.ts +29 -0
- package/dist/presentation-render.js +375 -0
- package/dist/presentation-render.js.map +1 -0
- package/dist/presentation.d.ts +565 -0
- package/dist/presentation.js +71 -0
- package/dist/presentation.js.map +1 -0
- package/dist/production-composite.d.ts +68 -0
- package/dist/production-document.d.ts +2 -0
- package/dist/production-document.js +176 -0
- package/dist/production-document.js.map +1 -0
- package/dist/production-native-codecs.d.ts +94 -0
- package/dist/production-presentation.d.ts +2 -0
- package/dist/production-presentation.js +176 -0
- package/dist/production-presentation.js.map +1 -0
- package/dist/production-sha256.d.ts +2 -0
- package/dist/production-spreadsheet-native.d.ts +12 -0
- package/dist/production-spreadsheet.d.ts +2 -0
- package/dist/production-spreadsheet.js +176 -0
- package/dist/production-spreadsheet.js.map +1 -0
- package/dist/production.d.ts +96 -0
- package/dist/raster-image.d.ts +34 -0
- package/dist/reference.d.ts +17 -0
- package/dist/reference.js +574 -0
- package/dist/reference.js.map +1 -0
- package/dist/runtime-cli-entry.d.ts +2 -0
- package/dist/runtime-cli-entry.js +21 -0
- package/dist/runtime-cli-entry.js.map +1 -0
- package/dist/runtime-cli.d.ts +76 -0
- package/dist/runtime-cli.js +26 -0
- package/dist/runtime-cli.js.map +1 -0
- package/dist/runtime-development.d.ts +73 -0
- package/dist/runtime-development.js +30 -0
- package/dist/runtime-development.js.map +1 -0
- package/dist/runtime-receipt.d.ts +19 -0
- package/dist/runtime.d.ts +355 -0
- package/dist/runtime.js +42 -0
- package/dist/runtime.js.map +1 -0
- package/dist/snapshot.d.ts +26 -0
- package/dist/snapshot.js +61 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/spreadsheet-address.d.ts +18 -0
- package/dist/spreadsheet-file.d.ts +17 -0
- package/dist/spreadsheet-formula.d.ts +67 -0
- package/dist/spreadsheet-image.d.ts +17 -0
- package/dist/spreadsheet-render.d.ts +5 -0
- package/dist/spreadsheet-render.js +752 -0
- package/dist/spreadsheet-render.js.map +1 -0
- package/dist/spreadsheet-types.d.ts +155 -0
- package/dist/spreadsheet-xlsx-api.d.ts +88 -0
- package/dist/spreadsheet-xlsx-codec.d.ts +16 -0
- package/dist/spreadsheet-xlsx-codec.js +16 -0
- package/dist/spreadsheet-xlsx-codec.js.map +1 -0
- package/dist/spreadsheet-xlsx-state.d.ts +16 -0
- package/dist/spreadsheet.d.ts +724 -0
- package/dist/spreadsheet.js +67 -0
- package/dist/spreadsheet.js.map +1 -0
- package/package.json +129 -0
- package/src/bounded-zip.ts +450 -0
- package/src/document-docx-api.ts +64 -0
- package/src/document-docx-codec.ts +2253 -0
- package/src/document-docx-import.ts +3814 -0
- package/src/document-docx-packer.ts +72 -0
- package/src/document-docx-state.ts +59 -0
- package/src/document-render.ts +615 -0
- package/src/document.ts +2800 -0
- package/src/errors.ts +45 -0
- package/src/file-blob.ts +43 -0
- package/src/index.ts +4 -0
- package/src/inspection.ts +50 -0
- package/src/kernel.ts +525 -0
- package/src/materializer-cli-entry.ts +11 -0
- package/src/materializer-cli.ts +743 -0
- package/src/native-raster.ts +141 -0
- package/src/native.ts +390 -0
- package/src/office-import.ts +243 -0
- package/src/presentation-pptx-api.ts +107 -0
- package/src/presentation-pptx-import.ts +2065 -0
- package/src/presentation-pptx-state-digest.ts +46 -0
- package/src/presentation-pptx-state.ts +18 -0
- package/src/presentation-pptx-xml.ts +473 -0
- package/src/presentation-pptx.ts +447 -0
- package/src/presentation-render.ts +532 -0
- package/src/presentation.ts +2599 -0
- package/src/production-composite.ts +805 -0
- package/src/production-document.ts +2 -0
- package/src/production-native-codecs.ts +1141 -0
- package/src/production-presentation.ts +2 -0
- package/src/production-sha256.ts +150 -0
- package/src/production-spreadsheet-native.ts +686 -0
- package/src/production-spreadsheet.ts +2 -0
- package/src/production.ts +1022 -0
- package/src/raster-image.ts +586 -0
- package/src/reference.ts +17 -0
- package/src/runtime-cli-entry.ts +13 -0
- package/src/runtime-cli.ts +670 -0
- package/src/runtime-development.ts +689 -0
- package/src/runtime-receipt.ts +169 -0
- package/src/runtime.ts +1463 -0
- package/src/snapshot.ts +88 -0
- package/src/spreadsheet-address.ts +90 -0
- package/src/spreadsheet-file.ts +71 -0
- package/src/spreadsheet-formula.ts +1212 -0
- package/src/spreadsheet-image.ts +242 -0
- package/src/spreadsheet-render.ts +985 -0
- package/src/spreadsheet-types.ts +171 -0
- package/src/spreadsheet-xlsx-api.ts +123 -0
- package/src/spreadsheet-xlsx-codec.ts +3063 -0
- package/src/spreadsheet-xlsx-state.ts +191 -0
- package/src/spreadsheet.ts +5195 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import { FileBlob } from "./file-blob";
|
|
2
|
+
import {
|
|
3
|
+
presentationColorValue,
|
|
4
|
+
PresentationFile,
|
|
5
|
+
type Presentation,
|
|
6
|
+
type PresentationChart,
|
|
7
|
+
type PresentationFill,
|
|
8
|
+
type PresentationImage,
|
|
9
|
+
type PresentationLine,
|
|
10
|
+
type PresentationPosition,
|
|
11
|
+
type PresentationGroup,
|
|
12
|
+
type PresentationShape,
|
|
13
|
+
type PresentationTable,
|
|
14
|
+
} from "./presentation";
|
|
15
|
+
import {
|
|
16
|
+
PPTX_MEDIA_TYPE,
|
|
17
|
+
PresentationFidelityError,
|
|
18
|
+
type PresentationPptxExportOptions,
|
|
19
|
+
} from "./presentation-pptx-api";
|
|
20
|
+
import { presentationModelDigest, sha256Hex } from "./presentation-pptx-state-digest";
|
|
21
|
+
import { presentationLossState } from "./presentation-pptx-state";
|
|
22
|
+
|
|
23
|
+
export { importPresentationPptx } from "./presentation-pptx-import";
|
|
24
|
+
|
|
25
|
+
const colorValue = presentationColorValue;
|
|
26
|
+
|
|
27
|
+
export async function exportPresentationPptx(
|
|
28
|
+
presentation: Presentation,
|
|
29
|
+
options: PresentationPptxExportOptions = {},
|
|
30
|
+
): Promise<FileBlob> {
|
|
31
|
+
const provenance = presentationLossState(presentation);
|
|
32
|
+
if (provenance) {
|
|
33
|
+
const [currentModelDigest, actualSourceDigest] = await Promise.all([
|
|
34
|
+
presentationModelDigest(presentation),
|
|
35
|
+
sha256Hex(provenance.sourceBytes),
|
|
36
|
+
]);
|
|
37
|
+
const sourceBindingValid =
|
|
38
|
+
provenance.version === 1 &&
|
|
39
|
+
provenance.mediaType === PPTX_MEDIA_TYPE &&
|
|
40
|
+
provenance.sourceDigest === actualSourceDigest;
|
|
41
|
+
if (sourceBindingValid && provenance.modelDigest === currentModelDigest) {
|
|
42
|
+
return FileBlob.fromBytes(provenance.sourceBytes, {
|
|
43
|
+
name: options.fileName ?? "presentation.pptx",
|
|
44
|
+
type: PPTX_MEDIA_TYPE,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (!sourceBindingValid && options.unsupportedContent !== "discard") {
|
|
48
|
+
throw new PresentationFidelityError(
|
|
49
|
+
"PPTX source-preservation envelope is invalid or no longer matches its source bytes",
|
|
50
|
+
[
|
|
51
|
+
{
|
|
52
|
+
code: "content-will-be-discarded",
|
|
53
|
+
severity: "error",
|
|
54
|
+
feature: "source-only",
|
|
55
|
+
message: "The retained source package failed its digest or version binding",
|
|
56
|
+
parts: [...provenance.unsupportedParts],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const issues = PresentationFile.fidelityReport(presentation).map((issue) => ({
|
|
63
|
+
...issue,
|
|
64
|
+
severity: "error" as const,
|
|
65
|
+
code: "content-will-be-discarded" as const,
|
|
66
|
+
}));
|
|
67
|
+
if (issues.length > 0 && options.unsupportedContent !== "discard") {
|
|
68
|
+
throw new PresentationFidelityError(
|
|
69
|
+
'PPTX export would discard source-only presentation content; use unsupportedContent: "discard" only when that loss is intended',
|
|
70
|
+
issues,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
const { default: PptxConstructor } = await import("pptxgenjs");
|
|
74
|
+
const pptx = new PptxConstructor() as unknown as PptxRuntime;
|
|
75
|
+
pptx.defineLayout({
|
|
76
|
+
name: "OPENGENI_CUSTOM",
|
|
77
|
+
width: presentation.slideSize.width / 96,
|
|
78
|
+
height: presentation.slideSize.height / 96,
|
|
79
|
+
});
|
|
80
|
+
pptx.layout = "OPENGENI_CUSTOM";
|
|
81
|
+
pptx.author = "OpenGeni";
|
|
82
|
+
|
|
83
|
+
for (const slide of presentation.slides.items) {
|
|
84
|
+
const target = pptx.addSlide();
|
|
85
|
+
target.background = { color: pptxColor(slide.background.fill) };
|
|
86
|
+
for (const element of slide.elements) {
|
|
87
|
+
if ("geometry" in element && "text" in element) exportShapeToPptx(target, element);
|
|
88
|
+
else if ("series" in element) exportChartToPptx(target, element);
|
|
89
|
+
else if ("sourceForPptx" in element) exportImageToPptx(target, element);
|
|
90
|
+
else if ("rows" in element) exportTableToPptx(target, element);
|
|
91
|
+
else if ("children" in element) exportGroupToPptx(target, element);
|
|
92
|
+
}
|
|
93
|
+
if (slide.notes.toString().length > 0) target.addNotes(slide.notes.toString());
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const output = await pptx.write({ outputType: "arraybuffer" });
|
|
97
|
+
const bytes =
|
|
98
|
+
output instanceof Uint8Array
|
|
99
|
+
? output
|
|
100
|
+
: output instanceof ArrayBuffer
|
|
101
|
+
? new Uint8Array(output)
|
|
102
|
+
: typeof output === "string"
|
|
103
|
+
? new TextEncoder().encode(output)
|
|
104
|
+
: undefined;
|
|
105
|
+
if (!bytes) throw new Error("PPTX writer returned an unsupported output type");
|
|
106
|
+
const canonicalBytes = await canonicalizeGeneratedPptx(bytes);
|
|
107
|
+
return FileBlob.fromBytes(canonicalBytes, {
|
|
108
|
+
name: options.fileName ?? "presentation.pptx",
|
|
109
|
+
type: PPTX_MEDIA_TYPE,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function canonicalizeGeneratedPptx(bytes: Uint8Array): Promise<Uint8Array> {
|
|
114
|
+
const { default: JSZip } = await import("jszip");
|
|
115
|
+
const source = await JSZip.loadAsync(bytes);
|
|
116
|
+
const chartIds = Object.keys(source.files)
|
|
117
|
+
.map((name) => /^ppt\/charts\/chart(\d+)\.xml$/.exec(name)?.[1])
|
|
118
|
+
.filter((value): value is string => value !== undefined)
|
|
119
|
+
.map(Number)
|
|
120
|
+
.sort((left, right) => left - right);
|
|
121
|
+
const chartIdMap = new Map(chartIds.map((id, index) => [id, index + 1]));
|
|
122
|
+
const rename = (value: string): string =>
|
|
123
|
+
value
|
|
124
|
+
.replace(/chart(\d+)\.xml/g, (match, raw: string) => {
|
|
125
|
+
const replacement = chartIdMap.get(Number(raw));
|
|
126
|
+
return replacement === undefined ? match : `chart${replacement}.xml`;
|
|
127
|
+
})
|
|
128
|
+
.replace(/Microsoft_Excel_Worksheet(\d+)\.xlsx/g, (match, raw: string) => {
|
|
129
|
+
const replacement = chartIdMap.get(Number(raw));
|
|
130
|
+
return replacement === undefined ? match : `Microsoft_Excel_Worksheet${replacement}.xlsx`;
|
|
131
|
+
});
|
|
132
|
+
return await canonicalizeGeneratedZip(source, rename, true);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function canonicalizeGeneratedZip(
|
|
136
|
+
source: GeneratedZip,
|
|
137
|
+
rename: (value: string) => string,
|
|
138
|
+
canonicalizeNestedWorkbooks: boolean,
|
|
139
|
+
): Promise<Uint8Array> {
|
|
140
|
+
const { default: JSZip } = await import("jszip");
|
|
141
|
+
const output = new JSZip();
|
|
142
|
+
const entries = Object.values(source.files)
|
|
143
|
+
.filter((entry) => !entry.dir)
|
|
144
|
+
.sort((left, right) => rename(left.name).localeCompare(rename(right.name)));
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
let data = await entry.async("uint8array");
|
|
147
|
+
if (canonicalizeNestedWorkbooks && /^ppt\/embeddings\/[^/]+\.xlsx$/i.test(entry.name)) {
|
|
148
|
+
data = await canonicalizeGeneratedZip(await JSZip.loadAsync(data), (value) => value, false);
|
|
149
|
+
} else if (/(?:\.xml|\.rels)$/i.test(entry.name) || entry.name === "[Content_Types].xml") {
|
|
150
|
+
const xml = new TextDecoder().decode(data);
|
|
151
|
+
data = new TextEncoder().encode(
|
|
152
|
+
rename(xml).replace(
|
|
153
|
+
/(<dcterms:(?:created|modified)\b[^>]*>)[^<]*(<\/dcterms:(?:created|modified)>)/g,
|
|
154
|
+
"$11980-01-01T00:00:00Z$2",
|
|
155
|
+
),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
output.file(rename(entry.name), data, {
|
|
159
|
+
binary: true,
|
|
160
|
+
createFolders: false,
|
|
161
|
+
date: DETERMINISTIC_ZIP_DATE,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return await output.generateAsync({
|
|
165
|
+
type: "uint8array",
|
|
166
|
+
compression: "DEFLATE",
|
|
167
|
+
compressionOptions: { level: 9 },
|
|
168
|
+
platform: "DOS",
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
type GeneratedZip = {
|
|
173
|
+
files: Record<string, GeneratedZipEntry>;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
type GeneratedZipEntry = {
|
|
177
|
+
name: string;
|
|
178
|
+
dir: boolean;
|
|
179
|
+
async(type: "uint8array"): Promise<Uint8Array>;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const DETERMINISTIC_ZIP_DATE = new Date("1980-01-01T00:00:00.000Z");
|
|
183
|
+
|
|
184
|
+
type PptxRuntime = {
|
|
185
|
+
layout: string;
|
|
186
|
+
author: string;
|
|
187
|
+
defineLayout(options: { name: string; width: number; height: number }): void;
|
|
188
|
+
addSlide(): PptxSlideRuntime;
|
|
189
|
+
write(options: { outputType: "arraybuffer" }): Promise<unknown>;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
type PptxSlideRuntime = {
|
|
193
|
+
background: { color: string };
|
|
194
|
+
addShape(type: string, options: Record<string, unknown>): void;
|
|
195
|
+
addText(text: string, options: Record<string, unknown>): void;
|
|
196
|
+
addChart(
|
|
197
|
+
type: string,
|
|
198
|
+
data: Array<Record<string, unknown>>,
|
|
199
|
+
options: Record<string, unknown>,
|
|
200
|
+
): void;
|
|
201
|
+
addImage(options: Record<string, unknown>): void;
|
|
202
|
+
addTable(rows: unknown[][], options: Record<string, unknown>): void;
|
|
203
|
+
addNotes(notes: string): void;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
function exportShapeToPptx(
|
|
207
|
+
slide: PptxSlideRuntime,
|
|
208
|
+
shape: PresentationShape,
|
|
209
|
+
position: PresentationPosition = shape.position,
|
|
210
|
+
): void {
|
|
211
|
+
const box = pptxPosition(position);
|
|
212
|
+
const fill = pptxFill(shape.fill);
|
|
213
|
+
const line = pptxLine(shape.line);
|
|
214
|
+
const text = shape.text.toString();
|
|
215
|
+
if (shape.geometry === "line" || text.length === 0) {
|
|
216
|
+
if (shape.geometry !== "textbox") {
|
|
217
|
+
slide.addShape(shape.geometry, {
|
|
218
|
+
...box,
|
|
219
|
+
fill,
|
|
220
|
+
line,
|
|
221
|
+
rotate: shape.rotation,
|
|
222
|
+
objectName: shape.name,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (shape.geometry === "textbox" || text) {
|
|
227
|
+
const style = shape.text.style;
|
|
228
|
+
slide.addText(text, {
|
|
229
|
+
...box,
|
|
230
|
+
margin: 0,
|
|
231
|
+
breakLine: false,
|
|
232
|
+
fontFace: style.fontFamily ?? "Aptos",
|
|
233
|
+
fontSize: (style.fontSize ?? 18) * 0.75,
|
|
234
|
+
bold: style.bold ?? false,
|
|
235
|
+
italic: style.italic ?? false,
|
|
236
|
+
underline: style.underline ? { style: "sng" } : undefined,
|
|
237
|
+
color: pptxColor(style.color ?? "slate-950"),
|
|
238
|
+
align: style.alignment ?? "left",
|
|
239
|
+
valign: style.verticalAlignment ?? "top",
|
|
240
|
+
shape: shape.geometry === "textbox" || shape.geometry === "line" ? undefined : shape.geometry,
|
|
241
|
+
fill: shape.geometry === "line" ? { color: "FFFFFF", transparency: 100 } : fill,
|
|
242
|
+
line: shape.geometry === "line" ? { color: "FFFFFF", transparency: 100 } : line,
|
|
243
|
+
rotate: shape.rotation,
|
|
244
|
+
objectName: shape.name,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function exportTableToPptx(
|
|
250
|
+
slide: PptxSlideRuntime,
|
|
251
|
+
table: PresentationTable,
|
|
252
|
+
position: PresentationPosition = table.position,
|
|
253
|
+
): void {
|
|
254
|
+
const rows = table.rows.map((row) =>
|
|
255
|
+
row
|
|
256
|
+
.filter((cell) => cell !== null)
|
|
257
|
+
.map((cell) => ({
|
|
258
|
+
text: cell.text.toString(),
|
|
259
|
+
options: {
|
|
260
|
+
fill: pptxFill(cell.fill),
|
|
261
|
+
color: pptxColor(cell.text.style.color ?? table.textStyle.color ?? "slate-950"),
|
|
262
|
+
fontFace: cell.text.style.fontFamily ?? table.textStyle.fontFamily ?? "Aptos",
|
|
263
|
+
fontSize: (cell.text.style.fontSize ?? table.textStyle.fontSize ?? 16) * 0.75,
|
|
264
|
+
bold: cell.text.style.bold ?? table.textStyle.bold ?? false,
|
|
265
|
+
italic: cell.text.style.italic ?? table.textStyle.italic ?? false,
|
|
266
|
+
align: cell.text.style.alignment ?? table.textStyle.alignment ?? "left",
|
|
267
|
+
valign: cell.text.style.verticalAlignment ?? table.textStyle.verticalAlignment ?? "top",
|
|
268
|
+
rowspan: cell.rowSpan > 1 ? cell.rowSpan : undefined,
|
|
269
|
+
colspan: cell.colSpan > 1 ? cell.colSpan : undefined,
|
|
270
|
+
},
|
|
271
|
+
})),
|
|
272
|
+
);
|
|
273
|
+
slide.addTable(rows, {
|
|
274
|
+
...pptxPosition(position),
|
|
275
|
+
border: pptxTableBorder(table.line),
|
|
276
|
+
fill: pptxFill(table.fill),
|
|
277
|
+
margin: 2,
|
|
278
|
+
objectName: table.name,
|
|
279
|
+
...(table.columnWidths.length > 0
|
|
280
|
+
? { colW: table.columnWidths.map((width) => width / 96) }
|
|
281
|
+
: {}),
|
|
282
|
+
...(table.rowHeights.length > 0 ? { rowH: table.rowHeights.map((height) => height / 96) } : {}),
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function exportGroupToPptx(
|
|
287
|
+
slide: PptxSlideRuntime,
|
|
288
|
+
group: PresentationGroup,
|
|
289
|
+
parentTransform: (position: PresentationPosition) => PresentationPosition = (position) =>
|
|
290
|
+
position,
|
|
291
|
+
): void {
|
|
292
|
+
// PptxGenJS has no group authoring primitive. Children retain absolute geometry;
|
|
293
|
+
// the typed fidelity report explicitly records that the grouping relation flattens.
|
|
294
|
+
const transform = (position: PresentationPosition): PresentationPosition =>
|
|
295
|
+
parentTransform(groupChildPosition(group, position));
|
|
296
|
+
for (const child of group.children) {
|
|
297
|
+
if ("geometry" in child && "text" in child)
|
|
298
|
+
exportShapeToPptx(slide, child, transform(child.position));
|
|
299
|
+
else if ("series" in child) exportChartToPptx(slide, child, transform(child.position));
|
|
300
|
+
else if ("sourceForPptx" in child) exportImageToPptx(slide, child, transform(child.position));
|
|
301
|
+
else if ("rows" in child) exportTableToPptx(slide, child, transform(child.position));
|
|
302
|
+
else if ("children" in child) exportGroupToPptx(slide, child, transform);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function exportChartToPptx(
|
|
307
|
+
slide: PptxSlideRuntime,
|
|
308
|
+
chart: PresentationChart,
|
|
309
|
+
position: PresentationPosition = chart.position,
|
|
310
|
+
): void {
|
|
311
|
+
const categories = chart.categories;
|
|
312
|
+
// PptxGenJS models scatter/bubble input as one shared X column followed by
|
|
313
|
+
// one or more Y series. The fidelity gate rejects differing per-series X
|
|
314
|
+
// vectors before this fallback writer is reached.
|
|
315
|
+
const data =
|
|
316
|
+
chart.type === "scatter" || chart.type === "bubble"
|
|
317
|
+
? [
|
|
318
|
+
{
|
|
319
|
+
name: "X Values",
|
|
320
|
+
values: chart.series.items[0]?.xValues ?? [],
|
|
321
|
+
},
|
|
322
|
+
...chart.series.items.map((series) => ({
|
|
323
|
+
name: series.name,
|
|
324
|
+
values: series.values,
|
|
325
|
+
sizes: series.bubbleSizes.length > 0 ? series.bubbleSizes : undefined,
|
|
326
|
+
})),
|
|
327
|
+
]
|
|
328
|
+
: chart.series.items.map((series) => ({
|
|
329
|
+
name: series.name,
|
|
330
|
+
labels: series.categories.length > 0 ? series.categories : categories,
|
|
331
|
+
values: series.values,
|
|
332
|
+
}));
|
|
333
|
+
slide.addChart(chart.type, data, {
|
|
334
|
+
...pptxPosition(position),
|
|
335
|
+
showTitle: chart.title.length > 0,
|
|
336
|
+
title: chart.title,
|
|
337
|
+
showLegend: chart.hasLegend,
|
|
338
|
+
legendPos: pptxLegendPosition(chart.legend?.position),
|
|
339
|
+
showValue: chart.dataLabels?.showValue ?? false,
|
|
340
|
+
showCategoryName: chart.dataLabels?.showCategoryName ?? false,
|
|
341
|
+
showPercent: chart.dataLabels?.showPercent ?? false,
|
|
342
|
+
showCatName: chart.dataLabels?.showCategoryName ?? false,
|
|
343
|
+
showSerName: chart.dataLabels?.showSeriesName ?? false,
|
|
344
|
+
catAxisHidden: chart.xAxis?.visible === false,
|
|
345
|
+
catAxisTitle: chart.xAxis?.title,
|
|
346
|
+
catAxisMinVal: chart.xAxis?.min,
|
|
347
|
+
catAxisMaxVal: chart.xAxis?.max,
|
|
348
|
+
valAxisHidden: chart.yAxis?.visible === false,
|
|
349
|
+
valAxisTitle: chart.yAxis?.title,
|
|
350
|
+
valAxisMinVal: chart.yAxis?.min,
|
|
351
|
+
valAxisMaxVal: chart.yAxis?.max,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function exportImageToPptx(
|
|
356
|
+
slide: PptxSlideRuntime,
|
|
357
|
+
image: PresentationImage,
|
|
358
|
+
position: PresentationPosition = image.position,
|
|
359
|
+
): void {
|
|
360
|
+
slide.addImage({
|
|
361
|
+
...image.sourceForPptx(),
|
|
362
|
+
...pptxPosition(position),
|
|
363
|
+
altText: image.alt,
|
|
364
|
+
rotate: image.rotation,
|
|
365
|
+
flipH: image.flipHorizontal,
|
|
366
|
+
flipV: image.flipVertical,
|
|
367
|
+
rounding: image.geometry === "ellipse" || image.geometry === "roundRect",
|
|
368
|
+
objectName: image.name,
|
|
369
|
+
sizing: {
|
|
370
|
+
type: image.crop ? "crop" : image.fit,
|
|
371
|
+
w: position.width / 96,
|
|
372
|
+
h: position.height / 96,
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function groupChildPosition(
|
|
378
|
+
group: PresentationGroup,
|
|
379
|
+
position: PresentationPosition,
|
|
380
|
+
): PresentationPosition {
|
|
381
|
+
const scaleX = group.position.width / group.childExtent.width;
|
|
382
|
+
const scaleY = group.position.height / group.childExtent.height;
|
|
383
|
+
return {
|
|
384
|
+
left: group.position.left + (position.left - group.childOffset.left) * scaleX,
|
|
385
|
+
top: group.position.top + (position.top - group.childOffset.top) * scaleY,
|
|
386
|
+
width: position.width * scaleX,
|
|
387
|
+
height: position.height * scaleY,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function pptxLegendPosition(
|
|
392
|
+
position: "left" | "top" | "topRight" | "right" | "bottom" | undefined,
|
|
393
|
+
): "l" | "t" | "tr" | "r" | "b" {
|
|
394
|
+
switch (position) {
|
|
395
|
+
case "left":
|
|
396
|
+
return "l";
|
|
397
|
+
case "top":
|
|
398
|
+
return "t";
|
|
399
|
+
case "topRight":
|
|
400
|
+
return "tr";
|
|
401
|
+
case "right":
|
|
402
|
+
return "r";
|
|
403
|
+
default:
|
|
404
|
+
return "b";
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function pptxPosition(position: PresentationPosition): Record<string, number> {
|
|
408
|
+
return {
|
|
409
|
+
x: position.left / 96,
|
|
410
|
+
y: position.top / 96,
|
|
411
|
+
w: position.width / 96,
|
|
412
|
+
h: position.height / 96,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function pptxColor(fill: PresentationFill): string {
|
|
417
|
+
const color = colorValue(fill);
|
|
418
|
+
return color === "none" ? "FFFFFF" : color.replace(/^#/, "").slice(0, 6).toUpperCase();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function pptxFill(fill: PresentationFill): Record<string, unknown> {
|
|
422
|
+
return colorValue(fill) === "none"
|
|
423
|
+
? { color: "FFFFFF", transparency: 100 }
|
|
424
|
+
: { color: pptxColor(fill), transparency: 0 };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function pptxLine(line: PresentationLine): Record<string, unknown> {
|
|
428
|
+
const none =
|
|
429
|
+
line.style === "none" || colorValue(line.fill ?? "none") === "none" || (line.width ?? 1) === 0;
|
|
430
|
+
return none
|
|
431
|
+
? { color: "FFFFFF", transparency: 100, width: 0 }
|
|
432
|
+
: {
|
|
433
|
+
color: pptxColor(line.fill ?? "slate-900"),
|
|
434
|
+
width: (line.width ?? 1) * 0.75,
|
|
435
|
+
dashType: line.style,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function pptxTableBorder(line: PresentationLine): Record<string, unknown> {
|
|
440
|
+
const shapeLine = pptxLine(line);
|
|
441
|
+
return {
|
|
442
|
+
color: shapeLine.color,
|
|
443
|
+
transparency: shapeLine.transparency,
|
|
444
|
+
pt: shapeLine.width,
|
|
445
|
+
type: shapeLine.dashType,
|
|
446
|
+
};
|
|
447
|
+
}
|