@extend-ai/react-pptx 0.1.2 → 0.2.1
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 +10 -0
- package/README.md +73 -0
- package/dist/index.css +0 -3
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +85 -6
- package/dist/index.js +697 -98
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9242,13 +9242,94 @@ async function renderWmfToDataUrl(buffer, maxWidth, maxHeight, optionsOrScale) {
|
|
|
9242
9242
|
}
|
|
9243
9243
|
}
|
|
9244
9244
|
|
|
9245
|
+
// src/zoom.ts
|
|
9246
|
+
var MIN_VIEWER_ZOOM = 10;
|
|
9247
|
+
var MAX_VIEWER_ZOOM = 400;
|
|
9248
|
+
function normalizeViewerZoomLevel(level) {
|
|
9249
|
+
if (typeof level !== "number") return level;
|
|
9250
|
+
if (Number.isNaN(level)) return 100;
|
|
9251
|
+
return Math.max(MIN_VIEWER_ZOOM, Math.min(MAX_VIEWER_ZOOM, level));
|
|
9252
|
+
}
|
|
9253
|
+
function resolveViewerZoom(level, viewportWidth, viewportHeight, contentWidth, contentHeight) {
|
|
9254
|
+
const normalized = normalizeViewerZoomLevel(level);
|
|
9255
|
+
if (typeof normalized === "number") return normalized;
|
|
9256
|
+
const safeContentWidth = Number.isFinite(contentWidth) && contentWidth > 0 ? contentWidth : 1;
|
|
9257
|
+
const safeContentHeight = Number.isFinite(contentHeight) && contentHeight > 0 ? contentHeight : 1;
|
|
9258
|
+
const fitWidth = (Number.isFinite(viewportWidth) && viewportWidth > 0 ? viewportWidth : safeContentWidth) / safeContentWidth * 100;
|
|
9259
|
+
const fitHeight = (Number.isFinite(viewportHeight) && viewportHeight > 0 ? viewportHeight : safeContentHeight) / safeContentHeight * 100;
|
|
9260
|
+
const resolved = normalized === "fit-width" ? fitWidth : normalized === "fit-page" ? Math.min(fitWidth, fitHeight) : Math.min(fitWidth, 100);
|
|
9261
|
+
return Math.max(MIN_VIEWER_ZOOM, Math.min(MAX_VIEWER_ZOOM, resolved));
|
|
9262
|
+
}
|
|
9263
|
+
|
|
9245
9264
|
// src/normalized-viewer.ts
|
|
9246
9265
|
var EMU_PER_CSS_PIXEL = 9525;
|
|
9247
9266
|
var DEFAULT_TEXT_HORIZONTAL_INSET_EMU = 91440;
|
|
9248
9267
|
var DEFAULT_TEXT_VERTICAL_INSET_EMU = 45720;
|
|
9249
9268
|
var POWERPOINT_SINGLE_LINE_HEIGHT = 1.15;
|
|
9269
|
+
var PresentationAssetCache = class {
|
|
9270
|
+
references = 0;
|
|
9271
|
+
objectUrls = /* @__PURE__ */ new Set();
|
|
9272
|
+
assetUrls = /* @__PURE__ */ new Map();
|
|
9273
|
+
metafileUrls = /* @__PURE__ */ new Map();
|
|
9274
|
+
acquire() {
|
|
9275
|
+
this.references += 1;
|
|
9276
|
+
return this;
|
|
9277
|
+
}
|
|
9278
|
+
metafileUrl(assetId, contentType, data) {
|
|
9279
|
+
const cached = this.metafileUrls.get(assetId);
|
|
9280
|
+
if (cached) return cached;
|
|
9281
|
+
const convert = contentType.includes("wmf") ? renderWmfToDataUrl : renderEmfToDataUrl;
|
|
9282
|
+
const ownedBytes = data.slice();
|
|
9283
|
+
const pending = convert(ownedBytes.buffer, 2048, 2048, {
|
|
9284
|
+
dpiScale: 2,
|
|
9285
|
+
maxCanvasDimension: 4096,
|
|
9286
|
+
maxRecords: 2e5,
|
|
9287
|
+
fontFamilyMap: METAFILE_FONT_MAP
|
|
9288
|
+
}).then((url) => url || void 0);
|
|
9289
|
+
this.metafileUrls.set(assetId, pending);
|
|
9290
|
+
return pending;
|
|
9291
|
+
}
|
|
9292
|
+
assetUrl(asset) {
|
|
9293
|
+
if (asset.url) return Promise.resolve(asset.url);
|
|
9294
|
+
if (!asset.data) return Promise.resolve(void 0);
|
|
9295
|
+
const contentType = asset.contentType.toLowerCase();
|
|
9296
|
+
if (contentType.includes("emf") || contentType.includes("wmf")) {
|
|
9297
|
+
return this.metafileUrl(asset.id, contentType, asset.data);
|
|
9298
|
+
}
|
|
9299
|
+
const cached = this.assetUrls.get(asset.id);
|
|
9300
|
+
if (cached) return Promise.resolve(cached);
|
|
9301
|
+
const ownedBytes = asset.data.slice();
|
|
9302
|
+
const url = URL.createObjectURL(
|
|
9303
|
+
new Blob([ownedBytes.buffer], { type: asset.contentType })
|
|
9304
|
+
);
|
|
9305
|
+
this.objectUrls.add(url);
|
|
9306
|
+
this.assetUrls.set(asset.id, url);
|
|
9307
|
+
return Promise.resolve(url);
|
|
9308
|
+
}
|
|
9309
|
+
release() {
|
|
9310
|
+
this.references = Math.max(0, this.references - 1);
|
|
9311
|
+
if (this.references > 0) return;
|
|
9312
|
+
for (const url of this.objectUrls) URL.revokeObjectURL(url);
|
|
9313
|
+
this.objectUrls.clear();
|
|
9314
|
+
this.assetUrls.clear();
|
|
9315
|
+
this.metafileUrls.clear();
|
|
9316
|
+
}
|
|
9317
|
+
};
|
|
9318
|
+
var presentationAssetCaches = /* @__PURE__ */ new WeakMap();
|
|
9319
|
+
function acquirePresentationAssetCache(presentation) {
|
|
9320
|
+
let cache = presentationAssetCaches.get(presentation);
|
|
9321
|
+
if (!cache) {
|
|
9322
|
+
cache = new PresentationAssetCache();
|
|
9323
|
+
presentationAssetCaches.set(presentation, cache);
|
|
9324
|
+
}
|
|
9325
|
+
return cache.acquire();
|
|
9326
|
+
}
|
|
9250
9327
|
var LIST_ITEM_GAP = 24;
|
|
9251
9328
|
var FALLBACK_VIEWPORT_RECT = { width: 800, height: 600 };
|
|
9329
|
+
function pixelValue(value) {
|
|
9330
|
+
const parsed = Number.parseFloat(value);
|
|
9331
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
9332
|
+
}
|
|
9252
9333
|
function scaleListAnchorOffset(offset, previousSlideHeight, nextSlideHeight) {
|
|
9253
9334
|
if (previousSlideHeight <= 0) return offset;
|
|
9254
9335
|
if (offset <= previousSlideHeight) return offset * (nextSlideHeight / previousSlideHeight);
|
|
@@ -9788,16 +9869,17 @@ var NormalizedPresentationViewer = class {
|
|
|
9788
9869
|
this.container = container;
|
|
9789
9870
|
this.presentation = presentation;
|
|
9790
9871
|
this.callbacks = callbacks;
|
|
9872
|
+
this.assetCache = acquirePresentationAssetCache(presentation);
|
|
9791
9873
|
}
|
|
9792
9874
|
container;
|
|
9793
9875
|
presentation;
|
|
9794
9876
|
callbacks;
|
|
9795
9877
|
current = 0;
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
|
|
9878
|
+
zoomLevel = "automatic";
|
|
9879
|
+
resolvedZoom = 100;
|
|
9880
|
+
assetCache;
|
|
9881
|
+
resizeObserver;
|
|
9882
|
+
activeViewport;
|
|
9801
9883
|
listCleanup;
|
|
9802
9884
|
highlight;
|
|
9803
9885
|
activeMountResources;
|
|
@@ -9809,6 +9891,7 @@ var NormalizedPresentationViewer = class {
|
|
|
9809
9891
|
renderGeneration = 0;
|
|
9810
9892
|
navigationGeneration = 0;
|
|
9811
9893
|
lastNotifiedSlide;
|
|
9894
|
+
lastZoomNotification;
|
|
9812
9895
|
listState;
|
|
9813
9896
|
renderState;
|
|
9814
9897
|
get slideCount() {
|
|
@@ -9817,11 +9900,17 @@ var NormalizedPresentationViewer = class {
|
|
|
9817
9900
|
get currentSlideIndex() {
|
|
9818
9901
|
return this.current;
|
|
9819
9902
|
}
|
|
9903
|
+
get zoom() {
|
|
9904
|
+
return this.zoomLevel;
|
|
9905
|
+
}
|
|
9820
9906
|
get zoomPercent() {
|
|
9821
|
-
return this.
|
|
9907
|
+
return this.resolvedZoom;
|
|
9908
|
+
}
|
|
9909
|
+
get resolvedZoomPercent() {
|
|
9910
|
+
return this.resolvedZoom;
|
|
9822
9911
|
}
|
|
9823
9912
|
get fitMode() {
|
|
9824
|
-
return this.
|
|
9913
|
+
return typeof this.zoomLevel === "number" ? "none" : "contain";
|
|
9825
9914
|
}
|
|
9826
9915
|
get naturalSlideWidth() {
|
|
9827
9916
|
const width = this.presentation.size.widthEmu / EMU_PER_CSS_PIXEL;
|
|
@@ -9831,9 +9920,57 @@ var NormalizedPresentationViewer = class {
|
|
|
9831
9920
|
const height = this.presentation.size.heightEmu / EMU_PER_CSS_PIXEL;
|
|
9832
9921
|
return Number.isFinite(height) && height > 0 ? height : 1;
|
|
9833
9922
|
}
|
|
9834
|
-
|
|
9835
|
-
const
|
|
9836
|
-
|
|
9923
|
+
viewportSize(viewport) {
|
|
9924
|
+
const rect = viewport.getBoundingClientRect();
|
|
9925
|
+
let width = viewport.clientWidth || rect.width;
|
|
9926
|
+
let height = viewport.clientHeight || rect.height;
|
|
9927
|
+
if (typeof getComputedStyle === "function") {
|
|
9928
|
+
const viewportStyle = getComputedStyle(viewport);
|
|
9929
|
+
width -= pixelValue(viewportStyle.paddingLeft) + pixelValue(viewportStyle.paddingRight);
|
|
9930
|
+
height -= pixelValue(viewportStyle.paddingTop) + pixelValue(viewportStyle.paddingBottom);
|
|
9931
|
+
if (viewport !== this.container) {
|
|
9932
|
+
const containerStyle = getComputedStyle(this.container);
|
|
9933
|
+
width -= pixelValue(containerStyle.paddingLeft) + pixelValue(containerStyle.paddingRight);
|
|
9934
|
+
height -= pixelValue(containerStyle.paddingTop) + pixelValue(containerStyle.paddingBottom);
|
|
9935
|
+
}
|
|
9936
|
+
}
|
|
9937
|
+
return { width: Math.max(0, width), height: Math.max(0, height) };
|
|
9938
|
+
}
|
|
9939
|
+
resolveZoom(level, viewport = this.activeViewport ?? this.container) {
|
|
9940
|
+
const size = this.viewportSize(viewport);
|
|
9941
|
+
return resolveViewerZoom(
|
|
9942
|
+
level,
|
|
9943
|
+
size.width,
|
|
9944
|
+
size.height,
|
|
9945
|
+
this.naturalSlideWidth,
|
|
9946
|
+
this.naturalSlideHeight
|
|
9947
|
+
);
|
|
9948
|
+
}
|
|
9949
|
+
scaleForViewport(viewport) {
|
|
9950
|
+
return this.resolveZoom(this.zoomLevel, viewport) / 100;
|
|
9951
|
+
}
|
|
9952
|
+
publishZoom(resolvedZoom) {
|
|
9953
|
+
const normalized = Math.max(MIN_VIEWER_ZOOM, Math.min(MAX_VIEWER_ZOOM, resolvedZoom));
|
|
9954
|
+
const levelChanged = this.lastZoomNotification?.level !== this.zoomLevel;
|
|
9955
|
+
const resolutionChanged = this.lastZoomNotification === void 0 || Math.abs(this.lastZoomNotification.resolvedZoom - normalized) > 0.01;
|
|
9956
|
+
this.resolvedZoom = normalized;
|
|
9957
|
+
if (!levelChanged && !resolutionChanged) return;
|
|
9958
|
+
const state = { level: this.zoomLevel, resolvedZoom: normalized };
|
|
9959
|
+
this.lastZoomNotification = state;
|
|
9960
|
+
this.callbacks.onZoomChange?.(state);
|
|
9961
|
+
}
|
|
9962
|
+
observeViewport(viewport) {
|
|
9963
|
+
if (this.activeViewport === viewport && this.resizeObserver) return;
|
|
9964
|
+
this.resizeObserver?.disconnect();
|
|
9965
|
+
this.resizeObserver = void 0;
|
|
9966
|
+
this.activeViewport = viewport;
|
|
9967
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
9968
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
9969
|
+
if (this.destroyed || typeof this.zoomLevel === "number") return;
|
|
9970
|
+
void this.updateRenderedScale();
|
|
9971
|
+
});
|
|
9972
|
+
this.resizeObserver.observe(viewport);
|
|
9973
|
+
if (viewport !== this.container) this.resizeObserver.observe(this.container);
|
|
9837
9974
|
}
|
|
9838
9975
|
isRenderActive(generation) {
|
|
9839
9976
|
return !this.destroyed && generation === this.renderGeneration;
|
|
@@ -9844,6 +9981,9 @@ var NormalizedPresentationViewer = class {
|
|
|
9844
9981
|
this.navigationGeneration += 1;
|
|
9845
9982
|
this.listCleanup?.();
|
|
9846
9983
|
this.listCleanup = void 0;
|
|
9984
|
+
this.resizeObserver?.disconnect();
|
|
9985
|
+
this.resizeObserver = void 0;
|
|
9986
|
+
this.activeViewport = void 0;
|
|
9847
9987
|
this.listState = void 0;
|
|
9848
9988
|
this.clearSearchHighlights();
|
|
9849
9989
|
return generation;
|
|
@@ -9853,36 +9993,8 @@ var NormalizedPresentationViewer = class {
|
|
|
9853
9993
|
this.lastNotifiedSlide = index;
|
|
9854
9994
|
this.callbacks.onSlideChange?.(index);
|
|
9855
9995
|
}
|
|
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
9996
|
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);
|
|
9997
|
+
return this.assetCache.assetUrl(asset);
|
|
9886
9998
|
}
|
|
9887
9999
|
applyAsset(assetId, apply, nodeId) {
|
|
9888
10000
|
const asset = this.presentation.assets[assetId];
|
|
@@ -10429,7 +10541,7 @@ var NormalizedPresentationViewer = class {
|
|
|
10429
10541
|
} finally {
|
|
10430
10542
|
this.activeMountResources = previousResources;
|
|
10431
10543
|
}
|
|
10432
|
-
const effectiveScale = scale ?? this.scaleForViewport(target
|
|
10544
|
+
const effectiveScale = scale ?? this.scaleForViewport(target);
|
|
10433
10545
|
const wrapper = document.createElement("div");
|
|
10434
10546
|
wrapper.dataset.rpvSlideWrapper = String(index);
|
|
10435
10547
|
wrapper.style.position = "relative";
|
|
@@ -10521,6 +10633,7 @@ var NormalizedPresentationViewer = class {
|
|
|
10521
10633
|
this.renderState = { mode: "single" };
|
|
10522
10634
|
const generation = this.beginRender();
|
|
10523
10635
|
if (generation === void 0) return;
|
|
10636
|
+
this.observeViewport(this.container);
|
|
10524
10637
|
this.current = Math.max(0, Math.min(this.slideCount - 1, index));
|
|
10525
10638
|
this.disposeContainerSlides();
|
|
10526
10639
|
this.container.replaceChildren();
|
|
@@ -10530,6 +10643,7 @@ var NormalizedPresentationViewer = class {
|
|
|
10530
10643
|
if (!this.isRenderActive(generation) || this.handlesByTarget.get(this.container) !== handle) {
|
|
10531
10644
|
return;
|
|
10532
10645
|
}
|
|
10646
|
+
this.publishZoom(this.scaleForViewport(this.container) * 100);
|
|
10533
10647
|
this.notifySlideChange(this.current);
|
|
10534
10648
|
}
|
|
10535
10649
|
createListPlaceholder(index, item, generation, scale) {
|
|
@@ -10590,7 +10704,8 @@ var NormalizedPresentationViewer = class {
|
|
|
10590
10704
|
Math.min(this.slideCount - 1, options.initialSlideIndex ?? this.current)
|
|
10591
10705
|
);
|
|
10592
10706
|
const scroller = options.scrollElement ?? this.container;
|
|
10593
|
-
|
|
10707
|
+
this.observeViewport(scroller);
|
|
10708
|
+
let effectiveScale = this.scaleForViewport(scroller);
|
|
10594
10709
|
let slideHeight = this.naturalSlideHeight * effectiveScale;
|
|
10595
10710
|
let itemStride = slideHeight + LIST_ITEM_GAP;
|
|
10596
10711
|
if (!windowingEnabled) {
|
|
@@ -10605,6 +10720,8 @@ var NormalizedPresentationViewer = class {
|
|
|
10605
10720
|
const state2 = { generation, options: normalizedOptions, placeholders: placeholders2 };
|
|
10606
10721
|
state2.updateScale = () => {
|
|
10607
10722
|
if (!this.isRenderActive(generation) || this.listState !== state2) return;
|
|
10723
|
+
const previousScale = effectiveScale;
|
|
10724
|
+
const previousScrollLeft = scroller.scrollLeft;
|
|
10608
10725
|
const previousStride = itemStride;
|
|
10609
10726
|
const previousSlideHeight = slideHeight;
|
|
10610
10727
|
const listStart = scroller === this.container ? 0 : this.container.getBoundingClientRect().top - scroller.getBoundingClientRect().top + scroller.scrollTop;
|
|
@@ -10614,13 +10731,17 @@ var NormalizedPresentationViewer = class {
|
|
|
10614
10731
|
Math.min(this.slideCount - 1, Math.floor(listOffset / previousStride))
|
|
10615
10732
|
);
|
|
10616
10733
|
const anchorOffset = listOffset - anchorIndex * previousStride;
|
|
10617
|
-
effectiveScale = this.scaleForViewport(scroller
|
|
10734
|
+
effectiveScale = this.scaleForViewport(scroller);
|
|
10618
10735
|
slideHeight = this.naturalSlideHeight * effectiveScale;
|
|
10619
10736
|
itemStride = slideHeight + LIST_ITEM_GAP;
|
|
10620
10737
|
placeholders2.forEach((placeholder) => {
|
|
10621
10738
|
placeholder.item.style.minHeight = `${slideHeight}px`;
|
|
10622
10739
|
placeholder.setScale(effectiveScale);
|
|
10623
10740
|
});
|
|
10741
|
+
if (previousScale > 0 && previousScrollLeft > 0) {
|
|
10742
|
+
scroller.scrollLeft = previousScrollLeft * (effectiveScale / previousScale);
|
|
10743
|
+
}
|
|
10744
|
+
this.publishZoom(effectiveScale * 100);
|
|
10624
10745
|
if (listOffset < 0) return;
|
|
10625
10746
|
const nextAnchorOffset = scaleListAnchorOffset(
|
|
10626
10747
|
anchorOffset,
|
|
@@ -10646,6 +10767,7 @@ var NormalizedPresentationViewer = class {
|
|
|
10646
10767
|
initialItem.scrollIntoView?.({ behavior: "instant", block: "start" });
|
|
10647
10768
|
}
|
|
10648
10769
|
this.current = initialIndex;
|
|
10770
|
+
this.publishZoom(effectiveScale * 100);
|
|
10649
10771
|
this.notifySlideChange(initialIndex);
|
|
10650
10772
|
return;
|
|
10651
10773
|
}
|
|
@@ -10789,6 +10911,8 @@ var NormalizedPresentationViewer = class {
|
|
|
10789
10911
|
};
|
|
10790
10912
|
state.updateScale = () => {
|
|
10791
10913
|
if (!isActive()) return;
|
|
10914
|
+
const previousScale = effectiveScale;
|
|
10915
|
+
const previousScrollLeft = scroller.scrollLeft;
|
|
10792
10916
|
const previousStride = itemStride;
|
|
10793
10917
|
const previousSlideHeight = slideHeight;
|
|
10794
10918
|
const anchorIndex = this.current;
|
|
@@ -10798,7 +10922,7 @@ var NormalizedPresentationViewer = class {
|
|
|
10798
10922
|
const previousScrollTop = Math.abs(observedIndex - anchorIndex) > 1 && intendedIndex !== void 0 && Math.abs(intendedIndex - anchorIndex) <= 1 ? lastProgrammaticTop : observedScrollTop;
|
|
10799
10923
|
const previousOffset = previousScrollTop - scrollMargin;
|
|
10800
10924
|
const anchorOffset = previousOffset - anchorIndex * previousStride;
|
|
10801
|
-
effectiveScale = this.scaleForViewport(scroller
|
|
10925
|
+
effectiveScale = this.scaleForViewport(scroller);
|
|
10802
10926
|
slideHeight = this.naturalSlideHeight * effectiveScale;
|
|
10803
10927
|
itemStride = slideHeight + LIST_ITEM_GAP;
|
|
10804
10928
|
sizer.style.height = `${this.slideCount * itemStride - LIST_ITEM_GAP}px`;
|
|
@@ -10807,6 +10931,9 @@ var NormalizedPresentationViewer = class {
|
|
|
10807
10931
|
placeholder.item.style.transform = `translateY(${index * itemStride}px)`;
|
|
10808
10932
|
placeholder.setScale(effectiveScale);
|
|
10809
10933
|
});
|
|
10934
|
+
if (previousScale > 0 && previousScrollLeft > 0) {
|
|
10935
|
+
scroller.scrollLeft = previousScrollLeft * (effectiveScale / previousScale);
|
|
10936
|
+
}
|
|
10810
10937
|
const nextAnchorOffset = scaleListAnchorOffset(
|
|
10811
10938
|
anchorOffset,
|
|
10812
10939
|
previousSlideHeight,
|
|
@@ -10819,6 +10946,7 @@ var NormalizedPresentationViewer = class {
|
|
|
10819
10946
|
writeScrollTop(nextScrollTop);
|
|
10820
10947
|
virtualizer.setOptions({ ...virtualizer.options, overscan });
|
|
10821
10948
|
virtualizer.measure();
|
|
10949
|
+
this.publishZoom(effectiveScale * 100);
|
|
10822
10950
|
};
|
|
10823
10951
|
const teardown = virtualizer._didMount();
|
|
10824
10952
|
this.listCleanup = teardown;
|
|
@@ -10837,6 +10965,7 @@ var NormalizedPresentationViewer = class {
|
|
|
10837
10965
|
this.container.scrollLeft = 0;
|
|
10838
10966
|
}
|
|
10839
10967
|
this.current = initialIndex;
|
|
10968
|
+
this.publishZoom(effectiveScale * 100);
|
|
10840
10969
|
this.notifySlideChange(initialIndex);
|
|
10841
10970
|
}
|
|
10842
10971
|
async goToSlide(index, scrollOptions) {
|
|
@@ -10873,10 +11002,7 @@ var NormalizedPresentationViewer = class {
|
|
|
10873
11002
|
if (!changed && this.container.querySelector(`[data-rpv-slide-index="${next}"]`)) return;
|
|
10874
11003
|
await this.renderSlide(next);
|
|
10875
11004
|
}
|
|
10876
|
-
async
|
|
10877
|
-
const next = Math.max(10, Math.min(400, percent));
|
|
10878
|
-
if (this.zoom === next) return;
|
|
10879
|
-
this.zoom = next;
|
|
11005
|
+
async updateRenderedScale() {
|
|
10880
11006
|
if (this.renderState?.mode === "continuous") {
|
|
10881
11007
|
const state = this.listState;
|
|
10882
11008
|
if (state && this.isRenderActive(state.generation) && state.updateScale) {
|
|
@@ -10886,25 +11012,34 @@ var NormalizedPresentationViewer = class {
|
|
|
10886
11012
|
}
|
|
10887
11013
|
} else if (this.renderState) {
|
|
10888
11014
|
const handle = this.handlesByTarget.get(this.container);
|
|
10889
|
-
if (handle)
|
|
10890
|
-
|
|
11015
|
+
if (handle) {
|
|
11016
|
+
const viewport = this.activeViewport ?? this.container;
|
|
11017
|
+
const previousScale = this.resolvedZoom / 100;
|
|
11018
|
+
const previousScrollTop = viewport.scrollTop;
|
|
11019
|
+
const previousScrollLeft = viewport.scrollLeft;
|
|
11020
|
+
const nextScale = this.scaleForViewport(viewport);
|
|
11021
|
+
handle.setScale(nextScale);
|
|
11022
|
+
if (previousScale > 0) {
|
|
11023
|
+
viewport.scrollTop = previousScrollTop * (nextScale / previousScale);
|
|
11024
|
+
viewport.scrollLeft = previousScrollLeft * (nextScale / previousScale);
|
|
11025
|
+
}
|
|
11026
|
+
this.publishZoom(nextScale * 100);
|
|
11027
|
+
} else await this.renderSlide(this.current);
|
|
11028
|
+
} else {
|
|
11029
|
+
this.publishZoom(this.resolveZoom(this.zoomLevel));
|
|
10891
11030
|
}
|
|
10892
11031
|
}
|
|
10893
|
-
async
|
|
10894
|
-
|
|
10895
|
-
this.
|
|
10896
|
-
|
|
10897
|
-
|
|
10898
|
-
if (state && this.isRenderActive(state.generation) && state.updateScale) {
|
|
10899
|
-
state.updateScale();
|
|
10900
|
-
} else {
|
|
10901
|
-
await this.renderList({ ...this.renderState.options, initialSlideIndex: this.current });
|
|
10902
|
-
}
|
|
10903
|
-
} else if (this.renderState) {
|
|
10904
|
-
const handle = this.handlesByTarget.get(this.container);
|
|
10905
|
-
if (handle) handle.setScale(this.scaleForViewport(this.container.clientWidth));
|
|
10906
|
-
else await this.renderSlide(this.current);
|
|
11032
|
+
async setZoom(level) {
|
|
11033
|
+
const next = normalizeViewerZoomLevel(level);
|
|
11034
|
+
if (this.zoomLevel === next) {
|
|
11035
|
+
if (typeof next !== "number") await this.updateRenderedScale();
|
|
11036
|
+
return;
|
|
10907
11037
|
}
|
|
11038
|
+
this.zoomLevel = next;
|
|
11039
|
+
await this.updateRenderedScale();
|
|
11040
|
+
}
|
|
11041
|
+
async setFitMode(mode) {
|
|
11042
|
+
await this.setZoom(mode === "contain" ? "automatic" : 100);
|
|
10908
11043
|
}
|
|
10909
11044
|
searchText(query, options) {
|
|
10910
11045
|
return searchDocument(this.presentation, query, options);
|
|
@@ -10980,12 +11115,12 @@ var NormalizedPresentationViewer = class {
|
|
|
10980
11115
|
this.navigationGeneration += 1;
|
|
10981
11116
|
this.listCleanup?.();
|
|
10982
11117
|
this.listCleanup = void 0;
|
|
11118
|
+
this.resizeObserver?.disconnect();
|
|
11119
|
+
this.resizeObserver = void 0;
|
|
11120
|
+
this.activeViewport = void 0;
|
|
10983
11121
|
this.listState = void 0;
|
|
10984
11122
|
this.clearSearchHighlights();
|
|
10985
|
-
|
|
10986
|
-
this.objectUrls.clear();
|
|
10987
|
-
this.assetUrls.clear();
|
|
10988
|
-
this.metafileUrls.clear();
|
|
11123
|
+
this.assetCache.release();
|
|
10989
11124
|
this.activeMountResources = void 0;
|
|
10990
11125
|
for (const handle of [...this.mountedHandles]) handle.dispose();
|
|
10991
11126
|
this.handlesByTarget.clear();
|
|
@@ -11145,6 +11280,18 @@ async function parseInWorker(bytes, signal, wasmSource) {
|
|
|
11145
11280
|
});
|
|
11146
11281
|
}
|
|
11147
11282
|
|
|
11283
|
+
// src/performance.ts
|
|
11284
|
+
function performanceNow() {
|
|
11285
|
+
return globalThis.performance?.now?.() ?? Date.now();
|
|
11286
|
+
}
|
|
11287
|
+
function performanceMeasurement(phase, startedAt, slideIndex) {
|
|
11288
|
+
return {
|
|
11289
|
+
durationMs: Math.max(0, performanceNow() - startedAt),
|
|
11290
|
+
phase,
|
|
11291
|
+
...slideIndex === void 0 ? {} : { slideIndex }
|
|
11292
|
+
};
|
|
11293
|
+
}
|
|
11294
|
+
|
|
11148
11295
|
// src/parse.ts
|
|
11149
11296
|
var DEFAULT_MAX_INPUT_BYTES = 100 * 1024 * 1024;
|
|
11150
11297
|
function isParsedPresentation(source) {
|
|
@@ -11229,14 +11376,20 @@ async function parsePresentation(source, options = {}) {
|
|
|
11229
11376
|
warnings: source.warnings
|
|
11230
11377
|
};
|
|
11231
11378
|
}
|
|
11379
|
+
const downloadStartedAt = performanceNow();
|
|
11232
11380
|
const buffer = await readSource(source, options);
|
|
11381
|
+
if (typeof source === "string" || source instanceof URL) {
|
|
11382
|
+
options.onPerformanceMeasurement?.(performanceMeasurement("download", downloadStartedAt));
|
|
11383
|
+
}
|
|
11233
11384
|
const format = detectFormat(buffer, options.formatHint);
|
|
11234
11385
|
const bytes = new Uint8Array(buffer);
|
|
11235
11386
|
throwIfAborted(options.signal);
|
|
11387
|
+
const parsingStartedAt = performanceNow();
|
|
11236
11388
|
const nativeDocument = await parseWithWasm(bytes, {
|
|
11237
11389
|
formatHint: format,
|
|
11238
11390
|
...options.signal ? { signal: options.signal } : {}
|
|
11239
11391
|
});
|
|
11392
|
+
options.onPerformanceMeasurement?.(performanceMeasurement("wasm-parsing", parsingStartedAt));
|
|
11240
11393
|
throwIfAborted(options.signal);
|
|
11241
11394
|
if (!nativeDocument) {
|
|
11242
11395
|
throw new PptxViewerError(
|
|
@@ -11309,11 +11462,15 @@ function requestInitKey(value) {
|
|
|
11309
11462
|
return String(value);
|
|
11310
11463
|
}
|
|
11311
11464
|
}
|
|
11312
|
-
async function createAdapter(target, parsed, props, getCurrentProps, onCurrentSlideChange, reportWarning, isCurrent) {
|
|
11465
|
+
async function createAdapter(target, parsed, props, getCurrentProps, onCurrentSlideChange, onZoomChange, reportWarning, isCurrent) {
|
|
11313
11466
|
const fontManager = new PptxFontManager(props.fonts, (warning) => {
|
|
11314
11467
|
if (isCurrent()) reportWarning(warning);
|
|
11315
11468
|
});
|
|
11469
|
+
const fontStartedAt = performanceNow();
|
|
11316
11470
|
await fontManager.prepare(parsed.document);
|
|
11471
|
+
getCurrentProps().onPerformanceMeasurement?.(
|
|
11472
|
+
performanceMeasurement("embedded-font-preparation", fontStartedAt)
|
|
11473
|
+
);
|
|
11317
11474
|
if (!isCurrent()) {
|
|
11318
11475
|
fontManager.destroy();
|
|
11319
11476
|
throw new Error("Viewer adapter creation was superseded.");
|
|
@@ -11324,6 +11481,9 @@ async function createAdapter(target, parsed, props, getCurrentProps, onCurrentSl
|
|
|
11324
11481
|
onSlideChange: (index) => {
|
|
11325
11482
|
if (active()) onCurrentSlideChange(index);
|
|
11326
11483
|
},
|
|
11484
|
+
onZoomChange: (state) => {
|
|
11485
|
+
if (active()) onZoomChange(state);
|
|
11486
|
+
},
|
|
11327
11487
|
onSlideRendered: (index, element) => {
|
|
11328
11488
|
if (!active()) return;
|
|
11329
11489
|
fontManager.applyTo(element, index);
|
|
@@ -11346,6 +11506,7 @@ async function createAdapter(target, parsed, props, getCurrentProps, onCurrentSl
|
|
|
11346
11506
|
const normalized = new NormalizedPresentationViewer(target, parsed.document, callbacks);
|
|
11347
11507
|
return {
|
|
11348
11508
|
async render(mode, slideIndex) {
|
|
11509
|
+
const startedAt = performanceNow();
|
|
11349
11510
|
if (mode === "continuous") {
|
|
11350
11511
|
const virtualization = typeof props.virtualization === "object" ? props.virtualization : {};
|
|
11351
11512
|
await normalized.renderList({
|
|
@@ -11359,10 +11520,13 @@ async function createAdapter(target, parsed, props, getCurrentProps, onCurrentSl
|
|
|
11359
11520
|
} else {
|
|
11360
11521
|
await normalized.renderSlide(slideIndex);
|
|
11361
11522
|
}
|
|
11523
|
+
getCurrentProps().onPerformanceMeasurement?.(
|
|
11524
|
+
performanceMeasurement("slide-dom-svg-rendering", startedAt, slideIndex)
|
|
11525
|
+
);
|
|
11362
11526
|
},
|
|
11363
11527
|
goToSlide: (index, options) => normalized.goToSlide(index, options),
|
|
11364
|
-
setZoom: (
|
|
11365
|
-
|
|
11528
|
+
setZoom: (level) => normalized.setZoom(level),
|
|
11529
|
+
resolveZoom: (level) => normalized.resolveZoom(level),
|
|
11366
11530
|
search: (query, options) => normalized.searchText(query, options),
|
|
11367
11531
|
highlight: (result, options) => normalized.highlightSearchResult(result, options),
|
|
11368
11532
|
clearHighlights: () => normalized.clearSearchHighlights(),
|
|
@@ -11430,7 +11594,7 @@ var ReactPptxViewer = forwardRef(
|
|
|
11430
11594
|
slideIndex: controlledSlide,
|
|
11431
11595
|
initialSlide = 0,
|
|
11432
11596
|
zoom: controlledZoom,
|
|
11433
|
-
defaultZoom
|
|
11597
|
+
defaultZoom,
|
|
11434
11598
|
fitMode = "contain",
|
|
11435
11599
|
showToolbar = false,
|
|
11436
11600
|
showThumbnails = false,
|
|
@@ -11461,6 +11625,7 @@ var ReactPptxViewer = forwardRef(
|
|
|
11461
11625
|
onError,
|
|
11462
11626
|
onWarning,
|
|
11463
11627
|
onSlideChange: _onSlideChange,
|
|
11628
|
+
onZoomChange: _onZoomChange,
|
|
11464
11629
|
onSlideRendered: _onSlideRendered,
|
|
11465
11630
|
onSlideUnmounted: _onSlideUnmounted,
|
|
11466
11631
|
onSearchResults,
|
|
@@ -11479,7 +11644,12 @@ var ReactPptxViewer = forwardRef(
|
|
|
11479
11644
|
const [error, setError] = useState(null);
|
|
11480
11645
|
const [loading, setLoading] = useState(true);
|
|
11481
11646
|
const [internalSlide, setInternalSlide] = useState(initialSlide);
|
|
11482
|
-
const [internalZoom, setInternalZoom] = useState(
|
|
11647
|
+
const [internalZoom, setInternalZoom] = useState(
|
|
11648
|
+
() => normalizeViewerZoomLevel(defaultZoom ?? (fitMode === "contain" ? "automatic" : 100))
|
|
11649
|
+
);
|
|
11650
|
+
const [resolvedZoom, setResolvedZoom] = useState(
|
|
11651
|
+
() => typeof defaultZoom === "number" ? normalizeViewerZoomLevel(defaultZoom) : 100
|
|
11652
|
+
);
|
|
11483
11653
|
const [results, setResults] = useState([]);
|
|
11484
11654
|
const [runtimeWarnings, setRuntimeWarnings] = useState([]);
|
|
11485
11655
|
const virtualizationOptions = typeof virtualization === "object" ? virtualization : {};
|
|
@@ -11493,14 +11663,33 @@ var ReactPptxViewer = forwardRef(
|
|
|
11493
11663
|
const parseFetchInit = parseOptions?.fetchInit;
|
|
11494
11664
|
const parseFetchInitKey = requestInitKey(parseFetchInit);
|
|
11495
11665
|
const slide = clampSlide(controlledSlide ?? internalSlide, parsed?.document.slides.length ?? 0);
|
|
11496
|
-
const zoom = controlledZoom ?? internalZoom;
|
|
11666
|
+
const zoom = normalizeViewerZoomLevel(controlledZoom ?? internalZoom);
|
|
11497
11667
|
const latestSlideRef = useRef(slide);
|
|
11498
11668
|
const latestZoomRef = useRef(zoom);
|
|
11499
|
-
const
|
|
11669
|
+
const latestResolvedZoomRef = useRef(resolvedZoom);
|
|
11670
|
+
const latestControlledZoomRef = useRef(controlledZoom);
|
|
11671
|
+
const lastPublishedZoomRef = useRef(void 0);
|
|
11500
11672
|
const lastViewerSlideRef = useRef(void 0);
|
|
11501
11673
|
latestSlideRef.current = slide;
|
|
11502
11674
|
latestZoomRef.current = zoom;
|
|
11503
|
-
|
|
11675
|
+
latestResolvedZoomRef.current = resolvedZoom;
|
|
11676
|
+
latestControlledZoomRef.current = controlledZoom;
|
|
11677
|
+
const notifyZoomChange = useCallback((state) => {
|
|
11678
|
+
const previous = lastPublishedZoomRef.current;
|
|
11679
|
+
if (previous?.level === state.level && Math.abs(previous.resolvedZoom - state.resolvedZoom) <= 0.01) {
|
|
11680
|
+
return;
|
|
11681
|
+
}
|
|
11682
|
+
lastPublishedZoomRef.current = state;
|
|
11683
|
+
latestPropsRef.current.onZoomChange?.(state);
|
|
11684
|
+
}, []);
|
|
11685
|
+
const publishZoomChange = useCallback(
|
|
11686
|
+
(state) => {
|
|
11687
|
+
setResolvedZoom(state.resolvedZoom);
|
|
11688
|
+
latestResolvedZoomRef.current = state.resolvedZoom;
|
|
11689
|
+
notifyZoomChange(state);
|
|
11690
|
+
},
|
|
11691
|
+
[notifyZoomChange]
|
|
11692
|
+
);
|
|
11504
11693
|
const thumbnailCount = showThumbnails ? parsed?.document.slides.length ?? 0 : 0;
|
|
11505
11694
|
const getThumbnailKey = useCallback(
|
|
11506
11695
|
(index) => parsed?.document.slides[index]?.id ?? index,
|
|
@@ -11541,13 +11730,28 @@ var ReactPptxViewer = forwardRef(
|
|
|
11541
11730
|
block: "center"
|
|
11542
11731
|
});
|
|
11543
11732
|
},
|
|
11544
|
-
async setZoom(
|
|
11545
|
-
const next =
|
|
11733
|
+
async setZoom(level) {
|
|
11734
|
+
const next = normalizeViewerZoomLevel(level);
|
|
11735
|
+
const adapter = adapterRef.current;
|
|
11736
|
+
if (latestControlledZoomRef.current !== void 0) {
|
|
11737
|
+
notifyZoomChange({
|
|
11738
|
+
level: next,
|
|
11739
|
+
resolvedZoom: adapter?.resolveZoom(next) ?? (typeof next === "number" ? next : latestResolvedZoomRef.current)
|
|
11740
|
+
});
|
|
11741
|
+
return;
|
|
11742
|
+
}
|
|
11546
11743
|
setInternalZoom(next);
|
|
11547
|
-
|
|
11744
|
+
latestZoomRef.current = next;
|
|
11745
|
+
if (adapter) await adapter.setZoom(next);
|
|
11746
|
+
else {
|
|
11747
|
+
publishZoomChange({
|
|
11748
|
+
level: next,
|
|
11749
|
+
resolvedZoom: typeof next === "number" ? next : latestResolvedZoomRef.current
|
|
11750
|
+
});
|
|
11751
|
+
}
|
|
11548
11752
|
},
|
|
11549
11753
|
async setFitMode(nextMode) {
|
|
11550
|
-
await
|
|
11754
|
+
await this.setZoom(nextMode === "contain" ? "automatic" : 100);
|
|
11551
11755
|
},
|
|
11552
11756
|
search(query, options) {
|
|
11553
11757
|
return adapterRef.current?.search(query, options) ?? [];
|
|
@@ -11571,9 +11775,10 @@ var ReactPptxViewer = forwardRef(
|
|
|
11571
11775
|
},
|
|
11572
11776
|
getDocument: () => parsed?.document ?? null,
|
|
11573
11777
|
getSlideIndex: () => latestSlideRef.current,
|
|
11574
|
-
getZoom: () => latestZoomRef.current
|
|
11778
|
+
getZoom: () => latestZoomRef.current,
|
|
11779
|
+
getResolvedZoom: () => latestResolvedZoomRef.current
|
|
11575
11780
|
}),
|
|
11576
|
-
[adapterState?.generation, parsed]
|
|
11781
|
+
[adapterState?.generation, notifyZoomChange, parsed, publishZoomChange]
|
|
11577
11782
|
);
|
|
11578
11783
|
useImperativeHandle(forwardedRef, () => controller, [controller]);
|
|
11579
11784
|
useEffect(() => {
|
|
@@ -11591,7 +11796,8 @@ var ReactPptxViewer = forwardRef(
|
|
|
11591
11796
|
signal: abort.signal,
|
|
11592
11797
|
...parseFormatHint ? { formatHint: parseFormatHint } : {},
|
|
11593
11798
|
...parseMaxInputBytes !== void 0 ? { maxInputBytes: parseMaxInputBytes } : {},
|
|
11594
|
-
...parseFetchInit ? { fetchInit: parseFetchInit } : {}
|
|
11799
|
+
...parseFetchInit ? { fetchInit: parseFetchInit } : {},
|
|
11800
|
+
onPerformanceMeasurement: (measurement) => latestPropsRef.current.onPerformanceMeasurement?.(measurement)
|
|
11595
11801
|
}).then((next) => {
|
|
11596
11802
|
if (abort.signal.aborted) return;
|
|
11597
11803
|
setParsed(next);
|
|
@@ -11646,6 +11852,7 @@ var ReactPptxViewer = forwardRef(
|
|
|
11646
11852
|
setInternalSlide(index);
|
|
11647
11853
|
latestPropsRef.current.onSlideChange?.(index);
|
|
11648
11854
|
},
|
|
11855
|
+
publishZoomChange,
|
|
11649
11856
|
(warning) => {
|
|
11650
11857
|
if (!isCurrent()) return;
|
|
11651
11858
|
setRuntimeWarnings(
|
|
@@ -11667,11 +11874,6 @@ var ReactPptxViewer = forwardRef(
|
|
|
11667
11874
|
adapter.destroy();
|
|
11668
11875
|
return;
|
|
11669
11876
|
}
|
|
11670
|
-
await adapter.setFitMode(latestFitModeRef.current);
|
|
11671
|
-
if (!isCurrent()) {
|
|
11672
|
-
adapter.destroy();
|
|
11673
|
-
return;
|
|
11674
|
-
}
|
|
11675
11877
|
await adapter.render(mode, latestSlideRef.current);
|
|
11676
11878
|
if (!isCurrent()) {
|
|
11677
11879
|
adapter.destroy();
|
|
@@ -11709,7 +11911,7 @@ var ReactPptxViewer = forwardRef(
|
|
|
11709
11911
|
overscanViewport,
|
|
11710
11912
|
batchSize,
|
|
11711
11913
|
scrollElement,
|
|
11712
|
-
|
|
11914
|
+
publishZoomChange
|
|
11713
11915
|
]);
|
|
11714
11916
|
useEffect(() => {
|
|
11715
11917
|
if (adapterState && controlledSlide !== void 0 && slide !== lastViewerSlideRef.current) {
|
|
@@ -11717,13 +11919,12 @@ var ReactPptxViewer = forwardRef(
|
|
|
11717
11919
|
}
|
|
11718
11920
|
}, [adapterState, controlledSlide, slide]);
|
|
11719
11921
|
useEffect(() => {
|
|
11720
|
-
if (adapterState
|
|
11721
|
-
|
|
11722
|
-
}
|
|
11723
|
-
}, [adapterState, controlledZoom, zoom]);
|
|
11922
|
+
if (adapterState) void adapterState.adapter.setZoom(zoom);
|
|
11923
|
+
}, [adapterState, zoom]);
|
|
11724
11924
|
useEffect(() => {
|
|
11725
|
-
if (
|
|
11726
|
-
|
|
11925
|
+
if (controlledZoom !== void 0 || defaultZoom !== void 0) return;
|
|
11926
|
+
setInternalZoom(fitMode === "contain" ? "automatic" : 100);
|
|
11927
|
+
}, [controlledZoom, defaultZoom, fitMode]);
|
|
11727
11928
|
useEffect(() => {
|
|
11728
11929
|
const adapter = adapterState?.adapter;
|
|
11729
11930
|
if (!adapter || searchQuery === void 0 || searchQuery === "") {
|
|
@@ -11800,20 +12001,20 @@ var ReactPptxViewer = forwardRef(
|
|
|
11800
12001
|
"button",
|
|
11801
12002
|
{
|
|
11802
12003
|
type: "button",
|
|
11803
|
-
onClick: () => void controller.setZoom(
|
|
12004
|
+
onClick: () => void controller.setZoom(resolvedZoom - 10),
|
|
11804
12005
|
"aria-label": "Zoom out",
|
|
11805
12006
|
children: "\u2212"
|
|
11806
12007
|
}
|
|
11807
12008
|
),
|
|
11808
12009
|
/* @__PURE__ */ jsxs("span", { className: "rpv-toolbar__count", children: [
|
|
11809
|
-
|
|
12010
|
+
Math.round(resolvedZoom),
|
|
11810
12011
|
"%"
|
|
11811
12012
|
] }),
|
|
11812
12013
|
/* @__PURE__ */ jsx(
|
|
11813
12014
|
"button",
|
|
11814
12015
|
{
|
|
11815
12016
|
type: "button",
|
|
11816
|
-
onClick: () => void controller.setZoom(
|
|
12017
|
+
onClick: () => void controller.setZoom(resolvedZoom + 10),
|
|
11817
12018
|
"aria-label": "Zoom in",
|
|
11818
12019
|
children: "+"
|
|
11819
12020
|
}
|
|
@@ -12284,6 +12485,404 @@ function usePptxViewerThumbnails(controller, options = {}) {
|
|
|
12284
12485
|
);
|
|
12285
12486
|
}
|
|
12286
12487
|
|
|
12287
|
-
|
|
12488
|
+
// src/thumbnail-renderer.ts
|
|
12489
|
+
var EMU_PER_CSS_PIXEL3 = 9525;
|
|
12490
|
+
var DEFAULT_MAX_DIMENSION3 = 160;
|
|
12491
|
+
var RenderQueue = class {
|
|
12492
|
+
constructor(limit) {
|
|
12493
|
+
this.limit = limit;
|
|
12494
|
+
}
|
|
12495
|
+
limit;
|
|
12496
|
+
active = 0;
|
|
12497
|
+
destroyed = false;
|
|
12498
|
+
waiters = [];
|
|
12499
|
+
acquire(signal) {
|
|
12500
|
+
throwIfAborted(signal);
|
|
12501
|
+
if (this.destroyed) return Promise.reject(abortedError());
|
|
12502
|
+
return new Promise((resolve, reject) => {
|
|
12503
|
+
const waiter = { resolve, reject, ...signal ? { signal } : {} };
|
|
12504
|
+
if (signal) {
|
|
12505
|
+
waiter.abort = () => {
|
|
12506
|
+
const index = this.waiters.indexOf(waiter);
|
|
12507
|
+
if (index >= 0) this.waiters.splice(index, 1);
|
|
12508
|
+
reject(abortedError(signal.reason));
|
|
12509
|
+
};
|
|
12510
|
+
signal.addEventListener("abort", waiter.abort, { once: true });
|
|
12511
|
+
}
|
|
12512
|
+
this.waiters.push(waiter);
|
|
12513
|
+
this.drain();
|
|
12514
|
+
});
|
|
12515
|
+
}
|
|
12516
|
+
destroy() {
|
|
12517
|
+
this.destroyed = true;
|
|
12518
|
+
for (const waiter of this.waiters.splice(0)) {
|
|
12519
|
+
if (waiter.abort && waiter.signal) {
|
|
12520
|
+
waiter.signal.removeEventListener("abort", waiter.abort);
|
|
12521
|
+
}
|
|
12522
|
+
waiter.reject(abortedError());
|
|
12523
|
+
}
|
|
12524
|
+
}
|
|
12525
|
+
drain() {
|
|
12526
|
+
while (!this.destroyed && this.active < this.limit && this.waiters.length) {
|
|
12527
|
+
const waiter = this.waiters.shift();
|
|
12528
|
+
if (waiter.abort && waiter.signal) {
|
|
12529
|
+
waiter.signal.removeEventListener("abort", waiter.abort);
|
|
12530
|
+
}
|
|
12531
|
+
if (waiter.signal?.aborted) {
|
|
12532
|
+
waiter.reject(abortedError(waiter.signal.reason));
|
|
12533
|
+
continue;
|
|
12534
|
+
}
|
|
12535
|
+
this.active += 1;
|
|
12536
|
+
let released = false;
|
|
12537
|
+
waiter.resolve(() => {
|
|
12538
|
+
if (released) return;
|
|
12539
|
+
released = true;
|
|
12540
|
+
this.active -= 1;
|
|
12541
|
+
this.drain();
|
|
12542
|
+
});
|
|
12543
|
+
}
|
|
12544
|
+
}
|
|
12545
|
+
};
|
|
12546
|
+
function abortedError(reason) {
|
|
12547
|
+
return new PptxViewerError("aborted", "Thumbnail rendering was cancelled.", { cause: reason });
|
|
12548
|
+
}
|
|
12549
|
+
function finitePositive3(value, fallback) {
|
|
12550
|
+
return value !== void 0 && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
12551
|
+
}
|
|
12552
|
+
function outputSize(presentation, options) {
|
|
12553
|
+
const naturalWidth = Math.max(1, presentation.document.size.widthEmu / EMU_PER_CSS_PIXEL3);
|
|
12554
|
+
const naturalHeight = Math.max(1, presentation.document.size.heightEmu / EMU_PER_CSS_PIXEL3);
|
|
12555
|
+
const hasWidth = options.maxWidth !== void 0 && Number.isFinite(options.maxWidth) && options.maxWidth > 0;
|
|
12556
|
+
const hasHeight = options.maxHeight !== void 0 && Number.isFinite(options.maxHeight) && options.maxHeight > 0;
|
|
12557
|
+
const maxWidth = hasWidth ? options.maxWidth : hasHeight ? Number.POSITIVE_INFINITY : DEFAULT_MAX_DIMENSION3;
|
|
12558
|
+
const maxHeight = hasHeight ? options.maxHeight : hasWidth ? Number.POSITIVE_INFINITY : DEFAULT_MAX_DIMENSION3;
|
|
12559
|
+
const scale = Math.min(maxWidth / naturalWidth, maxHeight / naturalHeight);
|
|
12560
|
+
return {
|
|
12561
|
+
height: Math.max(1, Math.round(naturalHeight * scale)),
|
|
12562
|
+
naturalHeight,
|
|
12563
|
+
naturalWidth,
|
|
12564
|
+
width: Math.max(1, Math.round(naturalWidth * scale))
|
|
12565
|
+
};
|
|
12566
|
+
}
|
|
12567
|
+
function blobToDataUrl(blob, signal) {
|
|
12568
|
+
return abortable(
|
|
12569
|
+
new Promise((resolve, reject) => {
|
|
12570
|
+
const reader = new FileReader();
|
|
12571
|
+
reader.onload = () => resolve(String(reader.result));
|
|
12572
|
+
reader.onerror = () => reject(reader.error ?? new Error("Could not read a slide asset."));
|
|
12573
|
+
reader.readAsDataURL(blob);
|
|
12574
|
+
}),
|
|
12575
|
+
signal
|
|
12576
|
+
);
|
|
12577
|
+
}
|
|
12578
|
+
async function inlineResourceUrl(url, signal) {
|
|
12579
|
+
if (!/^(?:blob:|https?:)/i.test(url)) return url;
|
|
12580
|
+
try {
|
|
12581
|
+
const response = await fetch(url, signal ? { signal } : void 0);
|
|
12582
|
+
if (!response.ok) return url;
|
|
12583
|
+
return await blobToDataUrl(await response.blob(), signal);
|
|
12584
|
+
} catch {
|
|
12585
|
+
throwIfAborted(signal);
|
|
12586
|
+
return url;
|
|
12587
|
+
}
|
|
12588
|
+
}
|
|
12589
|
+
async function inlineResources(root, signal) {
|
|
12590
|
+
const cache = /* @__PURE__ */ new Map();
|
|
12591
|
+
const inline = (url) => {
|
|
12592
|
+
let pending = cache.get(url);
|
|
12593
|
+
if (!pending) {
|
|
12594
|
+
pending = inlineResourceUrl(url, signal);
|
|
12595
|
+
cache.set(url, pending);
|
|
12596
|
+
}
|
|
12597
|
+
return pending;
|
|
12598
|
+
};
|
|
12599
|
+
const elements = [root, ...root.querySelectorAll("*")];
|
|
12600
|
+
await Promise.all(
|
|
12601
|
+
elements.flatMap((element) => {
|
|
12602
|
+
const attributeNames = ["src", "poster", ...element.localName === "image" ? ["href"] : []];
|
|
12603
|
+
const attributes = attributeNames.map((name) => ({ name, value: element.getAttribute(name) })).filter((entry) => Boolean(entry.value));
|
|
12604
|
+
const attributeTasks = attributes.map(async ({ name, value }) => {
|
|
12605
|
+
element.setAttribute(name, await inline(value));
|
|
12606
|
+
});
|
|
12607
|
+
const styleTasks = Array.from(
|
|
12608
|
+
{ length: element.style.length },
|
|
12609
|
+
(_, index) => element.style.item(index)
|
|
12610
|
+
).map((property) => ({ property, value: element.style.getPropertyValue(property) })).filter(({ value }) => value.includes("url(")).map(async ({ property, value }) => {
|
|
12611
|
+
const urls = [...value.matchAll(/url\(["']?([^"')]+)["']?\)/g)].map((match) => match[1]);
|
|
12612
|
+
let next = value;
|
|
12613
|
+
for (const url of urls) next = next.replace(url, await inline(url));
|
|
12614
|
+
element.style.setProperty(property, next);
|
|
12615
|
+
});
|
|
12616
|
+
return [...attributeTasks, ...styleTasks];
|
|
12617
|
+
})
|
|
12618
|
+
);
|
|
12619
|
+
}
|
|
12620
|
+
async function serializeSlide(slide, naturalWidth, naturalHeight, width, height, signal) {
|
|
12621
|
+
const clone = slide.cloneNode(true);
|
|
12622
|
+
clone.style.transform = "none";
|
|
12623
|
+
clone.style.transformOrigin = "top left";
|
|
12624
|
+
clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
12625
|
+
const sourceCanvases = slide.querySelectorAll("canvas");
|
|
12626
|
+
const clonedCanvases = clone.querySelectorAll("canvas");
|
|
12627
|
+
sourceCanvases.forEach((canvas, index) => {
|
|
12628
|
+
const cloned = clonedCanvases[index];
|
|
12629
|
+
if (!cloned) return;
|
|
12630
|
+
try {
|
|
12631
|
+
const image = document.createElement("img");
|
|
12632
|
+
image.src = canvas.toDataURL("image/png");
|
|
12633
|
+
image.setAttribute("style", cloned.getAttribute("style") ?? "");
|
|
12634
|
+
cloned.replaceWith(image);
|
|
12635
|
+
} catch {
|
|
12636
|
+
cloned.remove();
|
|
12637
|
+
}
|
|
12638
|
+
});
|
|
12639
|
+
await inlineResources(clone, signal);
|
|
12640
|
+
const content = new XMLSerializer().serializeToString(clone);
|
|
12641
|
+
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>`;
|
|
12642
|
+
}
|
|
12643
|
+
async function abortable(promise, signal) {
|
|
12644
|
+
throwIfAborted(signal);
|
|
12645
|
+
if (!signal) return promise;
|
|
12646
|
+
return new Promise((resolve, reject) => {
|
|
12647
|
+
const abort = () => reject(abortedError(signal.reason));
|
|
12648
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
12649
|
+
void promise.then(
|
|
12650
|
+
(value) => {
|
|
12651
|
+
signal.removeEventListener("abort", abort);
|
|
12652
|
+
resolve(value);
|
|
12653
|
+
},
|
|
12654
|
+
(error) => {
|
|
12655
|
+
signal.removeEventListener("abort", abort);
|
|
12656
|
+
reject(error);
|
|
12657
|
+
}
|
|
12658
|
+
);
|
|
12659
|
+
});
|
|
12660
|
+
}
|
|
12661
|
+
async function decodeElementImages(root, signal) {
|
|
12662
|
+
const images = [...root.querySelectorAll("img")];
|
|
12663
|
+
const svgImages = [...root.querySelectorAll("image")].map((image) => image.href.baseVal || image.getAttribute("href") || "").filter(Boolean);
|
|
12664
|
+
await abortable(
|
|
12665
|
+
Promise.all([
|
|
12666
|
+
...images.map(async (image) => {
|
|
12667
|
+
if (typeof image.decode === "function") await image.decode().catch(() => void 0);
|
|
12668
|
+
}),
|
|
12669
|
+
...svgImages.map(
|
|
12670
|
+
(source) => new Promise((resolve) => {
|
|
12671
|
+
const image = new Image();
|
|
12672
|
+
image.onload = () => resolve();
|
|
12673
|
+
image.onerror = () => resolve();
|
|
12674
|
+
image.src = source;
|
|
12675
|
+
})
|
|
12676
|
+
)
|
|
12677
|
+
]).then(() => void 0),
|
|
12678
|
+
signal
|
|
12679
|
+
);
|
|
12680
|
+
}
|
|
12681
|
+
async function decodeSvg(svg, signal) {
|
|
12682
|
+
return abortable(
|
|
12683
|
+
new Promise((resolve, reject) => {
|
|
12684
|
+
const image = new Image();
|
|
12685
|
+
image.onload = () => resolve(image);
|
|
12686
|
+
image.onerror = () => reject(new Error("The rendered slide SVG could not be decoded."));
|
|
12687
|
+
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
12688
|
+
}),
|
|
12689
|
+
signal
|
|
12690
|
+
);
|
|
12691
|
+
}
|
|
12692
|
+
function canvasToBlob(canvas, quality) {
|
|
12693
|
+
return new Promise((resolve, reject) => {
|
|
12694
|
+
canvas.toBlob(
|
|
12695
|
+
(blob) => {
|
|
12696
|
+
if (blob) resolve(blob);
|
|
12697
|
+
else reject(new Error("The browser could not encode the thumbnail."));
|
|
12698
|
+
},
|
|
12699
|
+
"image/png",
|
|
12700
|
+
quality
|
|
12701
|
+
);
|
|
12702
|
+
});
|
|
12703
|
+
}
|
|
12704
|
+
var ThumbnailRenderer = class {
|
|
12705
|
+
constructor(presentation, options) {
|
|
12706
|
+
this.presentation = presentation;
|
|
12707
|
+
this.options = options;
|
|
12708
|
+
if (typeof document === "undefined") {
|
|
12709
|
+
throw new PptxViewerError(
|
|
12710
|
+
"unsupported-environment",
|
|
12711
|
+
"Thumbnail rendering requires a browser DOM."
|
|
12712
|
+
);
|
|
12713
|
+
}
|
|
12714
|
+
const concurrency = Math.max(1, Math.floor(finitePositive3(options.concurrency, 2)));
|
|
12715
|
+
this.queue = new RenderQueue(concurrency);
|
|
12716
|
+
this.root = document.createElement("div");
|
|
12717
|
+
this.root.setAttribute("aria-hidden", "true");
|
|
12718
|
+
this.root.style.position = "fixed";
|
|
12719
|
+
this.root.style.left = "-100000px";
|
|
12720
|
+
this.root.style.top = "0";
|
|
12721
|
+
this.root.style.pointerEvents = "none";
|
|
12722
|
+
this.root.style.opacity = "0";
|
|
12723
|
+
document.body.append(this.root);
|
|
12724
|
+
this.fontManager = new PptxFontManager(
|
|
12725
|
+
{
|
|
12726
|
+
loadEmbeddedFonts: false,
|
|
12727
|
+
reportMissingFonts: false,
|
|
12728
|
+
waitForFonts: false,
|
|
12729
|
+
...options.fonts
|
|
12730
|
+
},
|
|
12731
|
+
(warning) => options.onWarning?.(warning)
|
|
12732
|
+
);
|
|
12733
|
+
this.viewer = new NormalizedPresentationViewer(this.root, presentation.document, {
|
|
12734
|
+
onNodeError: (nodeId, error) => options.onWarning?.({
|
|
12735
|
+
code: "degraded-rendering",
|
|
12736
|
+
severity: "warning",
|
|
12737
|
+
nodeId,
|
|
12738
|
+
feature: "thumbnail-renderer",
|
|
12739
|
+
message: `A slide element could not be rendered: ${error instanceof Error ? error.message : String(error)}`
|
|
12740
|
+
})
|
|
12741
|
+
});
|
|
12742
|
+
const startedAt = performanceNow();
|
|
12743
|
+
this.ready = this.fontManager.prepare(presentation.document).then(() => {
|
|
12744
|
+
this.report(performanceMeasurement("embedded-font-preparation", startedAt));
|
|
12745
|
+
});
|
|
12746
|
+
}
|
|
12747
|
+
presentation;
|
|
12748
|
+
options;
|
|
12749
|
+
ready;
|
|
12750
|
+
fontManager;
|
|
12751
|
+
queue;
|
|
12752
|
+
root;
|
|
12753
|
+
viewer;
|
|
12754
|
+
destroyed = false;
|
|
12755
|
+
async renderSlide(slideIndex, options = {}) {
|
|
12756
|
+
const release = await this.queue.acquire(options.signal);
|
|
12757
|
+
try {
|
|
12758
|
+
this.assertAvailable(options.signal);
|
|
12759
|
+
await abortable(this.ready, options.signal);
|
|
12760
|
+
this.assertAvailable(options.signal);
|
|
12761
|
+
if (!Number.isInteger(slideIndex) || slideIndex < 0 || slideIndex >= this.presentation.document.slides.length) {
|
|
12762
|
+
throw new RangeError(`Slide ${slideIndex} does not exist.`);
|
|
12763
|
+
}
|
|
12764
|
+
const measurements = [];
|
|
12765
|
+
const size = outputSize(this.presentation, options);
|
|
12766
|
+
const target = document.createElement("div");
|
|
12767
|
+
this.root.append(target);
|
|
12768
|
+
const domStartedAt = performanceNow();
|
|
12769
|
+
const handle = this.viewer.renderThumbnailToContainer(slideIndex, target, {
|
|
12770
|
+
width: size.naturalWidth
|
|
12771
|
+
});
|
|
12772
|
+
try {
|
|
12773
|
+
await abortable(handle.ready, options.signal);
|
|
12774
|
+
this.fontManager.applyTo(handle.element, slideIndex);
|
|
12775
|
+
const domDurationMs = performanceNow() - domStartedAt;
|
|
12776
|
+
const decodeStartedAt = performanceNow();
|
|
12777
|
+
await decodeElementImages(handle.element, options.signal);
|
|
12778
|
+
let decodeDurationMs = performanceNow() - decodeStartedAt;
|
|
12779
|
+
const serializationStartedAt = performanceNow();
|
|
12780
|
+
const svg = await serializeSlide(
|
|
12781
|
+
handle.element,
|
|
12782
|
+
size.naturalWidth,
|
|
12783
|
+
size.naturalHeight,
|
|
12784
|
+
size.width,
|
|
12785
|
+
size.height,
|
|
12786
|
+
options.signal
|
|
12787
|
+
);
|
|
12788
|
+
this.record(
|
|
12789
|
+
{
|
|
12790
|
+
durationMs: Math.max(0, domDurationMs + performanceNow() - serializationStartedAt),
|
|
12791
|
+
phase: "slide-dom-svg-rendering",
|
|
12792
|
+
slideIndex
|
|
12793
|
+
},
|
|
12794
|
+
measurements
|
|
12795
|
+
);
|
|
12796
|
+
const output = options.output ?? "blob";
|
|
12797
|
+
if (output === "svg") {
|
|
12798
|
+
this.record(
|
|
12799
|
+
{ durationMs: Math.max(0, decodeDurationMs), phase: "image-decoding", slideIndex },
|
|
12800
|
+
measurements
|
|
12801
|
+
);
|
|
12802
|
+
return this.result(svg, output, slideIndex, size, measurements);
|
|
12803
|
+
}
|
|
12804
|
+
const svgDecodeStartedAt = performanceNow();
|
|
12805
|
+
const image = await decodeSvg(svg, options.signal);
|
|
12806
|
+
decodeDurationMs += performanceNow() - svgDecodeStartedAt;
|
|
12807
|
+
this.record(
|
|
12808
|
+
{ durationMs: Math.max(0, decodeDurationMs), phase: "image-decoding", slideIndex },
|
|
12809
|
+
measurements
|
|
12810
|
+
);
|
|
12811
|
+
this.assertAvailable(options.signal);
|
|
12812
|
+
const encodeStartedAt = performanceNow();
|
|
12813
|
+
const pixelRatio = finitePositive3(options.pixelRatio, 1);
|
|
12814
|
+
const canvas = document.createElement("canvas");
|
|
12815
|
+
canvas.width = Math.max(1, Math.round(size.width * pixelRatio));
|
|
12816
|
+
canvas.height = Math.max(1, Math.round(size.height * pixelRatio));
|
|
12817
|
+
canvas.style.width = `${size.width}px`;
|
|
12818
|
+
canvas.style.height = `${size.height}px`;
|
|
12819
|
+
const context = canvas.getContext("2d");
|
|
12820
|
+
if (!context) throw new Error("The browser does not provide a 2D canvas renderer.");
|
|
12821
|
+
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
12822
|
+
let data;
|
|
12823
|
+
if (output === "canvas") data = canvas;
|
|
12824
|
+
else if (output === "imageBitmap") {
|
|
12825
|
+
if (typeof createImageBitmap !== "function") {
|
|
12826
|
+
throw new PptxViewerError(
|
|
12827
|
+
"unsupported-environment",
|
|
12828
|
+
"ImageBitmap output is not supported by this browser."
|
|
12829
|
+
);
|
|
12830
|
+
}
|
|
12831
|
+
data = await abortable(createImageBitmap(canvas), options.signal);
|
|
12832
|
+
} else data = await abortable(canvasToBlob(canvas, options.quality), options.signal);
|
|
12833
|
+
this.measure("raster-encoding", encodeStartedAt, slideIndex, measurements);
|
|
12834
|
+
return this.result(data, output, slideIndex, size, measurements);
|
|
12835
|
+
} finally {
|
|
12836
|
+
handle.dispose();
|
|
12837
|
+
target.remove();
|
|
12838
|
+
}
|
|
12839
|
+
} finally {
|
|
12840
|
+
release();
|
|
12841
|
+
}
|
|
12842
|
+
}
|
|
12843
|
+
renderSlides(slideIndexes, options = {}) {
|
|
12844
|
+
return Promise.all(slideIndexes.map((slideIndex) => this.renderDynamic(slideIndex, options)));
|
|
12845
|
+
}
|
|
12846
|
+
destroy() {
|
|
12847
|
+
if (this.destroyed) return;
|
|
12848
|
+
this.destroyed = true;
|
|
12849
|
+
this.queue.destroy();
|
|
12850
|
+
this.viewer.destroy();
|
|
12851
|
+
this.fontManager.destroy();
|
|
12852
|
+
this.root.remove();
|
|
12853
|
+
}
|
|
12854
|
+
renderDynamic(slideIndex, options) {
|
|
12855
|
+
return this.renderSlide(slideIndex, options);
|
|
12856
|
+
}
|
|
12857
|
+
assertAvailable(signal) {
|
|
12858
|
+
throwIfAborted(signal);
|
|
12859
|
+
if (this.destroyed) throw abortedError();
|
|
12860
|
+
}
|
|
12861
|
+
measure(phase, startedAt, slideIndex, measurements) {
|
|
12862
|
+
this.record(performanceMeasurement(phase, startedAt, slideIndex), measurements);
|
|
12863
|
+
}
|
|
12864
|
+
record(measurement, measurements) {
|
|
12865
|
+
measurements.push(measurement);
|
|
12866
|
+
this.report(measurement);
|
|
12867
|
+
}
|
|
12868
|
+
report(measurement) {
|
|
12869
|
+
this.options.onPerformanceMeasurement?.(measurement);
|
|
12870
|
+
}
|
|
12871
|
+
result(data, output, slideIndex, size, measurements) {
|
|
12872
|
+
return {
|
|
12873
|
+
data,
|
|
12874
|
+
height: size.height,
|
|
12875
|
+
measurements,
|
|
12876
|
+
output,
|
|
12877
|
+
slideIndex,
|
|
12878
|
+
width: size.width
|
|
12879
|
+
};
|
|
12880
|
+
}
|
|
12881
|
+
};
|
|
12882
|
+
function createPptxThumbnailRenderer(presentation, options = {}) {
|
|
12883
|
+
return new ThumbnailRenderer(presentation, options);
|
|
12884
|
+
}
|
|
12885
|
+
|
|
12886
|
+
export { OFFICE_FONT_FALLBACKS, PptxViewerError, ReactPptxViewer, createPptxThumbnailRenderer, initWasm, parsePresentation, presentationParsingDefaults, resolvePptxFontFamily, setWasmSource, usePptxModel, usePptxPresentation, usePptxViewer, usePptxViewerThumbnails };
|
|
12288
12887
|
//# sourceMappingURL=index.js.map
|
|
12289
12888
|
//# sourceMappingURL=index.js.map
|