@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/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
- objectUrls = /* @__PURE__ */ new Set();
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
- if (asset.url) return Promise.resolve(asset.url);
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
- for (const url of this.objectUrls) URL.revokeObjectURL(url);
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
- export { OFFICE_FONT_FALLBACKS, PptxViewerError, ReactPptxViewer, initWasm, parsePresentation, presentationParsingDefaults, resolvePptxFontFamily, setWasmSource, usePptxModel, usePptxPresentation, usePptxViewer, usePptxViewerThumbnails };
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