@extend-ai/react-pptx 0.1.2 → 0.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/CHANGELOG.md +3 -0
- package/README.md +43 -0
- package/dist/index.d.ts +64 -1
- package/dist/index.js +489 -38
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9247,6 +9247,64 @@ var EMU_PER_CSS_PIXEL = 9525;
|
|
|
9247
9247
|
var DEFAULT_TEXT_HORIZONTAL_INSET_EMU = 91440;
|
|
9248
9248
|
var DEFAULT_TEXT_VERTICAL_INSET_EMU = 45720;
|
|
9249
9249
|
var POWERPOINT_SINGLE_LINE_HEIGHT = 1.15;
|
|
9250
|
+
var PresentationAssetCache = class {
|
|
9251
|
+
references = 0;
|
|
9252
|
+
objectUrls = /* @__PURE__ */ new Set();
|
|
9253
|
+
assetUrls = /* @__PURE__ */ new Map();
|
|
9254
|
+
metafileUrls = /* @__PURE__ */ new Map();
|
|
9255
|
+
acquire() {
|
|
9256
|
+
this.references += 1;
|
|
9257
|
+
return this;
|
|
9258
|
+
}
|
|
9259
|
+
metafileUrl(assetId, contentType, data) {
|
|
9260
|
+
const cached = this.metafileUrls.get(assetId);
|
|
9261
|
+
if (cached) return cached;
|
|
9262
|
+
const convert = contentType.includes("wmf") ? renderWmfToDataUrl : renderEmfToDataUrl;
|
|
9263
|
+
const ownedBytes = data.slice();
|
|
9264
|
+
const pending = convert(ownedBytes.buffer, 2048, 2048, {
|
|
9265
|
+
dpiScale: 2,
|
|
9266
|
+
maxCanvasDimension: 4096,
|
|
9267
|
+
maxRecords: 2e5,
|
|
9268
|
+
fontFamilyMap: METAFILE_FONT_MAP
|
|
9269
|
+
}).then((url) => url || void 0);
|
|
9270
|
+
this.metafileUrls.set(assetId, pending);
|
|
9271
|
+
return pending;
|
|
9272
|
+
}
|
|
9273
|
+
assetUrl(asset) {
|
|
9274
|
+
if (asset.url) return Promise.resolve(asset.url);
|
|
9275
|
+
if (!asset.data) return Promise.resolve(void 0);
|
|
9276
|
+
const contentType = asset.contentType.toLowerCase();
|
|
9277
|
+
if (contentType.includes("emf") || contentType.includes("wmf")) {
|
|
9278
|
+
return this.metafileUrl(asset.id, contentType, asset.data);
|
|
9279
|
+
}
|
|
9280
|
+
const cached = this.assetUrls.get(asset.id);
|
|
9281
|
+
if (cached) return Promise.resolve(cached);
|
|
9282
|
+
const ownedBytes = asset.data.slice();
|
|
9283
|
+
const url = URL.createObjectURL(
|
|
9284
|
+
new Blob([ownedBytes.buffer], { type: asset.contentType })
|
|
9285
|
+
);
|
|
9286
|
+
this.objectUrls.add(url);
|
|
9287
|
+
this.assetUrls.set(asset.id, url);
|
|
9288
|
+
return Promise.resolve(url);
|
|
9289
|
+
}
|
|
9290
|
+
release() {
|
|
9291
|
+
this.references = Math.max(0, this.references - 1);
|
|
9292
|
+
if (this.references > 0) return;
|
|
9293
|
+
for (const url of this.objectUrls) URL.revokeObjectURL(url);
|
|
9294
|
+
this.objectUrls.clear();
|
|
9295
|
+
this.assetUrls.clear();
|
|
9296
|
+
this.metafileUrls.clear();
|
|
9297
|
+
}
|
|
9298
|
+
};
|
|
9299
|
+
var presentationAssetCaches = /* @__PURE__ */ new WeakMap();
|
|
9300
|
+
function acquirePresentationAssetCache(presentation) {
|
|
9301
|
+
let cache = presentationAssetCaches.get(presentation);
|
|
9302
|
+
if (!cache) {
|
|
9303
|
+
cache = new PresentationAssetCache();
|
|
9304
|
+
presentationAssetCaches.set(presentation, cache);
|
|
9305
|
+
}
|
|
9306
|
+
return cache.acquire();
|
|
9307
|
+
}
|
|
9250
9308
|
var LIST_ITEM_GAP = 24;
|
|
9251
9309
|
var FALLBACK_VIEWPORT_RECT = { width: 800, height: 600 };
|
|
9252
9310
|
function scaleListAnchorOffset(offset, previousSlideHeight, nextSlideHeight) {
|
|
@@ -9788,6 +9846,7 @@ var NormalizedPresentationViewer = class {
|
|
|
9788
9846
|
this.container = container;
|
|
9789
9847
|
this.presentation = presentation;
|
|
9790
9848
|
this.callbacks = callbacks;
|
|
9849
|
+
this.assetCache = acquirePresentationAssetCache(presentation);
|
|
9791
9850
|
}
|
|
9792
9851
|
container;
|
|
9793
9852
|
presentation;
|
|
@@ -9795,9 +9854,7 @@ var NormalizedPresentationViewer = class {
|
|
|
9795
9854
|
current = 0;
|
|
9796
9855
|
zoom = 100;
|
|
9797
9856
|
fit = "contain";
|
|
9798
|
-
|
|
9799
|
-
assetUrls = /* @__PURE__ */ new Map();
|
|
9800
|
-
metafileUrls = /* @__PURE__ */ new Map();
|
|
9857
|
+
assetCache;
|
|
9801
9858
|
listCleanup;
|
|
9802
9859
|
highlight;
|
|
9803
9860
|
activeMountResources;
|
|
@@ -9853,36 +9910,8 @@ var NormalizedPresentationViewer = class {
|
|
|
9853
9910
|
this.lastNotifiedSlide = index;
|
|
9854
9911
|
this.callbacks.onSlideChange?.(index);
|
|
9855
9912
|
}
|
|
9856
|
-
metafileUrl(assetId, contentType, data) {
|
|
9857
|
-
const cached = this.metafileUrls.get(assetId);
|
|
9858
|
-
if (cached) return cached;
|
|
9859
|
-
const convert = contentType.includes("wmf") ? renderWmfToDataUrl : renderEmfToDataUrl;
|
|
9860
|
-
const ownedBytes = data.slice();
|
|
9861
|
-
const pending = convert(ownedBytes.buffer, 2048, 2048, {
|
|
9862
|
-
dpiScale: 2,
|
|
9863
|
-
maxCanvasDimension: 4096,
|
|
9864
|
-
maxRecords: 2e5,
|
|
9865
|
-
fontFamilyMap: METAFILE_FONT_MAP
|
|
9866
|
-
}).then((url) => url || void 0);
|
|
9867
|
-
this.metafileUrls.set(assetId, pending);
|
|
9868
|
-
return pending;
|
|
9869
|
-
}
|
|
9870
9913
|
assetUrl(asset) {
|
|
9871
|
-
|
|
9872
|
-
if (!asset.data) return Promise.resolve(void 0);
|
|
9873
|
-
const contentType = asset.contentType.toLowerCase();
|
|
9874
|
-
if (contentType.includes("emf") || contentType.includes("wmf")) {
|
|
9875
|
-
return this.metafileUrl(asset.id, contentType, asset.data);
|
|
9876
|
-
}
|
|
9877
|
-
const cached = this.assetUrls.get(asset.id);
|
|
9878
|
-
if (cached) return Promise.resolve(cached);
|
|
9879
|
-
const ownedBytes = asset.data.slice();
|
|
9880
|
-
const url = URL.createObjectURL(
|
|
9881
|
-
new Blob([ownedBytes.buffer], { type: asset.contentType })
|
|
9882
|
-
);
|
|
9883
|
-
this.objectUrls.add(url);
|
|
9884
|
-
this.assetUrls.set(asset.id, url);
|
|
9885
|
-
return Promise.resolve(url);
|
|
9914
|
+
return this.assetCache.assetUrl(asset);
|
|
9886
9915
|
}
|
|
9887
9916
|
applyAsset(assetId, apply, nodeId) {
|
|
9888
9917
|
const asset = this.presentation.assets[assetId];
|
|
@@ -10982,10 +11011,7 @@ var NormalizedPresentationViewer = class {
|
|
|
10982
11011
|
this.listCleanup = void 0;
|
|
10983
11012
|
this.listState = void 0;
|
|
10984
11013
|
this.clearSearchHighlights();
|
|
10985
|
-
|
|
10986
|
-
this.objectUrls.clear();
|
|
10987
|
-
this.assetUrls.clear();
|
|
10988
|
-
this.metafileUrls.clear();
|
|
11014
|
+
this.assetCache.release();
|
|
10989
11015
|
this.activeMountResources = void 0;
|
|
10990
11016
|
for (const handle of [...this.mountedHandles]) handle.dispose();
|
|
10991
11017
|
this.handlesByTarget.clear();
|
|
@@ -11145,6 +11171,18 @@ async function parseInWorker(bytes, signal, wasmSource) {
|
|
|
11145
11171
|
});
|
|
11146
11172
|
}
|
|
11147
11173
|
|
|
11174
|
+
// src/performance.ts
|
|
11175
|
+
function performanceNow() {
|
|
11176
|
+
return globalThis.performance?.now?.() ?? Date.now();
|
|
11177
|
+
}
|
|
11178
|
+
function performanceMeasurement(phase, startedAt, slideIndex) {
|
|
11179
|
+
return {
|
|
11180
|
+
durationMs: Math.max(0, performanceNow() - startedAt),
|
|
11181
|
+
phase,
|
|
11182
|
+
...slideIndex === void 0 ? {} : { slideIndex }
|
|
11183
|
+
};
|
|
11184
|
+
}
|
|
11185
|
+
|
|
11148
11186
|
// src/parse.ts
|
|
11149
11187
|
var DEFAULT_MAX_INPUT_BYTES = 100 * 1024 * 1024;
|
|
11150
11188
|
function isParsedPresentation(source) {
|
|
@@ -11229,14 +11267,20 @@ async function parsePresentation(source, options = {}) {
|
|
|
11229
11267
|
warnings: source.warnings
|
|
11230
11268
|
};
|
|
11231
11269
|
}
|
|
11270
|
+
const downloadStartedAt = performanceNow();
|
|
11232
11271
|
const buffer = await readSource(source, options);
|
|
11272
|
+
if (typeof source === "string" || source instanceof URL) {
|
|
11273
|
+
options.onPerformanceMeasurement?.(performanceMeasurement("download", downloadStartedAt));
|
|
11274
|
+
}
|
|
11233
11275
|
const format = detectFormat(buffer, options.formatHint);
|
|
11234
11276
|
const bytes = new Uint8Array(buffer);
|
|
11235
11277
|
throwIfAborted(options.signal);
|
|
11278
|
+
const parsingStartedAt = performanceNow();
|
|
11236
11279
|
const nativeDocument = await parseWithWasm(bytes, {
|
|
11237
11280
|
formatHint: format,
|
|
11238
11281
|
...options.signal ? { signal: options.signal } : {}
|
|
11239
11282
|
});
|
|
11283
|
+
options.onPerformanceMeasurement?.(performanceMeasurement("wasm-parsing", parsingStartedAt));
|
|
11240
11284
|
throwIfAborted(options.signal);
|
|
11241
11285
|
if (!nativeDocument) {
|
|
11242
11286
|
throw new PptxViewerError(
|
|
@@ -11313,7 +11357,11 @@ async function createAdapter(target, parsed, props, getCurrentProps, onCurrentSl
|
|
|
11313
11357
|
const fontManager = new PptxFontManager(props.fonts, (warning) => {
|
|
11314
11358
|
if (isCurrent()) reportWarning(warning);
|
|
11315
11359
|
});
|
|
11360
|
+
const fontStartedAt = performanceNow();
|
|
11316
11361
|
await fontManager.prepare(parsed.document);
|
|
11362
|
+
getCurrentProps().onPerformanceMeasurement?.(
|
|
11363
|
+
performanceMeasurement("embedded-font-preparation", fontStartedAt)
|
|
11364
|
+
);
|
|
11317
11365
|
if (!isCurrent()) {
|
|
11318
11366
|
fontManager.destroy();
|
|
11319
11367
|
throw new Error("Viewer adapter creation was superseded.");
|
|
@@ -11346,6 +11394,7 @@ async function createAdapter(target, parsed, props, getCurrentProps, onCurrentSl
|
|
|
11346
11394
|
const normalized = new NormalizedPresentationViewer(target, parsed.document, callbacks);
|
|
11347
11395
|
return {
|
|
11348
11396
|
async render(mode, slideIndex) {
|
|
11397
|
+
const startedAt = performanceNow();
|
|
11349
11398
|
if (mode === "continuous") {
|
|
11350
11399
|
const virtualization = typeof props.virtualization === "object" ? props.virtualization : {};
|
|
11351
11400
|
await normalized.renderList({
|
|
@@ -11359,6 +11408,9 @@ async function createAdapter(target, parsed, props, getCurrentProps, onCurrentSl
|
|
|
11359
11408
|
} else {
|
|
11360
11409
|
await normalized.renderSlide(slideIndex);
|
|
11361
11410
|
}
|
|
11411
|
+
getCurrentProps().onPerformanceMeasurement?.(
|
|
11412
|
+
performanceMeasurement("slide-dom-svg-rendering", startedAt, slideIndex)
|
|
11413
|
+
);
|
|
11362
11414
|
},
|
|
11363
11415
|
goToSlide: (index, options) => normalized.goToSlide(index, options),
|
|
11364
11416
|
setZoom: (percent) => normalized.setZoom(percent),
|
|
@@ -11591,7 +11643,8 @@ var ReactPptxViewer = forwardRef(
|
|
|
11591
11643
|
signal: abort.signal,
|
|
11592
11644
|
...parseFormatHint ? { formatHint: parseFormatHint } : {},
|
|
11593
11645
|
...parseMaxInputBytes !== void 0 ? { maxInputBytes: parseMaxInputBytes } : {},
|
|
11594
|
-
...parseFetchInit ? { fetchInit: parseFetchInit } : {}
|
|
11646
|
+
...parseFetchInit ? { fetchInit: parseFetchInit } : {},
|
|
11647
|
+
onPerformanceMeasurement: (measurement) => latestPropsRef.current.onPerformanceMeasurement?.(measurement)
|
|
11595
11648
|
}).then((next) => {
|
|
11596
11649
|
if (abort.signal.aborted) return;
|
|
11597
11650
|
setParsed(next);
|
|
@@ -12284,6 +12337,404 @@ function usePptxViewerThumbnails(controller, options = {}) {
|
|
|
12284
12337
|
);
|
|
12285
12338
|
}
|
|
12286
12339
|
|
|
12287
|
-
|
|
12340
|
+
// src/thumbnail-renderer.ts
|
|
12341
|
+
var EMU_PER_CSS_PIXEL3 = 9525;
|
|
12342
|
+
var DEFAULT_MAX_DIMENSION3 = 160;
|
|
12343
|
+
var RenderQueue = class {
|
|
12344
|
+
constructor(limit) {
|
|
12345
|
+
this.limit = limit;
|
|
12346
|
+
}
|
|
12347
|
+
limit;
|
|
12348
|
+
active = 0;
|
|
12349
|
+
destroyed = false;
|
|
12350
|
+
waiters = [];
|
|
12351
|
+
acquire(signal) {
|
|
12352
|
+
throwIfAborted(signal);
|
|
12353
|
+
if (this.destroyed) return Promise.reject(abortedError());
|
|
12354
|
+
return new Promise((resolve, reject) => {
|
|
12355
|
+
const waiter = { resolve, reject, ...signal ? { signal } : {} };
|
|
12356
|
+
if (signal) {
|
|
12357
|
+
waiter.abort = () => {
|
|
12358
|
+
const index = this.waiters.indexOf(waiter);
|
|
12359
|
+
if (index >= 0) this.waiters.splice(index, 1);
|
|
12360
|
+
reject(abortedError(signal.reason));
|
|
12361
|
+
};
|
|
12362
|
+
signal.addEventListener("abort", waiter.abort, { once: true });
|
|
12363
|
+
}
|
|
12364
|
+
this.waiters.push(waiter);
|
|
12365
|
+
this.drain();
|
|
12366
|
+
});
|
|
12367
|
+
}
|
|
12368
|
+
destroy() {
|
|
12369
|
+
this.destroyed = true;
|
|
12370
|
+
for (const waiter of this.waiters.splice(0)) {
|
|
12371
|
+
if (waiter.abort && waiter.signal) {
|
|
12372
|
+
waiter.signal.removeEventListener("abort", waiter.abort);
|
|
12373
|
+
}
|
|
12374
|
+
waiter.reject(abortedError());
|
|
12375
|
+
}
|
|
12376
|
+
}
|
|
12377
|
+
drain() {
|
|
12378
|
+
while (!this.destroyed && this.active < this.limit && this.waiters.length) {
|
|
12379
|
+
const waiter = this.waiters.shift();
|
|
12380
|
+
if (waiter.abort && waiter.signal) {
|
|
12381
|
+
waiter.signal.removeEventListener("abort", waiter.abort);
|
|
12382
|
+
}
|
|
12383
|
+
if (waiter.signal?.aborted) {
|
|
12384
|
+
waiter.reject(abortedError(waiter.signal.reason));
|
|
12385
|
+
continue;
|
|
12386
|
+
}
|
|
12387
|
+
this.active += 1;
|
|
12388
|
+
let released = false;
|
|
12389
|
+
waiter.resolve(() => {
|
|
12390
|
+
if (released) return;
|
|
12391
|
+
released = true;
|
|
12392
|
+
this.active -= 1;
|
|
12393
|
+
this.drain();
|
|
12394
|
+
});
|
|
12395
|
+
}
|
|
12396
|
+
}
|
|
12397
|
+
};
|
|
12398
|
+
function abortedError(reason) {
|
|
12399
|
+
return new PptxViewerError("aborted", "Thumbnail rendering was cancelled.", { cause: reason });
|
|
12400
|
+
}
|
|
12401
|
+
function finitePositive3(value, fallback) {
|
|
12402
|
+
return value !== void 0 && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
12403
|
+
}
|
|
12404
|
+
function outputSize(presentation, options) {
|
|
12405
|
+
const naturalWidth = Math.max(1, presentation.document.size.widthEmu / EMU_PER_CSS_PIXEL3);
|
|
12406
|
+
const naturalHeight = Math.max(1, presentation.document.size.heightEmu / EMU_PER_CSS_PIXEL3);
|
|
12407
|
+
const hasWidth = options.maxWidth !== void 0 && Number.isFinite(options.maxWidth) && options.maxWidth > 0;
|
|
12408
|
+
const hasHeight = options.maxHeight !== void 0 && Number.isFinite(options.maxHeight) && options.maxHeight > 0;
|
|
12409
|
+
const maxWidth = hasWidth ? options.maxWidth : hasHeight ? Number.POSITIVE_INFINITY : DEFAULT_MAX_DIMENSION3;
|
|
12410
|
+
const maxHeight = hasHeight ? options.maxHeight : hasWidth ? Number.POSITIVE_INFINITY : DEFAULT_MAX_DIMENSION3;
|
|
12411
|
+
const scale = Math.min(maxWidth / naturalWidth, maxHeight / naturalHeight);
|
|
12412
|
+
return {
|
|
12413
|
+
height: Math.max(1, Math.round(naturalHeight * scale)),
|
|
12414
|
+
naturalHeight,
|
|
12415
|
+
naturalWidth,
|
|
12416
|
+
width: Math.max(1, Math.round(naturalWidth * scale))
|
|
12417
|
+
};
|
|
12418
|
+
}
|
|
12419
|
+
function blobToDataUrl(blob, signal) {
|
|
12420
|
+
return abortable(
|
|
12421
|
+
new Promise((resolve, reject) => {
|
|
12422
|
+
const reader = new FileReader();
|
|
12423
|
+
reader.onload = () => resolve(String(reader.result));
|
|
12424
|
+
reader.onerror = () => reject(reader.error ?? new Error("Could not read a slide asset."));
|
|
12425
|
+
reader.readAsDataURL(blob);
|
|
12426
|
+
}),
|
|
12427
|
+
signal
|
|
12428
|
+
);
|
|
12429
|
+
}
|
|
12430
|
+
async function inlineResourceUrl(url, signal) {
|
|
12431
|
+
if (!/^(?:blob:|https?:)/i.test(url)) return url;
|
|
12432
|
+
try {
|
|
12433
|
+
const response = await fetch(url, signal ? { signal } : void 0);
|
|
12434
|
+
if (!response.ok) return url;
|
|
12435
|
+
return await blobToDataUrl(await response.blob(), signal);
|
|
12436
|
+
} catch {
|
|
12437
|
+
throwIfAborted(signal);
|
|
12438
|
+
return url;
|
|
12439
|
+
}
|
|
12440
|
+
}
|
|
12441
|
+
async function inlineResources(root, signal) {
|
|
12442
|
+
const cache = /* @__PURE__ */ new Map();
|
|
12443
|
+
const inline = (url) => {
|
|
12444
|
+
let pending = cache.get(url);
|
|
12445
|
+
if (!pending) {
|
|
12446
|
+
pending = inlineResourceUrl(url, signal);
|
|
12447
|
+
cache.set(url, pending);
|
|
12448
|
+
}
|
|
12449
|
+
return pending;
|
|
12450
|
+
};
|
|
12451
|
+
const elements = [root, ...root.querySelectorAll("*")];
|
|
12452
|
+
await Promise.all(
|
|
12453
|
+
elements.flatMap((element) => {
|
|
12454
|
+
const attributeNames = ["src", "poster", ...element.localName === "image" ? ["href"] : []];
|
|
12455
|
+
const attributes = attributeNames.map((name) => ({ name, value: element.getAttribute(name) })).filter((entry) => Boolean(entry.value));
|
|
12456
|
+
const attributeTasks = attributes.map(async ({ name, value }) => {
|
|
12457
|
+
element.setAttribute(name, await inline(value));
|
|
12458
|
+
});
|
|
12459
|
+
const styleTasks = Array.from(
|
|
12460
|
+
{ length: element.style.length },
|
|
12461
|
+
(_, index) => element.style.item(index)
|
|
12462
|
+
).map((property) => ({ property, value: element.style.getPropertyValue(property) })).filter(({ value }) => value.includes("url(")).map(async ({ property, value }) => {
|
|
12463
|
+
const urls = [...value.matchAll(/url\(["']?([^"')]+)["']?\)/g)].map((match) => match[1]);
|
|
12464
|
+
let next = value;
|
|
12465
|
+
for (const url of urls) next = next.replace(url, await inline(url));
|
|
12466
|
+
element.style.setProperty(property, next);
|
|
12467
|
+
});
|
|
12468
|
+
return [...attributeTasks, ...styleTasks];
|
|
12469
|
+
})
|
|
12470
|
+
);
|
|
12471
|
+
}
|
|
12472
|
+
async function serializeSlide(slide, naturalWidth, naturalHeight, width, height, signal) {
|
|
12473
|
+
const clone = slide.cloneNode(true);
|
|
12474
|
+
clone.style.transform = "none";
|
|
12475
|
+
clone.style.transformOrigin = "top left";
|
|
12476
|
+
clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
12477
|
+
const sourceCanvases = slide.querySelectorAll("canvas");
|
|
12478
|
+
const clonedCanvases = clone.querySelectorAll("canvas");
|
|
12479
|
+
sourceCanvases.forEach((canvas, index) => {
|
|
12480
|
+
const cloned = clonedCanvases[index];
|
|
12481
|
+
if (!cloned) return;
|
|
12482
|
+
try {
|
|
12483
|
+
const image = document.createElement("img");
|
|
12484
|
+
image.src = canvas.toDataURL("image/png");
|
|
12485
|
+
image.setAttribute("style", cloned.getAttribute("style") ?? "");
|
|
12486
|
+
cloned.replaceWith(image);
|
|
12487
|
+
} catch {
|
|
12488
|
+
cloned.remove();
|
|
12489
|
+
}
|
|
12490
|
+
});
|
|
12491
|
+
await inlineResources(clone, signal);
|
|
12492
|
+
const content = new XMLSerializer().serializeToString(clone);
|
|
12493
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${naturalWidth} ${naturalHeight}"><foreignObject width="${naturalWidth}" height="${naturalHeight}">${content}</foreignObject></svg>`;
|
|
12494
|
+
}
|
|
12495
|
+
async function abortable(promise, signal) {
|
|
12496
|
+
throwIfAborted(signal);
|
|
12497
|
+
if (!signal) return promise;
|
|
12498
|
+
return new Promise((resolve, reject) => {
|
|
12499
|
+
const abort = () => reject(abortedError(signal.reason));
|
|
12500
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
12501
|
+
void promise.then(
|
|
12502
|
+
(value) => {
|
|
12503
|
+
signal.removeEventListener("abort", abort);
|
|
12504
|
+
resolve(value);
|
|
12505
|
+
},
|
|
12506
|
+
(error) => {
|
|
12507
|
+
signal.removeEventListener("abort", abort);
|
|
12508
|
+
reject(error);
|
|
12509
|
+
}
|
|
12510
|
+
);
|
|
12511
|
+
});
|
|
12512
|
+
}
|
|
12513
|
+
async function decodeElementImages(root, signal) {
|
|
12514
|
+
const images = [...root.querySelectorAll("img")];
|
|
12515
|
+
const svgImages = [...root.querySelectorAll("image")].map((image) => image.href.baseVal || image.getAttribute("href") || "").filter(Boolean);
|
|
12516
|
+
await abortable(
|
|
12517
|
+
Promise.all([
|
|
12518
|
+
...images.map(async (image) => {
|
|
12519
|
+
if (typeof image.decode === "function") await image.decode().catch(() => void 0);
|
|
12520
|
+
}),
|
|
12521
|
+
...svgImages.map(
|
|
12522
|
+
(source) => new Promise((resolve) => {
|
|
12523
|
+
const image = new Image();
|
|
12524
|
+
image.onload = () => resolve();
|
|
12525
|
+
image.onerror = () => resolve();
|
|
12526
|
+
image.src = source;
|
|
12527
|
+
})
|
|
12528
|
+
)
|
|
12529
|
+
]).then(() => void 0),
|
|
12530
|
+
signal
|
|
12531
|
+
);
|
|
12532
|
+
}
|
|
12533
|
+
async function decodeSvg(svg, signal) {
|
|
12534
|
+
return abortable(
|
|
12535
|
+
new Promise((resolve, reject) => {
|
|
12536
|
+
const image = new Image();
|
|
12537
|
+
image.onload = () => resolve(image);
|
|
12538
|
+
image.onerror = () => reject(new Error("The rendered slide SVG could not be decoded."));
|
|
12539
|
+
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
12540
|
+
}),
|
|
12541
|
+
signal
|
|
12542
|
+
);
|
|
12543
|
+
}
|
|
12544
|
+
function canvasToBlob(canvas, quality) {
|
|
12545
|
+
return new Promise((resolve, reject) => {
|
|
12546
|
+
canvas.toBlob(
|
|
12547
|
+
(blob) => {
|
|
12548
|
+
if (blob) resolve(blob);
|
|
12549
|
+
else reject(new Error("The browser could not encode the thumbnail."));
|
|
12550
|
+
},
|
|
12551
|
+
"image/png",
|
|
12552
|
+
quality
|
|
12553
|
+
);
|
|
12554
|
+
});
|
|
12555
|
+
}
|
|
12556
|
+
var ThumbnailRenderer = class {
|
|
12557
|
+
constructor(presentation, options) {
|
|
12558
|
+
this.presentation = presentation;
|
|
12559
|
+
this.options = options;
|
|
12560
|
+
if (typeof document === "undefined") {
|
|
12561
|
+
throw new PptxViewerError(
|
|
12562
|
+
"unsupported-environment",
|
|
12563
|
+
"Thumbnail rendering requires a browser DOM."
|
|
12564
|
+
);
|
|
12565
|
+
}
|
|
12566
|
+
const concurrency = Math.max(1, Math.floor(finitePositive3(options.concurrency, 2)));
|
|
12567
|
+
this.queue = new RenderQueue(concurrency);
|
|
12568
|
+
this.root = document.createElement("div");
|
|
12569
|
+
this.root.setAttribute("aria-hidden", "true");
|
|
12570
|
+
this.root.style.position = "fixed";
|
|
12571
|
+
this.root.style.left = "-100000px";
|
|
12572
|
+
this.root.style.top = "0";
|
|
12573
|
+
this.root.style.pointerEvents = "none";
|
|
12574
|
+
this.root.style.opacity = "0";
|
|
12575
|
+
document.body.append(this.root);
|
|
12576
|
+
this.fontManager = new PptxFontManager(
|
|
12577
|
+
{
|
|
12578
|
+
loadEmbeddedFonts: false,
|
|
12579
|
+
reportMissingFonts: false,
|
|
12580
|
+
waitForFonts: false,
|
|
12581
|
+
...options.fonts
|
|
12582
|
+
},
|
|
12583
|
+
(warning) => options.onWarning?.(warning)
|
|
12584
|
+
);
|
|
12585
|
+
this.viewer = new NormalizedPresentationViewer(this.root, presentation.document, {
|
|
12586
|
+
onNodeError: (nodeId, error) => options.onWarning?.({
|
|
12587
|
+
code: "degraded-rendering",
|
|
12588
|
+
severity: "warning",
|
|
12589
|
+
nodeId,
|
|
12590
|
+
feature: "thumbnail-renderer",
|
|
12591
|
+
message: `A slide element could not be rendered: ${error instanceof Error ? error.message : String(error)}`
|
|
12592
|
+
})
|
|
12593
|
+
});
|
|
12594
|
+
const startedAt = performanceNow();
|
|
12595
|
+
this.ready = this.fontManager.prepare(presentation.document).then(() => {
|
|
12596
|
+
this.report(performanceMeasurement("embedded-font-preparation", startedAt));
|
|
12597
|
+
});
|
|
12598
|
+
}
|
|
12599
|
+
presentation;
|
|
12600
|
+
options;
|
|
12601
|
+
ready;
|
|
12602
|
+
fontManager;
|
|
12603
|
+
queue;
|
|
12604
|
+
root;
|
|
12605
|
+
viewer;
|
|
12606
|
+
destroyed = false;
|
|
12607
|
+
async renderSlide(slideIndex, options = {}) {
|
|
12608
|
+
const release = await this.queue.acquire(options.signal);
|
|
12609
|
+
try {
|
|
12610
|
+
this.assertAvailable(options.signal);
|
|
12611
|
+
await abortable(this.ready, options.signal);
|
|
12612
|
+
this.assertAvailable(options.signal);
|
|
12613
|
+
if (!Number.isInteger(slideIndex) || slideIndex < 0 || slideIndex >= this.presentation.document.slides.length) {
|
|
12614
|
+
throw new RangeError(`Slide ${slideIndex} does not exist.`);
|
|
12615
|
+
}
|
|
12616
|
+
const measurements = [];
|
|
12617
|
+
const size = outputSize(this.presentation, options);
|
|
12618
|
+
const target = document.createElement("div");
|
|
12619
|
+
this.root.append(target);
|
|
12620
|
+
const domStartedAt = performanceNow();
|
|
12621
|
+
const handle = this.viewer.renderThumbnailToContainer(slideIndex, target, {
|
|
12622
|
+
width: size.naturalWidth
|
|
12623
|
+
});
|
|
12624
|
+
try {
|
|
12625
|
+
await abortable(handle.ready, options.signal);
|
|
12626
|
+
this.fontManager.applyTo(handle.element, slideIndex);
|
|
12627
|
+
const domDurationMs = performanceNow() - domStartedAt;
|
|
12628
|
+
const decodeStartedAt = performanceNow();
|
|
12629
|
+
await decodeElementImages(handle.element, options.signal);
|
|
12630
|
+
let decodeDurationMs = performanceNow() - decodeStartedAt;
|
|
12631
|
+
const serializationStartedAt = performanceNow();
|
|
12632
|
+
const svg = await serializeSlide(
|
|
12633
|
+
handle.element,
|
|
12634
|
+
size.naturalWidth,
|
|
12635
|
+
size.naturalHeight,
|
|
12636
|
+
size.width,
|
|
12637
|
+
size.height,
|
|
12638
|
+
options.signal
|
|
12639
|
+
);
|
|
12640
|
+
this.record(
|
|
12641
|
+
{
|
|
12642
|
+
durationMs: Math.max(0, domDurationMs + performanceNow() - serializationStartedAt),
|
|
12643
|
+
phase: "slide-dom-svg-rendering",
|
|
12644
|
+
slideIndex
|
|
12645
|
+
},
|
|
12646
|
+
measurements
|
|
12647
|
+
);
|
|
12648
|
+
const output = options.output ?? "blob";
|
|
12649
|
+
if (output === "svg") {
|
|
12650
|
+
this.record(
|
|
12651
|
+
{ durationMs: Math.max(0, decodeDurationMs), phase: "image-decoding", slideIndex },
|
|
12652
|
+
measurements
|
|
12653
|
+
);
|
|
12654
|
+
return this.result(svg, output, slideIndex, size, measurements);
|
|
12655
|
+
}
|
|
12656
|
+
const svgDecodeStartedAt = performanceNow();
|
|
12657
|
+
const image = await decodeSvg(svg, options.signal);
|
|
12658
|
+
decodeDurationMs += performanceNow() - svgDecodeStartedAt;
|
|
12659
|
+
this.record(
|
|
12660
|
+
{ durationMs: Math.max(0, decodeDurationMs), phase: "image-decoding", slideIndex },
|
|
12661
|
+
measurements
|
|
12662
|
+
);
|
|
12663
|
+
this.assertAvailable(options.signal);
|
|
12664
|
+
const encodeStartedAt = performanceNow();
|
|
12665
|
+
const pixelRatio = finitePositive3(options.pixelRatio, 1);
|
|
12666
|
+
const canvas = document.createElement("canvas");
|
|
12667
|
+
canvas.width = Math.max(1, Math.round(size.width * pixelRatio));
|
|
12668
|
+
canvas.height = Math.max(1, Math.round(size.height * pixelRatio));
|
|
12669
|
+
canvas.style.width = `${size.width}px`;
|
|
12670
|
+
canvas.style.height = `${size.height}px`;
|
|
12671
|
+
const context = canvas.getContext("2d");
|
|
12672
|
+
if (!context) throw new Error("The browser does not provide a 2D canvas renderer.");
|
|
12673
|
+
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
12674
|
+
let data;
|
|
12675
|
+
if (output === "canvas") data = canvas;
|
|
12676
|
+
else if (output === "imageBitmap") {
|
|
12677
|
+
if (typeof createImageBitmap !== "function") {
|
|
12678
|
+
throw new PptxViewerError(
|
|
12679
|
+
"unsupported-environment",
|
|
12680
|
+
"ImageBitmap output is not supported by this browser."
|
|
12681
|
+
);
|
|
12682
|
+
}
|
|
12683
|
+
data = await abortable(createImageBitmap(canvas), options.signal);
|
|
12684
|
+
} else data = await abortable(canvasToBlob(canvas, options.quality), options.signal);
|
|
12685
|
+
this.measure("raster-encoding", encodeStartedAt, slideIndex, measurements);
|
|
12686
|
+
return this.result(data, output, slideIndex, size, measurements);
|
|
12687
|
+
} finally {
|
|
12688
|
+
handle.dispose();
|
|
12689
|
+
target.remove();
|
|
12690
|
+
}
|
|
12691
|
+
} finally {
|
|
12692
|
+
release();
|
|
12693
|
+
}
|
|
12694
|
+
}
|
|
12695
|
+
renderSlides(slideIndexes, options = {}) {
|
|
12696
|
+
return Promise.all(slideIndexes.map((slideIndex) => this.renderDynamic(slideIndex, options)));
|
|
12697
|
+
}
|
|
12698
|
+
destroy() {
|
|
12699
|
+
if (this.destroyed) return;
|
|
12700
|
+
this.destroyed = true;
|
|
12701
|
+
this.queue.destroy();
|
|
12702
|
+
this.viewer.destroy();
|
|
12703
|
+
this.fontManager.destroy();
|
|
12704
|
+
this.root.remove();
|
|
12705
|
+
}
|
|
12706
|
+
renderDynamic(slideIndex, options) {
|
|
12707
|
+
return this.renderSlide(slideIndex, options);
|
|
12708
|
+
}
|
|
12709
|
+
assertAvailable(signal) {
|
|
12710
|
+
throwIfAborted(signal);
|
|
12711
|
+
if (this.destroyed) throw abortedError();
|
|
12712
|
+
}
|
|
12713
|
+
measure(phase, startedAt, slideIndex, measurements) {
|
|
12714
|
+
this.record(performanceMeasurement(phase, startedAt, slideIndex), measurements);
|
|
12715
|
+
}
|
|
12716
|
+
record(measurement, measurements) {
|
|
12717
|
+
measurements.push(measurement);
|
|
12718
|
+
this.report(measurement);
|
|
12719
|
+
}
|
|
12720
|
+
report(measurement) {
|
|
12721
|
+
this.options.onPerformanceMeasurement?.(measurement);
|
|
12722
|
+
}
|
|
12723
|
+
result(data, output, slideIndex, size, measurements) {
|
|
12724
|
+
return {
|
|
12725
|
+
data,
|
|
12726
|
+
height: size.height,
|
|
12727
|
+
measurements,
|
|
12728
|
+
output,
|
|
12729
|
+
slideIndex,
|
|
12730
|
+
width: size.width
|
|
12731
|
+
};
|
|
12732
|
+
}
|
|
12733
|
+
};
|
|
12734
|
+
function createPptxThumbnailRenderer(presentation, options = {}) {
|
|
12735
|
+
return new ThumbnailRenderer(presentation, options);
|
|
12736
|
+
}
|
|
12737
|
+
|
|
12738
|
+
export { OFFICE_FONT_FALLBACKS, PptxViewerError, ReactPptxViewer, createPptxThumbnailRenderer, initWasm, parsePresentation, presentationParsingDefaults, resolvePptxFontFamily, setWasmSource, usePptxModel, usePptxPresentation, usePptxViewer, usePptxViewerThumbnails };
|
|
12288
12739
|
//# sourceMappingURL=index.js.map
|
|
12289
12740
|
//# sourceMappingURL=index.js.map
|