@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/react.cjs CHANGED
@@ -1090,6 +1090,10 @@ var FilePreviewViewer = class _FilePreviewViewer {
1090
1090
  * Preview a file in the given container element.
1091
1091
  */
1092
1092
  async preview(container, source, options = {}) {
1093
+ options = {
1094
+ ...options,
1095
+ fitMode: options.fitMode ?? "width"
1096
+ };
1093
1097
  this.currentOptions = options;
1094
1098
  this.abort();
1095
1099
  this.abortController = new AbortController();
@@ -1138,8 +1142,8 @@ var FilePreviewViewer = class _FilePreviewViewer {
1138
1142
  this.activeInstance = instance;
1139
1143
  instance.openInSeparateWindow = () => this.openInSeparateWindow();
1140
1144
  instance.toggleThumbnails = () => this.toggleThumbnails();
1141
- if (!instance.resetZoom && instance.fitToPage) {
1142
- instance.resetZoom = () => instance.fitToPage?.();
1145
+ if (!instance.resetZoom) {
1146
+ instance.resetZoom = () => instance.fitToWidth ? instance.fitToWidth() : instance.fitToPage?.();
1143
1147
  }
1144
1148
  this.hideLoading();
1145
1149
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
@@ -1536,7 +1540,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1536
1540
  this.wrapperEl?.classList.toggle("fp-fullscreen-active");
1537
1541
  }
1538
1542
  setTimeout(() => {
1539
- this.activeInstance?.fitToPage?.();
1543
+ this.triggerAutoFit();
1540
1544
  }, 120);
1541
1545
  }
1542
1546
  /**
@@ -1663,18 +1667,29 @@ var FilePreviewViewer = class _FilePreviewViewer {
1663
1667
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl, (isOpen) => {
1664
1668
  this.toolbar?.setActionActive("thumbnails", isOpen);
1665
1669
  setTimeout(() => {
1666
- this.activeInstance?.fitToPage?.();
1670
+ this.triggerAutoFit();
1667
1671
  }, 260);
1668
1672
  });
1669
1673
  this.setupKeyboardShortcuts();
1670
1674
  this.setupDragAndDrop(container, options);
1671
1675
  this.setupResizeAndFullscreenListeners();
1672
1676
  }
1677
+ triggerAutoFit() {
1678
+ if (!this.activeInstance) return;
1679
+ const mode = this.currentOptions?.fitMode ?? "width";
1680
+ if (mode === "width" && this.activeInstance.fitToWidth) {
1681
+ this.activeInstance.fitToWidth();
1682
+ } else if (this.activeInstance.fitToPage) {
1683
+ this.activeInstance.fitToPage();
1684
+ } else if (this.activeInstance.resetZoom) {
1685
+ this.activeInstance.resetZoom();
1686
+ }
1687
+ }
1673
1688
  setupResizeAndFullscreenListeners() {
1674
1689
  if (this.fullscreenHandler) return;
1675
1690
  this.fullscreenHandler = () => {
1676
1691
  setTimeout(() => {
1677
- this.activeInstance?.fitToPage?.();
1692
+ this.triggerAutoFit();
1678
1693
  }, 100);
1679
1694
  };
1680
1695
  document.addEventListener("fullscreenchange", this.fullscreenHandler);
@@ -1682,7 +1697,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1682
1697
  if (typeof ResizeObserver !== "undefined" && this.contentEl) {
1683
1698
  this.resizeObserver = new ResizeObserver(() => {
1684
1699
  if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
1685
- this.activeInstance.fitToPage?.();
1700
+ this.triggerAutoFit();
1686
1701
  }
1687
1702
  });
1688
1703
  this.resizeObserver.observe(this.contentEl);
@@ -1710,7 +1725,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1710
1725
  } else if (e.key === "-" || e.key === "_") {
1711
1726
  this.activeInstance.zoomOut?.();
1712
1727
  } else if (e.key === "0") {
1713
- this.activeInstance.fitToPage?.();
1728
+ this.resetZoom();
1714
1729
  } else if (e.key === "r" || e.key === "R") {
1715
1730
  this.activeInstance.rotateCW?.();
1716
1731
  } else if (e.key === "f" || e.key === "F") {
@@ -2118,16 +2133,22 @@ var PdfPlugin = class {
2118
2133
  container.className = "fp-pdf-container";
2119
2134
  container.style.width = "100%";
2120
2135
  container.style.height = "100%";
2121
- container.style.overflowX = "hidden";
2122
- container.style.overflowY = "auto";
2123
- container.style.display = "flex";
2124
- container.style.flexDirection = "column";
2125
- container.style.alignItems = "center";
2126
- container.style.justifyContent = "flex-start";
2127
- container.style.padding = "16px 8px";
2136
+ container.style.overflow = "auto";
2128
2137
  container.style.backgroundColor = "#0f172a";
2129
2138
  container.style.boxSizing = "border-box";
2130
2139
  container.style.position = "relative";
2140
+ const scrollWrapper = document.createElement("div");
2141
+ scrollWrapper.className = "fp-pdf-scroll-wrapper";
2142
+ scrollWrapper.style.minWidth = "100%";
2143
+ scrollWrapper.style.minHeight = "100%";
2144
+ scrollWrapper.style.width = "max-content";
2145
+ scrollWrapper.style.height = "max-content";
2146
+ scrollWrapper.style.display = "flex";
2147
+ scrollWrapper.style.flexDirection = "column";
2148
+ scrollWrapper.style.alignItems = "center";
2149
+ scrollWrapper.style.justifyContent = "flex-start";
2150
+ scrollWrapper.style.padding = "16px 8px";
2151
+ scrollWrapper.style.boxSizing = "border-box";
2131
2152
  const pageCard = document.createElement("div");
2132
2153
  pageCard.className = "fp-pdf-page-card";
2133
2154
  pageCard.style.boxShadow = "0 10px 35px rgba(0, 0, 0, 0.5)";
@@ -2140,7 +2161,8 @@ var PdfPlugin = class {
2140
2161
  pageCard.style.transition = "box-shadow 0.2s ease";
2141
2162
  let canvas = document.createElement("canvas");
2142
2163
  pageCard.appendChild(canvas);
2143
- container.appendChild(pageCard);
2164
+ scrollWrapper.appendChild(pageCard);
2165
+ container.appendChild(scrollWrapper);
2144
2166
  ctx.container.appendChild(container);
2145
2167
  const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
2146
2168
  const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
@@ -2326,7 +2348,7 @@ var PdfPlugin = class {
2326
2348
  renderPage(currentPage);
2327
2349
  },
2328
2350
  resetZoom: () => {
2329
- fitMode = "page";
2351
+ fitMode = ctx?.options?.fitMode || "width";
2330
2352
  zoomScale = 1;
2331
2353
  rotation = 0;
2332
2354
  container.scrollTop = 0;
@@ -2510,6 +2532,16 @@ var MediaPlugin = class {
2510
2532
  instance.resetZoom?.();
2511
2533
  }
2512
2534
  },
2535
+ {
2536
+ id: "fit-width",
2537
+ icon: "fit-width",
2538
+ label: "Fit to Width",
2539
+ type: "button",
2540
+ group: "zoom",
2541
+ execute: () => {
2542
+ instance.fitToWidth?.();
2543
+ }
2544
+ },
2513
2545
  {
2514
2546
  id: "rotate-cw",
2515
2547
  icon: "rotate-cw",
@@ -2606,9 +2638,30 @@ var MediaPlugin = class {
2606
2638
  const isVideo = mimeType.startsWith("video/") || VIDEO_EXTS.includes(ctx.metadata.extension || "");
2607
2639
  const isAudio = mimeType.startsWith("audio/") || AUDIO_EXTS.includes(ctx.metadata.extension || "");
2608
2640
  let url = "";
2641
+ let naturalW = 800;
2642
+ let naturalH = 600;
2609
2643
  if (ctx.metadata.extension === ".svg" || mimeType === "image/svg+xml") {
2610
2644
  const decoder = new TextDecoder("utf-8");
2611
2645
  const svgText = decoder.decode(ctx.buffer);
2646
+ try {
2647
+ const vbMatch = svgText.match(/viewBox=["']\s*([0-9.-]+)\s+([0-9.-]+)\s+([0-9.-]+)\s+([0-9.-]+)\s*["']/i);
2648
+ if (vbMatch) {
2649
+ const vw = parseFloat(vbMatch[3]);
2650
+ const vh = parseFloat(vbMatch[4]);
2651
+ if (vw > 0 && vh > 0) {
2652
+ naturalW = vw;
2653
+ naturalH = vh;
2654
+ }
2655
+ } else {
2656
+ const wMatch = svgText.match(/width=["']([0-9.]+)(?:px)?["']/i);
2657
+ const hMatch = svgText.match(/height=["']([0-9.]+)(?:px)?["']/i);
2658
+ if (wMatch && hMatch) {
2659
+ naturalW = parseFloat(wMatch[1]) || naturalW;
2660
+ naturalH = parseFloat(hMatch[1]) || naturalH;
2661
+ }
2662
+ }
2663
+ } catch {
2664
+ }
2612
2665
  const blob = new Blob([svgText], { type: "image/svg+xml" });
2613
2666
  url = URL.createObjectURL(blob);
2614
2667
  } else {
@@ -2649,45 +2702,150 @@ var MediaPlugin = class {
2649
2702
  element = document.createElement("div");
2650
2703
  element.textContent = "Unsupported media type";
2651
2704
  }
2652
- ctx.container.style.display = "flex";
2653
- ctx.container.style.alignItems = "center";
2654
- ctx.container.style.justifyContent = "center";
2655
- ctx.container.style.overflow = "hidden";
2656
- ctx.container.appendChild(element);
2705
+ const scrollWrapper = document.createElement("div");
2706
+ scrollWrapper.className = "fp-media-scroll-wrapper";
2707
+ scrollWrapper.style.minWidth = "100%";
2708
+ scrollWrapper.style.minHeight = "100%";
2709
+ scrollWrapper.style.width = "max-content";
2710
+ scrollWrapper.style.height = "max-content";
2711
+ scrollWrapper.style.display = "flex";
2712
+ scrollWrapper.style.flexDirection = "column";
2713
+ scrollWrapper.style.alignItems = "center";
2714
+ scrollWrapper.style.padding = "16px";
2715
+ scrollWrapper.style.boxSizing = "border-box";
2716
+ const sizer = document.createElement("div");
2717
+ sizer.className = "fp-media-sizer";
2718
+ sizer.style.position = "relative";
2719
+ sizer.style.flexShrink = "0";
2720
+ sizer.style.display = "flex";
2721
+ sizer.style.justifyContent = "center";
2722
+ sizer.style.alignItems = "center";
2723
+ sizer.style.margin = "auto 0";
2724
+ sizer.appendChild(element);
2725
+ scrollWrapper.appendChild(sizer);
2726
+ ctx.container.style.overflow = "auto";
2727
+ ctx.container.style.padding = "0";
2728
+ ctx.container.innerHTML = "";
2729
+ ctx.container.appendChild(scrollWrapper);
2730
+ let baseW = naturalW;
2731
+ let baseH = naturalH;
2732
+ let fitMode = ctx?.options?.fitMode || "width";
2733
+ let isUserZoomed = false;
2734
+ const updateBaseDimensions = () => {
2735
+ if (!isImage) return;
2736
+ const img = element;
2737
+ if (img.naturalWidth > 0 && img.naturalHeight > 0) {
2738
+ naturalW = img.naturalWidth;
2739
+ naturalH = img.naturalHeight;
2740
+ }
2741
+ const availW = Math.max(100, ctx.container.clientWidth - 32);
2742
+ const availH = Math.max(100, ctx.container.clientHeight - 32);
2743
+ let fitRatio;
2744
+ if (fitMode === "page") {
2745
+ fitRatio = Math.min(availW / naturalW, availH / naturalH);
2746
+ } else {
2747
+ fitRatio = availW / naturalW;
2748
+ }
2749
+ baseW = Math.max(1, Math.round(naturalW * fitRatio));
2750
+ baseH = Math.max(1, Math.round(naturalH * fitRatio));
2751
+ };
2657
2752
  const applyTransform = () => {
2658
- element.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
2753
+ if (isImage) {
2754
+ if (!isUserZoomed) {
2755
+ updateBaseDimensions();
2756
+ }
2757
+ const availH = Math.max(100, ctx.container.clientHeight - 32);
2758
+ const isRotated90 = rotation % 180 !== 0;
2759
+ const boxW = Math.round((isRotated90 ? baseH : baseW) * currentZoom);
2760
+ const boxH = Math.round((isRotated90 ? baseW : baseH) * currentZoom);
2761
+ sizer.style.width = `${boxW}px`;
2762
+ sizer.style.height = `${boxH}px`;
2763
+ sizer.style.margin = boxH < availH ? "auto 0" : "0";
2764
+ const imgW = isRotated90 ? boxH : boxW;
2765
+ const imgH = isRotated90 ? boxW : boxH;
2766
+ element.style.width = `${imgW}px`;
2767
+ element.style.height = `${imgH}px`;
2768
+ element.style.maxWidth = "none";
2769
+ element.style.maxHeight = "none";
2770
+ element.style.transform = rotation ? `rotate(${rotation}deg)` : "none";
2771
+ element.style.transformOrigin = "center center";
2772
+ element.style.flexShrink = "0";
2773
+ } else {
2774
+ element.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
2775
+ }
2659
2776
  };
2777
+ if (isImage) {
2778
+ const img = element;
2779
+ if (img.complete && img.naturalWidth > 0) {
2780
+ updateBaseDimensions();
2781
+ applyTransform();
2782
+ } else {
2783
+ img.onload = () => {
2784
+ updateBaseDimensions();
2785
+ applyTransform();
2786
+ };
2787
+ }
2788
+ }
2789
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
2790
+ if (!isUserZoomed && currentZoom === 1) {
2791
+ updateBaseDimensions();
2792
+ applyTransform();
2793
+ }
2794
+ }) : null;
2795
+ ro?.observe(ctx.container);
2660
2796
  const cleanup = () => {
2797
+ ro?.disconnect();
2661
2798
  if (url) URL.revokeObjectURL(url);
2662
- element.remove();
2799
+ scrollWrapper.remove();
2663
2800
  ctx.container.innerHTML = "";
2664
2801
  };
2665
2802
  ctx.signal.addEventListener("abort", cleanup);
2666
2803
  return {
2667
2804
  destroy: cleanup,
2668
2805
  zoomIn: isImage ? () => {
2669
- currentZoom += 0.1;
2806
+ isUserZoomed = true;
2807
+ currentZoom = Math.min(5, Math.round((currentZoom + 0.15) * 100) / 100);
2670
2808
  applyTransform();
2671
2809
  } : void 0,
2672
2810
  zoomOut: isImage ? () => {
2673
- currentZoom = Math.max(0.1, currentZoom - 0.1);
2811
+ isUserZoomed = true;
2812
+ currentZoom = Math.max(0.1, Math.round((currentZoom - 0.15) * 100) / 100);
2674
2813
  applyTransform();
2675
2814
  } : void 0,
2676
2815
  getZoom: isImage ? () => currentZoom : void 0,
2677
2816
  setZoom: isImage ? (level) => {
2817
+ isUserZoomed = true;
2678
2818
  currentZoom = level;
2679
2819
  applyTransform();
2680
2820
  } : void 0,
2681
2821
  fitToPage: isImage ? () => {
2822
+ isUserZoomed = false;
2823
+ fitMode = "page";
2824
+ currentZoom = 1;
2825
+ rotation = 0;
2826
+ ctx.container.scrollTop = 0;
2827
+ ctx.container.scrollLeft = 0;
2828
+ updateBaseDimensions();
2829
+ applyTransform();
2830
+ } : void 0,
2831
+ fitToWidth: isImage ? () => {
2832
+ isUserZoomed = false;
2833
+ fitMode = "width";
2682
2834
  currentZoom = 1;
2683
2835
  rotation = 0;
2836
+ ctx.container.scrollTop = 0;
2837
+ ctx.container.scrollLeft = 0;
2838
+ updateBaseDimensions();
2684
2839
  applyTransform();
2685
2840
  } : void 0,
2686
2841
  resetZoom: isImage ? () => {
2842
+ isUserZoomed = false;
2843
+ fitMode = ctx?.options?.fitMode || "width";
2687
2844
  currentZoom = 1;
2688
2845
  rotation = 0;
2689
2846
  ctx.container.scrollTop = 0;
2690
2847
  ctx.container.scrollLeft = 0;
2848
+ updateBaseDimensions();
2691
2849
  applyTransform();
2692
2850
  } : void 0,
2693
2851
  rotateCW: isImage || isVideo ? () => {
@@ -2814,6 +2972,14 @@ var DocxPlugin = class {
2814
2972
  group: "zoom",
2815
2973
  execute: () => instance.resetZoom?.()
2816
2974
  },
2975
+ {
2976
+ id: "fit-width",
2977
+ icon: "fit-width",
2978
+ label: "Fit to Width",
2979
+ type: "button",
2980
+ group: "zoom",
2981
+ execute: () => instance.fitToWidth?.()
2982
+ },
2817
2983
  {
2818
2984
  id: "rotate-cw",
2819
2985
  icon: "rotate-cw",
@@ -2852,7 +3018,7 @@ var DocxPlugin = class {
2852
3018
  async render(ctx) {
2853
3019
  const wrapper = document.createElement("div");
2854
3020
  wrapper.className = "fp-docx-wrapper";
2855
- wrapper.style.transformOrigin = "top center";
3021
+ wrapper.style.transformOrigin = "center center";
2856
3022
  wrapper.style.transition = "transform 0.2s ease";
2857
3023
  wrapper.style.padding = "0";
2858
3024
  wrapper.style.maxWidth = "none";
@@ -2862,11 +3028,31 @@ var DocxPlugin = class {
2862
3028
  wrapper.style.flexDirection = "column";
2863
3029
  wrapper.style.alignItems = "center";
2864
3030
  wrapper.style.boxSizing = "border-box";
2865
- ctx.container.style.overflowX = "hidden";
2866
- ctx.container.style.overflowY = "auto";
2867
- ctx.container.style.padding = "16px 8px";
3031
+ wrapper.style.flexShrink = "0";
3032
+ const sizer = document.createElement("div");
3033
+ sizer.className = "fp-docx-sizer";
3034
+ sizer.style.position = "relative";
3035
+ sizer.style.flexShrink = "0";
3036
+ sizer.style.display = "flex";
3037
+ sizer.style.justifyContent = "center";
3038
+ sizer.style.alignItems = "center";
3039
+ const scrollWrapper = document.createElement("div");
3040
+ scrollWrapper.className = "fp-docx-scroll-wrapper";
3041
+ scrollWrapper.style.minWidth = "100%";
3042
+ scrollWrapper.style.minHeight = "100%";
3043
+ scrollWrapper.style.width = "max-content";
3044
+ scrollWrapper.style.height = "max-content";
3045
+ scrollWrapper.style.display = "flex";
3046
+ scrollWrapper.style.justifyContent = "center";
3047
+ scrollWrapper.style.alignItems = "flex-start";
3048
+ scrollWrapper.style.padding = "16px 8px";
3049
+ scrollWrapper.style.boxSizing = "border-box";
3050
+ sizer.appendChild(wrapper);
3051
+ scrollWrapper.appendChild(sizer);
3052
+ ctx.container.style.overflow = "auto";
3053
+ ctx.container.style.padding = "0";
2868
3054
  ctx.container.style.backgroundColor = "#f1f5f9";
2869
- ctx.container.appendChild(wrapper);
3055
+ ctx.container.appendChild(scrollWrapper);
2870
3056
  const styleOverride = document.createElement("style");
2871
3057
  styleOverride.textContent = `
2872
3058
  .fp-docx-wrapper .docx-wrapper {
@@ -2905,8 +3091,8 @@ var DocxPlugin = class {
2905
3091
  renderEndnotes: true,
2906
3092
  useBase64URL: true
2907
3093
  });
2908
- if (!ctx.container.contains(wrapper)) {
2909
- ctx.container.appendChild(wrapper);
3094
+ if (!sizer.contains(wrapper)) {
3095
+ sizer.appendChild(wrapper);
2910
3096
  }
2911
3097
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
2912
3098
  renderedSuccessfully = true;
@@ -2943,8 +3129,8 @@ var DocxPlugin = class {
2943
3129
  try {
2944
3130
  wrapper.innerHTML = "";
2945
3131
  this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
2946
- if (!ctx.container.contains(wrapper)) {
2947
- ctx.container.appendChild(wrapper);
3132
+ if (!sizer.contains(wrapper)) {
3133
+ sizer.appendChild(wrapper);
2948
3134
  }
2949
3135
  renderedSuccessfully = true;
2950
3136
  } catch (fallbackErr) {
@@ -2959,8 +3145,8 @@ var DocxPlugin = class {
2959
3145
  </p>
2960
3146
  </div>
2961
3147
  `;
2962
- if (!ctx.container.contains(wrapper)) {
2963
- ctx.container.appendChild(wrapper);
3148
+ if (!sizer.contains(wrapper)) {
3149
+ sizer.appendChild(wrapper);
2964
3150
  }
2965
3151
  }
2966
3152
  }
@@ -3053,31 +3239,40 @@ var DocxPlugin = class {
3053
3239
  }
3054
3240
  scale = 1;
3055
3241
  let rotation = 0;
3056
- let fitMode = "page";
3242
+ let fitMode = ctx?.options?.fitMode || "width";
3057
3243
  let isUserZoomed = false;
3058
- const calculateFitScale = (_mode = fitMode) => {
3244
+ const calculateFitScale = (mode = fitMode) => {
3059
3245
  const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3060
3246
  const elW = activeEl.offsetWidth || 816;
3247
+ const elH = activeEl.offsetHeight || 1056;
3061
3248
  const availW = Math.max(280, ctx.container.clientWidth - 32);
3249
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
3062
3250
  const sW = availW / elW;
3251
+ const sH = availH / elH;
3252
+ if (mode === "page") {
3253
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
3254
+ }
3063
3255
  return Math.max(0.35, Math.min(3, sW));
3064
3256
  };
3065
3257
  const applyTransform = () => {
3066
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3067
- wrapper.style.transformOrigin = "top center";
3068
3258
  const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3259
+ const elW = activeEl.offsetWidth || 816;
3069
3260
  const elH = activeEl.offsetHeight || 1056;
3070
- if (scale > 1) {
3071
- wrapper.style.marginBottom = `${Math.round(elH * scale - elH + 32)}px`;
3072
- } else {
3073
- wrapper.style.marginBottom = "32px";
3074
- }
3261
+ const isRotated90 = rotation % 180 !== 0;
3262
+ const boxW = Math.round((isRotated90 ? elH : elW) * scale);
3263
+ const boxH = Math.round((isRotated90 ? elW : elH) * scale);
3264
+ sizer.style.width = `${boxW}px`;
3265
+ sizer.style.height = `${boxH}px`;
3266
+ wrapper.style.width = `${elW}px`;
3267
+ wrapper.style.height = `${elH}px`;
3268
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3269
+ wrapper.style.transformOrigin = "center center";
3075
3270
  };
3076
3271
  let resizeObserver = null;
3077
3272
  if (typeof ResizeObserver !== "undefined") {
3078
3273
  resizeObserver = new ResizeObserver(() => {
3079
3274
  if (!isUserZoomed) {
3080
- const newFit = calculateFitScale("page");
3275
+ const newFit = calculateFitScale(fitMode);
3081
3276
  if (Math.abs(scale - newFit) > 0.015) {
3082
3277
  scale = newFit;
3083
3278
  applyTransform();
@@ -3087,7 +3282,7 @@ var DocxPlugin = class {
3087
3282
  resizeObserver.observe(ctx.container);
3088
3283
  }
3089
3284
  setTimeout(() => {
3090
- scale = calculateFitScale("page");
3285
+ scale = calculateFitScale(fitMode);
3091
3286
  applyTransform();
3092
3287
  }, 40);
3093
3288
  const cleanup = () => {
@@ -3096,7 +3291,7 @@ var DocxPlugin = class {
3096
3291
  for (const url of createdBlobUrls) {
3097
3292
  URL.revokeObjectURL(url);
3098
3293
  }
3099
- wrapper.remove();
3294
+ scrollWrapper.remove();
3100
3295
  styleOverride.remove();
3101
3296
  ctx.container.innerHTML = "";
3102
3297
  };
@@ -3126,13 +3321,22 @@ var DocxPlugin = class {
3126
3321
  },
3127
3322
  fitToPage: () => {
3128
3323
  isUserZoomed = false;
3324
+ fitMode = "page";
3129
3325
  scale = calculateFitScale("page");
3130
3326
  rotation = 0;
3131
3327
  applyTransform();
3132
3328
  },
3329
+ fitToWidth: () => {
3330
+ isUserZoomed = false;
3331
+ fitMode = "width";
3332
+ scale = calculateFitScale("width");
3333
+ rotation = 0;
3334
+ applyTransform();
3335
+ },
3133
3336
  resetZoom: () => {
3134
3337
  isUserZoomed = false;
3135
- scale = calculateFitScale("page");
3338
+ fitMode = ctx?.options?.fitMode || "width";
3339
+ scale = calculateFitScale(fitMode);
3136
3340
  rotation = 0;
3137
3341
  ctx.container.scrollTop = 0;
3138
3342
  ctx.container.scrollLeft = 0;
@@ -3597,6 +3801,16 @@ var ExcelPlugin = class {
3597
3801
  instance.resetZoom?.();
3598
3802
  }
3599
3803
  },
3804
+ {
3805
+ id: "fit-width",
3806
+ icon: "fit-width",
3807
+ label: "Fit to Width",
3808
+ type: "button",
3809
+ group: "zoom",
3810
+ execute: () => {
3811
+ instance.fitToWidth?.();
3812
+ }
3813
+ },
3600
3814
  {
3601
3815
  id: "download",
3602
3816
  icon: "download",
@@ -3643,7 +3857,7 @@ var ExcelPlugin = class {
3643
3857
  contentArea.style.flex = "1";
3644
3858
  contentArea.style.overflow = "auto";
3645
3859
  contentArea.style.padding = "16px";
3646
- contentArea.style.transformOrigin = "top left";
3860
+ contentArea.style.boxSizing = "border-box";
3647
3861
  const tabsArea = document.createElement("div");
3648
3862
  tabsArea.className = "fp-excel-tabs";
3649
3863
  tabsArea.style.display = "flex";
@@ -3657,18 +3871,35 @@ var ExcelPlugin = class {
3657
3871
  container.appendChild(tabsArea);
3658
3872
  ctx.container.appendChild(container);
3659
3873
  let scale = 1;
3874
+ let isUserZoomed = false;
3660
3875
  let currentSheetIndex = 1;
3661
3876
  let sheetNames = [];
3662
3877
  let wb = null;
3663
3878
  const applyTransform = () => {
3664
- contentArea.style.transform = `scale(${scale})`;
3665
- contentArea.style.transformOrigin = "top left";
3879
+ const sizer = contentArea.querySelector(".fp-excel-sizer");
3880
+ const table = contentArea.querySelector("table");
3881
+ if (!sizer || !table) return;
3882
+ if (!table._baseWidth && table.offsetWidth > 0) {
3883
+ table._baseWidth = table.offsetWidth;
3884
+ table._baseHeight = table.offsetHeight;
3885
+ }
3886
+ const baseW = table._baseWidth || table.offsetWidth || 800;
3887
+ const baseH = table._baseHeight || table.offsetHeight || 600;
3888
+ const scaledW = Math.round(baseW * scale);
3889
+ const scaledH = Math.round(baseH * scale);
3890
+ sizer.style.width = `${scaledW}px`;
3891
+ sizer.style.height = `${scaledH}px`;
3892
+ table.style.position = "absolute";
3893
+ table.style.top = "0";
3894
+ table.style.left = "0";
3895
+ table.style.transform = `scale(${scale})`;
3896
+ table.style.transformOrigin = "top left";
3666
3897
  };
3667
3898
  const calculateFitScale = () => {
3668
3899
  const table = contentArea.querySelector("table");
3669
3900
  if (!table) return 1;
3670
3901
  const availW = Math.max(280, container.clientWidth - 48);
3671
- const tW = table.offsetWidth || 800;
3902
+ const tW = table._baseWidth || table.offsetWidth || 800;
3672
3903
  return Math.max(0.4, Math.min(1, availW / tW));
3673
3904
  };
3674
3905
  try {
@@ -3688,9 +3919,13 @@ var ExcelPlugin = class {
3688
3919
  contentArea.innerHTML = '<div style="padding: 24px; color: #888;">Empty sheet</div>';
3689
3920
  return;
3690
3921
  }
3922
+ const sizer = document.createElement("div");
3923
+ sizer.className = "fp-excel-sizer";
3924
+ sizer.style.position = "relative";
3691
3925
  const html = XLSX__namespace.utils.sheet_to_html(ws, { id: "fp-sheet-table", editable: false });
3692
- contentArea.innerHTML = html;
3693
- const table = contentArea.querySelector("table");
3926
+ sizer.innerHTML = html;
3927
+ contentArea.appendChild(sizer);
3928
+ const table = sizer.querySelector("table");
3694
3929
  if (table) {
3695
3930
  table.style.borderCollapse = "collapse";
3696
3931
  table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
@@ -3725,13 +3960,17 @@ var ExcelPlugin = class {
3725
3960
  });
3726
3961
  ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
3727
3962
  requestAnimationFrame(() => {
3728
- scale = calculateFitScale();
3963
+ if (!isUserZoomed) {
3964
+ scale = calculateFitScale();
3965
+ }
3729
3966
  applyTransform();
3730
3967
  });
3731
3968
  };
3732
3969
  const ro = new ResizeObserver(() => {
3733
- scale = calculateFitScale();
3734
- applyTransform();
3970
+ if (!isUserZoomed) {
3971
+ scale = calculateFitScale();
3972
+ applyTransform();
3973
+ }
3735
3974
  });
3736
3975
  ro.observe(container);
3737
3976
  if (sheetNames.length > 0) {
@@ -3768,23 +4007,33 @@ var ExcelPlugin = class {
3768
4007
  return {
3769
4008
  destroy: cleanup,
3770
4009
  zoomIn: () => {
4010
+ isUserZoomed = true;
3771
4011
  scale += 0.15;
3772
4012
  applyTransform();
3773
4013
  },
3774
4014
  zoomOut: () => {
4015
+ isUserZoomed = true;
3775
4016
  scale = Math.max(0.2, scale - 0.15);
3776
4017
  applyTransform();
3777
4018
  },
3778
4019
  getZoom: () => scale,
3779
4020
  setZoom: (level) => {
4021
+ isUserZoomed = true;
3780
4022
  scale = level;
3781
4023
  applyTransform();
3782
4024
  },
3783
4025
  fitToPage: () => {
4026
+ isUserZoomed = false;
4027
+ scale = calculateFitScale();
4028
+ applyTransform();
4029
+ },
4030
+ fitToWidth: () => {
4031
+ isUserZoomed = false;
3784
4032
  scale = calculateFitScale();
3785
4033
  applyTransform();
3786
4034
  },
3787
4035
  resetZoom: () => {
4036
+ isUserZoomed = false;
3788
4037
  scale = calculateFitScale();
3789
4038
  contentArea.scrollTop = 0;
3790
4039
  contentArea.scrollLeft = 0;
@@ -3930,6 +4179,16 @@ var CsvPlugin = class {
3930
4179
  instance.resetZoom?.();
3931
4180
  }
3932
4181
  },
4182
+ {
4183
+ id: "fit-width",
4184
+ icon: "fit-width",
4185
+ label: "Fit to Width",
4186
+ type: "button",
4187
+ group: "zoom",
4188
+ execute: () => {
4189
+ instance.fitToWidth?.();
4190
+ }
4191
+ },
3933
4192
  {
3934
4193
  id: "download",
3935
4194
  icon: "download",
@@ -4016,21 +4275,41 @@ var CsvPlugin = class {
4016
4275
  container.appendChild(tableWrapper);
4017
4276
  ctx.container.appendChild(container);
4018
4277
  let scale = 1;
4278
+ let isUserZoomed = false;
4019
4279
  const calculateFitScale = () => {
4020
4280
  const availW = Math.max(280, container.clientWidth - 48);
4021
- const tW = table.offsetWidth || 800;
4281
+ const tW = table._baseWidth || table.offsetWidth || 800;
4022
4282
  return Math.max(0.4, Math.min(1, availW / tW));
4023
4283
  };
4024
4284
  const applyScale = () => {
4025
- tableWrapper.style.transform = `scale(${scale})`;
4285
+ if (!table._baseWidth && table.offsetWidth > 0) {
4286
+ table._baseWidth = table.offsetWidth;
4287
+ table._baseHeight = table.offsetHeight;
4288
+ }
4289
+ const baseW = table._baseWidth || table.offsetWidth || 800;
4290
+ const baseH = table._baseHeight || table.offsetHeight || 600;
4291
+ const scaledW = Math.round(baseW * scale);
4292
+ const scaledH = Math.round(baseH * scale);
4293
+ tableWrapper.style.position = "relative";
4294
+ tableWrapper.style.width = `${scaledW}px`;
4295
+ tableWrapper.style.height = `${scaledH}px`;
4296
+ table.style.position = "absolute";
4297
+ table.style.top = "0";
4298
+ table.style.left = "0";
4299
+ table.style.transform = `scale(${scale})`;
4300
+ table.style.transformOrigin = "top left";
4026
4301
  };
4027
4302
  requestAnimationFrame(() => {
4028
- scale = calculateFitScale();
4303
+ if (!isUserZoomed) {
4304
+ scale = calculateFitScale();
4305
+ }
4029
4306
  applyScale();
4030
4307
  });
4031
4308
  const ro = new ResizeObserver(() => {
4032
- scale = calculateFitScale();
4033
- applyScale();
4309
+ if (!isUserZoomed) {
4310
+ scale = calculateFitScale();
4311
+ applyScale();
4312
+ }
4034
4313
  });
4035
4314
  ro.observe(container);
4036
4315
  const cleanup = () => {
@@ -4103,23 +4382,33 @@ var CsvPlugin = class {
4103
4382
  }];
4104
4383
  },
4105
4384
  zoomIn: () => {
4385
+ isUserZoomed = true;
4106
4386
  scale += 0.1;
4107
4387
  applyScale();
4108
4388
  },
4109
4389
  zoomOut: () => {
4390
+ isUserZoomed = true;
4110
4391
  scale = Math.max(0.2, scale - 0.1);
4111
4392
  applyScale();
4112
4393
  },
4113
4394
  getZoom: () => scale,
4114
4395
  setZoom: (level) => {
4396
+ isUserZoomed = true;
4115
4397
  scale = level;
4116
4398
  applyScale();
4117
4399
  },
4118
4400
  fitToPage: () => {
4401
+ isUserZoomed = false;
4402
+ scale = calculateFitScale();
4403
+ applyScale();
4404
+ },
4405
+ fitToWidth: () => {
4406
+ isUserZoomed = false;
4119
4407
  scale = calculateFitScale();
4120
4408
  applyScale();
4121
4409
  },
4122
4410
  resetZoom: () => {
4411
+ isUserZoomed = false;
4123
4412
  scale = calculateFitScale();
4124
4413
  container.scrollTop = 0;
4125
4414
  container.scrollLeft = 0;
@@ -4250,6 +4539,16 @@ var CodePlugin = class {
4250
4539
  instance.resetZoom?.();
4251
4540
  }
4252
4541
  },
4542
+ {
4543
+ id: "fit-width",
4544
+ icon: "fit-width",
4545
+ label: "Fit to Width",
4546
+ type: "button",
4547
+ group: "zoom",
4548
+ execute: () => {
4549
+ instance.fitToWidth?.();
4550
+ }
4551
+ },
4253
4552
  {
4254
4553
  id: "copy",
4255
4554
  icon: "copy",
@@ -4355,15 +4654,27 @@ var CodePlugin = class {
4355
4654
  let isUserZoomed = false;
4356
4655
  const pre = document.createElement("pre");
4357
4656
  const code = document.createElement("code");
4657
+ const sizer = document.createElement("div");
4658
+ const scrollWrapper = document.createElement("div");
4358
4659
  if (isTxt) {
4359
- container.style.overflowX = "hidden";
4360
- container.style.overflowY = "auto";
4660
+ container.style.overflow = "auto";
4361
4661
  container.style.backgroundColor = "#f1f5f9";
4362
- container.style.padding = "16px 8px";
4363
- container.style.boxSizing = "border-box";
4364
- container.style.display = "flex";
4365
- container.style.flexDirection = "column";
4366
- container.style.alignItems = "center";
4662
+ scrollWrapper.className = "fp-code-scroll-wrapper";
4663
+ scrollWrapper.style.minWidth = "100%";
4664
+ scrollWrapper.style.minHeight = "100%";
4665
+ scrollWrapper.style.width = "max-content";
4666
+ scrollWrapper.style.height = "max-content";
4667
+ scrollWrapper.style.display = "flex";
4668
+ scrollWrapper.style.justifyContent = "center";
4669
+ scrollWrapper.style.alignItems = "flex-start";
4670
+ scrollWrapper.style.padding = "16px 8px";
4671
+ scrollWrapper.style.boxSizing = "border-box";
4672
+ sizer.className = "fp-code-sizer";
4673
+ sizer.style.position = "relative";
4674
+ sizer.style.flexShrink = "0";
4675
+ sizer.style.display = "flex";
4676
+ sizer.style.justifyContent = "center";
4677
+ sizer.style.alignItems = "center";
4367
4678
  pre.style.margin = "0 auto";
4368
4679
  pre.style.fontFamily = "Consolas, 'Courier New', monospace";
4369
4680
  pre.style.fontSize = "13px";
@@ -4378,8 +4689,9 @@ var CodePlugin = class {
4378
4689
  pre.style.boxSizing = "border-box";
4379
4690
  pre.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
4380
4691
  pre.style.borderRadius = "4px";
4381
- pre.style.transformOrigin = "top center";
4692
+ pre.style.transformOrigin = "center center";
4382
4693
  pre.style.transition = "transform 0.15s ease";
4694
+ pre.style.flexShrink = "0";
4383
4695
  } else {
4384
4696
  container.style.overflow = "auto";
4385
4697
  container.style.backgroundColor = "#1e1e1e";
@@ -4412,7 +4724,13 @@ var CodePlugin = class {
4412
4724
  };
4413
4725
  renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
4414
4726
  pre.appendChild(code);
4415
- container.appendChild(pre);
4727
+ if (isTxt) {
4728
+ sizer.appendChild(pre);
4729
+ scrollWrapper.appendChild(sizer);
4730
+ container.appendChild(scrollWrapper);
4731
+ } else {
4732
+ container.appendChild(pre);
4733
+ }
4416
4734
  const showPage = (pageNum) => {
4417
4735
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4418
4736
  renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
@@ -4429,10 +4747,16 @@ var CodePlugin = class {
4429
4747
  };
4430
4748
  const updateTransform = () => {
4431
4749
  if (isTxt) {
4750
+ const elW = 816;
4751
+ const baseH = pre.offsetHeight || 1056;
4752
+ const isRotated90 = rotation % 180 !== 0;
4753
+ const boxW = Math.round((isRotated90 ? baseH : elW) * zoomLevel);
4754
+ const boxH = Math.round((isRotated90 ? elW : baseH) * zoomLevel);
4755
+ sizer.style.width = `${boxW}px`;
4756
+ sizer.style.height = `${boxH}px`;
4757
+ pre.style.width = `${elW}px`;
4432
4758
  pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
4433
- const scaledH = 1056 * zoomLevel;
4434
- const extraH = Math.max(0, scaledH - 1056);
4435
- pre.style.marginBottom = `${extraH + 32}px`;
4759
+ pre.style.transformOrigin = "center center";
4436
4760
  } else {
4437
4761
  pre.style.transform = `rotate(${rotation}deg)`;
4438
4762
  pre.style.fontSize = `${fontSize}px`;
@@ -4529,6 +4853,13 @@ var CodePlugin = class {
4529
4853
  zoomLevel = isTxt ? calculateTxtFit() : 1;
4530
4854
  updateTransform();
4531
4855
  },
4856
+ fitToWidth: () => {
4857
+ isUserZoomed = false;
4858
+ fontSize = 13;
4859
+ rotation = 0;
4860
+ zoomLevel = isTxt ? calculateTxtFit() : 1;
4861
+ updateTransform();
4862
+ },
4532
4863
  resetZoom: () => {
4533
4864
  isUserZoomed = false;
4534
4865
  fontSize = 13;
@@ -4613,6 +4944,14 @@ var ArchivePlugin = class {
4613
4944
  group: "zoom",
4614
4945
  execute: () => instance.resetZoom?.()
4615
4946
  },
4947
+ {
4948
+ id: "fit-width",
4949
+ icon: "fit-width",
4950
+ label: "Fit to Width",
4951
+ type: "button",
4952
+ group: "zoom",
4953
+ execute: () => instance.fitToWidth?.()
4954
+ },
4616
4955
  {
4617
4956
  id: "download",
4618
4957
  icon: "download",
@@ -4638,16 +4977,56 @@ var ArchivePlugin = class {
4638
4977
  width: 100%;
4639
4978
  height: 100%;
4640
4979
  overflow: auto;
4641
- padding: 24px;
4642
4980
  box-sizing: border-box;
4643
4981
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
4644
- transform-origin: top left;
4645
- transition: transform 0.2s ease;
4646
4982
  `;
4983
+ const scrollWrapper = document.createElement("div");
4984
+ scrollWrapper.className = "fp-archive-scroll-wrapper";
4985
+ scrollWrapper.style.cssText = `
4986
+ min-width: 100%;
4987
+ min-height: 100%;
4988
+ width: max-content;
4989
+ height: max-content;
4990
+ display: flex;
4991
+ justify-content: center;
4992
+ align-items: flex-start;
4993
+ padding: 24px;
4994
+ box-sizing: border-box;
4995
+ `;
4996
+ const sizer = document.createElement("div");
4997
+ sizer.className = "fp-archive-sizer";
4998
+ sizer.style.cssText = `
4999
+ position: relative;
5000
+ flex-shrink: 0;
5001
+ display: flex;
5002
+ justify-content: center;
5003
+ align-items: flex-start;
5004
+ `;
5005
+ const card = document.createElement("div");
5006
+ card.className = "fp-archive-card";
5007
+ card.style.cssText = `
5008
+ width: 900px;
5009
+ box-sizing: border-box;
5010
+ transform-origin: top center;
5011
+ transition: transform 0.15s ease;
5012
+ flex-shrink: 0;
5013
+ `;
5014
+ sizer.appendChild(card);
5015
+ scrollWrapper.appendChild(sizer);
5016
+ wrapper.appendChild(scrollWrapper);
4647
5017
  ctx.container.innerHTML = "";
4648
5018
  ctx.container.style.overflow = "auto";
4649
5019
  ctx.container.appendChild(wrapper);
4650
5020
  let scale = 1;
5021
+ const applyTransform = () => {
5022
+ const boxW = Math.round(900 * scale);
5023
+ const cardH = card.offsetHeight || 600;
5024
+ const boxH = Math.round(cardH * scale);
5025
+ sizer.style.width = `${boxW}px`;
5026
+ sizer.style.height = `${boxH}px`;
5027
+ card.style.transform = `scale(${scale})`;
5028
+ card.style.transformOrigin = "top center";
5029
+ };
4651
5030
  const entries = await new Promise((resolve, reject) => {
4652
5031
  fflate.unzip(new Uint8Array(ctx.buffer), (err, unzipped) => {
4653
5032
  if (err) {
@@ -4671,7 +5050,7 @@ var ArchivePlugin = class {
4671
5050
  });
4672
5051
  const totalUncompressedSize = entries.reduce((acc, e) => acc + e.size, 0);
4673
5052
  const renderUI = (filteredEntries) => {
4674
- wrapper.innerHTML = `
5053
+ card.innerHTML = `
4675
5054
  <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;">
4676
5055
  <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;">
4677
5056
  <div>
@@ -4701,7 +5080,7 @@ var ArchivePlugin = class {
4701
5080
  </div>
4702
5081
  </div>
4703
5082
  `;
4704
- const tbody = wrapper.querySelector("#fp-archive-body");
5083
+ const tbody = card.querySelector("#fp-archive-body");
4705
5084
  if (filteredEntries.length === 0) {
4706
5085
  tbody.innerHTML = `<tr><td colspan="3" style="padding: 24px; text-align: center; color: #9ca3af;">No matching files found</td></tr>`;
4707
5086
  } else {
@@ -4730,19 +5109,20 @@ var ArchivePlugin = class {
4730
5109
  }
4731
5110
  });
4732
5111
  });
4733
- const searchInput = wrapper.querySelector("#fp-archive-search");
5112
+ const searchInput = card.querySelector("#fp-archive-search");
4734
5113
  if (searchInput) {
4735
5114
  searchInput.addEventListener("input", () => {
4736
5115
  const q = searchInput.value.toLowerCase().trim();
4737
5116
  const filtered = q ? entries.filter((e) => e.path.toLowerCase().includes(q)) : entries;
4738
5117
  renderUI(filtered);
4739
- const newSearch = wrapper.querySelector("#fp-archive-search");
5118
+ const newSearch = card.querySelector("#fp-archive-search");
4740
5119
  if (newSearch) {
4741
5120
  newSearch.value = q;
4742
5121
  newSearch.focus();
4743
5122
  }
4744
5123
  });
4745
5124
  }
5125
+ applyTransform();
4746
5126
  };
4747
5127
  renderUI(entries);
4748
5128
  const cleanup = () => {
@@ -4754,26 +5134,30 @@ var ArchivePlugin = class {
4754
5134
  destroy: cleanup,
4755
5135
  zoomIn: () => {
4756
5136
  scale += 0.1;
4757
- wrapper.style.transform = `scale(${scale})`;
5137
+ applyTransform();
4758
5138
  },
4759
5139
  zoomOut: () => {
4760
5140
  scale = Math.max(0.3, scale - 0.1);
4761
- wrapper.style.transform = `scale(${scale})`;
5141
+ applyTransform();
4762
5142
  },
4763
5143
  getZoom: () => scale,
4764
5144
  setZoom: (level) => {
4765
5145
  scale = level;
4766
- wrapper.style.transform = `scale(${scale})`;
5146
+ applyTransform();
4767
5147
  },
4768
5148
  fitToPage: () => {
4769
5149
  scale = 1;
4770
- wrapper.style.transform = "scale(1)";
5150
+ applyTransform();
5151
+ },
5152
+ fitToWidth: () => {
5153
+ scale = 1;
5154
+ applyTransform();
4771
5155
  },
4772
5156
  resetZoom: () => {
4773
5157
  scale = 1;
4774
- wrapper.style.transform = "scale(1)";
4775
- ctx.container.scrollTop = 0;
4776
- ctx.container.scrollLeft = 0;
5158
+ wrapper.scrollTop = 0;
5159
+ wrapper.scrollLeft = 0;
5160
+ applyTransform();
4777
5161
  },
4778
5162
  download: () => {
4779
5163
  downloadFile(ctx.buffer, ctx.metadata.name || "archive.zip", "application/zip");
@@ -4833,6 +5217,14 @@ var MarkdownPlugin = class {
4833
5217
  group: "zoom",
4834
5218
  execute: () => instance.resetZoom?.()
4835
5219
  },
5220
+ {
5221
+ id: "fit-width",
5222
+ icon: "fit-width",
5223
+ label: "Fit to Width",
5224
+ type: "button",
5225
+ group: "zoom",
5226
+ execute: () => instance.fitToWidth?.()
5227
+ },
4836
5228
  {
4837
5229
  id: "copy",
4838
5230
  icon: "copy",
@@ -4986,6 +5378,10 @@ var MarkdownPlugin = class {
4986
5378
  fontSize = 15;
4987
5379
  wrapper.style.fontSize = "15px";
4988
5380
  },
5381
+ fitToWidth: () => {
5382
+ fontSize = 15;
5383
+ wrapper.style.fontSize = "15px";
5384
+ },
4989
5385
  resetZoom: () => {
4990
5386
  fontSize = 15;
4991
5387
  wrapper.style.fontSize = "15px";
@@ -5093,6 +5489,14 @@ var PptxPlugin = class {
5093
5489
  group: "zoom",
5094
5490
  execute: () => instance.resetZoom?.()
5095
5491
  },
5492
+ {
5493
+ id: "fit-width",
5494
+ icon: "fit-width",
5495
+ label: "Fit to Width",
5496
+ type: "button",
5497
+ group: "zoom",
5498
+ execute: () => instance.fitToWidth?.()
5499
+ },
5096
5500
  {
5097
5501
  id: "rotate-cw",
5098
5502
  icon: "rotate-cw",
@@ -5132,12 +5536,30 @@ var PptxPlugin = class {
5132
5536
  width: 100%;
5133
5537
  height: 100%;
5134
5538
  overflow: auto;
5539
+ box-sizing: border-box;
5540
+ background: var(--fp-bg-canvas, #525659);
5541
+ `;
5542
+ const scrollWrapper = document.createElement("div");
5543
+ scrollWrapper.className = "fp-pptx-scroll-wrapper";
5544
+ scrollWrapper.style.cssText = `
5545
+ min-width: 100%;
5546
+ min-height: 100%;
5547
+ width: max-content;
5548
+ height: max-content;
5135
5549
  display: flex;
5136
5550
  justify-content: center;
5137
- align-items: flex-start;
5551
+ align-items: center;
5138
5552
  padding: 24px;
5139
5553
  box-sizing: border-box;
5140
- background: var(--fp-bg-canvas, #525659);
5554
+ `;
5555
+ const sizer = document.createElement("div");
5556
+ sizer.className = "fp-pptx-sizer";
5557
+ sizer.style.cssText = `
5558
+ position: relative;
5559
+ flex-shrink: 0;
5560
+ display: flex;
5561
+ justify-content: center;
5562
+ align-items: center;
5141
5563
  `;
5142
5564
  const slideContainer = document.createElement("div");
5143
5565
  slideContainer.className = "fp-slide-container";
@@ -5146,39 +5568,65 @@ var PptxPlugin = class {
5146
5568
  border-radius: 4px;
5147
5569
  overflow: hidden;
5148
5570
  background: #ffffff;
5149
- transform-origin: top center;
5571
+ transform-origin: center center;
5150
5572
  transition: transform 0.2s ease;
5151
- max-width: calc(100% - 32px);
5152
- max-height: calc(100% - 48px);
5573
+ flex-shrink: 0;
5153
5574
  `;
5154
5575
  const canvas = document.createElement("canvas");
5155
5576
  slideContainer.appendChild(canvas);
5156
- wrapper.appendChild(slideContainer);
5577
+ sizer.appendChild(slideContainer);
5578
+ scrollWrapper.appendChild(sizer);
5579
+ wrapper.appendChild(scrollWrapper);
5157
5580
  ctx.container.innerHTML = "";
5158
5581
  ctx.container.style.overflow = "auto";
5159
5582
  ctx.container.appendChild(wrapper);
5160
- const calculateFitScale = () => {
5161
- const availW = Math.max(200, ctx.container.clientWidth - 48);
5162
- const availH = Math.max(200, ctx.container.clientHeight - 72);
5583
+ let fitMode = ctx?.options?.fitMode || "width";
5584
+ let isUserZoomed = false;
5585
+ const calculateFitScale = (mode = fitMode) => {
5586
+ const availW = Math.max(200, ctx.container.clientWidth - 32);
5587
+ const availH = Math.max(200, ctx.container.clientHeight - 32);
5163
5588
  const cW = canvas.offsetWidth || 1280;
5164
5589
  const cH = canvas.offsetHeight || 720;
5165
- return Math.min(1, Math.min(availW / cW, availH / cH));
5590
+ if (mode === "page") {
5591
+ return Math.min(2.5, Math.min(availW / cW, availH / cH));
5592
+ }
5593
+ return Math.min(2.5, availW / cW);
5166
5594
  };
5167
5595
  const applyTransform = () => {
5596
+ const cW = canvas.offsetWidth || 1280;
5597
+ const cH = canvas.offsetHeight || 720;
5598
+ const isRotated90 = rotation % 180 !== 0;
5599
+ const boxW = Math.round((isRotated90 ? cH : cW) * scale);
5600
+ const boxH = Math.round((isRotated90 ? cW : cH) * scale);
5601
+ sizer.style.width = `${boxW}px`;
5602
+ sizer.style.height = `${boxH}px`;
5603
+ slideContainer.style.width = `${cW}px`;
5604
+ slideContainer.style.height = `${cH}px`;
5168
5605
  slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5606
+ slideContainer.style.transformOrigin = "center center";
5169
5607
  };
5170
5608
  const renderCurrentSlide = async () => {
5171
5609
  try {
5172
5610
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
5173
5611
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
5174
- scale = calculateFitScale();
5612
+ if (!isUserZoomed) {
5613
+ scale = calculateFitScale(fitMode);
5614
+ }
5175
5615
  applyTransform();
5176
5616
  } catch (err) {
5177
5617
  console.error("[PptxPlugin] Failed to render slide:", err);
5178
5618
  }
5179
5619
  };
5180
5620
  await renderCurrentSlide();
5621
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5622
+ if (!isUserZoomed) {
5623
+ scale = calculateFitScale(fitMode);
5624
+ applyTransform();
5625
+ }
5626
+ }) : null;
5627
+ ro?.observe(ctx.container);
5181
5628
  const cleanup = () => {
5629
+ ro?.disconnect();
5182
5630
  renderer.destroy();
5183
5631
  wrapper.remove();
5184
5632
  ctx.container.innerHTML = "";
@@ -5195,28 +5643,44 @@ var PptxPlugin = class {
5195
5643
  getPageCount: () => slideCount,
5196
5644
  getCurrentPage: () => currentSlide,
5197
5645
  zoomIn: () => {
5646
+ isUserZoomed = true;
5198
5647
  scale += 0.15;
5199
5648
  applyTransform();
5200
5649
  },
5201
5650
  zoomOut: () => {
5651
+ isUserZoomed = true;
5202
5652
  scale = Math.max(0.2, scale - 0.15);
5203
5653
  applyTransform();
5204
5654
  },
5205
5655
  getZoom: () => scale,
5206
5656
  setZoom: (level) => {
5657
+ isUserZoomed = true;
5207
5658
  scale = level;
5208
5659
  applyTransform();
5209
5660
  },
5210
5661
  fitToPage: () => {
5211
- scale = calculateFitScale();
5662
+ isUserZoomed = false;
5663
+ fitMode = "page";
5664
+ scale = calculateFitScale("page");
5665
+ rotation = 0;
5666
+ applyTransform();
5667
+ },
5668
+ fitToWidth: () => {
5669
+ isUserZoomed = false;
5670
+ fitMode = "width";
5671
+ scale = calculateFitScale("width");
5212
5672
  rotation = 0;
5213
5673
  applyTransform();
5214
5674
  },
5215
5675
  resetZoom: () => {
5216
- scale = calculateFitScale();
5676
+ isUserZoomed = false;
5677
+ fitMode = ctx?.options?.fitMode || "width";
5678
+ scale = calculateFitScale(fitMode);
5217
5679
  rotation = 0;
5218
5680
  wrapper.scrollTop = 0;
5219
5681
  wrapper.scrollLeft = 0;
5682
+ ctx.container.scrollTop = 0;
5683
+ ctx.container.scrollLeft = 0;
5220
5684
  applyTransform();
5221
5685
  },
5222
5686
  rotateCW: () => {
@@ -5661,6 +6125,14 @@ var RtfPlugin = class {
5661
6125
  group: "zoom",
5662
6126
  execute: () => instance.resetZoom?.()
5663
6127
  },
6128
+ {
6129
+ id: "fit-width",
6130
+ icon: "fit-width",
6131
+ label: "Fit to Width",
6132
+ type: "button",
6133
+ group: "zoom",
6134
+ execute: () => instance.fitToWidth?.()
6135
+ },
5664
6136
  {
5665
6137
  id: "rotate-cw",
5666
6138
  icon: "rotate-cw",
@@ -5771,13 +6243,33 @@ var RtfPlugin = class {
5771
6243
  wrapper.style.flexDirection = "column";
5772
6244
  wrapper.style.alignItems = "center";
5773
6245
  wrapper.style.backgroundColor = "transparent";
5774
- wrapper.style.transformOrigin = "top center";
6246
+ wrapper.style.transformOrigin = "center center";
5775
6247
  wrapper.style.transition = "transform 0.15s ease";
5776
- ctx.container.style.overflowX = "hidden";
5777
- ctx.container.style.overflowY = "auto";
5778
- ctx.container.style.padding = "16px 8px";
6248
+ wrapper.style.flexShrink = "0";
6249
+ const sizer = document.createElement("div");
6250
+ sizer.className = "fp-rtf-sizer";
6251
+ sizer.style.position = "relative";
6252
+ sizer.style.flexShrink = "0";
6253
+ sizer.style.display = "flex";
6254
+ sizer.style.justifyContent = "center";
6255
+ sizer.style.alignItems = "center";
6256
+ const scrollWrapper = document.createElement("div");
6257
+ scrollWrapper.className = "fp-rtf-scroll-wrapper";
6258
+ scrollWrapper.style.minWidth = "100%";
6259
+ scrollWrapper.style.minHeight = "100%";
6260
+ scrollWrapper.style.width = "max-content";
6261
+ scrollWrapper.style.height = "max-content";
6262
+ scrollWrapper.style.display = "flex";
6263
+ scrollWrapper.style.justifyContent = "center";
6264
+ scrollWrapper.style.alignItems = "flex-start";
6265
+ scrollWrapper.style.padding = "16px 8px";
6266
+ scrollWrapper.style.boxSizing = "border-box";
6267
+ sizer.appendChild(wrapper);
6268
+ scrollWrapper.appendChild(sizer);
6269
+ ctx.container.style.overflow = "auto";
6270
+ ctx.container.style.padding = "0";
5779
6271
  ctx.container.style.backgroundColor = "#f1f5f9";
5780
- ctx.container.appendChild(wrapper);
6272
+ ctx.container.appendChild(scrollWrapper);
5781
6273
  let scale = 1;
5782
6274
  let rotation = 0;
5783
6275
  let pageElements = [];
@@ -5796,12 +6288,17 @@ var RtfPlugin = class {
5796
6288
  return Math.max(0.4, Math.min(3, sW));
5797
6289
  };
5798
6290
  const applyTransform = () => {
5799
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5800
- wrapper.style.transformOrigin = "top center";
6291
+ const elW = pageWidth;
5801
6292
  const baseH = pageHeight;
5802
- const scaledH = baseH * scale;
5803
- const extraH = Math.max(0, scaledH - baseH);
5804
- wrapper.style.marginBottom = `${extraH + 32}px`;
6293
+ const isRotated90 = rotation % 180 !== 0;
6294
+ const boxW = Math.round((isRotated90 ? baseH : elW) * scale);
6295
+ const boxH = Math.round((isRotated90 ? elW : baseH) * scale);
6296
+ sizer.style.width = `${boxW}px`;
6297
+ sizer.style.height = `${boxH}px`;
6298
+ wrapper.style.width = `${elW}px`;
6299
+ wrapper.style.height = `${baseH}px`;
6300
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6301
+ wrapper.style.transformOrigin = "center center";
5805
6302
  };
5806
6303
  const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5807
6304
  if (!isUserZoomed) {
@@ -6072,7 +6569,7 @@ var RtfPlugin = class {
6072
6569
  }
6073
6570
  const cleanup = () => {
6074
6571
  resizeObserver?.disconnect();
6075
- wrapper.remove();
6572
+ scrollWrapper.remove();
6076
6573
  ctx.container.innerHTML = "";
6077
6574
  };
6078
6575
  ctx.signal.addEventListener("abort", cleanup);
@@ -6124,6 +6621,13 @@ var RtfPlugin = class {
6124
6621
  applyTransform();
6125
6622
  },
6126
6623
  fitToPage: () => {
6624
+ isUserZoomed = false;
6625
+ fitMode = "page";
6626
+ scale = calculateFitScale("page");
6627
+ rotation = 0;
6628
+ applyTransform();
6629
+ },
6630
+ fitToWidth: () => {
6127
6631
  isUserZoomed = false;
6128
6632
  fitMode = "width";
6129
6633
  scale = calculateFitScale("width");
@@ -6132,8 +6636,8 @@ var RtfPlugin = class {
6132
6636
  },
6133
6637
  resetZoom: () => {
6134
6638
  isUserZoomed = false;
6135
- fitMode = "width";
6136
- scale = calculateFitScale("width");
6639
+ fitMode = ctx?.options?.fitMode || "width";
6640
+ scale = calculateFitScale(fitMode);
6137
6641
  rotation = 0;
6138
6642
  ctx.container.scrollTop = 0;
6139
6643
  ctx.container.scrollLeft = 0;
@@ -6214,6 +6718,14 @@ var HtmlPreviewPlugin = class {
6214
6718
  group: "zoom",
6215
6719
  execute: () => instance.resetZoom?.()
6216
6720
  },
6721
+ {
6722
+ id: "fit-width",
6723
+ icon: "fit-width",
6724
+ label: "Fit to Width",
6725
+ type: "button",
6726
+ group: "zoom",
6727
+ execute: () => instance.fitToWidth?.()
6728
+ },
6217
6729
  {
6218
6730
  id: "copy",
6219
6731
  icon: "copy",
@@ -6247,23 +6759,44 @@ var HtmlPreviewPlugin = class {
6247
6759
  ADD_TAGS: ["style", "link"],
6248
6760
  ADD_ATTR: ["target", "rel"]
6249
6761
  });
6762
+ const sizer = document.createElement("div");
6763
+ sizer.className = "fp-html-sizer";
6764
+ sizer.style.position = "relative";
6250
6765
  const iframe = document.createElement("iframe");
6251
6766
  iframe.className = "fp-html-iframe";
6252
- iframe.style.width = "100%";
6253
- iframe.style.height = "100%";
6254
6767
  iframe.style.border = "none";
6255
6768
  iframe.style.backgroundColor = "#ffffff";
6256
6769
  iframe.style.transformOrigin = "top left";
6257
6770
  iframe.style.transition = "transform 0.2s ease";
6258
6771
  iframe.sandbox.add("allow-same-origin");
6772
+ sizer.appendChild(iframe);
6259
6773
  ctx.container.style.overflow = "auto";
6260
6774
  ctx.container.style.width = "100%";
6261
6775
  ctx.container.style.height = "100%";
6262
- ctx.container.appendChild(iframe);
6776
+ ctx.container.innerHTML = "";
6777
+ ctx.container.appendChild(sizer);
6263
6778
  iframe.srcdoc = sanitized;
6264
6779
  let scale = 1;
6780
+ const applyTransform = () => {
6781
+ const baseW = Math.max(600, ctx.container.clientWidth || 800);
6782
+ const baseH = Math.max(400, ctx.container.clientHeight || 600);
6783
+ const scaledW = Math.round(baseW * scale);
6784
+ const scaledH = Math.round(baseH * scale);
6785
+ sizer.style.width = `${scaledW}px`;
6786
+ sizer.style.height = `${scaledH}px`;
6787
+ iframe.style.position = "absolute";
6788
+ iframe.style.top = "0";
6789
+ iframe.style.left = "0";
6790
+ iframe.style.width = `${baseW}px`;
6791
+ iframe.style.height = `${baseH}px`;
6792
+ iframe.style.transform = `scale(${scale})`;
6793
+ iframe.style.transformOrigin = "top left";
6794
+ };
6795
+ requestAnimationFrame(() => {
6796
+ applyTransform();
6797
+ });
6265
6798
  const cleanup = () => {
6266
- iframe.remove();
6799
+ sizer.remove();
6267
6800
  ctx.container.innerHTML = "";
6268
6801
  };
6269
6802
  ctx.signal.addEventListener("abort", cleanup);
@@ -6271,26 +6804,30 @@ var HtmlPreviewPlugin = class {
6271
6804
  destroy: cleanup,
6272
6805
  zoomIn: () => {
6273
6806
  scale += 0.1;
6274
- iframe.style.transform = `scale(${scale})`;
6807
+ applyTransform();
6275
6808
  },
6276
6809
  zoomOut: () => {
6277
6810
  scale = Math.max(0.2, scale - 0.1);
6278
- iframe.style.transform = `scale(${scale})`;
6811
+ applyTransform();
6279
6812
  },
6280
6813
  getZoom: () => scale,
6281
6814
  setZoom: (level) => {
6282
6815
  scale = level;
6283
- iframe.style.transform = `scale(${scale})`;
6816
+ applyTransform();
6284
6817
  },
6285
6818
  fitToPage: () => {
6286
6819
  scale = 1;
6287
- iframe.style.transform = "scale(1)";
6820
+ applyTransform();
6821
+ },
6822
+ fitToWidth: () => {
6823
+ scale = 1;
6824
+ applyTransform();
6288
6825
  },
6289
6826
  resetZoom: () => {
6290
6827
  scale = 1;
6291
- iframe.style.transform = "scale(1)";
6292
6828
  ctx.container.scrollTop = 0;
6293
6829
  ctx.container.scrollLeft = 0;
6830
+ applyTransform();
6294
6831
  },
6295
6832
  copy: () => {
6296
6833
  navigator.clipboard.writeText(rawHtml);
@@ -6400,6 +6937,14 @@ var OpenDocumentPlugin = class {
6400
6937
  group: "zoom",
6401
6938
  execute: () => instance.resetZoom?.()
6402
6939
  },
6940
+ {
6941
+ id: "fit-width",
6942
+ icon: "fit-width",
6943
+ label: "Fit to Width",
6944
+ type: "button",
6945
+ group: "zoom",
6946
+ execute: () => instance.fitToWidth?.()
6947
+ },
6403
6948
  {
6404
6949
  id: "rotate-cw",
6405
6950
  icon: "rotate-cw",
@@ -6465,10 +7010,26 @@ var OpenDocumentPlugin = class {
6465
7010
  container.className = "fp-odf-container";
6466
7011
  container.style.width = "100%";
6467
7012
  container.style.height = "100%";
6468
- container.style.overflowX = "hidden";
6469
- container.style.overflowY = "auto";
6470
- container.style.padding = "16px 8px";
7013
+ container.style.overflow = "auto";
6471
7014
  container.style.backgroundColor = "#f1f5f9";
7015
+ const scrollWrapper = document.createElement("div");
7016
+ scrollWrapper.className = "fp-odf-scroll-wrapper";
7017
+ scrollWrapper.style.minWidth = "100%";
7018
+ scrollWrapper.style.minHeight = "100%";
7019
+ scrollWrapper.style.width = "max-content";
7020
+ scrollWrapper.style.height = "max-content";
7021
+ scrollWrapper.style.display = "flex";
7022
+ scrollWrapper.style.justifyContent = "center";
7023
+ scrollWrapper.style.alignItems = "flex-start";
7024
+ scrollWrapper.style.padding = "16px 8px";
7025
+ scrollWrapper.style.boxSizing = "border-box";
7026
+ const sizer = document.createElement("div");
7027
+ sizer.className = "fp-odf-sizer";
7028
+ sizer.style.position = "relative";
7029
+ sizer.style.flexShrink = "0";
7030
+ sizer.style.display = "flex";
7031
+ sizer.style.justifyContent = "center";
7032
+ sizer.style.alignItems = "center";
6472
7033
  const wrapper = document.createElement("div");
6473
7034
  wrapper.className = "fp-odf-wrapper";
6474
7035
  wrapper.style.margin = "0 auto";
@@ -6477,9 +7038,12 @@ var OpenDocumentPlugin = class {
6477
7038
  wrapper.style.backgroundColor = "#ffffff";
6478
7039
  wrapper.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
6479
7040
  wrapper.style.borderRadius = "4px";
6480
- wrapper.style.transformOrigin = "top center";
7041
+ wrapper.style.transformOrigin = "center center";
6481
7042
  wrapper.style.transition = "transform 0.15s ease";
6482
- container.appendChild(wrapper);
7043
+ wrapper.style.flexShrink = "0";
7044
+ sizer.appendChild(wrapper);
7045
+ scrollWrapper.appendChild(sizer);
7046
+ container.appendChild(scrollWrapper);
6483
7047
  ctx.container.appendChild(container);
6484
7048
  let scale = 1;
6485
7049
  let rotation = 0;
@@ -6497,7 +7061,10 @@ var OpenDocumentPlugin = class {
6497
7061
  const sW = availW / elW;
6498
7062
  const sH = availH / (isPresentation ? 540 : singlePageH);
6499
7063
  if (isPresentation) {
6500
- return Math.min(2.5, Math.min(sW, sH));
7064
+ if (mode === "page") {
7065
+ return Math.min(2.5, Math.min(sW, sH));
7066
+ }
7067
+ return Math.min(2.5, sW);
6501
7068
  }
6502
7069
  if (mode === "page") {
6503
7070
  return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
@@ -6505,13 +7072,18 @@ var OpenDocumentPlugin = class {
6505
7072
  return Math.max(0.4, Math.min(3, sW));
6506
7073
  };
6507
7074
  const applyTransform = () => {
6508
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6509
- wrapper.style.transformOrigin = "top center";
6510
7075
  const activeSlide = slides[currentPage - 1];
7076
+ const elW = isPresentation ? 960 : 816;
6511
7077
  const baseH = activeSlide?.offsetHeight || wrapper.offsetHeight || 1056;
6512
- const scaledH = baseH * scale;
6513
- const extraH = Math.max(0, scaledH - baseH);
6514
- wrapper.style.marginBottom = `${extraH + 32}px`;
7078
+ const isRotated90 = rotation % 180 !== 0;
7079
+ const boxW = Math.round((isRotated90 ? baseH : elW) * scale);
7080
+ const boxH = Math.round((isRotated90 ? elW : baseH) * scale);
7081
+ sizer.style.width = `${boxW}px`;
7082
+ sizer.style.height = `${boxH}px`;
7083
+ wrapper.style.width = `${elW}px`;
7084
+ wrapper.style.height = `${baseH}px`;
7085
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7086
+ wrapper.style.transformOrigin = "center center";
6515
7087
  };
6516
7088
  const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6517
7089
  if (!isUserZoomed) {
@@ -6661,6 +7233,13 @@ var OpenDocumentPlugin = class {
6661
7233
  applyTransform();
6662
7234
  },
6663
7235
  fitToPage: () => {
7236
+ isUserZoomed = false;
7237
+ fitMode = "page";
7238
+ scale = calculateFitScale("page");
7239
+ rotation = 0;
7240
+ applyTransform();
7241
+ },
7242
+ fitToWidth: () => {
6664
7243
  isUserZoomed = false;
6665
7244
  fitMode = "width";
6666
7245
  scale = calculateFitScale("width");
@@ -6669,8 +7248,8 @@ var OpenDocumentPlugin = class {
6669
7248
  },
6670
7249
  resetZoom: () => {
6671
7250
  isUserZoomed = false;
6672
- fitMode = "width";
6673
- scale = calculateFitScale("width");
7251
+ fitMode = ctx?.options?.fitMode || "width";
7252
+ scale = calculateFitScale(fitMode);
6674
7253
  rotation = 0;
6675
7254
  container.scrollTop = 0;
6676
7255
  container.scrollLeft = 0;
@@ -7027,6 +7606,14 @@ var DocPlugin = class {
7027
7606
  group: "zoom",
7028
7607
  execute: () => instance.resetZoom?.()
7029
7608
  },
7609
+ {
7610
+ id: "fit-width",
7611
+ icon: "fit-width",
7612
+ label: "Fit to Width",
7613
+ type: "button",
7614
+ group: "zoom",
7615
+ execute: () => instance.fitToWidth?.()
7616
+ },
7030
7617
  {
7031
7618
  id: "rotate-cw",
7032
7619
  icon: "rotate-cw",
@@ -7075,13 +7662,28 @@ var DocPlugin = class {
7075
7662
  container.className = "fp-doc-container";
7076
7663
  container.style.width = "100%";
7077
7664
  container.style.height = "100%";
7078
- container.style.overflowX = "hidden";
7079
- container.style.overflowY = "auto";
7080
- container.style.padding = "16px 8px";
7665
+ container.style.overflow = "auto";
7081
7666
  container.style.backgroundColor = "#f1f5f9";
7082
- container.style.display = "flex";
7083
- container.style.justifyContent = "center";
7084
- container.style.alignItems = "flex-start";
7667
+ const scrollWrapper = document.createElement("div");
7668
+ scrollWrapper.className = "fp-doc-scroll-wrapper";
7669
+ scrollWrapper.style.minWidth = "100%";
7670
+ scrollWrapper.style.minHeight = "100%";
7671
+ scrollWrapper.style.width = "max-content";
7672
+ scrollWrapper.style.height = "max-content";
7673
+ scrollWrapper.style.display = "flex";
7674
+ scrollWrapper.style.justifyContent = "center";
7675
+ scrollWrapper.style.alignItems = "flex-start";
7676
+ scrollWrapper.style.padding = "16px 8px";
7677
+ scrollWrapper.style.boxSizing = "border-box";
7678
+ const sizer = document.createElement("div");
7679
+ sizer.className = "fp-doc-sizer";
7680
+ sizer.style.position = "relative";
7681
+ sizer.style.flexShrink = "0";
7682
+ sizer.style.display = "flex";
7683
+ sizer.style.justifyContent = "center";
7684
+ sizer.style.alignItems = "center";
7685
+ scrollWrapper.appendChild(sizer);
7686
+ container.appendChild(scrollWrapper);
7085
7687
  ctx.container.appendChild(container);
7086
7688
  let scale = 1;
7087
7689
  let extractedRawText = "";
@@ -7129,10 +7731,11 @@ var DocPlugin = class {
7129
7731
  pageCard.style.padding = "72px 56px";
7130
7732
  pageCard.style.boxSizing = "border-box";
7131
7733
  pageCard.style.display = i === 0 ? "block" : "none";
7132
- pageCard.style.transformOrigin = "top center";
7734
+ pageCard.style.transformOrigin = "center center";
7133
7735
  pageCard.style.transition = "transform 0.15s ease";
7134
7736
  pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
7135
7737
  pageCard.style.color = "#1e293b";
7738
+ pageCard.style.flexShrink = "0";
7136
7739
  if (isFallback && i === 0) {
7137
7740
  pageCard.innerHTML = `
7138
7741
  <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
@@ -7144,18 +7747,23 @@ var DocPlugin = class {
7144
7747
  } else {
7145
7748
  pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
7146
7749
  }
7147
- container.appendChild(pageCard);
7750
+ sizer.appendChild(pageCard);
7148
7751
  pageCards.push(pageCard);
7149
7752
  }
7150
7753
  let rotation = 0;
7151
7754
  const applyTransform = () => {
7152
7755
  const activeCard = pageCards[currentPage - 1];
7153
7756
  if (activeCard) {
7154
- activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7757
+ const baseW = 816;
7155
7758
  const baseH = activeCard.offsetHeight || 1056;
7156
- const scaledH = baseH * scale;
7157
- const extraH = Math.max(0, scaledH - baseH);
7158
- activeCard.style.marginBottom = `${extraH + 32}px`;
7759
+ const isRotated90 = rotation % 180 !== 0;
7760
+ const boxW = Math.round((isRotated90 ? baseH : baseW) * scale);
7761
+ const boxH = Math.round((isRotated90 ? baseW : baseH) * scale);
7762
+ sizer.style.width = `${boxW}px`;
7763
+ sizer.style.height = `${boxH}px`;
7764
+ activeCard.style.width = `${baseW}px`;
7765
+ activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7766
+ activeCard.style.transformOrigin = "center center";
7159
7767
  }
7160
7768
  };
7161
7769
  const showPage = (pageNum) => {
@@ -7163,11 +7771,11 @@ var DocPlugin = class {
7163
7771
  pageCards.forEach((card, idx) => {
7164
7772
  if (idx + 1 === currentPage) {
7165
7773
  card.style.display = "block";
7166
- card.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7167
7774
  } else {
7168
7775
  card.style.display = "none";
7169
7776
  }
7170
7777
  });
7778
+ applyTransform();
7171
7779
  container.scrollTop = 0;
7172
7780
  ctx.emit("page-change", { page: currentPage, total: totalPages });
7173
7781
  };
@@ -7227,6 +7835,13 @@ var DocPlugin = class {
7227
7835
  applyTransform();
7228
7836
  },
7229
7837
  fitToPage: () => {
7838
+ isUserZoomed = false;
7839
+ fitMode = "page";
7840
+ scale = calculateFitScale("page");
7841
+ rotation = 0;
7842
+ applyTransform();
7843
+ },
7844
+ fitToWidth: () => {
7230
7845
  isUserZoomed = false;
7231
7846
  fitMode = "width";
7232
7847
  scale = calculateFitScale("width");
@@ -7235,9 +7850,11 @@ var DocPlugin = class {
7235
7850
  },
7236
7851
  resetZoom: () => {
7237
7852
  isUserZoomed = false;
7238
- fitMode = "width";
7239
- scale = calculateFitScale("width");
7853
+ fitMode = ctx?.options?.fitMode || "width";
7854
+ scale = calculateFitScale(fitMode);
7240
7855
  rotation = 0;
7856
+ container.scrollTop = 0;
7857
+ container.scrollLeft = 0;
7241
7858
  ctx.container.scrollTop = 0;
7242
7859
  ctx.container.scrollLeft = 0;
7243
7860
  applyTransform();
@@ -7730,6 +8347,14 @@ var PptPlugin = class {
7730
8347
  group: "zoom",
7731
8348
  execute: () => instance.resetZoom?.()
7732
8349
  },
8350
+ {
8351
+ id: "fit-width",
8352
+ icon: "fit-width",
8353
+ label: "Fit to Width",
8354
+ type: "button",
8355
+ group: "zoom",
8356
+ execute: () => instance.fitToWidth?.()
8357
+ },
7733
8358
  {
7734
8359
  id: "rotate-cw",
7735
8360
  icon: "rotate-cw",
@@ -7762,17 +8387,30 @@ var PptPlugin = class {
7762
8387
  container.style.width = "100%";
7763
8388
  container.style.height = "100%";
7764
8389
  container.style.overflow = "auto";
7765
- container.style.display = "flex";
7766
- container.style.justifyContent = "center";
7767
- container.style.alignItems = "center";
7768
- container.style.padding = "32px 16px";
7769
8390
  container.style.backgroundColor = "#0f172a";
7770
8391
  container.style.boxSizing = "border-box";
8392
+ const scrollWrapper = document.createElement("div");
8393
+ scrollWrapper.className = "fp-ppt-scroll-wrapper";
8394
+ scrollWrapper.style.minWidth = "100%";
8395
+ scrollWrapper.style.minHeight = "100%";
8396
+ scrollWrapper.style.width = "max-content";
8397
+ scrollWrapper.style.height = "max-content";
8398
+ scrollWrapper.style.display = "flex";
8399
+ scrollWrapper.style.justifyContent = "center";
8400
+ scrollWrapper.style.alignItems = "center";
8401
+ scrollWrapper.style.padding = "32px 16px";
8402
+ scrollWrapper.style.boxSizing = "border-box";
8403
+ const sizer = document.createElement("div");
8404
+ sizer.className = "fp-ppt-sizer";
8405
+ sizer.style.position = "relative";
8406
+ sizer.style.flexShrink = "0";
8407
+ sizer.style.display = "flex";
8408
+ sizer.style.justifyContent = "center";
8409
+ sizer.style.alignItems = "center";
7771
8410
  const slideCard = document.createElement("div");
7772
8411
  slideCard.className = "fp-ppt-slide-card";
7773
8412
  slideCard.style.width = "960px";
7774
- slideCard.style.maxWidth = "calc(100% - 32px)";
7775
- slideCard.style.maxHeight = "calc(100% - 48px)";
8413
+ slideCard.style.height = "540px";
7776
8414
  slideCard.style.aspectRatio = "16 / 9";
7777
8415
  slideCard.style.backgroundColor = "#ffffff";
7778
8416
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
@@ -7782,23 +8420,47 @@ var PptPlugin = class {
7782
8420
  slideCard.style.transition = "transform 0.2s ease";
7783
8421
  slideCard.style.transformOrigin = "center center";
7784
8422
  slideCard.style.flexShrink = "0";
7785
- container.appendChild(slideCard);
8423
+ sizer.appendChild(slideCard);
8424
+ scrollWrapper.appendChild(sizer);
8425
+ container.appendChild(scrollWrapper);
7786
8426
  ctx.container.appendChild(container);
7787
8427
  let scale = 1;
7788
8428
  let rotation = 0;
7789
8429
  let currentSlide = 1;
7790
8430
  let slides = [];
7791
8431
  const createdBlobUrls = [];
7792
- const calculateFitScale = () => {
7793
- const availW = Math.max(200, container.clientWidth - 48);
7794
- const availH = Math.max(200, container.clientHeight - 72);
7795
- return Math.min(1, Math.min(availW / 960, availH / 540));
8432
+ let fitMode = ctx?.options?.fitMode || "width";
8433
+ let isUserZoomed = false;
8434
+ const calculateFitScale = (mode = fitMode) => {
8435
+ const availW = Math.max(200, container.clientWidth - 32);
8436
+ const availH = Math.max(200, container.clientHeight - 32);
8437
+ if (mode === "page") {
8438
+ return Math.min(2.5, Math.min(availW / 960, availH / 540));
8439
+ }
8440
+ return Math.min(2.5, availW / 960);
7796
8441
  };
7797
8442
  const applyTransform = () => {
8443
+ const cW = 960;
8444
+ const cH = 540;
8445
+ const isRotated90 = rotation % 180 !== 0;
8446
+ const boxW = Math.round((isRotated90 ? cH : cW) * scale);
8447
+ const boxH = Math.round((isRotated90 ? cW : cH) * scale);
8448
+ sizer.style.width = `${boxW}px`;
8449
+ sizer.style.height = `${boxH}px`;
8450
+ slideCard.style.width = `${cW}px`;
8451
+ slideCard.style.height = `${cH}px`;
7798
8452
  slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
8453
+ slideCard.style.transformOrigin = "center center";
7799
8454
  };
8455
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
8456
+ if (!isUserZoomed) {
8457
+ scale = calculateFitScale(fitMode);
8458
+ applyTransform();
8459
+ }
8460
+ }) : null;
8461
+ ro?.observe(container);
7800
8462
  setTimeout(() => {
7801
- scale = calculateFitScale();
8463
+ scale = calculateFitScale(fitMode);
7802
8464
  applyTransform();
7803
8465
  }, 60);
7804
8466
  try {
@@ -7891,6 +8553,7 @@ var PptPlugin = class {
7891
8553
  };
7892
8554
  renderSlide(1);
7893
8555
  const cleanup = () => {
8556
+ ro?.disconnect();
7894
8557
  for (const u of createdBlobUrls) {
7895
8558
  URL.revokeObjectURL(u);
7896
8559
  }
@@ -7901,28 +8564,44 @@ var PptPlugin = class {
7901
8564
  return {
7902
8565
  destroy: cleanup,
7903
8566
  zoomIn: () => {
8567
+ isUserZoomed = true;
7904
8568
  scale += 0.15;
7905
8569
  applyTransform();
7906
8570
  },
7907
8571
  zoomOut: () => {
8572
+ isUserZoomed = true;
7908
8573
  scale = Math.max(0.2, scale - 0.15);
7909
8574
  applyTransform();
7910
8575
  },
7911
8576
  getZoom: () => scale,
7912
8577
  setZoom: (level) => {
8578
+ isUserZoomed = true;
7913
8579
  scale = level;
7914
8580
  applyTransform();
7915
8581
  },
7916
8582
  fitToPage: () => {
7917
- scale = calculateFitScale();
8583
+ isUserZoomed = false;
8584
+ fitMode = "page";
8585
+ scale = calculateFitScale("page");
8586
+ rotation = 0;
8587
+ applyTransform();
8588
+ },
8589
+ fitToWidth: () => {
8590
+ isUserZoomed = false;
8591
+ fitMode = "width";
8592
+ scale = calculateFitScale("width");
7918
8593
  rotation = 0;
7919
8594
  applyTransform();
7920
8595
  },
7921
8596
  resetZoom: () => {
7922
- scale = calculateFitScale();
8597
+ isUserZoomed = false;
8598
+ fitMode = ctx?.options?.fitMode || "width";
8599
+ scale = calculateFitScale(fitMode);
7923
8600
  rotation = 0;
7924
8601
  container.scrollTop = 0;
7925
8602
  container.scrollLeft = 0;
8603
+ ctx.container.scrollTop = 0;
8604
+ ctx.container.scrollLeft = 0;
7926
8605
  applyTransform();
7927
8606
  },
7928
8607
  rotateCW: () => {