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