@file-viewer/pptx 2.0.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.
@@ -0,0 +1,60 @@
1
+ export type PptxFitMode = 'contain' | 'none';
2
+ export interface PptxZipLimits {
3
+ maxFileBytes?: number;
4
+ }
5
+ export interface PptxListOptions {
6
+ windowed?: boolean;
7
+ initialSlides?: number;
8
+ batchSize?: number;
9
+ overscanViewport?: number;
10
+ }
11
+ export interface PptxWorkerFactoryOptions {
12
+ workerFactory?: () => Worker;
13
+ workerUrl?: string | URL;
14
+ workerType?: WorkerType;
15
+ }
16
+ export interface NativePptxEngineOptions {
17
+ slidesScale?: string;
18
+ slideMode?: boolean;
19
+ slideType?: string;
20
+ revealjsPath?: string;
21
+ keyBoardShortCut?: boolean;
22
+ mediaProcess?: boolean;
23
+ jsZipV2?: boolean;
24
+ themeProcess?: boolean | 'colorsAndImageOnly';
25
+ incSlide?: {
26
+ width: number;
27
+ height: number;
28
+ };
29
+ slideModeConfig?: Record<string, unknown>;
30
+ revealjsConfig?: Record<string, unknown>;
31
+ }
32
+ export interface PptxSlideSize {
33
+ width?: number;
34
+ height?: number;
35
+ [key: string]: unknown;
36
+ }
37
+ export interface PptxViewerOptions extends PptxWorkerFactoryOptions {
38
+ fitMode?: PptxFitMode;
39
+ zoomPercent?: number;
40
+ zipLimits?: PptxZipLimits;
41
+ engineOptions?: Partial<NativePptxEngineOptions>;
42
+ lazySlides?: boolean;
43
+ lazyMedia?: boolean;
44
+ listOptions?: PptxListOptions;
45
+ onProgress?: (progress: number, message: unknown) => void;
46
+ onThumbnail?: (base64Jpeg: string) => void;
47
+ onSlideSize?: (size: PptxSlideSize) => void;
48
+ onSlideRendered?: (slideIndex: number, element: Element | null) => void;
49
+ onSlideError?: (slideIndex: number, error: unknown) => void;
50
+ onRenderComplete?: () => void;
51
+ onWarning?: (warning: unknown) => void;
52
+ onError?: (error: unknown) => void;
53
+ }
54
+ export interface PptxWorkerMessage {
55
+ type: string;
56
+ data?: unknown;
57
+ slide_num?: number;
58
+ file_name?: string;
59
+ charts?: unknown;
60
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,33 @@
1
+ import type { PptxViewerOptions } from './types';
2
+ export declare class PptxViewer {
3
+ static open(buffer: ArrayBuffer, target: HTMLElement, options?: PptxViewerOptions): Promise<PptxViewer>;
4
+ readonly target: HTMLElement;
5
+ readonly content: HTMLDivElement;
6
+ readonly scaleBox: HTMLDivElement;
7
+ private readonly buffer;
8
+ private readonly options;
9
+ private worker;
10
+ private resizeObserver;
11
+ private resizeFrame;
12
+ private fitScale;
13
+ private userZoomPercent;
14
+ private currentZoomPercent;
15
+ private thumbnailElement;
16
+ private disposed;
17
+ private completed;
18
+ private constructor();
19
+ get zoomPercent(): number;
20
+ open(): Promise<void>;
21
+ setZoom(percent: number): Promise<void>;
22
+ destroy(): void;
23
+ private startWorker;
24
+ private processMessage;
25
+ private appendGlobalCss;
26
+ private showThumbnail;
27
+ private clearThumbnail;
28
+ private complete;
29
+ private fail;
30
+ private attachResizeObserver;
31
+ private scheduleResize;
32
+ private resize;
33
+ }
package/dist/viewer.js ADDED
@@ -0,0 +1,229 @@
1
+ import { renderPptxPostProcessing } from './chart';
2
+ import { resolvePptxEngineOptions, RECOMMENDED_ZIP_LIMITS } from './options';
3
+ import { ensurePptxViewerStyles } from './styles';
4
+ import { createPptxWorker } from './worker';
5
+ const clamp = (value, min, max) => {
6
+ return Math.min(max, Math.max(min, value));
7
+ };
8
+ const toPercent = (value) => {
9
+ const numeric = typeof value === 'number' && Number.isFinite(value) ? value : 100;
10
+ return clamp(numeric, 25, 300);
11
+ };
12
+ const ensureZipWithinLimits = (buffer, options) => {
13
+ const limits = {
14
+ ...RECOMMENDED_ZIP_LIMITS,
15
+ ...options.zipLimits,
16
+ };
17
+ if (limits.maxFileBytes && buffer.byteLength > limits.maxFileBytes) {
18
+ throw new Error(`PPTX file is too large to preview safely (${buffer.byteLength} bytes).`);
19
+ }
20
+ };
21
+ const appendHtml = (container, html) => {
22
+ const template = container.ownerDocument.createElement('template');
23
+ template.innerHTML = html;
24
+ const nodes = Array.from(template.content.children);
25
+ container.append(template.content);
26
+ return nodes[0] || null;
27
+ };
28
+ export class PptxViewer {
29
+ static async open(buffer, target, options = {}) {
30
+ const viewer = new PptxViewer(buffer, target, options);
31
+ await viewer.open();
32
+ return viewer;
33
+ }
34
+ constructor(buffer, target, options) {
35
+ this.worker = null;
36
+ this.resizeObserver = null;
37
+ this.resizeFrame = 0;
38
+ this.fitScale = 1;
39
+ this.userZoomPercent = 100;
40
+ this.currentZoomPercent = 100;
41
+ this.thumbnailElement = null;
42
+ this.disposed = false;
43
+ this.completed = false;
44
+ this.buffer = buffer;
45
+ this.target = target;
46
+ this.options = options;
47
+ this.userZoomPercent = toPercent(options.zoomPercent);
48
+ this.currentZoomPercent = this.userZoomPercent;
49
+ const documentRef = target.ownerDocument || document;
50
+ this.scaleBox = documentRef.createElement('div');
51
+ this.scaleBox.className = 'flyfish-pptx-scale-box';
52
+ this.content = documentRef.createElement('div');
53
+ this.content.className = 'flyfish-pptx-content';
54
+ this.content.dataset.renderState = 'loading';
55
+ this.scaleBox.append(this.content);
56
+ }
57
+ get zoomPercent() {
58
+ return Math.round(this.currentZoomPercent);
59
+ }
60
+ async open() {
61
+ ensureZipWithinLimits(this.buffer, this.options);
62
+ ensurePptxViewerStyles(this.target.ownerDocument || document);
63
+ this.target.replaceChildren(this.scaleBox);
64
+ this.attachResizeObserver();
65
+ this.startWorker();
66
+ }
67
+ async setZoom(percent) {
68
+ const desiredEffectivePercent = toPercent(percent);
69
+ this.userZoomPercent = clamp(desiredEffectivePercent / Math.max(this.fitScale, 0.01), 25, 600);
70
+ this.scheduleResize();
71
+ }
72
+ destroy() {
73
+ var _a, _b, _c;
74
+ this.disposed = true;
75
+ (_a = this.worker) === null || _a === void 0 ? void 0 : _a.terminate();
76
+ this.worker = null;
77
+ (_b = this.resizeObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
78
+ this.resizeObserver = null;
79
+ if (this.resizeFrame) {
80
+ (_c = this.target.ownerDocument.defaultView) === null || _c === void 0 ? void 0 : _c.cancelAnimationFrame(this.resizeFrame);
81
+ }
82
+ this.target.replaceChildren();
83
+ }
84
+ startWorker() {
85
+ var _a;
86
+ (_a = this.worker) === null || _a === void 0 ? void 0 : _a.terminate();
87
+ this.completed = false;
88
+ this.content.dataset.renderState = 'loading';
89
+ this.worker = createPptxWorker(this.options);
90
+ this.worker.addEventListener('message', event => this.processMessage(event.data));
91
+ this.worker.addEventListener('error', event => this.fail(event.error || event.message));
92
+ this.worker.postMessage({
93
+ type: 'processPPTX',
94
+ data: this.buffer,
95
+ IE11: false,
96
+ options: resolvePptxEngineOptions(this.options.engineOptions),
97
+ });
98
+ }
99
+ processMessage(message) {
100
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
101
+ if (this.disposed || this.completed) {
102
+ return;
103
+ }
104
+ switch (message.type) {
105
+ case 'slide': {
106
+ this.clearThumbnail();
107
+ const element = appendHtml(this.content, String(message.data || ''));
108
+ this.scheduleResize();
109
+ (_b = (_a = this.options).onSlideRendered) === null || _b === void 0 ? void 0 : _b.call(_a, Number(message.slide_num || 0), element);
110
+ break;
111
+ }
112
+ case 'pptx-thumb':
113
+ this.showThumbnail(String(message.data || ''));
114
+ (_d = (_c = this.options).onThumbnail) === null || _d === void 0 ? void 0 : _d.call(_c, String(message.data || ''));
115
+ break;
116
+ case 'slideSize':
117
+ (_f = (_e = this.options).onSlideSize) === null || _f === void 0 ? void 0 : _f.call(_e, (message.data || {}));
118
+ break;
119
+ case 'globalCSS':
120
+ this.appendGlobalCss(String(message.data || ''));
121
+ break;
122
+ case 'progress-update':
123
+ (_h = (_g = this.options).onProgress) === null || _h === void 0 ? void 0 : _h.call(_g, Number(message.data || 0), message);
124
+ break;
125
+ case 'ExecutionTime':
126
+ case 'Done':
127
+ void this.complete(message.charts);
128
+ break;
129
+ case 'WARN':
130
+ (_k = (_j = this.options).onWarning) === null || _k === void 0 ? void 0 : _k.call(_j, message.data);
131
+ break;
132
+ case 'ERROR':
133
+ this.fail(message.data);
134
+ break;
135
+ default:
136
+ break;
137
+ }
138
+ }
139
+ appendGlobalCss(css) {
140
+ if (!css) {
141
+ return;
142
+ }
143
+ const style = this.target.ownerDocument.createElement('style');
144
+ style.textContent = css;
145
+ this.content.append(style);
146
+ }
147
+ showThumbnail(base64Jpeg) {
148
+ if (!base64Jpeg || this.content.children.length > 0) {
149
+ return;
150
+ }
151
+ const image = this.target.ownerDocument.createElement('img');
152
+ image.className = 'flyfish-pptx-thumbnail';
153
+ image.alt = 'PPTX preview thumbnail';
154
+ image.src = `data:image/jpeg;base64,${base64Jpeg}`;
155
+ this.thumbnailElement = image;
156
+ this.scaleBox.insertBefore(image, this.content);
157
+ }
158
+ clearThumbnail() {
159
+ var _a;
160
+ (_a = this.thumbnailElement) === null || _a === void 0 ? void 0 : _a.remove();
161
+ this.thumbnailElement = null;
162
+ }
163
+ async complete(charts) {
164
+ var _a, _b, _c;
165
+ if (this.disposed || this.completed) {
166
+ return;
167
+ }
168
+ this.completed = true;
169
+ this.content.dataset.renderState = 'ready';
170
+ (_a = this.worker) === null || _a === void 0 ? void 0 : _a.terminate();
171
+ this.worker = null;
172
+ this.scheduleResize();
173
+ await renderPptxPostProcessing(charts, this.content);
174
+ this.scheduleResize();
175
+ (_c = (_b = this.options).onRenderComplete) === null || _c === void 0 ? void 0 : _c.call(_b);
176
+ }
177
+ fail(error) {
178
+ var _a, _b, _c;
179
+ if (this.disposed) {
180
+ return;
181
+ }
182
+ this.completed = true;
183
+ (_a = this.worker) === null || _a === void 0 ? void 0 : _a.terminate();
184
+ this.worker = null;
185
+ this.content.dataset.renderState = 'error';
186
+ (_c = (_b = this.options).onError) === null || _c === void 0 ? void 0 : _c.call(_b, error);
187
+ }
188
+ attachResizeObserver() {
189
+ if (typeof ResizeObserver === 'undefined') {
190
+ return;
191
+ }
192
+ this.resizeObserver = new ResizeObserver(() => this.scheduleResize());
193
+ this.resizeObserver.observe(this.target);
194
+ if (this.target.parentElement) {
195
+ this.resizeObserver.observe(this.target.parentElement);
196
+ }
197
+ }
198
+ scheduleResize() {
199
+ const view = this.target.ownerDocument.defaultView || window;
200
+ if (this.resizeFrame) {
201
+ view.cancelAnimationFrame(this.resizeFrame);
202
+ }
203
+ this.resizeFrame = view.requestAnimationFrame(() => this.resize());
204
+ }
205
+ resize() {
206
+ var _a, _b;
207
+ const HTMLElementCtor = ((_a = this.target.ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.HTMLElement) || HTMLElement;
208
+ const slides = Array.from(this.content.children).filter((child) => {
209
+ return child instanceof HTMLElementCtor && (child.classList.contains('slide') || child.tagName === 'SECTION');
210
+ });
211
+ if (!slides.length) {
212
+ return;
213
+ }
214
+ const slideWidth = Math.max(...slides.map(slide => slide.offsetWidth), 0);
215
+ if (!slideWidth) {
216
+ return;
217
+ }
218
+ const viewportWidth = this.target.clientWidth || ((_b = this.target.parentElement) === null || _b === void 0 ? void 0 : _b.clientWidth) || slideWidth;
219
+ this.fitScale = this.options.fitMode === 'none'
220
+ ? 1
221
+ : Math.min(1, viewportWidth / slideWidth);
222
+ const effectiveScale = clamp(this.fitScale * (this.userZoomPercent / 100), 0.25, 3);
223
+ this.currentZoomPercent = effectiveScale * 100;
224
+ this.content.style.width = `${slideWidth}px`;
225
+ this.content.style.transform = `scale(${effectiveScale})`;
226
+ this.scaleBox.style.width = `${Math.ceil(slideWidth * effectiveScale)}px`;
227
+ this.scaleBox.style.minHeight = `${Math.ceil(this.content.scrollHeight * effectiveScale)}px`;
228
+ }
229
+ }