@files-preview-app/preview-file 1.3.6 → 1.3.8

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/angular.js CHANGED
@@ -1104,6 +1104,10 @@ var FilePreviewViewer = class _FilePreviewViewer {
1104
1104
  * Preview a file in the given container element.
1105
1105
  */
1106
1106
  async preview(container, source, options = {}) {
1107
+ options = {
1108
+ ...options,
1109
+ fitMode: options.fitMode ?? "width"
1110
+ };
1107
1111
  this.currentOptions = options;
1108
1112
  this.abort();
1109
1113
  this.abortController = new AbortController();
@@ -1152,8 +1156,8 @@ var FilePreviewViewer = class _FilePreviewViewer {
1152
1156
  this.activeInstance = instance;
1153
1157
  instance.openInSeparateWindow = () => this.openInSeparateWindow();
1154
1158
  instance.toggleThumbnails = () => this.toggleThumbnails();
1155
- if (!instance.resetZoom && instance.fitToPage) {
1156
- instance.resetZoom = () => instance.fitToPage?.();
1159
+ if (!instance.resetZoom) {
1160
+ instance.resetZoom = () => instance.fitToWidth ? instance.fitToWidth() : instance.fitToPage?.();
1157
1161
  }
1158
1162
  this.hideLoading();
1159
1163
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
@@ -1550,7 +1554,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1550
1554
  this.wrapperEl?.classList.toggle("fp-fullscreen-active");
1551
1555
  }
1552
1556
  setTimeout(() => {
1553
- this.activeInstance?.fitToPage?.();
1557
+ this.triggerAutoFit();
1554
1558
  }, 120);
1555
1559
  }
1556
1560
  /**
@@ -1677,18 +1681,29 @@ var FilePreviewViewer = class _FilePreviewViewer {
1677
1681
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl, (isOpen) => {
1678
1682
  this.toolbar?.setActionActive("thumbnails", isOpen);
1679
1683
  setTimeout(() => {
1680
- this.activeInstance?.fitToPage?.();
1684
+ this.triggerAutoFit();
1681
1685
  }, 260);
1682
1686
  });
1683
1687
  this.setupKeyboardShortcuts();
1684
1688
  this.setupDragAndDrop(container, options);
1685
1689
  this.setupResizeAndFullscreenListeners();
1686
1690
  }
1691
+ triggerAutoFit() {
1692
+ if (!this.activeInstance) return;
1693
+ const mode = this.currentOptions?.fitMode ?? "width";
1694
+ if (mode === "width" && this.activeInstance.fitToWidth) {
1695
+ this.activeInstance.fitToWidth();
1696
+ } else if (this.activeInstance.fitToPage) {
1697
+ this.activeInstance.fitToPage();
1698
+ } else if (this.activeInstance.resetZoom) {
1699
+ this.activeInstance.resetZoom();
1700
+ }
1701
+ }
1687
1702
  setupResizeAndFullscreenListeners() {
1688
1703
  if (this.fullscreenHandler) return;
1689
1704
  this.fullscreenHandler = () => {
1690
1705
  setTimeout(() => {
1691
- this.activeInstance?.fitToPage?.();
1706
+ this.triggerAutoFit();
1692
1707
  }, 100);
1693
1708
  };
1694
1709
  document.addEventListener("fullscreenchange", this.fullscreenHandler);
@@ -1696,7 +1711,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1696
1711
  if (typeof ResizeObserver !== "undefined" && this.contentEl) {
1697
1712
  this.resizeObserver = new ResizeObserver(() => {
1698
1713
  if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
1699
- this.activeInstance.fitToPage?.();
1714
+ this.triggerAutoFit();
1700
1715
  }
1701
1716
  });
1702
1717
  this.resizeObserver.observe(this.contentEl);
@@ -1724,7 +1739,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1724
1739
  } else if (e.key === "-" || e.key === "_") {
1725
1740
  this.activeInstance.zoomOut?.();
1726
1741
  } else if (e.key === "0") {
1727
- this.activeInstance.fitToPage?.();
1742
+ this.resetZoom();
1728
1743
  } else if (e.key === "r" || e.key === "R") {
1729
1744
  this.activeInstance.rotateCW?.();
1730
1745
  } else if (e.key === "f" || e.key === "F") {
@@ -2132,16 +2147,22 @@ var PdfPlugin = class {
2132
2147
  container.className = "fp-pdf-container";
2133
2148
  container.style.width = "100%";
2134
2149
  container.style.height = "100%";
2135
- container.style.overflowX = "hidden";
2136
- container.style.overflowY = "auto";
2137
- container.style.display = "flex";
2138
- container.style.flexDirection = "column";
2139
- container.style.alignItems = "center";
2140
- container.style.justifyContent = "flex-start";
2141
- container.style.padding = "16px 8px";
2150
+ container.style.overflow = "auto";
2142
2151
  container.style.backgroundColor = "#0f172a";
2143
2152
  container.style.boxSizing = "border-box";
2144
2153
  container.style.position = "relative";
2154
+ const scrollWrapper = document.createElement("div");
2155
+ scrollWrapper.className = "fp-pdf-scroll-wrapper";
2156
+ scrollWrapper.style.minWidth = "100%";
2157
+ scrollWrapper.style.minHeight = "100%";
2158
+ scrollWrapper.style.width = "max-content";
2159
+ scrollWrapper.style.height = "max-content";
2160
+ scrollWrapper.style.display = "flex";
2161
+ scrollWrapper.style.flexDirection = "column";
2162
+ scrollWrapper.style.alignItems = "center";
2163
+ scrollWrapper.style.justifyContent = "flex-start";
2164
+ scrollWrapper.style.padding = "16px 8px";
2165
+ scrollWrapper.style.boxSizing = "border-box";
2145
2166
  const pageCard = document.createElement("div");
2146
2167
  pageCard.className = "fp-pdf-page-card";
2147
2168
  pageCard.style.boxShadow = "0 10px 35px rgba(0, 0, 0, 0.5)";
@@ -2154,7 +2175,8 @@ var PdfPlugin = class {
2154
2175
  pageCard.style.transition = "box-shadow 0.2s ease";
2155
2176
  let canvas = document.createElement("canvas");
2156
2177
  pageCard.appendChild(canvas);
2157
- container.appendChild(pageCard);
2178
+ scrollWrapper.appendChild(pageCard);
2179
+ container.appendChild(scrollWrapper);
2158
2180
  ctx.container.appendChild(container);
2159
2181
  const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
2160
2182
  const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
@@ -2340,7 +2362,7 @@ var PdfPlugin = class {
2340
2362
  renderPage(currentPage);
2341
2363
  },
2342
2364
  resetZoom: () => {
2343
- fitMode = "page";
2365
+ fitMode = ctx?.options?.fitMode || "width";
2344
2366
  zoomScale = 1;
2345
2367
  rotation = 0;
2346
2368
  container.scrollTop = 0;
@@ -2524,6 +2546,16 @@ var MediaPlugin = class {
2524
2546
  instance.resetZoom?.();
2525
2547
  }
2526
2548
  },
2549
+ {
2550
+ id: "fit-width",
2551
+ icon: "fit-width",
2552
+ label: "Fit to Width",
2553
+ type: "button",
2554
+ group: "zoom",
2555
+ execute: () => {
2556
+ instance.fitToWidth?.();
2557
+ }
2558
+ },
2527
2559
  {
2528
2560
  id: "rotate-cw",
2529
2561
  icon: "rotate-cw",
@@ -2620,9 +2652,30 @@ var MediaPlugin = class {
2620
2652
  const isVideo = mimeType.startsWith("video/") || VIDEO_EXTS.includes(ctx.metadata.extension || "");
2621
2653
  const isAudio = mimeType.startsWith("audio/") || AUDIO_EXTS.includes(ctx.metadata.extension || "");
2622
2654
  let url = "";
2655
+ let naturalW = 800;
2656
+ let naturalH = 600;
2623
2657
  if (ctx.metadata.extension === ".svg" || mimeType === "image/svg+xml") {
2624
2658
  const decoder = new TextDecoder("utf-8");
2625
2659
  const svgText = decoder.decode(ctx.buffer);
2660
+ try {
2661
+ const vbMatch = svgText.match(/viewBox=["']\s*([0-9.-]+)\s+([0-9.-]+)\s+([0-9.-]+)\s+([0-9.-]+)\s*["']/i);
2662
+ if (vbMatch) {
2663
+ const vw = parseFloat(vbMatch[3]);
2664
+ const vh = parseFloat(vbMatch[4]);
2665
+ if (vw > 0 && vh > 0) {
2666
+ naturalW = vw;
2667
+ naturalH = vh;
2668
+ }
2669
+ } else {
2670
+ const wMatch = svgText.match(/width=["']([0-9.]+)(?:px)?["']/i);
2671
+ const hMatch = svgText.match(/height=["']([0-9.]+)(?:px)?["']/i);
2672
+ if (wMatch && hMatch) {
2673
+ naturalW = parseFloat(wMatch[1]) || naturalW;
2674
+ naturalH = parseFloat(hMatch[1]) || naturalH;
2675
+ }
2676
+ }
2677
+ } catch {
2678
+ }
2626
2679
  const blob = new Blob([svgText], { type: "image/svg+xml" });
2627
2680
  url = URL.createObjectURL(blob);
2628
2681
  } else {
@@ -2663,45 +2716,150 @@ var MediaPlugin = class {
2663
2716
  element = document.createElement("div");
2664
2717
  element.textContent = "Unsupported media type";
2665
2718
  }
2666
- ctx.container.style.display = "flex";
2667
- ctx.container.style.alignItems = "center";
2668
- ctx.container.style.justifyContent = "center";
2669
- ctx.container.style.overflow = "hidden";
2670
- ctx.container.appendChild(element);
2719
+ const scrollWrapper = document.createElement("div");
2720
+ scrollWrapper.className = "fp-media-scroll-wrapper";
2721
+ scrollWrapper.style.minWidth = "100%";
2722
+ scrollWrapper.style.minHeight = "100%";
2723
+ scrollWrapper.style.width = "max-content";
2724
+ scrollWrapper.style.height = "max-content";
2725
+ scrollWrapper.style.display = "flex";
2726
+ scrollWrapper.style.flexDirection = "column";
2727
+ scrollWrapper.style.alignItems = "center";
2728
+ scrollWrapper.style.padding = "16px";
2729
+ scrollWrapper.style.boxSizing = "border-box";
2730
+ const sizer = document.createElement("div");
2731
+ sizer.className = "fp-media-sizer";
2732
+ sizer.style.position = "relative";
2733
+ sizer.style.flexShrink = "0";
2734
+ sizer.style.display = "flex";
2735
+ sizer.style.justifyContent = "center";
2736
+ sizer.style.alignItems = "center";
2737
+ sizer.style.margin = "auto 0";
2738
+ sizer.appendChild(element);
2739
+ scrollWrapper.appendChild(sizer);
2740
+ ctx.container.style.overflow = "auto";
2741
+ ctx.container.style.padding = "0";
2742
+ ctx.container.innerHTML = "";
2743
+ ctx.container.appendChild(scrollWrapper);
2744
+ let baseW = naturalW;
2745
+ let baseH = naturalH;
2746
+ let fitMode = ctx?.options?.fitMode || "width";
2747
+ let isUserZoomed = false;
2748
+ const updateBaseDimensions = () => {
2749
+ if (!isImage) return;
2750
+ const img = element;
2751
+ if (img.naturalWidth > 0 && img.naturalHeight > 0) {
2752
+ naturalW = img.naturalWidth;
2753
+ naturalH = img.naturalHeight;
2754
+ }
2755
+ const availW = Math.max(100, ctx.container.clientWidth - 32);
2756
+ const availH = Math.max(100, ctx.container.clientHeight - 32);
2757
+ let fitRatio;
2758
+ if (fitMode === "page") {
2759
+ fitRatio = Math.min(availW / naturalW, availH / naturalH);
2760
+ } else {
2761
+ fitRatio = availW / naturalW;
2762
+ }
2763
+ baseW = Math.max(1, Math.round(naturalW * fitRatio));
2764
+ baseH = Math.max(1, Math.round(naturalH * fitRatio));
2765
+ };
2671
2766
  const applyTransform = () => {
2672
- element.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
2767
+ if (isImage) {
2768
+ if (!isUserZoomed) {
2769
+ updateBaseDimensions();
2770
+ }
2771
+ const availH = Math.max(100, ctx.container.clientHeight - 32);
2772
+ const isRotated90 = rotation % 180 !== 0;
2773
+ const boxW = Math.round((isRotated90 ? baseH : baseW) * currentZoom);
2774
+ const boxH = Math.round((isRotated90 ? baseW : baseH) * currentZoom);
2775
+ sizer.style.width = `${boxW}px`;
2776
+ sizer.style.height = `${boxH}px`;
2777
+ sizer.style.margin = boxH < availH ? "auto 0" : "0";
2778
+ const imgW = isRotated90 ? boxH : boxW;
2779
+ const imgH = isRotated90 ? boxW : boxH;
2780
+ element.style.width = `${imgW}px`;
2781
+ element.style.height = `${imgH}px`;
2782
+ element.style.maxWidth = "none";
2783
+ element.style.maxHeight = "none";
2784
+ element.style.transform = rotation ? `rotate(${rotation}deg)` : "none";
2785
+ element.style.transformOrigin = "center center";
2786
+ element.style.flexShrink = "0";
2787
+ } else {
2788
+ element.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
2789
+ }
2673
2790
  };
2791
+ if (isImage) {
2792
+ const img = element;
2793
+ if (img.complete && img.naturalWidth > 0) {
2794
+ updateBaseDimensions();
2795
+ applyTransform();
2796
+ } else {
2797
+ img.onload = () => {
2798
+ updateBaseDimensions();
2799
+ applyTransform();
2800
+ };
2801
+ }
2802
+ }
2803
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
2804
+ if (!isUserZoomed && currentZoom === 1) {
2805
+ updateBaseDimensions();
2806
+ applyTransform();
2807
+ }
2808
+ }) : null;
2809
+ ro?.observe(ctx.container);
2674
2810
  const cleanup = () => {
2811
+ ro?.disconnect();
2675
2812
  if (url) URL.revokeObjectURL(url);
2676
- element.remove();
2813
+ scrollWrapper.remove();
2677
2814
  ctx.container.innerHTML = "";
2678
2815
  };
2679
2816
  ctx.signal.addEventListener("abort", cleanup);
2680
2817
  return {
2681
2818
  destroy: cleanup,
2682
2819
  zoomIn: isImage ? () => {
2683
- currentZoom += 0.1;
2820
+ isUserZoomed = true;
2821
+ currentZoom = Math.min(5, Math.round((currentZoom + 0.15) * 100) / 100);
2684
2822
  applyTransform();
2685
2823
  } : void 0,
2686
2824
  zoomOut: isImage ? () => {
2687
- currentZoom = Math.max(0.1, currentZoom - 0.1);
2825
+ isUserZoomed = true;
2826
+ currentZoom = Math.max(0.1, Math.round((currentZoom - 0.15) * 100) / 100);
2688
2827
  applyTransform();
2689
2828
  } : void 0,
2690
2829
  getZoom: isImage ? () => currentZoom : void 0,
2691
2830
  setZoom: isImage ? (level) => {
2831
+ isUserZoomed = true;
2692
2832
  currentZoom = level;
2693
2833
  applyTransform();
2694
2834
  } : void 0,
2695
2835
  fitToPage: isImage ? () => {
2836
+ isUserZoomed = false;
2837
+ fitMode = "page";
2838
+ currentZoom = 1;
2839
+ rotation = 0;
2840
+ ctx.container.scrollTop = 0;
2841
+ ctx.container.scrollLeft = 0;
2842
+ updateBaseDimensions();
2843
+ applyTransform();
2844
+ } : void 0,
2845
+ fitToWidth: isImage ? () => {
2846
+ isUserZoomed = false;
2847
+ fitMode = "width";
2696
2848
  currentZoom = 1;
2697
2849
  rotation = 0;
2850
+ ctx.container.scrollTop = 0;
2851
+ ctx.container.scrollLeft = 0;
2852
+ updateBaseDimensions();
2698
2853
  applyTransform();
2699
2854
  } : void 0,
2700
2855
  resetZoom: isImage ? () => {
2856
+ isUserZoomed = false;
2857
+ fitMode = ctx?.options?.fitMode || "width";
2701
2858
  currentZoom = 1;
2702
2859
  rotation = 0;
2703
2860
  ctx.container.scrollTop = 0;
2704
2861
  ctx.container.scrollLeft = 0;
2862
+ updateBaseDimensions();
2705
2863
  applyTransform();
2706
2864
  } : void 0,
2707
2865
  rotateCW: isImage || isVideo ? () => {
@@ -2828,6 +2986,14 @@ var DocxPlugin = class {
2828
2986
  group: "zoom",
2829
2987
  execute: () => instance.resetZoom?.()
2830
2988
  },
2989
+ {
2990
+ id: "fit-width",
2991
+ icon: "fit-width",
2992
+ label: "Fit to Width",
2993
+ type: "button",
2994
+ group: "zoom",
2995
+ execute: () => instance.fitToWidth?.()
2996
+ },
2831
2997
  {
2832
2998
  id: "rotate-cw",
2833
2999
  icon: "rotate-cw",
@@ -2866,7 +3032,7 @@ var DocxPlugin = class {
2866
3032
  async render(ctx) {
2867
3033
  const wrapper = document.createElement("div");
2868
3034
  wrapper.className = "fp-docx-wrapper";
2869
- wrapper.style.transformOrigin = "top center";
3035
+ wrapper.style.transformOrigin = "center center";
2870
3036
  wrapper.style.transition = "transform 0.2s ease";
2871
3037
  wrapper.style.padding = "0";
2872
3038
  wrapper.style.maxWidth = "none";
@@ -2876,11 +3042,31 @@ var DocxPlugin = class {
2876
3042
  wrapper.style.flexDirection = "column";
2877
3043
  wrapper.style.alignItems = "center";
2878
3044
  wrapper.style.boxSizing = "border-box";
2879
- ctx.container.style.overflowX = "hidden";
2880
- ctx.container.style.overflowY = "auto";
2881
- ctx.container.style.padding = "16px 8px";
3045
+ wrapper.style.flexShrink = "0";
3046
+ const sizer = document.createElement("div");
3047
+ sizer.className = "fp-docx-sizer";
3048
+ sizer.style.position = "relative";
3049
+ sizer.style.flexShrink = "0";
3050
+ sizer.style.display = "flex";
3051
+ sizer.style.justifyContent = "center";
3052
+ sizer.style.alignItems = "center";
3053
+ const scrollWrapper = document.createElement("div");
3054
+ scrollWrapper.className = "fp-docx-scroll-wrapper";
3055
+ scrollWrapper.style.minWidth = "100%";
3056
+ scrollWrapper.style.minHeight = "100%";
3057
+ scrollWrapper.style.width = "max-content";
3058
+ scrollWrapper.style.height = "max-content";
3059
+ scrollWrapper.style.display = "flex";
3060
+ scrollWrapper.style.justifyContent = "center";
3061
+ scrollWrapper.style.alignItems = "flex-start";
3062
+ scrollWrapper.style.padding = "16px 8px";
3063
+ scrollWrapper.style.boxSizing = "border-box";
3064
+ sizer.appendChild(wrapper);
3065
+ scrollWrapper.appendChild(sizer);
3066
+ ctx.container.style.overflow = "auto";
3067
+ ctx.container.style.padding = "0";
2882
3068
  ctx.container.style.backgroundColor = "#f1f5f9";
2883
- ctx.container.appendChild(wrapper);
3069
+ ctx.container.appendChild(scrollWrapper);
2884
3070
  const styleOverride = document.createElement("style");
2885
3071
  styleOverride.textContent = `
2886
3072
  .fp-docx-wrapper .docx-wrapper {
@@ -2919,8 +3105,8 @@ var DocxPlugin = class {
2919
3105
  renderEndnotes: true,
2920
3106
  useBase64URL: true
2921
3107
  });
2922
- if (!ctx.container.contains(wrapper)) {
2923
- ctx.container.appendChild(wrapper);
3108
+ if (!sizer.contains(wrapper)) {
3109
+ sizer.appendChild(wrapper);
2924
3110
  }
2925
3111
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
2926
3112
  renderedSuccessfully = true;
@@ -2957,8 +3143,8 @@ var DocxPlugin = class {
2957
3143
  try {
2958
3144
  wrapper.innerHTML = "";
2959
3145
  this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
2960
- if (!ctx.container.contains(wrapper)) {
2961
- ctx.container.appendChild(wrapper);
3146
+ if (!sizer.contains(wrapper)) {
3147
+ sizer.appendChild(wrapper);
2962
3148
  }
2963
3149
  renderedSuccessfully = true;
2964
3150
  } catch (fallbackErr) {
@@ -2973,8 +3159,8 @@ var DocxPlugin = class {
2973
3159
  </p>
2974
3160
  </div>
2975
3161
  `;
2976
- if (!ctx.container.contains(wrapper)) {
2977
- ctx.container.appendChild(wrapper);
3162
+ if (!sizer.contains(wrapper)) {
3163
+ sizer.appendChild(wrapper);
2978
3164
  }
2979
3165
  }
2980
3166
  }
@@ -3067,31 +3253,40 @@ var DocxPlugin = class {
3067
3253
  }
3068
3254
  scale = 1;
3069
3255
  let rotation = 0;
3070
- let fitMode = "page";
3256
+ let fitMode = ctx?.options?.fitMode || "width";
3071
3257
  let isUserZoomed = false;
3072
- const calculateFitScale = (_mode = fitMode) => {
3258
+ const calculateFitScale = (mode = fitMode) => {
3073
3259
  const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3074
3260
  const elW = activeEl.offsetWidth || 816;
3261
+ const elH = activeEl.offsetHeight || 1056;
3075
3262
  const availW = Math.max(280, ctx.container.clientWidth - 32);
3263
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
3076
3264
  const sW = availW / elW;
3265
+ const sH = availH / elH;
3266
+ if (mode === "page") {
3267
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
3268
+ }
3077
3269
  return Math.max(0.35, Math.min(3, sW));
3078
3270
  };
3079
3271
  const applyTransform = () => {
3080
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3081
- wrapper.style.transformOrigin = "top center";
3082
3272
  const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3273
+ const elW = activeEl.offsetWidth || 816;
3083
3274
  const elH = activeEl.offsetHeight || 1056;
3084
- if (scale > 1) {
3085
- wrapper.style.marginBottom = `${Math.round(elH * scale - elH + 32)}px`;
3086
- } else {
3087
- wrapper.style.marginBottom = "32px";
3088
- }
3275
+ const isRotated90 = rotation % 180 !== 0;
3276
+ const boxW = Math.round((isRotated90 ? elH : elW) * scale);
3277
+ const boxH = Math.round((isRotated90 ? elW : elH) * scale);
3278
+ sizer.style.width = `${boxW}px`;
3279
+ sizer.style.height = `${boxH}px`;
3280
+ wrapper.style.width = `${elW}px`;
3281
+ wrapper.style.height = `${elH}px`;
3282
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3283
+ wrapper.style.transformOrigin = "center center";
3089
3284
  };
3090
3285
  let resizeObserver = null;
3091
3286
  if (typeof ResizeObserver !== "undefined") {
3092
3287
  resizeObserver = new ResizeObserver(() => {
3093
3288
  if (!isUserZoomed) {
3094
- const newFit = calculateFitScale("page");
3289
+ const newFit = calculateFitScale(fitMode);
3095
3290
  if (Math.abs(scale - newFit) > 0.015) {
3096
3291
  scale = newFit;
3097
3292
  applyTransform();
@@ -3101,7 +3296,7 @@ var DocxPlugin = class {
3101
3296
  resizeObserver.observe(ctx.container);
3102
3297
  }
3103
3298
  setTimeout(() => {
3104
- scale = calculateFitScale("page");
3299
+ scale = calculateFitScale(fitMode);
3105
3300
  applyTransform();
3106
3301
  }, 40);
3107
3302
  const cleanup = () => {
@@ -3110,7 +3305,7 @@ var DocxPlugin = class {
3110
3305
  for (const url of createdBlobUrls) {
3111
3306
  URL.revokeObjectURL(url);
3112
3307
  }
3113
- wrapper.remove();
3308
+ scrollWrapper.remove();
3114
3309
  styleOverride.remove();
3115
3310
  ctx.container.innerHTML = "";
3116
3311
  };
@@ -3140,13 +3335,22 @@ var DocxPlugin = class {
3140
3335
  },
3141
3336
  fitToPage: () => {
3142
3337
  isUserZoomed = false;
3338
+ fitMode = "page";
3143
3339
  scale = calculateFitScale("page");
3144
3340
  rotation = 0;
3145
3341
  applyTransform();
3146
3342
  },
3343
+ fitToWidth: () => {
3344
+ isUserZoomed = false;
3345
+ fitMode = "width";
3346
+ scale = calculateFitScale("width");
3347
+ rotation = 0;
3348
+ applyTransform();
3349
+ },
3147
3350
  resetZoom: () => {
3148
3351
  isUserZoomed = false;
3149
- scale = calculateFitScale("page");
3352
+ fitMode = ctx?.options?.fitMode || "width";
3353
+ scale = calculateFitScale(fitMode);
3150
3354
  rotation = 0;
3151
3355
  ctx.container.scrollTop = 0;
3152
3356
  ctx.container.scrollLeft = 0;
@@ -3611,6 +3815,16 @@ var ExcelPlugin = class {
3611
3815
  instance.resetZoom?.();
3612
3816
  }
3613
3817
  },
3818
+ {
3819
+ id: "fit-width",
3820
+ icon: "fit-width",
3821
+ label: "Fit to Width",
3822
+ type: "button",
3823
+ group: "zoom",
3824
+ execute: () => {
3825
+ instance.fitToWidth?.();
3826
+ }
3827
+ },
3614
3828
  {
3615
3829
  id: "download",
3616
3830
  icon: "download",
@@ -3657,7 +3871,7 @@ var ExcelPlugin = class {
3657
3871
  contentArea.style.flex = "1";
3658
3872
  contentArea.style.overflow = "auto";
3659
3873
  contentArea.style.padding = "16px";
3660
- contentArea.style.transformOrigin = "top left";
3874
+ contentArea.style.boxSizing = "border-box";
3661
3875
  const tabsArea = document.createElement("div");
3662
3876
  tabsArea.className = "fp-excel-tabs";
3663
3877
  tabsArea.style.display = "flex";
@@ -3671,18 +3885,35 @@ var ExcelPlugin = class {
3671
3885
  container.appendChild(tabsArea);
3672
3886
  ctx.container.appendChild(container);
3673
3887
  let scale = 1;
3888
+ let isUserZoomed = false;
3674
3889
  let currentSheetIndex = 1;
3675
3890
  let sheetNames = [];
3676
3891
  let wb = null;
3677
3892
  const applyTransform = () => {
3678
- contentArea.style.transform = `scale(${scale})`;
3679
- contentArea.style.transformOrigin = "top left";
3893
+ const sizer = contentArea.querySelector(".fp-excel-sizer");
3894
+ const table = contentArea.querySelector("table");
3895
+ if (!sizer || !table) return;
3896
+ if (!table._baseWidth && table.offsetWidth > 0) {
3897
+ table._baseWidth = table.offsetWidth;
3898
+ table._baseHeight = table.offsetHeight;
3899
+ }
3900
+ const baseW = table._baseWidth || table.offsetWidth || 800;
3901
+ const baseH = table._baseHeight || table.offsetHeight || 600;
3902
+ const scaledW = Math.round(baseW * scale);
3903
+ const scaledH = Math.round(baseH * scale);
3904
+ sizer.style.width = `${scaledW}px`;
3905
+ sizer.style.height = `${scaledH}px`;
3906
+ table.style.position = "absolute";
3907
+ table.style.top = "0";
3908
+ table.style.left = "0";
3909
+ table.style.transform = `scale(${scale})`;
3910
+ table.style.transformOrigin = "top left";
3680
3911
  };
3681
3912
  const calculateFitScale = () => {
3682
3913
  const table = contentArea.querySelector("table");
3683
3914
  if (!table) return 1;
3684
3915
  const availW = Math.max(280, container.clientWidth - 48);
3685
- const tW = table.offsetWidth || 800;
3916
+ const tW = table._baseWidth || table.offsetWidth || 800;
3686
3917
  return Math.max(0.4, Math.min(1, availW / tW));
3687
3918
  };
3688
3919
  try {
@@ -3702,9 +3933,13 @@ var ExcelPlugin = class {
3702
3933
  contentArea.innerHTML = '<div style="padding: 24px; color: #888;">Empty sheet</div>';
3703
3934
  return;
3704
3935
  }
3936
+ const sizer = document.createElement("div");
3937
+ sizer.className = "fp-excel-sizer";
3938
+ sizer.style.position = "relative";
3705
3939
  const html = XLSX.utils.sheet_to_html(ws, { id: "fp-sheet-table", editable: false });
3706
- contentArea.innerHTML = html;
3707
- const table = contentArea.querySelector("table");
3940
+ sizer.innerHTML = html;
3941
+ contentArea.appendChild(sizer);
3942
+ const table = sizer.querySelector("table");
3708
3943
  if (table) {
3709
3944
  table.style.borderCollapse = "collapse";
3710
3945
  table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
@@ -3739,13 +3974,17 @@ var ExcelPlugin = class {
3739
3974
  });
3740
3975
  ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
3741
3976
  requestAnimationFrame(() => {
3742
- scale = calculateFitScale();
3977
+ if (!isUserZoomed) {
3978
+ scale = calculateFitScale();
3979
+ }
3743
3980
  applyTransform();
3744
3981
  });
3745
3982
  };
3746
3983
  const ro = new ResizeObserver(() => {
3747
- scale = calculateFitScale();
3748
- applyTransform();
3984
+ if (!isUserZoomed) {
3985
+ scale = calculateFitScale();
3986
+ applyTransform();
3987
+ }
3749
3988
  });
3750
3989
  ro.observe(container);
3751
3990
  if (sheetNames.length > 0) {
@@ -3782,23 +4021,33 @@ var ExcelPlugin = class {
3782
4021
  return {
3783
4022
  destroy: cleanup,
3784
4023
  zoomIn: () => {
4024
+ isUserZoomed = true;
3785
4025
  scale += 0.15;
3786
4026
  applyTransform();
3787
4027
  },
3788
4028
  zoomOut: () => {
4029
+ isUserZoomed = true;
3789
4030
  scale = Math.max(0.2, scale - 0.15);
3790
4031
  applyTransform();
3791
4032
  },
3792
4033
  getZoom: () => scale,
3793
4034
  setZoom: (level) => {
4035
+ isUserZoomed = true;
3794
4036
  scale = level;
3795
4037
  applyTransform();
3796
4038
  },
3797
4039
  fitToPage: () => {
4040
+ isUserZoomed = false;
4041
+ scale = calculateFitScale();
4042
+ applyTransform();
4043
+ },
4044
+ fitToWidth: () => {
4045
+ isUserZoomed = false;
3798
4046
  scale = calculateFitScale();
3799
4047
  applyTransform();
3800
4048
  },
3801
4049
  resetZoom: () => {
4050
+ isUserZoomed = false;
3802
4051
  scale = calculateFitScale();
3803
4052
  contentArea.scrollTop = 0;
3804
4053
  contentArea.scrollLeft = 0;
@@ -3944,6 +4193,16 @@ var CsvPlugin = class {
3944
4193
  instance.resetZoom?.();
3945
4194
  }
3946
4195
  },
4196
+ {
4197
+ id: "fit-width",
4198
+ icon: "fit-width",
4199
+ label: "Fit to Width",
4200
+ type: "button",
4201
+ group: "zoom",
4202
+ execute: () => {
4203
+ instance.fitToWidth?.();
4204
+ }
4205
+ },
3947
4206
  {
3948
4207
  id: "download",
3949
4208
  icon: "download",
@@ -4030,21 +4289,41 @@ var CsvPlugin = class {
4030
4289
  container.appendChild(tableWrapper);
4031
4290
  ctx.container.appendChild(container);
4032
4291
  let scale = 1;
4292
+ let isUserZoomed = false;
4033
4293
  const calculateFitScale = () => {
4034
4294
  const availW = Math.max(280, container.clientWidth - 48);
4035
- const tW = table.offsetWidth || 800;
4295
+ const tW = table._baseWidth || table.offsetWidth || 800;
4036
4296
  return Math.max(0.4, Math.min(1, availW / tW));
4037
4297
  };
4038
4298
  const applyScale = () => {
4039
- tableWrapper.style.transform = `scale(${scale})`;
4299
+ if (!table._baseWidth && table.offsetWidth > 0) {
4300
+ table._baseWidth = table.offsetWidth;
4301
+ table._baseHeight = table.offsetHeight;
4302
+ }
4303
+ const baseW = table._baseWidth || table.offsetWidth || 800;
4304
+ const baseH = table._baseHeight || table.offsetHeight || 600;
4305
+ const scaledW = Math.round(baseW * scale);
4306
+ const scaledH = Math.round(baseH * scale);
4307
+ tableWrapper.style.position = "relative";
4308
+ tableWrapper.style.width = `${scaledW}px`;
4309
+ tableWrapper.style.height = `${scaledH}px`;
4310
+ table.style.position = "absolute";
4311
+ table.style.top = "0";
4312
+ table.style.left = "0";
4313
+ table.style.transform = `scale(${scale})`;
4314
+ table.style.transformOrigin = "top left";
4040
4315
  };
4041
4316
  requestAnimationFrame(() => {
4042
- scale = calculateFitScale();
4317
+ if (!isUserZoomed) {
4318
+ scale = calculateFitScale();
4319
+ }
4043
4320
  applyScale();
4044
4321
  });
4045
4322
  const ro = new ResizeObserver(() => {
4046
- scale = calculateFitScale();
4047
- applyScale();
4323
+ if (!isUserZoomed) {
4324
+ scale = calculateFitScale();
4325
+ applyScale();
4326
+ }
4048
4327
  });
4049
4328
  ro.observe(container);
4050
4329
  const cleanup = () => {
@@ -4117,23 +4396,33 @@ var CsvPlugin = class {
4117
4396
  }];
4118
4397
  },
4119
4398
  zoomIn: () => {
4399
+ isUserZoomed = true;
4120
4400
  scale += 0.1;
4121
4401
  applyScale();
4122
4402
  },
4123
4403
  zoomOut: () => {
4404
+ isUserZoomed = true;
4124
4405
  scale = Math.max(0.2, scale - 0.1);
4125
4406
  applyScale();
4126
4407
  },
4127
4408
  getZoom: () => scale,
4128
4409
  setZoom: (level) => {
4410
+ isUserZoomed = true;
4129
4411
  scale = level;
4130
4412
  applyScale();
4131
4413
  },
4132
4414
  fitToPage: () => {
4415
+ isUserZoomed = false;
4416
+ scale = calculateFitScale();
4417
+ applyScale();
4418
+ },
4419
+ fitToWidth: () => {
4420
+ isUserZoomed = false;
4133
4421
  scale = calculateFitScale();
4134
4422
  applyScale();
4135
4423
  },
4136
4424
  resetZoom: () => {
4425
+ isUserZoomed = false;
4137
4426
  scale = calculateFitScale();
4138
4427
  container.scrollTop = 0;
4139
4428
  container.scrollLeft = 0;
@@ -4264,6 +4553,16 @@ var CodePlugin = class {
4264
4553
  instance.resetZoom?.();
4265
4554
  }
4266
4555
  },
4556
+ {
4557
+ id: "fit-width",
4558
+ icon: "fit-width",
4559
+ label: "Fit to Width",
4560
+ type: "button",
4561
+ group: "zoom",
4562
+ execute: () => {
4563
+ instance.fitToWidth?.();
4564
+ }
4565
+ },
4267
4566
  {
4268
4567
  id: "copy",
4269
4568
  icon: "copy",
@@ -4369,15 +4668,27 @@ var CodePlugin = class {
4369
4668
  let isUserZoomed = false;
4370
4669
  const pre = document.createElement("pre");
4371
4670
  const code = document.createElement("code");
4671
+ const sizer = document.createElement("div");
4672
+ const scrollWrapper = document.createElement("div");
4372
4673
  if (isTxt) {
4373
- container.style.overflowX = "hidden";
4374
- container.style.overflowY = "auto";
4674
+ container.style.overflow = "auto";
4375
4675
  container.style.backgroundColor = "#f1f5f9";
4376
- container.style.padding = "16px 8px";
4377
- container.style.boxSizing = "border-box";
4378
- container.style.display = "flex";
4379
- container.style.flexDirection = "column";
4380
- container.style.alignItems = "center";
4676
+ scrollWrapper.className = "fp-code-scroll-wrapper";
4677
+ scrollWrapper.style.minWidth = "100%";
4678
+ scrollWrapper.style.minHeight = "100%";
4679
+ scrollWrapper.style.width = "max-content";
4680
+ scrollWrapper.style.height = "max-content";
4681
+ scrollWrapper.style.display = "flex";
4682
+ scrollWrapper.style.justifyContent = "center";
4683
+ scrollWrapper.style.alignItems = "flex-start";
4684
+ scrollWrapper.style.padding = "16px 8px";
4685
+ scrollWrapper.style.boxSizing = "border-box";
4686
+ sizer.className = "fp-code-sizer";
4687
+ sizer.style.position = "relative";
4688
+ sizer.style.flexShrink = "0";
4689
+ sizer.style.display = "flex";
4690
+ sizer.style.justifyContent = "center";
4691
+ sizer.style.alignItems = "center";
4381
4692
  pre.style.margin = "0 auto";
4382
4693
  pre.style.fontFamily = "Consolas, 'Courier New', monospace";
4383
4694
  pre.style.fontSize = "13px";
@@ -4392,8 +4703,9 @@ var CodePlugin = class {
4392
4703
  pre.style.boxSizing = "border-box";
4393
4704
  pre.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
4394
4705
  pre.style.borderRadius = "4px";
4395
- pre.style.transformOrigin = "top center";
4706
+ pre.style.transformOrigin = "center center";
4396
4707
  pre.style.transition = "transform 0.15s ease";
4708
+ pre.style.flexShrink = "0";
4397
4709
  } else {
4398
4710
  container.style.overflow = "auto";
4399
4711
  container.style.backgroundColor = "#1e1e1e";
@@ -4426,7 +4738,13 @@ var CodePlugin = class {
4426
4738
  };
4427
4739
  renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
4428
4740
  pre.appendChild(code);
4429
- container.appendChild(pre);
4741
+ if (isTxt) {
4742
+ sizer.appendChild(pre);
4743
+ scrollWrapper.appendChild(sizer);
4744
+ container.appendChild(scrollWrapper);
4745
+ } else {
4746
+ container.appendChild(pre);
4747
+ }
4430
4748
  const showPage = (pageNum) => {
4431
4749
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4432
4750
  renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
@@ -4443,10 +4761,16 @@ var CodePlugin = class {
4443
4761
  };
4444
4762
  const updateTransform = () => {
4445
4763
  if (isTxt) {
4764
+ const elW = 816;
4765
+ const baseH = pre.offsetHeight || 1056;
4766
+ const isRotated90 = rotation % 180 !== 0;
4767
+ const boxW = Math.round((isRotated90 ? baseH : elW) * zoomLevel);
4768
+ const boxH = Math.round((isRotated90 ? elW : baseH) * zoomLevel);
4769
+ sizer.style.width = `${boxW}px`;
4770
+ sizer.style.height = `${boxH}px`;
4771
+ pre.style.width = `${elW}px`;
4446
4772
  pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
4447
- const scaledH = 1056 * zoomLevel;
4448
- const extraH = Math.max(0, scaledH - 1056);
4449
- pre.style.marginBottom = `${extraH + 32}px`;
4773
+ pre.style.transformOrigin = "center center";
4450
4774
  } else {
4451
4775
  pre.style.transform = `rotate(${rotation}deg)`;
4452
4776
  pre.style.fontSize = `${fontSize}px`;
@@ -4543,6 +4867,13 @@ var CodePlugin = class {
4543
4867
  zoomLevel = isTxt ? calculateTxtFit() : 1;
4544
4868
  updateTransform();
4545
4869
  },
4870
+ fitToWidth: () => {
4871
+ isUserZoomed = false;
4872
+ fontSize = 13;
4873
+ rotation = 0;
4874
+ zoomLevel = isTxt ? calculateTxtFit() : 1;
4875
+ updateTransform();
4876
+ },
4546
4877
  resetZoom: () => {
4547
4878
  isUserZoomed = false;
4548
4879
  fontSize = 13;
@@ -4627,6 +4958,14 @@ var ArchivePlugin = class {
4627
4958
  group: "zoom",
4628
4959
  execute: () => instance.resetZoom?.()
4629
4960
  },
4961
+ {
4962
+ id: "fit-width",
4963
+ icon: "fit-width",
4964
+ label: "Fit to Width",
4965
+ type: "button",
4966
+ group: "zoom",
4967
+ execute: () => instance.fitToWidth?.()
4968
+ },
4630
4969
  {
4631
4970
  id: "download",
4632
4971
  icon: "download",
@@ -4652,16 +4991,56 @@ var ArchivePlugin = class {
4652
4991
  width: 100%;
4653
4992
  height: 100%;
4654
4993
  overflow: auto;
4655
- padding: 24px;
4656
4994
  box-sizing: border-box;
4657
4995
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
4658
- transform-origin: top left;
4659
- transition: transform 0.2s ease;
4660
4996
  `;
4997
+ const scrollWrapper = document.createElement("div");
4998
+ scrollWrapper.className = "fp-archive-scroll-wrapper";
4999
+ scrollWrapper.style.cssText = `
5000
+ min-width: 100%;
5001
+ min-height: 100%;
5002
+ width: max-content;
5003
+ height: max-content;
5004
+ display: flex;
5005
+ justify-content: center;
5006
+ align-items: flex-start;
5007
+ padding: 24px;
5008
+ box-sizing: border-box;
5009
+ `;
5010
+ const sizer = document.createElement("div");
5011
+ sizer.className = "fp-archive-sizer";
5012
+ sizer.style.cssText = `
5013
+ position: relative;
5014
+ flex-shrink: 0;
5015
+ display: flex;
5016
+ justify-content: center;
5017
+ align-items: flex-start;
5018
+ `;
5019
+ const card = document.createElement("div");
5020
+ card.className = "fp-archive-card";
5021
+ card.style.cssText = `
5022
+ width: 900px;
5023
+ box-sizing: border-box;
5024
+ transform-origin: top center;
5025
+ transition: transform 0.15s ease;
5026
+ flex-shrink: 0;
5027
+ `;
5028
+ sizer.appendChild(card);
5029
+ scrollWrapper.appendChild(sizer);
5030
+ wrapper.appendChild(scrollWrapper);
4661
5031
  ctx.container.innerHTML = "";
4662
5032
  ctx.container.style.overflow = "auto";
4663
5033
  ctx.container.appendChild(wrapper);
4664
5034
  let scale = 1;
5035
+ const applyTransform = () => {
5036
+ const boxW = Math.round(900 * scale);
5037
+ const cardH = card.offsetHeight || 600;
5038
+ const boxH = Math.round(cardH * scale);
5039
+ sizer.style.width = `${boxW}px`;
5040
+ sizer.style.height = `${boxH}px`;
5041
+ card.style.transform = `scale(${scale})`;
5042
+ card.style.transformOrigin = "top center";
5043
+ };
4665
5044
  const entries = await new Promise((resolve, reject) => {
4666
5045
  unzip(new Uint8Array(ctx.buffer), (err, unzipped) => {
4667
5046
  if (err) {
@@ -4685,7 +5064,7 @@ var ArchivePlugin = class {
4685
5064
  });
4686
5065
  const totalUncompressedSize = entries.reduce((acc, e) => acc + e.size, 0);
4687
5066
  const renderUI = (filteredEntries) => {
4688
- wrapper.innerHTML = `
5067
+ card.innerHTML = `
4689
5068
  <div style="max-width: 900px; margin: 0 auto; background: var(--fp-bg, #ffffff); border: 1px solid var(--fp-border, #e5e7eb); border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); overflow: hidden;">
4690
5069
  <div style="padding: 16px 20px; background: var(--fp-toolbar-bg, #f9fafb); border-bottom: 1px solid var(--fp-border, #e5e7eb); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px;">
4691
5070
  <div>
@@ -4715,7 +5094,7 @@ var ArchivePlugin = class {
4715
5094
  </div>
4716
5095
  </div>
4717
5096
  `;
4718
- const tbody = wrapper.querySelector("#fp-archive-body");
5097
+ const tbody = card.querySelector("#fp-archive-body");
4719
5098
  if (filteredEntries.length === 0) {
4720
5099
  tbody.innerHTML = `<tr><td colspan="3" style="padding: 24px; text-align: center; color: #9ca3af;">No matching files found</td></tr>`;
4721
5100
  } else {
@@ -4744,19 +5123,20 @@ var ArchivePlugin = class {
4744
5123
  }
4745
5124
  });
4746
5125
  });
4747
- const searchInput = wrapper.querySelector("#fp-archive-search");
5126
+ const searchInput = card.querySelector("#fp-archive-search");
4748
5127
  if (searchInput) {
4749
5128
  searchInput.addEventListener("input", () => {
4750
5129
  const q = searchInput.value.toLowerCase().trim();
4751
5130
  const filtered = q ? entries.filter((e) => e.path.toLowerCase().includes(q)) : entries;
4752
5131
  renderUI(filtered);
4753
- const newSearch = wrapper.querySelector("#fp-archive-search");
5132
+ const newSearch = card.querySelector("#fp-archive-search");
4754
5133
  if (newSearch) {
4755
5134
  newSearch.value = q;
4756
5135
  newSearch.focus();
4757
5136
  }
4758
5137
  });
4759
5138
  }
5139
+ applyTransform();
4760
5140
  };
4761
5141
  renderUI(entries);
4762
5142
  const cleanup = () => {
@@ -4768,26 +5148,30 @@ var ArchivePlugin = class {
4768
5148
  destroy: cleanup,
4769
5149
  zoomIn: () => {
4770
5150
  scale += 0.1;
4771
- wrapper.style.transform = `scale(${scale})`;
5151
+ applyTransform();
4772
5152
  },
4773
5153
  zoomOut: () => {
4774
5154
  scale = Math.max(0.3, scale - 0.1);
4775
- wrapper.style.transform = `scale(${scale})`;
5155
+ applyTransform();
4776
5156
  },
4777
5157
  getZoom: () => scale,
4778
5158
  setZoom: (level) => {
4779
5159
  scale = level;
4780
- wrapper.style.transform = `scale(${scale})`;
5160
+ applyTransform();
4781
5161
  },
4782
5162
  fitToPage: () => {
4783
5163
  scale = 1;
4784
- wrapper.style.transform = "scale(1)";
5164
+ applyTransform();
5165
+ },
5166
+ fitToWidth: () => {
5167
+ scale = 1;
5168
+ applyTransform();
4785
5169
  },
4786
5170
  resetZoom: () => {
4787
5171
  scale = 1;
4788
- wrapper.style.transform = "scale(1)";
4789
- ctx.container.scrollTop = 0;
4790
- ctx.container.scrollLeft = 0;
5172
+ wrapper.scrollTop = 0;
5173
+ wrapper.scrollLeft = 0;
5174
+ applyTransform();
4791
5175
  },
4792
5176
  download: () => {
4793
5177
  downloadFile(ctx.buffer, ctx.metadata.name || "archive.zip", "application/zip");
@@ -4847,6 +5231,14 @@ var MarkdownPlugin = class {
4847
5231
  group: "zoom",
4848
5232
  execute: () => instance.resetZoom?.()
4849
5233
  },
5234
+ {
5235
+ id: "fit-width",
5236
+ icon: "fit-width",
5237
+ label: "Fit to Width",
5238
+ type: "button",
5239
+ group: "zoom",
5240
+ execute: () => instance.fitToWidth?.()
5241
+ },
4850
5242
  {
4851
5243
  id: "copy",
4852
5244
  icon: "copy",
@@ -5000,6 +5392,10 @@ var MarkdownPlugin = class {
5000
5392
  fontSize = 15;
5001
5393
  wrapper.style.fontSize = "15px";
5002
5394
  },
5395
+ fitToWidth: () => {
5396
+ fontSize = 15;
5397
+ wrapper.style.fontSize = "15px";
5398
+ },
5003
5399
  resetZoom: () => {
5004
5400
  fontSize = 15;
5005
5401
  wrapper.style.fontSize = "15px";
@@ -5107,6 +5503,14 @@ var PptxPlugin = class {
5107
5503
  group: "zoom",
5108
5504
  execute: () => instance.resetZoom?.()
5109
5505
  },
5506
+ {
5507
+ id: "fit-width",
5508
+ icon: "fit-width",
5509
+ label: "Fit to Width",
5510
+ type: "button",
5511
+ group: "zoom",
5512
+ execute: () => instance.fitToWidth?.()
5513
+ },
5110
5514
  {
5111
5515
  id: "rotate-cw",
5112
5516
  icon: "rotate-cw",
@@ -5146,12 +5550,30 @@ var PptxPlugin = class {
5146
5550
  width: 100%;
5147
5551
  height: 100%;
5148
5552
  overflow: auto;
5553
+ box-sizing: border-box;
5554
+ background: var(--fp-bg-canvas, #525659);
5555
+ `;
5556
+ const scrollWrapper = document.createElement("div");
5557
+ scrollWrapper.className = "fp-pptx-scroll-wrapper";
5558
+ scrollWrapper.style.cssText = `
5559
+ min-width: 100%;
5560
+ min-height: 100%;
5561
+ width: max-content;
5562
+ height: max-content;
5149
5563
  display: flex;
5150
5564
  justify-content: center;
5151
- align-items: flex-start;
5565
+ align-items: center;
5152
5566
  padding: 24px;
5153
5567
  box-sizing: border-box;
5154
- background: var(--fp-bg-canvas, #525659);
5568
+ `;
5569
+ const sizer = document.createElement("div");
5570
+ sizer.className = "fp-pptx-sizer";
5571
+ sizer.style.cssText = `
5572
+ position: relative;
5573
+ flex-shrink: 0;
5574
+ display: flex;
5575
+ justify-content: center;
5576
+ align-items: center;
5155
5577
  `;
5156
5578
  const slideContainer = document.createElement("div");
5157
5579
  slideContainer.className = "fp-slide-container";
@@ -5160,39 +5582,65 @@ var PptxPlugin = class {
5160
5582
  border-radius: 4px;
5161
5583
  overflow: hidden;
5162
5584
  background: #ffffff;
5163
- transform-origin: top center;
5585
+ transform-origin: center center;
5164
5586
  transition: transform 0.2s ease;
5165
- max-width: calc(100% - 32px);
5166
- max-height: calc(100% - 48px);
5587
+ flex-shrink: 0;
5167
5588
  `;
5168
5589
  const canvas = document.createElement("canvas");
5169
5590
  slideContainer.appendChild(canvas);
5170
- wrapper.appendChild(slideContainer);
5591
+ sizer.appendChild(slideContainer);
5592
+ scrollWrapper.appendChild(sizer);
5593
+ wrapper.appendChild(scrollWrapper);
5171
5594
  ctx.container.innerHTML = "";
5172
5595
  ctx.container.style.overflow = "auto";
5173
5596
  ctx.container.appendChild(wrapper);
5174
- const calculateFitScale = () => {
5175
- const availW = Math.max(200, ctx.container.clientWidth - 48);
5176
- const availH = Math.max(200, ctx.container.clientHeight - 72);
5597
+ let fitMode = ctx?.options?.fitMode || "width";
5598
+ let isUserZoomed = false;
5599
+ const calculateFitScale = (mode = fitMode) => {
5600
+ const availW = Math.max(200, ctx.container.clientWidth - 32);
5601
+ const availH = Math.max(200, ctx.container.clientHeight - 32);
5177
5602
  const cW = canvas.offsetWidth || 1280;
5178
5603
  const cH = canvas.offsetHeight || 720;
5179
- return Math.min(1, Math.min(availW / cW, availH / cH));
5604
+ if (mode === "page") {
5605
+ return Math.min(2.5, Math.min(availW / cW, availH / cH));
5606
+ }
5607
+ return Math.min(2.5, availW / cW);
5180
5608
  };
5181
5609
  const applyTransform = () => {
5610
+ const cW = canvas.offsetWidth || 1280;
5611
+ const cH = canvas.offsetHeight || 720;
5612
+ const isRotated90 = rotation % 180 !== 0;
5613
+ const boxW = Math.round((isRotated90 ? cH : cW) * scale);
5614
+ const boxH = Math.round((isRotated90 ? cW : cH) * scale);
5615
+ sizer.style.width = `${boxW}px`;
5616
+ sizer.style.height = `${boxH}px`;
5617
+ slideContainer.style.width = `${cW}px`;
5618
+ slideContainer.style.height = `${cH}px`;
5182
5619
  slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5620
+ slideContainer.style.transformOrigin = "center center";
5183
5621
  };
5184
5622
  const renderCurrentSlide = async () => {
5185
5623
  try {
5186
5624
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
5187
5625
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
5188
- scale = calculateFitScale();
5626
+ if (!isUserZoomed) {
5627
+ scale = calculateFitScale(fitMode);
5628
+ }
5189
5629
  applyTransform();
5190
5630
  } catch (err) {
5191
5631
  console.error("[PptxPlugin] Failed to render slide:", err);
5192
5632
  }
5193
5633
  };
5194
5634
  await renderCurrentSlide();
5635
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5636
+ if (!isUserZoomed) {
5637
+ scale = calculateFitScale(fitMode);
5638
+ applyTransform();
5639
+ }
5640
+ }) : null;
5641
+ ro?.observe(ctx.container);
5195
5642
  const cleanup = () => {
5643
+ ro?.disconnect();
5196
5644
  renderer.destroy();
5197
5645
  wrapper.remove();
5198
5646
  ctx.container.innerHTML = "";
@@ -5209,28 +5657,44 @@ var PptxPlugin = class {
5209
5657
  getPageCount: () => slideCount,
5210
5658
  getCurrentPage: () => currentSlide,
5211
5659
  zoomIn: () => {
5660
+ isUserZoomed = true;
5212
5661
  scale += 0.15;
5213
5662
  applyTransform();
5214
5663
  },
5215
5664
  zoomOut: () => {
5665
+ isUserZoomed = true;
5216
5666
  scale = Math.max(0.2, scale - 0.15);
5217
5667
  applyTransform();
5218
5668
  },
5219
5669
  getZoom: () => scale,
5220
5670
  setZoom: (level) => {
5671
+ isUserZoomed = true;
5221
5672
  scale = level;
5222
5673
  applyTransform();
5223
5674
  },
5224
5675
  fitToPage: () => {
5225
- scale = calculateFitScale();
5676
+ isUserZoomed = false;
5677
+ fitMode = "page";
5678
+ scale = calculateFitScale("page");
5679
+ rotation = 0;
5680
+ applyTransform();
5681
+ },
5682
+ fitToWidth: () => {
5683
+ isUserZoomed = false;
5684
+ fitMode = "width";
5685
+ scale = calculateFitScale("width");
5226
5686
  rotation = 0;
5227
5687
  applyTransform();
5228
5688
  },
5229
5689
  resetZoom: () => {
5230
- scale = calculateFitScale();
5690
+ isUserZoomed = false;
5691
+ fitMode = ctx?.options?.fitMode || "width";
5692
+ scale = calculateFitScale(fitMode);
5231
5693
  rotation = 0;
5232
5694
  wrapper.scrollTop = 0;
5233
5695
  wrapper.scrollLeft = 0;
5696
+ ctx.container.scrollTop = 0;
5697
+ ctx.container.scrollLeft = 0;
5234
5698
  applyTransform();
5235
5699
  },
5236
5700
  rotateCW: () => {
@@ -5675,6 +6139,14 @@ var RtfPlugin = class {
5675
6139
  group: "zoom",
5676
6140
  execute: () => instance.resetZoom?.()
5677
6141
  },
6142
+ {
6143
+ id: "fit-width",
6144
+ icon: "fit-width",
6145
+ label: "Fit to Width",
6146
+ type: "button",
6147
+ group: "zoom",
6148
+ execute: () => instance.fitToWidth?.()
6149
+ },
5678
6150
  {
5679
6151
  id: "rotate-cw",
5680
6152
  icon: "rotate-cw",
@@ -5785,13 +6257,33 @@ var RtfPlugin = class {
5785
6257
  wrapper.style.flexDirection = "column";
5786
6258
  wrapper.style.alignItems = "center";
5787
6259
  wrapper.style.backgroundColor = "transparent";
5788
- wrapper.style.transformOrigin = "top center";
6260
+ wrapper.style.transformOrigin = "center center";
5789
6261
  wrapper.style.transition = "transform 0.15s ease";
5790
- ctx.container.style.overflowX = "hidden";
5791
- ctx.container.style.overflowY = "auto";
5792
- ctx.container.style.padding = "16px 8px";
6262
+ wrapper.style.flexShrink = "0";
6263
+ const sizer = document.createElement("div");
6264
+ sizer.className = "fp-rtf-sizer";
6265
+ sizer.style.position = "relative";
6266
+ sizer.style.flexShrink = "0";
6267
+ sizer.style.display = "flex";
6268
+ sizer.style.justifyContent = "center";
6269
+ sizer.style.alignItems = "center";
6270
+ const scrollWrapper = document.createElement("div");
6271
+ scrollWrapper.className = "fp-rtf-scroll-wrapper";
6272
+ scrollWrapper.style.minWidth = "100%";
6273
+ scrollWrapper.style.minHeight = "100%";
6274
+ scrollWrapper.style.width = "max-content";
6275
+ scrollWrapper.style.height = "max-content";
6276
+ scrollWrapper.style.display = "flex";
6277
+ scrollWrapper.style.justifyContent = "center";
6278
+ scrollWrapper.style.alignItems = "flex-start";
6279
+ scrollWrapper.style.padding = "16px 8px";
6280
+ scrollWrapper.style.boxSizing = "border-box";
6281
+ sizer.appendChild(wrapper);
6282
+ scrollWrapper.appendChild(sizer);
6283
+ ctx.container.style.overflow = "auto";
6284
+ ctx.container.style.padding = "0";
5793
6285
  ctx.container.style.backgroundColor = "#f1f5f9";
5794
- ctx.container.appendChild(wrapper);
6286
+ ctx.container.appendChild(scrollWrapper);
5795
6287
  let scale = 1;
5796
6288
  let rotation = 0;
5797
6289
  let pageElements = [];
@@ -5810,12 +6302,17 @@ var RtfPlugin = class {
5810
6302
  return Math.max(0.4, Math.min(3, sW));
5811
6303
  };
5812
6304
  const applyTransform = () => {
5813
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5814
- wrapper.style.transformOrigin = "top center";
6305
+ const elW = pageWidth;
5815
6306
  const baseH = pageHeight;
5816
- const scaledH = baseH * scale;
5817
- const extraH = Math.max(0, scaledH - baseH);
5818
- wrapper.style.marginBottom = `${extraH + 32}px`;
6307
+ const isRotated90 = rotation % 180 !== 0;
6308
+ const boxW = Math.round((isRotated90 ? baseH : elW) * scale);
6309
+ const boxH = Math.round((isRotated90 ? elW : baseH) * scale);
6310
+ sizer.style.width = `${boxW}px`;
6311
+ sizer.style.height = `${boxH}px`;
6312
+ wrapper.style.width = `${elW}px`;
6313
+ wrapper.style.height = `${baseH}px`;
6314
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6315
+ wrapper.style.transformOrigin = "center center";
5819
6316
  };
5820
6317
  const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5821
6318
  if (!isUserZoomed) {
@@ -6086,7 +6583,7 @@ var RtfPlugin = class {
6086
6583
  }
6087
6584
  const cleanup = () => {
6088
6585
  resizeObserver?.disconnect();
6089
- wrapper.remove();
6586
+ scrollWrapper.remove();
6090
6587
  ctx.container.innerHTML = "";
6091
6588
  };
6092
6589
  ctx.signal.addEventListener("abort", cleanup);
@@ -6138,6 +6635,13 @@ var RtfPlugin = class {
6138
6635
  applyTransform();
6139
6636
  },
6140
6637
  fitToPage: () => {
6638
+ isUserZoomed = false;
6639
+ fitMode = "page";
6640
+ scale = calculateFitScale("page");
6641
+ rotation = 0;
6642
+ applyTransform();
6643
+ },
6644
+ fitToWidth: () => {
6141
6645
  isUserZoomed = false;
6142
6646
  fitMode = "width";
6143
6647
  scale = calculateFitScale("width");
@@ -6146,8 +6650,8 @@ var RtfPlugin = class {
6146
6650
  },
6147
6651
  resetZoom: () => {
6148
6652
  isUserZoomed = false;
6149
- fitMode = "width";
6150
- scale = calculateFitScale("width");
6653
+ fitMode = ctx?.options?.fitMode || "width";
6654
+ scale = calculateFitScale(fitMode);
6151
6655
  rotation = 0;
6152
6656
  ctx.container.scrollTop = 0;
6153
6657
  ctx.container.scrollLeft = 0;
@@ -6228,6 +6732,14 @@ var HtmlPreviewPlugin = class {
6228
6732
  group: "zoom",
6229
6733
  execute: () => instance.resetZoom?.()
6230
6734
  },
6735
+ {
6736
+ id: "fit-width",
6737
+ icon: "fit-width",
6738
+ label: "Fit to Width",
6739
+ type: "button",
6740
+ group: "zoom",
6741
+ execute: () => instance.fitToWidth?.()
6742
+ },
6231
6743
  {
6232
6744
  id: "copy",
6233
6745
  icon: "copy",
@@ -6261,23 +6773,44 @@ var HtmlPreviewPlugin = class {
6261
6773
  ADD_TAGS: ["style", "link"],
6262
6774
  ADD_ATTR: ["target", "rel"]
6263
6775
  });
6776
+ const sizer = document.createElement("div");
6777
+ sizer.className = "fp-html-sizer";
6778
+ sizer.style.position = "relative";
6264
6779
  const iframe = document.createElement("iframe");
6265
6780
  iframe.className = "fp-html-iframe";
6266
- iframe.style.width = "100%";
6267
- iframe.style.height = "100%";
6268
6781
  iframe.style.border = "none";
6269
6782
  iframe.style.backgroundColor = "#ffffff";
6270
6783
  iframe.style.transformOrigin = "top left";
6271
6784
  iframe.style.transition = "transform 0.2s ease";
6272
6785
  iframe.sandbox.add("allow-same-origin");
6786
+ sizer.appendChild(iframe);
6273
6787
  ctx.container.style.overflow = "auto";
6274
6788
  ctx.container.style.width = "100%";
6275
6789
  ctx.container.style.height = "100%";
6276
- ctx.container.appendChild(iframe);
6790
+ ctx.container.innerHTML = "";
6791
+ ctx.container.appendChild(sizer);
6277
6792
  iframe.srcdoc = sanitized;
6278
6793
  let scale = 1;
6794
+ const applyTransform = () => {
6795
+ const baseW = Math.max(600, ctx.container.clientWidth || 800);
6796
+ const baseH = Math.max(400, ctx.container.clientHeight || 600);
6797
+ const scaledW = Math.round(baseW * scale);
6798
+ const scaledH = Math.round(baseH * scale);
6799
+ sizer.style.width = `${scaledW}px`;
6800
+ sizer.style.height = `${scaledH}px`;
6801
+ iframe.style.position = "absolute";
6802
+ iframe.style.top = "0";
6803
+ iframe.style.left = "0";
6804
+ iframe.style.width = `${baseW}px`;
6805
+ iframe.style.height = `${baseH}px`;
6806
+ iframe.style.transform = `scale(${scale})`;
6807
+ iframe.style.transformOrigin = "top left";
6808
+ };
6809
+ requestAnimationFrame(() => {
6810
+ applyTransform();
6811
+ });
6279
6812
  const cleanup = () => {
6280
- iframe.remove();
6813
+ sizer.remove();
6281
6814
  ctx.container.innerHTML = "";
6282
6815
  };
6283
6816
  ctx.signal.addEventListener("abort", cleanup);
@@ -6285,26 +6818,30 @@ var HtmlPreviewPlugin = class {
6285
6818
  destroy: cleanup,
6286
6819
  zoomIn: () => {
6287
6820
  scale += 0.1;
6288
- iframe.style.transform = `scale(${scale})`;
6821
+ applyTransform();
6289
6822
  },
6290
6823
  zoomOut: () => {
6291
6824
  scale = Math.max(0.2, scale - 0.1);
6292
- iframe.style.transform = `scale(${scale})`;
6825
+ applyTransform();
6293
6826
  },
6294
6827
  getZoom: () => scale,
6295
6828
  setZoom: (level) => {
6296
6829
  scale = level;
6297
- iframe.style.transform = `scale(${scale})`;
6830
+ applyTransform();
6298
6831
  },
6299
6832
  fitToPage: () => {
6300
6833
  scale = 1;
6301
- iframe.style.transform = "scale(1)";
6834
+ applyTransform();
6835
+ },
6836
+ fitToWidth: () => {
6837
+ scale = 1;
6838
+ applyTransform();
6302
6839
  },
6303
6840
  resetZoom: () => {
6304
6841
  scale = 1;
6305
- iframe.style.transform = "scale(1)";
6306
6842
  ctx.container.scrollTop = 0;
6307
6843
  ctx.container.scrollLeft = 0;
6844
+ applyTransform();
6308
6845
  },
6309
6846
  copy: () => {
6310
6847
  navigator.clipboard.writeText(rawHtml);
@@ -6414,6 +6951,14 @@ var OpenDocumentPlugin = class {
6414
6951
  group: "zoom",
6415
6952
  execute: () => instance.resetZoom?.()
6416
6953
  },
6954
+ {
6955
+ id: "fit-width",
6956
+ icon: "fit-width",
6957
+ label: "Fit to Width",
6958
+ type: "button",
6959
+ group: "zoom",
6960
+ execute: () => instance.fitToWidth?.()
6961
+ },
6417
6962
  {
6418
6963
  id: "rotate-cw",
6419
6964
  icon: "rotate-cw",
@@ -6479,10 +7024,26 @@ var OpenDocumentPlugin = class {
6479
7024
  container.className = "fp-odf-container";
6480
7025
  container.style.width = "100%";
6481
7026
  container.style.height = "100%";
6482
- container.style.overflowX = "hidden";
6483
- container.style.overflowY = "auto";
6484
- container.style.padding = "16px 8px";
7027
+ container.style.overflow = "auto";
6485
7028
  container.style.backgroundColor = "#f1f5f9";
7029
+ const scrollWrapper = document.createElement("div");
7030
+ scrollWrapper.className = "fp-odf-scroll-wrapper";
7031
+ scrollWrapper.style.minWidth = "100%";
7032
+ scrollWrapper.style.minHeight = "100%";
7033
+ scrollWrapper.style.width = "max-content";
7034
+ scrollWrapper.style.height = "max-content";
7035
+ scrollWrapper.style.display = "flex";
7036
+ scrollWrapper.style.justifyContent = "center";
7037
+ scrollWrapper.style.alignItems = "flex-start";
7038
+ scrollWrapper.style.padding = "16px 8px";
7039
+ scrollWrapper.style.boxSizing = "border-box";
7040
+ const sizer = document.createElement("div");
7041
+ sizer.className = "fp-odf-sizer";
7042
+ sizer.style.position = "relative";
7043
+ sizer.style.flexShrink = "0";
7044
+ sizer.style.display = "flex";
7045
+ sizer.style.justifyContent = "center";
7046
+ sizer.style.alignItems = "center";
6486
7047
  const wrapper = document.createElement("div");
6487
7048
  wrapper.className = "fp-odf-wrapper";
6488
7049
  wrapper.style.margin = "0 auto";
@@ -6491,9 +7052,12 @@ var OpenDocumentPlugin = class {
6491
7052
  wrapper.style.backgroundColor = "#ffffff";
6492
7053
  wrapper.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
6493
7054
  wrapper.style.borderRadius = "4px";
6494
- wrapper.style.transformOrigin = "top center";
7055
+ wrapper.style.transformOrigin = "center center";
6495
7056
  wrapper.style.transition = "transform 0.15s ease";
6496
- container.appendChild(wrapper);
7057
+ wrapper.style.flexShrink = "0";
7058
+ sizer.appendChild(wrapper);
7059
+ scrollWrapper.appendChild(sizer);
7060
+ container.appendChild(scrollWrapper);
6497
7061
  ctx.container.appendChild(container);
6498
7062
  let scale = 1;
6499
7063
  let rotation = 0;
@@ -6511,7 +7075,10 @@ var OpenDocumentPlugin = class {
6511
7075
  const sW = availW / elW;
6512
7076
  const sH = availH / (isPresentation ? 540 : singlePageH);
6513
7077
  if (isPresentation) {
6514
- return Math.min(2.5, Math.min(sW, sH));
7078
+ if (mode === "page") {
7079
+ return Math.min(2.5, Math.min(sW, sH));
7080
+ }
7081
+ return Math.min(2.5, sW);
6515
7082
  }
6516
7083
  if (mode === "page") {
6517
7084
  return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
@@ -6519,13 +7086,18 @@ var OpenDocumentPlugin = class {
6519
7086
  return Math.max(0.4, Math.min(3, sW));
6520
7087
  };
6521
7088
  const applyTransform = () => {
6522
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6523
- wrapper.style.transformOrigin = "top center";
6524
7089
  const activeSlide = slides[currentPage - 1];
7090
+ const elW = isPresentation ? 960 : 816;
6525
7091
  const baseH = activeSlide?.offsetHeight || wrapper.offsetHeight || 1056;
6526
- const scaledH = baseH * scale;
6527
- const extraH = Math.max(0, scaledH - baseH);
6528
- wrapper.style.marginBottom = `${extraH + 32}px`;
7092
+ const isRotated90 = rotation % 180 !== 0;
7093
+ const boxW = Math.round((isRotated90 ? baseH : elW) * scale);
7094
+ const boxH = Math.round((isRotated90 ? elW : baseH) * scale);
7095
+ sizer.style.width = `${boxW}px`;
7096
+ sizer.style.height = `${boxH}px`;
7097
+ wrapper.style.width = `${elW}px`;
7098
+ wrapper.style.height = `${baseH}px`;
7099
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7100
+ wrapper.style.transformOrigin = "center center";
6529
7101
  };
6530
7102
  const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6531
7103
  if (!isUserZoomed) {
@@ -6675,6 +7247,13 @@ var OpenDocumentPlugin = class {
6675
7247
  applyTransform();
6676
7248
  },
6677
7249
  fitToPage: () => {
7250
+ isUserZoomed = false;
7251
+ fitMode = "page";
7252
+ scale = calculateFitScale("page");
7253
+ rotation = 0;
7254
+ applyTransform();
7255
+ },
7256
+ fitToWidth: () => {
6678
7257
  isUserZoomed = false;
6679
7258
  fitMode = "width";
6680
7259
  scale = calculateFitScale("width");
@@ -6683,8 +7262,8 @@ var OpenDocumentPlugin = class {
6683
7262
  },
6684
7263
  resetZoom: () => {
6685
7264
  isUserZoomed = false;
6686
- fitMode = "width";
6687
- scale = calculateFitScale("width");
7265
+ fitMode = ctx?.options?.fitMode || "width";
7266
+ scale = calculateFitScale(fitMode);
6688
7267
  rotation = 0;
6689
7268
  container.scrollTop = 0;
6690
7269
  container.scrollLeft = 0;
@@ -7041,6 +7620,14 @@ var DocPlugin = class {
7041
7620
  group: "zoom",
7042
7621
  execute: () => instance.resetZoom?.()
7043
7622
  },
7623
+ {
7624
+ id: "fit-width",
7625
+ icon: "fit-width",
7626
+ label: "Fit to Width",
7627
+ type: "button",
7628
+ group: "zoom",
7629
+ execute: () => instance.fitToWidth?.()
7630
+ },
7044
7631
  {
7045
7632
  id: "rotate-cw",
7046
7633
  icon: "rotate-cw",
@@ -7089,13 +7676,28 @@ var DocPlugin = class {
7089
7676
  container.className = "fp-doc-container";
7090
7677
  container.style.width = "100%";
7091
7678
  container.style.height = "100%";
7092
- container.style.overflowX = "hidden";
7093
- container.style.overflowY = "auto";
7094
- container.style.padding = "16px 8px";
7679
+ container.style.overflow = "auto";
7095
7680
  container.style.backgroundColor = "#f1f5f9";
7096
- container.style.display = "flex";
7097
- container.style.justifyContent = "center";
7098
- container.style.alignItems = "flex-start";
7681
+ const scrollWrapper = document.createElement("div");
7682
+ scrollWrapper.className = "fp-doc-scroll-wrapper";
7683
+ scrollWrapper.style.minWidth = "100%";
7684
+ scrollWrapper.style.minHeight = "100%";
7685
+ scrollWrapper.style.width = "max-content";
7686
+ scrollWrapper.style.height = "max-content";
7687
+ scrollWrapper.style.display = "flex";
7688
+ scrollWrapper.style.justifyContent = "center";
7689
+ scrollWrapper.style.alignItems = "flex-start";
7690
+ scrollWrapper.style.padding = "16px 8px";
7691
+ scrollWrapper.style.boxSizing = "border-box";
7692
+ const sizer = document.createElement("div");
7693
+ sizer.className = "fp-doc-sizer";
7694
+ sizer.style.position = "relative";
7695
+ sizer.style.flexShrink = "0";
7696
+ sizer.style.display = "flex";
7697
+ sizer.style.justifyContent = "center";
7698
+ sizer.style.alignItems = "center";
7699
+ scrollWrapper.appendChild(sizer);
7700
+ container.appendChild(scrollWrapper);
7099
7701
  ctx.container.appendChild(container);
7100
7702
  let scale = 1;
7101
7703
  let extractedRawText = "";
@@ -7143,10 +7745,11 @@ var DocPlugin = class {
7143
7745
  pageCard.style.padding = "72px 56px";
7144
7746
  pageCard.style.boxSizing = "border-box";
7145
7747
  pageCard.style.display = i === 0 ? "block" : "none";
7146
- pageCard.style.transformOrigin = "top center";
7748
+ pageCard.style.transformOrigin = "center center";
7147
7749
  pageCard.style.transition = "transform 0.15s ease";
7148
7750
  pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
7149
7751
  pageCard.style.color = "#1e293b";
7752
+ pageCard.style.flexShrink = "0";
7150
7753
  if (isFallback && i === 0) {
7151
7754
  pageCard.innerHTML = `
7152
7755
  <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
@@ -7158,18 +7761,23 @@ var DocPlugin = class {
7158
7761
  } else {
7159
7762
  pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
7160
7763
  }
7161
- container.appendChild(pageCard);
7764
+ sizer.appendChild(pageCard);
7162
7765
  pageCards.push(pageCard);
7163
7766
  }
7164
7767
  let rotation = 0;
7165
7768
  const applyTransform = () => {
7166
7769
  const activeCard = pageCards[currentPage - 1];
7167
7770
  if (activeCard) {
7168
- activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7771
+ const baseW = 816;
7169
7772
  const baseH = activeCard.offsetHeight || 1056;
7170
- const scaledH = baseH * scale;
7171
- const extraH = Math.max(0, scaledH - baseH);
7172
- activeCard.style.marginBottom = `${extraH + 32}px`;
7773
+ const isRotated90 = rotation % 180 !== 0;
7774
+ const boxW = Math.round((isRotated90 ? baseH : baseW) * scale);
7775
+ const boxH = Math.round((isRotated90 ? baseW : baseH) * scale);
7776
+ sizer.style.width = `${boxW}px`;
7777
+ sizer.style.height = `${boxH}px`;
7778
+ activeCard.style.width = `${baseW}px`;
7779
+ activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7780
+ activeCard.style.transformOrigin = "center center";
7173
7781
  }
7174
7782
  };
7175
7783
  const showPage = (pageNum) => {
@@ -7177,11 +7785,11 @@ var DocPlugin = class {
7177
7785
  pageCards.forEach((card, idx) => {
7178
7786
  if (idx + 1 === currentPage) {
7179
7787
  card.style.display = "block";
7180
- card.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7181
7788
  } else {
7182
7789
  card.style.display = "none";
7183
7790
  }
7184
7791
  });
7792
+ applyTransform();
7185
7793
  container.scrollTop = 0;
7186
7794
  ctx.emit("page-change", { page: currentPage, total: totalPages });
7187
7795
  };
@@ -7241,6 +7849,13 @@ var DocPlugin = class {
7241
7849
  applyTransform();
7242
7850
  },
7243
7851
  fitToPage: () => {
7852
+ isUserZoomed = false;
7853
+ fitMode = "page";
7854
+ scale = calculateFitScale("page");
7855
+ rotation = 0;
7856
+ applyTransform();
7857
+ },
7858
+ fitToWidth: () => {
7244
7859
  isUserZoomed = false;
7245
7860
  fitMode = "width";
7246
7861
  scale = calculateFitScale("width");
@@ -7249,9 +7864,11 @@ var DocPlugin = class {
7249
7864
  },
7250
7865
  resetZoom: () => {
7251
7866
  isUserZoomed = false;
7252
- fitMode = "width";
7253
- scale = calculateFitScale("width");
7867
+ fitMode = ctx?.options?.fitMode || "width";
7868
+ scale = calculateFitScale(fitMode);
7254
7869
  rotation = 0;
7870
+ container.scrollTop = 0;
7871
+ container.scrollLeft = 0;
7255
7872
  ctx.container.scrollTop = 0;
7256
7873
  ctx.container.scrollLeft = 0;
7257
7874
  applyTransform();
@@ -7744,6 +8361,14 @@ var PptPlugin = class {
7744
8361
  group: "zoom",
7745
8362
  execute: () => instance.resetZoom?.()
7746
8363
  },
8364
+ {
8365
+ id: "fit-width",
8366
+ icon: "fit-width",
8367
+ label: "Fit to Width",
8368
+ type: "button",
8369
+ group: "zoom",
8370
+ execute: () => instance.fitToWidth?.()
8371
+ },
7747
8372
  {
7748
8373
  id: "rotate-cw",
7749
8374
  icon: "rotate-cw",
@@ -7776,17 +8401,30 @@ var PptPlugin = class {
7776
8401
  container.style.width = "100%";
7777
8402
  container.style.height = "100%";
7778
8403
  container.style.overflow = "auto";
7779
- container.style.display = "flex";
7780
- container.style.justifyContent = "center";
7781
- container.style.alignItems = "center";
7782
- container.style.padding = "32px 16px";
7783
8404
  container.style.backgroundColor = "#0f172a";
7784
8405
  container.style.boxSizing = "border-box";
8406
+ const scrollWrapper = document.createElement("div");
8407
+ scrollWrapper.className = "fp-ppt-scroll-wrapper";
8408
+ scrollWrapper.style.minWidth = "100%";
8409
+ scrollWrapper.style.minHeight = "100%";
8410
+ scrollWrapper.style.width = "max-content";
8411
+ scrollWrapper.style.height = "max-content";
8412
+ scrollWrapper.style.display = "flex";
8413
+ scrollWrapper.style.justifyContent = "center";
8414
+ scrollWrapper.style.alignItems = "center";
8415
+ scrollWrapper.style.padding = "32px 16px";
8416
+ scrollWrapper.style.boxSizing = "border-box";
8417
+ const sizer = document.createElement("div");
8418
+ sizer.className = "fp-ppt-sizer";
8419
+ sizer.style.position = "relative";
8420
+ sizer.style.flexShrink = "0";
8421
+ sizer.style.display = "flex";
8422
+ sizer.style.justifyContent = "center";
8423
+ sizer.style.alignItems = "center";
7785
8424
  const slideCard = document.createElement("div");
7786
8425
  slideCard.className = "fp-ppt-slide-card";
7787
8426
  slideCard.style.width = "960px";
7788
- slideCard.style.maxWidth = "calc(100% - 32px)";
7789
- slideCard.style.maxHeight = "calc(100% - 48px)";
8427
+ slideCard.style.height = "540px";
7790
8428
  slideCard.style.aspectRatio = "16 / 9";
7791
8429
  slideCard.style.backgroundColor = "#ffffff";
7792
8430
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
@@ -7796,23 +8434,47 @@ var PptPlugin = class {
7796
8434
  slideCard.style.transition = "transform 0.2s ease";
7797
8435
  slideCard.style.transformOrigin = "center center";
7798
8436
  slideCard.style.flexShrink = "0";
7799
- container.appendChild(slideCard);
8437
+ sizer.appendChild(slideCard);
8438
+ scrollWrapper.appendChild(sizer);
8439
+ container.appendChild(scrollWrapper);
7800
8440
  ctx.container.appendChild(container);
7801
8441
  let scale = 1;
7802
8442
  let rotation = 0;
7803
8443
  let currentSlide = 1;
7804
8444
  let slides = [];
7805
8445
  const createdBlobUrls = [];
7806
- const calculateFitScale = () => {
7807
- const availW = Math.max(200, container.clientWidth - 48);
7808
- const availH = Math.max(200, container.clientHeight - 72);
7809
- return Math.min(1, Math.min(availW / 960, availH / 540));
8446
+ let fitMode = ctx?.options?.fitMode || "width";
8447
+ let isUserZoomed = false;
8448
+ const calculateFitScale = (mode = fitMode) => {
8449
+ const availW = Math.max(200, container.clientWidth - 32);
8450
+ const availH = Math.max(200, container.clientHeight - 32);
8451
+ if (mode === "page") {
8452
+ return Math.min(2.5, Math.min(availW / 960, availH / 540));
8453
+ }
8454
+ return Math.min(2.5, availW / 960);
7810
8455
  };
7811
8456
  const applyTransform = () => {
8457
+ const cW = 960;
8458
+ const cH = 540;
8459
+ const isRotated90 = rotation % 180 !== 0;
8460
+ const boxW = Math.round((isRotated90 ? cH : cW) * scale);
8461
+ const boxH = Math.round((isRotated90 ? cW : cH) * scale);
8462
+ sizer.style.width = `${boxW}px`;
8463
+ sizer.style.height = `${boxH}px`;
8464
+ slideCard.style.width = `${cW}px`;
8465
+ slideCard.style.height = `${cH}px`;
7812
8466
  slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
8467
+ slideCard.style.transformOrigin = "center center";
7813
8468
  };
8469
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
8470
+ if (!isUserZoomed) {
8471
+ scale = calculateFitScale(fitMode);
8472
+ applyTransform();
8473
+ }
8474
+ }) : null;
8475
+ ro?.observe(container);
7814
8476
  setTimeout(() => {
7815
- scale = calculateFitScale();
8477
+ scale = calculateFitScale(fitMode);
7816
8478
  applyTransform();
7817
8479
  }, 60);
7818
8480
  try {
@@ -7905,6 +8567,7 @@ var PptPlugin = class {
7905
8567
  };
7906
8568
  renderSlide(1);
7907
8569
  const cleanup = () => {
8570
+ ro?.disconnect();
7908
8571
  for (const u of createdBlobUrls) {
7909
8572
  URL.revokeObjectURL(u);
7910
8573
  }
@@ -7915,28 +8578,44 @@ var PptPlugin = class {
7915
8578
  return {
7916
8579
  destroy: cleanup,
7917
8580
  zoomIn: () => {
8581
+ isUserZoomed = true;
7918
8582
  scale += 0.15;
7919
8583
  applyTransform();
7920
8584
  },
7921
8585
  zoomOut: () => {
8586
+ isUserZoomed = true;
7922
8587
  scale = Math.max(0.2, scale - 0.15);
7923
8588
  applyTransform();
7924
8589
  },
7925
8590
  getZoom: () => scale,
7926
8591
  setZoom: (level) => {
8592
+ isUserZoomed = true;
7927
8593
  scale = level;
7928
8594
  applyTransform();
7929
8595
  },
7930
8596
  fitToPage: () => {
7931
- scale = calculateFitScale();
8597
+ isUserZoomed = false;
8598
+ fitMode = "page";
8599
+ scale = calculateFitScale("page");
8600
+ rotation = 0;
8601
+ applyTransform();
8602
+ },
8603
+ fitToWidth: () => {
8604
+ isUserZoomed = false;
8605
+ fitMode = "width";
8606
+ scale = calculateFitScale("width");
7932
8607
  rotation = 0;
7933
8608
  applyTransform();
7934
8609
  },
7935
8610
  resetZoom: () => {
7936
- scale = calculateFitScale();
8611
+ isUserZoomed = false;
8612
+ fitMode = ctx?.options?.fitMode || "width";
8613
+ scale = calculateFitScale(fitMode);
7937
8614
  rotation = 0;
7938
8615
  container.scrollTop = 0;
7939
8616
  container.scrollLeft = 0;
8617
+ ctx.container.scrollTop = 0;
8618
+ ctx.container.scrollLeft = 0;
7940
8619
  applyTransform();
7941
8620
  },
7942
8621
  rotateCW: () => {