@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/index.cjs CHANGED
@@ -1171,6 +1171,10 @@ var FilePreviewViewer = class _FilePreviewViewer {
1171
1171
  * Preview a file in the given container element.
1172
1172
  */
1173
1173
  async preview(container, source, options = {}) {
1174
+ options = {
1175
+ ...options,
1176
+ fitMode: options.fitMode ?? "width"
1177
+ };
1174
1178
  this.currentOptions = options;
1175
1179
  this.abort();
1176
1180
  this.abortController = new AbortController();
@@ -1219,8 +1223,8 @@ var FilePreviewViewer = class _FilePreviewViewer {
1219
1223
  this.activeInstance = instance;
1220
1224
  instance.openInSeparateWindow = () => this.openInSeparateWindow();
1221
1225
  instance.toggleThumbnails = () => this.toggleThumbnails();
1222
- if (!instance.resetZoom && instance.fitToPage) {
1223
- instance.resetZoom = () => instance.fitToPage?.();
1226
+ if (!instance.resetZoom) {
1227
+ instance.resetZoom = () => instance.fitToWidth ? instance.fitToWidth() : instance.fitToPage?.();
1224
1228
  }
1225
1229
  this.hideLoading();
1226
1230
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
@@ -1617,7 +1621,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1617
1621
  this.wrapperEl?.classList.toggle("fp-fullscreen-active");
1618
1622
  }
1619
1623
  setTimeout(() => {
1620
- this.activeInstance?.fitToPage?.();
1624
+ this.triggerAutoFit();
1621
1625
  }, 120);
1622
1626
  }
1623
1627
  /**
@@ -1744,18 +1748,29 @@ var FilePreviewViewer = class _FilePreviewViewer {
1744
1748
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl, (isOpen) => {
1745
1749
  this.toolbar?.setActionActive("thumbnails", isOpen);
1746
1750
  setTimeout(() => {
1747
- this.activeInstance?.fitToPage?.();
1751
+ this.triggerAutoFit();
1748
1752
  }, 260);
1749
1753
  });
1750
1754
  this.setupKeyboardShortcuts();
1751
1755
  this.setupDragAndDrop(container, options);
1752
1756
  this.setupResizeAndFullscreenListeners();
1753
1757
  }
1758
+ triggerAutoFit() {
1759
+ if (!this.activeInstance) return;
1760
+ const mode = this.currentOptions?.fitMode ?? "width";
1761
+ if (mode === "width" && this.activeInstance.fitToWidth) {
1762
+ this.activeInstance.fitToWidth();
1763
+ } else if (this.activeInstance.fitToPage) {
1764
+ this.activeInstance.fitToPage();
1765
+ } else if (this.activeInstance.resetZoom) {
1766
+ this.activeInstance.resetZoom();
1767
+ }
1768
+ }
1754
1769
  setupResizeAndFullscreenListeners() {
1755
1770
  if (this.fullscreenHandler) return;
1756
1771
  this.fullscreenHandler = () => {
1757
1772
  setTimeout(() => {
1758
- this.activeInstance?.fitToPage?.();
1773
+ this.triggerAutoFit();
1759
1774
  }, 100);
1760
1775
  };
1761
1776
  document.addEventListener("fullscreenchange", this.fullscreenHandler);
@@ -1763,7 +1778,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1763
1778
  if (typeof ResizeObserver !== "undefined" && this.contentEl) {
1764
1779
  this.resizeObserver = new ResizeObserver(() => {
1765
1780
  if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
1766
- this.activeInstance.fitToPage?.();
1781
+ this.triggerAutoFit();
1767
1782
  }
1768
1783
  });
1769
1784
  this.resizeObserver.observe(this.contentEl);
@@ -1791,7 +1806,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1791
1806
  } else if (e.key === "-" || e.key === "_") {
1792
1807
  this.activeInstance.zoomOut?.();
1793
1808
  } else if (e.key === "0") {
1794
- this.activeInstance.fitToPage?.();
1809
+ this.resetZoom();
1795
1810
  } else if (e.key === "r" || e.key === "R") {
1796
1811
  this.activeInstance.rotateCW?.();
1797
1812
  } else if (e.key === "f" || e.key === "F") {
@@ -2199,16 +2214,22 @@ var PdfPlugin = class {
2199
2214
  container.className = "fp-pdf-container";
2200
2215
  container.style.width = "100%";
2201
2216
  container.style.height = "100%";
2202
- container.style.overflowX = "hidden";
2203
- container.style.overflowY = "auto";
2204
- container.style.display = "flex";
2205
- container.style.flexDirection = "column";
2206
- container.style.alignItems = "center";
2207
- container.style.justifyContent = "flex-start";
2208
- container.style.padding = "16px 8px";
2217
+ container.style.overflow = "auto";
2209
2218
  container.style.backgroundColor = "#0f172a";
2210
2219
  container.style.boxSizing = "border-box";
2211
2220
  container.style.position = "relative";
2221
+ const scrollWrapper = document.createElement("div");
2222
+ scrollWrapper.className = "fp-pdf-scroll-wrapper";
2223
+ scrollWrapper.style.minWidth = "100%";
2224
+ scrollWrapper.style.minHeight = "100%";
2225
+ scrollWrapper.style.width = "max-content";
2226
+ scrollWrapper.style.height = "max-content";
2227
+ scrollWrapper.style.display = "flex";
2228
+ scrollWrapper.style.flexDirection = "column";
2229
+ scrollWrapper.style.alignItems = "center";
2230
+ scrollWrapper.style.justifyContent = "flex-start";
2231
+ scrollWrapper.style.padding = "16px 8px";
2232
+ scrollWrapper.style.boxSizing = "border-box";
2212
2233
  const pageCard = document.createElement("div");
2213
2234
  pageCard.className = "fp-pdf-page-card";
2214
2235
  pageCard.style.boxShadow = "0 10px 35px rgba(0, 0, 0, 0.5)";
@@ -2221,7 +2242,8 @@ var PdfPlugin = class {
2221
2242
  pageCard.style.transition = "box-shadow 0.2s ease";
2222
2243
  let canvas = document.createElement("canvas");
2223
2244
  pageCard.appendChild(canvas);
2224
- container.appendChild(pageCard);
2245
+ scrollWrapper.appendChild(pageCard);
2246
+ container.appendChild(scrollWrapper);
2225
2247
  ctx.container.appendChild(container);
2226
2248
  const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
2227
2249
  const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
@@ -2407,7 +2429,7 @@ var PdfPlugin = class {
2407
2429
  renderPage(currentPage);
2408
2430
  },
2409
2431
  resetZoom: () => {
2410
- fitMode = "page";
2432
+ fitMode = ctx?.options?.fitMode || "width";
2411
2433
  zoomScale = 1;
2412
2434
  rotation = 0;
2413
2435
  container.scrollTop = 0;
@@ -2591,6 +2613,16 @@ var MediaPlugin = class {
2591
2613
  instance.resetZoom?.();
2592
2614
  }
2593
2615
  },
2616
+ {
2617
+ id: "fit-width",
2618
+ icon: "fit-width",
2619
+ label: "Fit to Width",
2620
+ type: "button",
2621
+ group: "zoom",
2622
+ execute: () => {
2623
+ instance.fitToWidth?.();
2624
+ }
2625
+ },
2594
2626
  {
2595
2627
  id: "rotate-cw",
2596
2628
  icon: "rotate-cw",
@@ -2687,9 +2719,30 @@ var MediaPlugin = class {
2687
2719
  const isVideo = mimeType.startsWith("video/") || VIDEO_EXTS.includes(ctx.metadata.extension || "");
2688
2720
  const isAudio = mimeType.startsWith("audio/") || AUDIO_EXTS.includes(ctx.metadata.extension || "");
2689
2721
  let url = "";
2722
+ let naturalW = 800;
2723
+ let naturalH = 600;
2690
2724
  if (ctx.metadata.extension === ".svg" || mimeType === "image/svg+xml") {
2691
2725
  const decoder = new TextDecoder("utf-8");
2692
2726
  const svgText = decoder.decode(ctx.buffer);
2727
+ try {
2728
+ const vbMatch = svgText.match(/viewBox=["']\s*([0-9.-]+)\s+([0-9.-]+)\s+([0-9.-]+)\s+([0-9.-]+)\s*["']/i);
2729
+ if (vbMatch) {
2730
+ const vw = parseFloat(vbMatch[3]);
2731
+ const vh = parseFloat(vbMatch[4]);
2732
+ if (vw > 0 && vh > 0) {
2733
+ naturalW = vw;
2734
+ naturalH = vh;
2735
+ }
2736
+ } else {
2737
+ const wMatch = svgText.match(/width=["']([0-9.]+)(?:px)?["']/i);
2738
+ const hMatch = svgText.match(/height=["']([0-9.]+)(?:px)?["']/i);
2739
+ if (wMatch && hMatch) {
2740
+ naturalW = parseFloat(wMatch[1]) || naturalW;
2741
+ naturalH = parseFloat(hMatch[1]) || naturalH;
2742
+ }
2743
+ }
2744
+ } catch {
2745
+ }
2693
2746
  const blob = new Blob([svgText], { type: "image/svg+xml" });
2694
2747
  url = URL.createObjectURL(blob);
2695
2748
  } else {
@@ -2730,45 +2783,150 @@ var MediaPlugin = class {
2730
2783
  element = document.createElement("div");
2731
2784
  element.textContent = "Unsupported media type";
2732
2785
  }
2733
- ctx.container.style.display = "flex";
2734
- ctx.container.style.alignItems = "center";
2735
- ctx.container.style.justifyContent = "center";
2736
- ctx.container.style.overflow = "hidden";
2737
- ctx.container.appendChild(element);
2786
+ const scrollWrapper = document.createElement("div");
2787
+ scrollWrapper.className = "fp-media-scroll-wrapper";
2788
+ scrollWrapper.style.minWidth = "100%";
2789
+ scrollWrapper.style.minHeight = "100%";
2790
+ scrollWrapper.style.width = "max-content";
2791
+ scrollWrapper.style.height = "max-content";
2792
+ scrollWrapper.style.display = "flex";
2793
+ scrollWrapper.style.flexDirection = "column";
2794
+ scrollWrapper.style.alignItems = "center";
2795
+ scrollWrapper.style.padding = "16px";
2796
+ scrollWrapper.style.boxSizing = "border-box";
2797
+ const sizer = document.createElement("div");
2798
+ sizer.className = "fp-media-sizer";
2799
+ sizer.style.position = "relative";
2800
+ sizer.style.flexShrink = "0";
2801
+ sizer.style.display = "flex";
2802
+ sizer.style.justifyContent = "center";
2803
+ sizer.style.alignItems = "center";
2804
+ sizer.style.margin = "auto 0";
2805
+ sizer.appendChild(element);
2806
+ scrollWrapper.appendChild(sizer);
2807
+ ctx.container.style.overflow = "auto";
2808
+ ctx.container.style.padding = "0";
2809
+ ctx.container.innerHTML = "";
2810
+ ctx.container.appendChild(scrollWrapper);
2811
+ let baseW = naturalW;
2812
+ let baseH = naturalH;
2813
+ let fitMode = ctx?.options?.fitMode || "width";
2814
+ let isUserZoomed = false;
2815
+ const updateBaseDimensions = () => {
2816
+ if (!isImage) return;
2817
+ const img = element;
2818
+ if (img.naturalWidth > 0 && img.naturalHeight > 0) {
2819
+ naturalW = img.naturalWidth;
2820
+ naturalH = img.naturalHeight;
2821
+ }
2822
+ const availW = Math.max(100, ctx.container.clientWidth - 32);
2823
+ const availH = Math.max(100, ctx.container.clientHeight - 32);
2824
+ let fitRatio;
2825
+ if (fitMode === "page") {
2826
+ fitRatio = Math.min(availW / naturalW, availH / naturalH);
2827
+ } else {
2828
+ fitRatio = availW / naturalW;
2829
+ }
2830
+ baseW = Math.max(1, Math.round(naturalW * fitRatio));
2831
+ baseH = Math.max(1, Math.round(naturalH * fitRatio));
2832
+ };
2738
2833
  const applyTransform = () => {
2739
- element.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
2834
+ if (isImage) {
2835
+ if (!isUserZoomed) {
2836
+ updateBaseDimensions();
2837
+ }
2838
+ const availH = Math.max(100, ctx.container.clientHeight - 32);
2839
+ const isRotated90 = rotation % 180 !== 0;
2840
+ const boxW = Math.round((isRotated90 ? baseH : baseW) * currentZoom);
2841
+ const boxH = Math.round((isRotated90 ? baseW : baseH) * currentZoom);
2842
+ sizer.style.width = `${boxW}px`;
2843
+ sizer.style.height = `${boxH}px`;
2844
+ sizer.style.margin = boxH < availH ? "auto 0" : "0";
2845
+ const imgW = isRotated90 ? boxH : boxW;
2846
+ const imgH = isRotated90 ? boxW : boxH;
2847
+ element.style.width = `${imgW}px`;
2848
+ element.style.height = `${imgH}px`;
2849
+ element.style.maxWidth = "none";
2850
+ element.style.maxHeight = "none";
2851
+ element.style.transform = rotation ? `rotate(${rotation}deg)` : "none";
2852
+ element.style.transformOrigin = "center center";
2853
+ element.style.flexShrink = "0";
2854
+ } else {
2855
+ element.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
2856
+ }
2740
2857
  };
2858
+ if (isImage) {
2859
+ const img = element;
2860
+ if (img.complete && img.naturalWidth > 0) {
2861
+ updateBaseDimensions();
2862
+ applyTransform();
2863
+ } else {
2864
+ img.onload = () => {
2865
+ updateBaseDimensions();
2866
+ applyTransform();
2867
+ };
2868
+ }
2869
+ }
2870
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
2871
+ if (!isUserZoomed && currentZoom === 1) {
2872
+ updateBaseDimensions();
2873
+ applyTransform();
2874
+ }
2875
+ }) : null;
2876
+ ro?.observe(ctx.container);
2741
2877
  const cleanup = () => {
2878
+ ro?.disconnect();
2742
2879
  if (url) URL.revokeObjectURL(url);
2743
- element.remove();
2880
+ scrollWrapper.remove();
2744
2881
  ctx.container.innerHTML = "";
2745
2882
  };
2746
2883
  ctx.signal.addEventListener("abort", cleanup);
2747
2884
  return {
2748
2885
  destroy: cleanup,
2749
2886
  zoomIn: isImage ? () => {
2750
- currentZoom += 0.1;
2887
+ isUserZoomed = true;
2888
+ currentZoom = Math.min(5, Math.round((currentZoom + 0.15) * 100) / 100);
2751
2889
  applyTransform();
2752
2890
  } : void 0,
2753
2891
  zoomOut: isImage ? () => {
2754
- currentZoom = Math.max(0.1, currentZoom - 0.1);
2892
+ isUserZoomed = true;
2893
+ currentZoom = Math.max(0.1, Math.round((currentZoom - 0.15) * 100) / 100);
2755
2894
  applyTransform();
2756
2895
  } : void 0,
2757
2896
  getZoom: isImage ? () => currentZoom : void 0,
2758
2897
  setZoom: isImage ? (level) => {
2898
+ isUserZoomed = true;
2759
2899
  currentZoom = level;
2760
2900
  applyTransform();
2761
2901
  } : void 0,
2762
2902
  fitToPage: isImage ? () => {
2903
+ isUserZoomed = false;
2904
+ fitMode = "page";
2905
+ currentZoom = 1;
2906
+ rotation = 0;
2907
+ ctx.container.scrollTop = 0;
2908
+ ctx.container.scrollLeft = 0;
2909
+ updateBaseDimensions();
2910
+ applyTransform();
2911
+ } : void 0,
2912
+ fitToWidth: isImage ? () => {
2913
+ isUserZoomed = false;
2914
+ fitMode = "width";
2763
2915
  currentZoom = 1;
2764
2916
  rotation = 0;
2917
+ ctx.container.scrollTop = 0;
2918
+ ctx.container.scrollLeft = 0;
2919
+ updateBaseDimensions();
2765
2920
  applyTransform();
2766
2921
  } : void 0,
2767
2922
  resetZoom: isImage ? () => {
2923
+ isUserZoomed = false;
2924
+ fitMode = ctx?.options?.fitMode || "width";
2768
2925
  currentZoom = 1;
2769
2926
  rotation = 0;
2770
2927
  ctx.container.scrollTop = 0;
2771
2928
  ctx.container.scrollLeft = 0;
2929
+ updateBaseDimensions();
2772
2930
  applyTransform();
2773
2931
  } : void 0,
2774
2932
  rotateCW: isImage || isVideo ? () => {
@@ -2895,6 +3053,14 @@ var DocxPlugin = class {
2895
3053
  group: "zoom",
2896
3054
  execute: () => instance.resetZoom?.()
2897
3055
  },
3056
+ {
3057
+ id: "fit-width",
3058
+ icon: "fit-width",
3059
+ label: "Fit to Width",
3060
+ type: "button",
3061
+ group: "zoom",
3062
+ execute: () => instance.fitToWidth?.()
3063
+ },
2898
3064
  {
2899
3065
  id: "rotate-cw",
2900
3066
  icon: "rotate-cw",
@@ -2933,7 +3099,7 @@ var DocxPlugin = class {
2933
3099
  async render(ctx) {
2934
3100
  const wrapper = document.createElement("div");
2935
3101
  wrapper.className = "fp-docx-wrapper";
2936
- wrapper.style.transformOrigin = "top center";
3102
+ wrapper.style.transformOrigin = "center center";
2937
3103
  wrapper.style.transition = "transform 0.2s ease";
2938
3104
  wrapper.style.padding = "0";
2939
3105
  wrapper.style.maxWidth = "none";
@@ -2943,11 +3109,31 @@ var DocxPlugin = class {
2943
3109
  wrapper.style.flexDirection = "column";
2944
3110
  wrapper.style.alignItems = "center";
2945
3111
  wrapper.style.boxSizing = "border-box";
2946
- ctx.container.style.overflowX = "hidden";
2947
- ctx.container.style.overflowY = "auto";
2948
- ctx.container.style.padding = "16px 8px";
3112
+ wrapper.style.flexShrink = "0";
3113
+ const sizer = document.createElement("div");
3114
+ sizer.className = "fp-docx-sizer";
3115
+ sizer.style.position = "relative";
3116
+ sizer.style.flexShrink = "0";
3117
+ sizer.style.display = "flex";
3118
+ sizer.style.justifyContent = "center";
3119
+ sizer.style.alignItems = "center";
3120
+ const scrollWrapper = document.createElement("div");
3121
+ scrollWrapper.className = "fp-docx-scroll-wrapper";
3122
+ scrollWrapper.style.minWidth = "100%";
3123
+ scrollWrapper.style.minHeight = "100%";
3124
+ scrollWrapper.style.width = "max-content";
3125
+ scrollWrapper.style.height = "max-content";
3126
+ scrollWrapper.style.display = "flex";
3127
+ scrollWrapper.style.justifyContent = "center";
3128
+ scrollWrapper.style.alignItems = "flex-start";
3129
+ scrollWrapper.style.padding = "16px 8px";
3130
+ scrollWrapper.style.boxSizing = "border-box";
3131
+ sizer.appendChild(wrapper);
3132
+ scrollWrapper.appendChild(sizer);
3133
+ ctx.container.style.overflow = "auto";
3134
+ ctx.container.style.padding = "0";
2949
3135
  ctx.container.style.backgroundColor = "#f1f5f9";
2950
- ctx.container.appendChild(wrapper);
3136
+ ctx.container.appendChild(scrollWrapper);
2951
3137
  const styleOverride = document.createElement("style");
2952
3138
  styleOverride.textContent = `
2953
3139
  .fp-docx-wrapper .docx-wrapper {
@@ -2986,8 +3172,8 @@ var DocxPlugin = class {
2986
3172
  renderEndnotes: true,
2987
3173
  useBase64URL: true
2988
3174
  });
2989
- if (!ctx.container.contains(wrapper)) {
2990
- ctx.container.appendChild(wrapper);
3175
+ if (!sizer.contains(wrapper)) {
3176
+ sizer.appendChild(wrapper);
2991
3177
  }
2992
3178
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
2993
3179
  renderedSuccessfully = true;
@@ -3024,8 +3210,8 @@ var DocxPlugin = class {
3024
3210
  try {
3025
3211
  wrapper.innerHTML = "";
3026
3212
  this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
3027
- if (!ctx.container.contains(wrapper)) {
3028
- ctx.container.appendChild(wrapper);
3213
+ if (!sizer.contains(wrapper)) {
3214
+ sizer.appendChild(wrapper);
3029
3215
  }
3030
3216
  renderedSuccessfully = true;
3031
3217
  } catch (fallbackErr) {
@@ -3040,8 +3226,8 @@ var DocxPlugin = class {
3040
3226
  </p>
3041
3227
  </div>
3042
3228
  `;
3043
- if (!ctx.container.contains(wrapper)) {
3044
- ctx.container.appendChild(wrapper);
3229
+ if (!sizer.contains(wrapper)) {
3230
+ sizer.appendChild(wrapper);
3045
3231
  }
3046
3232
  }
3047
3233
  }
@@ -3134,31 +3320,40 @@ var DocxPlugin = class {
3134
3320
  }
3135
3321
  scale = 1;
3136
3322
  let rotation = 0;
3137
- let fitMode = "page";
3323
+ let fitMode = ctx?.options?.fitMode || "width";
3138
3324
  let isUserZoomed = false;
3139
- const calculateFitScale = (_mode = fitMode) => {
3325
+ const calculateFitScale = (mode = fitMode) => {
3140
3326
  const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3141
3327
  const elW = activeEl.offsetWidth || 816;
3328
+ const elH = activeEl.offsetHeight || 1056;
3142
3329
  const availW = Math.max(280, ctx.container.clientWidth - 32);
3330
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
3143
3331
  const sW = availW / elW;
3332
+ const sH = availH / elH;
3333
+ if (mode === "page") {
3334
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
3335
+ }
3144
3336
  return Math.max(0.35, Math.min(3, sW));
3145
3337
  };
3146
3338
  const applyTransform = () => {
3147
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3148
- wrapper.style.transformOrigin = "top center";
3149
3339
  const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3340
+ const elW = activeEl.offsetWidth || 816;
3150
3341
  const elH = activeEl.offsetHeight || 1056;
3151
- if (scale > 1) {
3152
- wrapper.style.marginBottom = `${Math.round(elH * scale - elH + 32)}px`;
3153
- } else {
3154
- wrapper.style.marginBottom = "32px";
3155
- }
3342
+ const isRotated90 = rotation % 180 !== 0;
3343
+ const boxW = Math.round((isRotated90 ? elH : elW) * scale);
3344
+ const boxH = Math.round((isRotated90 ? elW : elH) * scale);
3345
+ sizer.style.width = `${boxW}px`;
3346
+ sizer.style.height = `${boxH}px`;
3347
+ wrapper.style.width = `${elW}px`;
3348
+ wrapper.style.height = `${elH}px`;
3349
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3350
+ wrapper.style.transformOrigin = "center center";
3156
3351
  };
3157
3352
  let resizeObserver = null;
3158
3353
  if (typeof ResizeObserver !== "undefined") {
3159
3354
  resizeObserver = new ResizeObserver(() => {
3160
3355
  if (!isUserZoomed) {
3161
- const newFit = calculateFitScale("page");
3356
+ const newFit = calculateFitScale(fitMode);
3162
3357
  if (Math.abs(scale - newFit) > 0.015) {
3163
3358
  scale = newFit;
3164
3359
  applyTransform();
@@ -3168,7 +3363,7 @@ var DocxPlugin = class {
3168
3363
  resizeObserver.observe(ctx.container);
3169
3364
  }
3170
3365
  setTimeout(() => {
3171
- scale = calculateFitScale("page");
3366
+ scale = calculateFitScale(fitMode);
3172
3367
  applyTransform();
3173
3368
  }, 40);
3174
3369
  const cleanup = () => {
@@ -3177,7 +3372,7 @@ var DocxPlugin = class {
3177
3372
  for (const url of createdBlobUrls) {
3178
3373
  URL.revokeObjectURL(url);
3179
3374
  }
3180
- wrapper.remove();
3375
+ scrollWrapper.remove();
3181
3376
  styleOverride.remove();
3182
3377
  ctx.container.innerHTML = "";
3183
3378
  };
@@ -3207,13 +3402,22 @@ var DocxPlugin = class {
3207
3402
  },
3208
3403
  fitToPage: () => {
3209
3404
  isUserZoomed = false;
3405
+ fitMode = "page";
3210
3406
  scale = calculateFitScale("page");
3211
3407
  rotation = 0;
3212
3408
  applyTransform();
3213
3409
  },
3410
+ fitToWidth: () => {
3411
+ isUserZoomed = false;
3412
+ fitMode = "width";
3413
+ scale = calculateFitScale("width");
3414
+ rotation = 0;
3415
+ applyTransform();
3416
+ },
3214
3417
  resetZoom: () => {
3215
3418
  isUserZoomed = false;
3216
- scale = calculateFitScale("page");
3419
+ fitMode = ctx?.options?.fitMode || "width";
3420
+ scale = calculateFitScale(fitMode);
3217
3421
  rotation = 0;
3218
3422
  ctx.container.scrollTop = 0;
3219
3423
  ctx.container.scrollLeft = 0;
@@ -3678,6 +3882,16 @@ var ExcelPlugin = class {
3678
3882
  instance.resetZoom?.();
3679
3883
  }
3680
3884
  },
3885
+ {
3886
+ id: "fit-width",
3887
+ icon: "fit-width",
3888
+ label: "Fit to Width",
3889
+ type: "button",
3890
+ group: "zoom",
3891
+ execute: () => {
3892
+ instance.fitToWidth?.();
3893
+ }
3894
+ },
3681
3895
  {
3682
3896
  id: "download",
3683
3897
  icon: "download",
@@ -3724,7 +3938,7 @@ var ExcelPlugin = class {
3724
3938
  contentArea.style.flex = "1";
3725
3939
  contentArea.style.overflow = "auto";
3726
3940
  contentArea.style.padding = "16px";
3727
- contentArea.style.transformOrigin = "top left";
3941
+ contentArea.style.boxSizing = "border-box";
3728
3942
  const tabsArea = document.createElement("div");
3729
3943
  tabsArea.className = "fp-excel-tabs";
3730
3944
  tabsArea.style.display = "flex";
@@ -3738,18 +3952,35 @@ var ExcelPlugin = class {
3738
3952
  container.appendChild(tabsArea);
3739
3953
  ctx.container.appendChild(container);
3740
3954
  let scale = 1;
3955
+ let isUserZoomed = false;
3741
3956
  let currentSheetIndex = 1;
3742
3957
  let sheetNames = [];
3743
3958
  let wb = null;
3744
3959
  const applyTransform = () => {
3745
- contentArea.style.transform = `scale(${scale})`;
3746
- contentArea.style.transformOrigin = "top left";
3960
+ const sizer = contentArea.querySelector(".fp-excel-sizer");
3961
+ const table = contentArea.querySelector("table");
3962
+ if (!sizer || !table) return;
3963
+ if (!table._baseWidth && table.offsetWidth > 0) {
3964
+ table._baseWidth = table.offsetWidth;
3965
+ table._baseHeight = table.offsetHeight;
3966
+ }
3967
+ const baseW = table._baseWidth || table.offsetWidth || 800;
3968
+ const baseH = table._baseHeight || table.offsetHeight || 600;
3969
+ const scaledW = Math.round(baseW * scale);
3970
+ const scaledH = Math.round(baseH * scale);
3971
+ sizer.style.width = `${scaledW}px`;
3972
+ sizer.style.height = `${scaledH}px`;
3973
+ table.style.position = "absolute";
3974
+ table.style.top = "0";
3975
+ table.style.left = "0";
3976
+ table.style.transform = `scale(${scale})`;
3977
+ table.style.transformOrigin = "top left";
3747
3978
  };
3748
3979
  const calculateFitScale = () => {
3749
3980
  const table = contentArea.querySelector("table");
3750
3981
  if (!table) return 1;
3751
3982
  const availW = Math.max(280, container.clientWidth - 48);
3752
- const tW = table.offsetWidth || 800;
3983
+ const tW = table._baseWidth || table.offsetWidth || 800;
3753
3984
  return Math.max(0.4, Math.min(1, availW / tW));
3754
3985
  };
3755
3986
  try {
@@ -3769,9 +4000,13 @@ var ExcelPlugin = class {
3769
4000
  contentArea.innerHTML = '<div style="padding: 24px; color: #888;">Empty sheet</div>';
3770
4001
  return;
3771
4002
  }
4003
+ const sizer = document.createElement("div");
4004
+ sizer.className = "fp-excel-sizer";
4005
+ sizer.style.position = "relative";
3772
4006
  const html = XLSX__namespace.utils.sheet_to_html(ws, { id: "fp-sheet-table", editable: false });
3773
- contentArea.innerHTML = html;
3774
- const table = contentArea.querySelector("table");
4007
+ sizer.innerHTML = html;
4008
+ contentArea.appendChild(sizer);
4009
+ const table = sizer.querySelector("table");
3775
4010
  if (table) {
3776
4011
  table.style.borderCollapse = "collapse";
3777
4012
  table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
@@ -3806,13 +4041,17 @@ var ExcelPlugin = class {
3806
4041
  });
3807
4042
  ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
3808
4043
  requestAnimationFrame(() => {
3809
- scale = calculateFitScale();
4044
+ if (!isUserZoomed) {
4045
+ scale = calculateFitScale();
4046
+ }
3810
4047
  applyTransform();
3811
4048
  });
3812
4049
  };
3813
4050
  const ro = new ResizeObserver(() => {
3814
- scale = calculateFitScale();
3815
- applyTransform();
4051
+ if (!isUserZoomed) {
4052
+ scale = calculateFitScale();
4053
+ applyTransform();
4054
+ }
3816
4055
  });
3817
4056
  ro.observe(container);
3818
4057
  if (sheetNames.length > 0) {
@@ -3849,23 +4088,33 @@ var ExcelPlugin = class {
3849
4088
  return {
3850
4089
  destroy: cleanup,
3851
4090
  zoomIn: () => {
4091
+ isUserZoomed = true;
3852
4092
  scale += 0.15;
3853
4093
  applyTransform();
3854
4094
  },
3855
4095
  zoomOut: () => {
4096
+ isUserZoomed = true;
3856
4097
  scale = Math.max(0.2, scale - 0.15);
3857
4098
  applyTransform();
3858
4099
  },
3859
4100
  getZoom: () => scale,
3860
4101
  setZoom: (level) => {
4102
+ isUserZoomed = true;
3861
4103
  scale = level;
3862
4104
  applyTransform();
3863
4105
  },
3864
4106
  fitToPage: () => {
4107
+ isUserZoomed = false;
4108
+ scale = calculateFitScale();
4109
+ applyTransform();
4110
+ },
4111
+ fitToWidth: () => {
4112
+ isUserZoomed = false;
3865
4113
  scale = calculateFitScale();
3866
4114
  applyTransform();
3867
4115
  },
3868
4116
  resetZoom: () => {
4117
+ isUserZoomed = false;
3869
4118
  scale = calculateFitScale();
3870
4119
  contentArea.scrollTop = 0;
3871
4120
  contentArea.scrollLeft = 0;
@@ -4011,6 +4260,16 @@ var CsvPlugin = class {
4011
4260
  instance.resetZoom?.();
4012
4261
  }
4013
4262
  },
4263
+ {
4264
+ id: "fit-width",
4265
+ icon: "fit-width",
4266
+ label: "Fit to Width",
4267
+ type: "button",
4268
+ group: "zoom",
4269
+ execute: () => {
4270
+ instance.fitToWidth?.();
4271
+ }
4272
+ },
4014
4273
  {
4015
4274
  id: "download",
4016
4275
  icon: "download",
@@ -4097,21 +4356,41 @@ var CsvPlugin = class {
4097
4356
  container.appendChild(tableWrapper);
4098
4357
  ctx.container.appendChild(container);
4099
4358
  let scale = 1;
4359
+ let isUserZoomed = false;
4100
4360
  const calculateFitScale = () => {
4101
4361
  const availW = Math.max(280, container.clientWidth - 48);
4102
- const tW = table.offsetWidth || 800;
4362
+ const tW = table._baseWidth || table.offsetWidth || 800;
4103
4363
  return Math.max(0.4, Math.min(1, availW / tW));
4104
4364
  };
4105
4365
  const applyScale = () => {
4106
- tableWrapper.style.transform = `scale(${scale})`;
4366
+ if (!table._baseWidth && table.offsetWidth > 0) {
4367
+ table._baseWidth = table.offsetWidth;
4368
+ table._baseHeight = table.offsetHeight;
4369
+ }
4370
+ const baseW = table._baseWidth || table.offsetWidth || 800;
4371
+ const baseH = table._baseHeight || table.offsetHeight || 600;
4372
+ const scaledW = Math.round(baseW * scale);
4373
+ const scaledH = Math.round(baseH * scale);
4374
+ tableWrapper.style.position = "relative";
4375
+ tableWrapper.style.width = `${scaledW}px`;
4376
+ tableWrapper.style.height = `${scaledH}px`;
4377
+ table.style.position = "absolute";
4378
+ table.style.top = "0";
4379
+ table.style.left = "0";
4380
+ table.style.transform = `scale(${scale})`;
4381
+ table.style.transformOrigin = "top left";
4107
4382
  };
4108
4383
  requestAnimationFrame(() => {
4109
- scale = calculateFitScale();
4384
+ if (!isUserZoomed) {
4385
+ scale = calculateFitScale();
4386
+ }
4110
4387
  applyScale();
4111
4388
  });
4112
4389
  const ro = new ResizeObserver(() => {
4113
- scale = calculateFitScale();
4114
- applyScale();
4390
+ if (!isUserZoomed) {
4391
+ scale = calculateFitScale();
4392
+ applyScale();
4393
+ }
4115
4394
  });
4116
4395
  ro.observe(container);
4117
4396
  const cleanup = () => {
@@ -4184,23 +4463,33 @@ var CsvPlugin = class {
4184
4463
  }];
4185
4464
  },
4186
4465
  zoomIn: () => {
4466
+ isUserZoomed = true;
4187
4467
  scale += 0.1;
4188
4468
  applyScale();
4189
4469
  },
4190
4470
  zoomOut: () => {
4471
+ isUserZoomed = true;
4191
4472
  scale = Math.max(0.2, scale - 0.1);
4192
4473
  applyScale();
4193
4474
  },
4194
4475
  getZoom: () => scale,
4195
4476
  setZoom: (level) => {
4477
+ isUserZoomed = true;
4196
4478
  scale = level;
4197
4479
  applyScale();
4198
4480
  },
4199
4481
  fitToPage: () => {
4482
+ isUserZoomed = false;
4483
+ scale = calculateFitScale();
4484
+ applyScale();
4485
+ },
4486
+ fitToWidth: () => {
4487
+ isUserZoomed = false;
4200
4488
  scale = calculateFitScale();
4201
4489
  applyScale();
4202
4490
  },
4203
4491
  resetZoom: () => {
4492
+ isUserZoomed = false;
4204
4493
  scale = calculateFitScale();
4205
4494
  container.scrollTop = 0;
4206
4495
  container.scrollLeft = 0;
@@ -4331,6 +4620,16 @@ var CodePlugin = class {
4331
4620
  instance.resetZoom?.();
4332
4621
  }
4333
4622
  },
4623
+ {
4624
+ id: "fit-width",
4625
+ icon: "fit-width",
4626
+ label: "Fit to Width",
4627
+ type: "button",
4628
+ group: "zoom",
4629
+ execute: () => {
4630
+ instance.fitToWidth?.();
4631
+ }
4632
+ },
4334
4633
  {
4335
4634
  id: "copy",
4336
4635
  icon: "copy",
@@ -4436,15 +4735,27 @@ var CodePlugin = class {
4436
4735
  let isUserZoomed = false;
4437
4736
  const pre = document.createElement("pre");
4438
4737
  const code = document.createElement("code");
4738
+ const sizer = document.createElement("div");
4739
+ const scrollWrapper = document.createElement("div");
4439
4740
  if (isTxt) {
4440
- container.style.overflowX = "hidden";
4441
- container.style.overflowY = "auto";
4741
+ container.style.overflow = "auto";
4442
4742
  container.style.backgroundColor = "#f1f5f9";
4443
- container.style.padding = "16px 8px";
4444
- container.style.boxSizing = "border-box";
4445
- container.style.display = "flex";
4446
- container.style.flexDirection = "column";
4447
- container.style.alignItems = "center";
4743
+ scrollWrapper.className = "fp-code-scroll-wrapper";
4744
+ scrollWrapper.style.minWidth = "100%";
4745
+ scrollWrapper.style.minHeight = "100%";
4746
+ scrollWrapper.style.width = "max-content";
4747
+ scrollWrapper.style.height = "max-content";
4748
+ scrollWrapper.style.display = "flex";
4749
+ scrollWrapper.style.justifyContent = "center";
4750
+ scrollWrapper.style.alignItems = "flex-start";
4751
+ scrollWrapper.style.padding = "16px 8px";
4752
+ scrollWrapper.style.boxSizing = "border-box";
4753
+ sizer.className = "fp-code-sizer";
4754
+ sizer.style.position = "relative";
4755
+ sizer.style.flexShrink = "0";
4756
+ sizer.style.display = "flex";
4757
+ sizer.style.justifyContent = "center";
4758
+ sizer.style.alignItems = "center";
4448
4759
  pre.style.margin = "0 auto";
4449
4760
  pre.style.fontFamily = "Consolas, 'Courier New', monospace";
4450
4761
  pre.style.fontSize = "13px";
@@ -4459,8 +4770,9 @@ var CodePlugin = class {
4459
4770
  pre.style.boxSizing = "border-box";
4460
4771
  pre.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
4461
4772
  pre.style.borderRadius = "4px";
4462
- pre.style.transformOrigin = "top center";
4773
+ pre.style.transformOrigin = "center center";
4463
4774
  pre.style.transition = "transform 0.15s ease";
4775
+ pre.style.flexShrink = "0";
4464
4776
  } else {
4465
4777
  container.style.overflow = "auto";
4466
4778
  container.style.backgroundColor = "#1e1e1e";
@@ -4493,7 +4805,13 @@ var CodePlugin = class {
4493
4805
  };
4494
4806
  renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
4495
4807
  pre.appendChild(code);
4496
- container.appendChild(pre);
4808
+ if (isTxt) {
4809
+ sizer.appendChild(pre);
4810
+ scrollWrapper.appendChild(sizer);
4811
+ container.appendChild(scrollWrapper);
4812
+ } else {
4813
+ container.appendChild(pre);
4814
+ }
4497
4815
  const showPage = (pageNum) => {
4498
4816
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4499
4817
  renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
@@ -4510,10 +4828,16 @@ var CodePlugin = class {
4510
4828
  };
4511
4829
  const updateTransform = () => {
4512
4830
  if (isTxt) {
4831
+ const elW = 816;
4832
+ const baseH = pre.offsetHeight || 1056;
4833
+ const isRotated90 = rotation % 180 !== 0;
4834
+ const boxW = Math.round((isRotated90 ? baseH : elW) * zoomLevel);
4835
+ const boxH = Math.round((isRotated90 ? elW : baseH) * zoomLevel);
4836
+ sizer.style.width = `${boxW}px`;
4837
+ sizer.style.height = `${boxH}px`;
4838
+ pre.style.width = `${elW}px`;
4513
4839
  pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
4514
- const scaledH = 1056 * zoomLevel;
4515
- const extraH = Math.max(0, scaledH - 1056);
4516
- pre.style.marginBottom = `${extraH + 32}px`;
4840
+ pre.style.transformOrigin = "center center";
4517
4841
  } else {
4518
4842
  pre.style.transform = `rotate(${rotation}deg)`;
4519
4843
  pre.style.fontSize = `${fontSize}px`;
@@ -4610,6 +4934,13 @@ var CodePlugin = class {
4610
4934
  zoomLevel = isTxt ? calculateTxtFit() : 1;
4611
4935
  updateTransform();
4612
4936
  },
4937
+ fitToWidth: () => {
4938
+ isUserZoomed = false;
4939
+ fontSize = 13;
4940
+ rotation = 0;
4941
+ zoomLevel = isTxt ? calculateTxtFit() : 1;
4942
+ updateTransform();
4943
+ },
4613
4944
  resetZoom: () => {
4614
4945
  isUserZoomed = false;
4615
4946
  fontSize = 13;
@@ -4694,6 +5025,14 @@ var ArchivePlugin = class {
4694
5025
  group: "zoom",
4695
5026
  execute: () => instance.resetZoom?.()
4696
5027
  },
5028
+ {
5029
+ id: "fit-width",
5030
+ icon: "fit-width",
5031
+ label: "Fit to Width",
5032
+ type: "button",
5033
+ group: "zoom",
5034
+ execute: () => instance.fitToWidth?.()
5035
+ },
4697
5036
  {
4698
5037
  id: "download",
4699
5038
  icon: "download",
@@ -4719,16 +5058,56 @@ var ArchivePlugin = class {
4719
5058
  width: 100%;
4720
5059
  height: 100%;
4721
5060
  overflow: auto;
4722
- padding: 24px;
4723
5061
  box-sizing: border-box;
4724
5062
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
4725
- transform-origin: top left;
4726
- transition: transform 0.2s ease;
4727
5063
  `;
5064
+ const scrollWrapper = document.createElement("div");
5065
+ scrollWrapper.className = "fp-archive-scroll-wrapper";
5066
+ scrollWrapper.style.cssText = `
5067
+ min-width: 100%;
5068
+ min-height: 100%;
5069
+ width: max-content;
5070
+ height: max-content;
5071
+ display: flex;
5072
+ justify-content: center;
5073
+ align-items: flex-start;
5074
+ padding: 24px;
5075
+ box-sizing: border-box;
5076
+ `;
5077
+ const sizer = document.createElement("div");
5078
+ sizer.className = "fp-archive-sizer";
5079
+ sizer.style.cssText = `
5080
+ position: relative;
5081
+ flex-shrink: 0;
5082
+ display: flex;
5083
+ justify-content: center;
5084
+ align-items: flex-start;
5085
+ `;
5086
+ const card = document.createElement("div");
5087
+ card.className = "fp-archive-card";
5088
+ card.style.cssText = `
5089
+ width: 900px;
5090
+ box-sizing: border-box;
5091
+ transform-origin: top center;
5092
+ transition: transform 0.15s ease;
5093
+ flex-shrink: 0;
5094
+ `;
5095
+ sizer.appendChild(card);
5096
+ scrollWrapper.appendChild(sizer);
5097
+ wrapper.appendChild(scrollWrapper);
4728
5098
  ctx.container.innerHTML = "";
4729
5099
  ctx.container.style.overflow = "auto";
4730
5100
  ctx.container.appendChild(wrapper);
4731
5101
  let scale = 1;
5102
+ const applyTransform = () => {
5103
+ const boxW = Math.round(900 * scale);
5104
+ const cardH = card.offsetHeight || 600;
5105
+ const boxH = Math.round(cardH * scale);
5106
+ sizer.style.width = `${boxW}px`;
5107
+ sizer.style.height = `${boxH}px`;
5108
+ card.style.transform = `scale(${scale})`;
5109
+ card.style.transformOrigin = "top center";
5110
+ };
4732
5111
  const entries = await new Promise((resolve, reject) => {
4733
5112
  fflate.unzip(new Uint8Array(ctx.buffer), (err, unzipped) => {
4734
5113
  if (err) {
@@ -4752,7 +5131,7 @@ var ArchivePlugin = class {
4752
5131
  });
4753
5132
  const totalUncompressedSize = entries.reduce((acc, e) => acc + e.size, 0);
4754
5133
  const renderUI = (filteredEntries) => {
4755
- wrapper.innerHTML = `
5134
+ card.innerHTML = `
4756
5135
  <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;">
4757
5136
  <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;">
4758
5137
  <div>
@@ -4782,7 +5161,7 @@ var ArchivePlugin = class {
4782
5161
  </div>
4783
5162
  </div>
4784
5163
  `;
4785
- const tbody = wrapper.querySelector("#fp-archive-body");
5164
+ const tbody = card.querySelector("#fp-archive-body");
4786
5165
  if (filteredEntries.length === 0) {
4787
5166
  tbody.innerHTML = `<tr><td colspan="3" style="padding: 24px; text-align: center; color: #9ca3af;">No matching files found</td></tr>`;
4788
5167
  } else {
@@ -4811,19 +5190,20 @@ var ArchivePlugin = class {
4811
5190
  }
4812
5191
  });
4813
5192
  });
4814
- const searchInput = wrapper.querySelector("#fp-archive-search");
5193
+ const searchInput = card.querySelector("#fp-archive-search");
4815
5194
  if (searchInput) {
4816
5195
  searchInput.addEventListener("input", () => {
4817
5196
  const q = searchInput.value.toLowerCase().trim();
4818
5197
  const filtered = q ? entries.filter((e) => e.path.toLowerCase().includes(q)) : entries;
4819
5198
  renderUI(filtered);
4820
- const newSearch = wrapper.querySelector("#fp-archive-search");
5199
+ const newSearch = card.querySelector("#fp-archive-search");
4821
5200
  if (newSearch) {
4822
5201
  newSearch.value = q;
4823
5202
  newSearch.focus();
4824
5203
  }
4825
5204
  });
4826
5205
  }
5206
+ applyTransform();
4827
5207
  };
4828
5208
  renderUI(entries);
4829
5209
  const cleanup = () => {
@@ -4835,26 +5215,30 @@ var ArchivePlugin = class {
4835
5215
  destroy: cleanup,
4836
5216
  zoomIn: () => {
4837
5217
  scale += 0.1;
4838
- wrapper.style.transform = `scale(${scale})`;
5218
+ applyTransform();
4839
5219
  },
4840
5220
  zoomOut: () => {
4841
5221
  scale = Math.max(0.3, scale - 0.1);
4842
- wrapper.style.transform = `scale(${scale})`;
5222
+ applyTransform();
4843
5223
  },
4844
5224
  getZoom: () => scale,
4845
5225
  setZoom: (level) => {
4846
5226
  scale = level;
4847
- wrapper.style.transform = `scale(${scale})`;
5227
+ applyTransform();
4848
5228
  },
4849
5229
  fitToPage: () => {
4850
5230
  scale = 1;
4851
- wrapper.style.transform = "scale(1)";
5231
+ applyTransform();
5232
+ },
5233
+ fitToWidth: () => {
5234
+ scale = 1;
5235
+ applyTransform();
4852
5236
  },
4853
5237
  resetZoom: () => {
4854
5238
  scale = 1;
4855
- wrapper.style.transform = "scale(1)";
4856
- ctx.container.scrollTop = 0;
4857
- ctx.container.scrollLeft = 0;
5239
+ wrapper.scrollTop = 0;
5240
+ wrapper.scrollLeft = 0;
5241
+ applyTransform();
4858
5242
  },
4859
5243
  download: () => {
4860
5244
  downloadFile(ctx.buffer, ctx.metadata.name || "archive.zip", "application/zip");
@@ -4914,6 +5298,14 @@ var MarkdownPlugin = class {
4914
5298
  group: "zoom",
4915
5299
  execute: () => instance.resetZoom?.()
4916
5300
  },
5301
+ {
5302
+ id: "fit-width",
5303
+ icon: "fit-width",
5304
+ label: "Fit to Width",
5305
+ type: "button",
5306
+ group: "zoom",
5307
+ execute: () => instance.fitToWidth?.()
5308
+ },
4917
5309
  {
4918
5310
  id: "copy",
4919
5311
  icon: "copy",
@@ -5067,6 +5459,10 @@ var MarkdownPlugin = class {
5067
5459
  fontSize = 15;
5068
5460
  wrapper.style.fontSize = "15px";
5069
5461
  },
5462
+ fitToWidth: () => {
5463
+ fontSize = 15;
5464
+ wrapper.style.fontSize = "15px";
5465
+ },
5070
5466
  resetZoom: () => {
5071
5467
  fontSize = 15;
5072
5468
  wrapper.style.fontSize = "15px";
@@ -5174,6 +5570,14 @@ var PptxPlugin = class {
5174
5570
  group: "zoom",
5175
5571
  execute: () => instance.resetZoom?.()
5176
5572
  },
5573
+ {
5574
+ id: "fit-width",
5575
+ icon: "fit-width",
5576
+ label: "Fit to Width",
5577
+ type: "button",
5578
+ group: "zoom",
5579
+ execute: () => instance.fitToWidth?.()
5580
+ },
5177
5581
  {
5178
5582
  id: "rotate-cw",
5179
5583
  icon: "rotate-cw",
@@ -5213,12 +5617,30 @@ var PptxPlugin = class {
5213
5617
  width: 100%;
5214
5618
  height: 100%;
5215
5619
  overflow: auto;
5620
+ box-sizing: border-box;
5621
+ background: var(--fp-bg-canvas, #525659);
5622
+ `;
5623
+ const scrollWrapper = document.createElement("div");
5624
+ scrollWrapper.className = "fp-pptx-scroll-wrapper";
5625
+ scrollWrapper.style.cssText = `
5626
+ min-width: 100%;
5627
+ min-height: 100%;
5628
+ width: max-content;
5629
+ height: max-content;
5216
5630
  display: flex;
5217
5631
  justify-content: center;
5218
- align-items: flex-start;
5632
+ align-items: center;
5219
5633
  padding: 24px;
5220
5634
  box-sizing: border-box;
5221
- background: var(--fp-bg-canvas, #525659);
5635
+ `;
5636
+ const sizer = document.createElement("div");
5637
+ sizer.className = "fp-pptx-sizer";
5638
+ sizer.style.cssText = `
5639
+ position: relative;
5640
+ flex-shrink: 0;
5641
+ display: flex;
5642
+ justify-content: center;
5643
+ align-items: center;
5222
5644
  `;
5223
5645
  const slideContainer = document.createElement("div");
5224
5646
  slideContainer.className = "fp-slide-container";
@@ -5227,39 +5649,65 @@ var PptxPlugin = class {
5227
5649
  border-radius: 4px;
5228
5650
  overflow: hidden;
5229
5651
  background: #ffffff;
5230
- transform-origin: top center;
5652
+ transform-origin: center center;
5231
5653
  transition: transform 0.2s ease;
5232
- max-width: calc(100% - 32px);
5233
- max-height: calc(100% - 48px);
5654
+ flex-shrink: 0;
5234
5655
  `;
5235
5656
  const canvas = document.createElement("canvas");
5236
5657
  slideContainer.appendChild(canvas);
5237
- wrapper.appendChild(slideContainer);
5658
+ sizer.appendChild(slideContainer);
5659
+ scrollWrapper.appendChild(sizer);
5660
+ wrapper.appendChild(scrollWrapper);
5238
5661
  ctx.container.innerHTML = "";
5239
5662
  ctx.container.style.overflow = "auto";
5240
5663
  ctx.container.appendChild(wrapper);
5241
- const calculateFitScale = () => {
5242
- const availW = Math.max(200, ctx.container.clientWidth - 48);
5243
- const availH = Math.max(200, ctx.container.clientHeight - 72);
5664
+ let fitMode = ctx?.options?.fitMode || "width";
5665
+ let isUserZoomed = false;
5666
+ const calculateFitScale = (mode = fitMode) => {
5667
+ const availW = Math.max(200, ctx.container.clientWidth - 32);
5668
+ const availH = Math.max(200, ctx.container.clientHeight - 32);
5244
5669
  const cW = canvas.offsetWidth || 1280;
5245
5670
  const cH = canvas.offsetHeight || 720;
5246
- return Math.min(1, Math.min(availW / cW, availH / cH));
5671
+ if (mode === "page") {
5672
+ return Math.min(2.5, Math.min(availW / cW, availH / cH));
5673
+ }
5674
+ return Math.min(2.5, availW / cW);
5247
5675
  };
5248
5676
  const applyTransform = () => {
5677
+ const cW = canvas.offsetWidth || 1280;
5678
+ const cH = canvas.offsetHeight || 720;
5679
+ const isRotated90 = rotation % 180 !== 0;
5680
+ const boxW = Math.round((isRotated90 ? cH : cW) * scale);
5681
+ const boxH = Math.round((isRotated90 ? cW : cH) * scale);
5682
+ sizer.style.width = `${boxW}px`;
5683
+ sizer.style.height = `${boxH}px`;
5684
+ slideContainer.style.width = `${cW}px`;
5685
+ slideContainer.style.height = `${cH}px`;
5249
5686
  slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5687
+ slideContainer.style.transformOrigin = "center center";
5250
5688
  };
5251
5689
  const renderCurrentSlide = async () => {
5252
5690
  try {
5253
5691
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
5254
5692
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
5255
- scale = calculateFitScale();
5693
+ if (!isUserZoomed) {
5694
+ scale = calculateFitScale(fitMode);
5695
+ }
5256
5696
  applyTransform();
5257
5697
  } catch (err) {
5258
5698
  console.error("[PptxPlugin] Failed to render slide:", err);
5259
5699
  }
5260
5700
  };
5261
5701
  await renderCurrentSlide();
5702
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5703
+ if (!isUserZoomed) {
5704
+ scale = calculateFitScale(fitMode);
5705
+ applyTransform();
5706
+ }
5707
+ }) : null;
5708
+ ro?.observe(ctx.container);
5262
5709
  const cleanup = () => {
5710
+ ro?.disconnect();
5263
5711
  renderer.destroy();
5264
5712
  wrapper.remove();
5265
5713
  ctx.container.innerHTML = "";
@@ -5276,28 +5724,44 @@ var PptxPlugin = class {
5276
5724
  getPageCount: () => slideCount,
5277
5725
  getCurrentPage: () => currentSlide,
5278
5726
  zoomIn: () => {
5727
+ isUserZoomed = true;
5279
5728
  scale += 0.15;
5280
5729
  applyTransform();
5281
5730
  },
5282
5731
  zoomOut: () => {
5732
+ isUserZoomed = true;
5283
5733
  scale = Math.max(0.2, scale - 0.15);
5284
5734
  applyTransform();
5285
5735
  },
5286
5736
  getZoom: () => scale,
5287
5737
  setZoom: (level) => {
5738
+ isUserZoomed = true;
5288
5739
  scale = level;
5289
5740
  applyTransform();
5290
5741
  },
5291
5742
  fitToPage: () => {
5292
- scale = calculateFitScale();
5743
+ isUserZoomed = false;
5744
+ fitMode = "page";
5745
+ scale = calculateFitScale("page");
5746
+ rotation = 0;
5747
+ applyTransform();
5748
+ },
5749
+ fitToWidth: () => {
5750
+ isUserZoomed = false;
5751
+ fitMode = "width";
5752
+ scale = calculateFitScale("width");
5293
5753
  rotation = 0;
5294
5754
  applyTransform();
5295
5755
  },
5296
5756
  resetZoom: () => {
5297
- scale = calculateFitScale();
5757
+ isUserZoomed = false;
5758
+ fitMode = ctx?.options?.fitMode || "width";
5759
+ scale = calculateFitScale(fitMode);
5298
5760
  rotation = 0;
5299
5761
  wrapper.scrollTop = 0;
5300
5762
  wrapper.scrollLeft = 0;
5763
+ ctx.container.scrollTop = 0;
5764
+ ctx.container.scrollLeft = 0;
5301
5765
  applyTransform();
5302
5766
  },
5303
5767
  rotateCW: () => {
@@ -5742,6 +6206,14 @@ var RtfPlugin = class {
5742
6206
  group: "zoom",
5743
6207
  execute: () => instance.resetZoom?.()
5744
6208
  },
6209
+ {
6210
+ id: "fit-width",
6211
+ icon: "fit-width",
6212
+ label: "Fit to Width",
6213
+ type: "button",
6214
+ group: "zoom",
6215
+ execute: () => instance.fitToWidth?.()
6216
+ },
5745
6217
  {
5746
6218
  id: "rotate-cw",
5747
6219
  icon: "rotate-cw",
@@ -5852,13 +6324,33 @@ var RtfPlugin = class {
5852
6324
  wrapper.style.flexDirection = "column";
5853
6325
  wrapper.style.alignItems = "center";
5854
6326
  wrapper.style.backgroundColor = "transparent";
5855
- wrapper.style.transformOrigin = "top center";
6327
+ wrapper.style.transformOrigin = "center center";
5856
6328
  wrapper.style.transition = "transform 0.15s ease";
5857
- ctx.container.style.overflowX = "hidden";
5858
- ctx.container.style.overflowY = "auto";
5859
- ctx.container.style.padding = "16px 8px";
6329
+ wrapper.style.flexShrink = "0";
6330
+ const sizer = document.createElement("div");
6331
+ sizer.className = "fp-rtf-sizer";
6332
+ sizer.style.position = "relative";
6333
+ sizer.style.flexShrink = "0";
6334
+ sizer.style.display = "flex";
6335
+ sizer.style.justifyContent = "center";
6336
+ sizer.style.alignItems = "center";
6337
+ const scrollWrapper = document.createElement("div");
6338
+ scrollWrapper.className = "fp-rtf-scroll-wrapper";
6339
+ scrollWrapper.style.minWidth = "100%";
6340
+ scrollWrapper.style.minHeight = "100%";
6341
+ scrollWrapper.style.width = "max-content";
6342
+ scrollWrapper.style.height = "max-content";
6343
+ scrollWrapper.style.display = "flex";
6344
+ scrollWrapper.style.justifyContent = "center";
6345
+ scrollWrapper.style.alignItems = "flex-start";
6346
+ scrollWrapper.style.padding = "16px 8px";
6347
+ scrollWrapper.style.boxSizing = "border-box";
6348
+ sizer.appendChild(wrapper);
6349
+ scrollWrapper.appendChild(sizer);
6350
+ ctx.container.style.overflow = "auto";
6351
+ ctx.container.style.padding = "0";
5860
6352
  ctx.container.style.backgroundColor = "#f1f5f9";
5861
- ctx.container.appendChild(wrapper);
6353
+ ctx.container.appendChild(scrollWrapper);
5862
6354
  let scale = 1;
5863
6355
  let rotation = 0;
5864
6356
  let pageElements = [];
@@ -5877,12 +6369,17 @@ var RtfPlugin = class {
5877
6369
  return Math.max(0.4, Math.min(3, sW));
5878
6370
  };
5879
6371
  const applyTransform = () => {
5880
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5881
- wrapper.style.transformOrigin = "top center";
6372
+ const elW = pageWidth;
5882
6373
  const baseH = pageHeight;
5883
- const scaledH = baseH * scale;
5884
- const extraH = Math.max(0, scaledH - baseH);
5885
- wrapper.style.marginBottom = `${extraH + 32}px`;
6374
+ const isRotated90 = rotation % 180 !== 0;
6375
+ const boxW = Math.round((isRotated90 ? baseH : elW) * scale);
6376
+ const boxH = Math.round((isRotated90 ? elW : baseH) * scale);
6377
+ sizer.style.width = `${boxW}px`;
6378
+ sizer.style.height = `${boxH}px`;
6379
+ wrapper.style.width = `${elW}px`;
6380
+ wrapper.style.height = `${baseH}px`;
6381
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6382
+ wrapper.style.transformOrigin = "center center";
5886
6383
  };
5887
6384
  const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5888
6385
  if (!isUserZoomed) {
@@ -6153,7 +6650,7 @@ var RtfPlugin = class {
6153
6650
  }
6154
6651
  const cleanup = () => {
6155
6652
  resizeObserver?.disconnect();
6156
- wrapper.remove();
6653
+ scrollWrapper.remove();
6157
6654
  ctx.container.innerHTML = "";
6158
6655
  };
6159
6656
  ctx.signal.addEventListener("abort", cleanup);
@@ -6205,6 +6702,13 @@ var RtfPlugin = class {
6205
6702
  applyTransform();
6206
6703
  },
6207
6704
  fitToPage: () => {
6705
+ isUserZoomed = false;
6706
+ fitMode = "page";
6707
+ scale = calculateFitScale("page");
6708
+ rotation = 0;
6709
+ applyTransform();
6710
+ },
6711
+ fitToWidth: () => {
6208
6712
  isUserZoomed = false;
6209
6713
  fitMode = "width";
6210
6714
  scale = calculateFitScale("width");
@@ -6213,8 +6717,8 @@ var RtfPlugin = class {
6213
6717
  },
6214
6718
  resetZoom: () => {
6215
6719
  isUserZoomed = false;
6216
- fitMode = "width";
6217
- scale = calculateFitScale("width");
6720
+ fitMode = ctx?.options?.fitMode || "width";
6721
+ scale = calculateFitScale(fitMode);
6218
6722
  rotation = 0;
6219
6723
  ctx.container.scrollTop = 0;
6220
6724
  ctx.container.scrollLeft = 0;
@@ -6295,6 +6799,14 @@ var HtmlPreviewPlugin = class {
6295
6799
  group: "zoom",
6296
6800
  execute: () => instance.resetZoom?.()
6297
6801
  },
6802
+ {
6803
+ id: "fit-width",
6804
+ icon: "fit-width",
6805
+ label: "Fit to Width",
6806
+ type: "button",
6807
+ group: "zoom",
6808
+ execute: () => instance.fitToWidth?.()
6809
+ },
6298
6810
  {
6299
6811
  id: "copy",
6300
6812
  icon: "copy",
@@ -6328,23 +6840,44 @@ var HtmlPreviewPlugin = class {
6328
6840
  ADD_TAGS: ["style", "link"],
6329
6841
  ADD_ATTR: ["target", "rel"]
6330
6842
  });
6843
+ const sizer = document.createElement("div");
6844
+ sizer.className = "fp-html-sizer";
6845
+ sizer.style.position = "relative";
6331
6846
  const iframe = document.createElement("iframe");
6332
6847
  iframe.className = "fp-html-iframe";
6333
- iframe.style.width = "100%";
6334
- iframe.style.height = "100%";
6335
6848
  iframe.style.border = "none";
6336
6849
  iframe.style.backgroundColor = "#ffffff";
6337
6850
  iframe.style.transformOrigin = "top left";
6338
6851
  iframe.style.transition = "transform 0.2s ease";
6339
6852
  iframe.sandbox.add("allow-same-origin");
6853
+ sizer.appendChild(iframe);
6340
6854
  ctx.container.style.overflow = "auto";
6341
6855
  ctx.container.style.width = "100%";
6342
6856
  ctx.container.style.height = "100%";
6343
- ctx.container.appendChild(iframe);
6857
+ ctx.container.innerHTML = "";
6858
+ ctx.container.appendChild(sizer);
6344
6859
  iframe.srcdoc = sanitized;
6345
6860
  let scale = 1;
6861
+ const applyTransform = () => {
6862
+ const baseW = Math.max(600, ctx.container.clientWidth || 800);
6863
+ const baseH = Math.max(400, ctx.container.clientHeight || 600);
6864
+ const scaledW = Math.round(baseW * scale);
6865
+ const scaledH = Math.round(baseH * scale);
6866
+ sizer.style.width = `${scaledW}px`;
6867
+ sizer.style.height = `${scaledH}px`;
6868
+ iframe.style.position = "absolute";
6869
+ iframe.style.top = "0";
6870
+ iframe.style.left = "0";
6871
+ iframe.style.width = `${baseW}px`;
6872
+ iframe.style.height = `${baseH}px`;
6873
+ iframe.style.transform = `scale(${scale})`;
6874
+ iframe.style.transformOrigin = "top left";
6875
+ };
6876
+ requestAnimationFrame(() => {
6877
+ applyTransform();
6878
+ });
6346
6879
  const cleanup = () => {
6347
- iframe.remove();
6880
+ sizer.remove();
6348
6881
  ctx.container.innerHTML = "";
6349
6882
  };
6350
6883
  ctx.signal.addEventListener("abort", cleanup);
@@ -6352,26 +6885,30 @@ var HtmlPreviewPlugin = class {
6352
6885
  destroy: cleanup,
6353
6886
  zoomIn: () => {
6354
6887
  scale += 0.1;
6355
- iframe.style.transform = `scale(${scale})`;
6888
+ applyTransform();
6356
6889
  },
6357
6890
  zoomOut: () => {
6358
6891
  scale = Math.max(0.2, scale - 0.1);
6359
- iframe.style.transform = `scale(${scale})`;
6892
+ applyTransform();
6360
6893
  },
6361
6894
  getZoom: () => scale,
6362
6895
  setZoom: (level) => {
6363
6896
  scale = level;
6364
- iframe.style.transform = `scale(${scale})`;
6897
+ applyTransform();
6365
6898
  },
6366
6899
  fitToPage: () => {
6367
6900
  scale = 1;
6368
- iframe.style.transform = "scale(1)";
6901
+ applyTransform();
6902
+ },
6903
+ fitToWidth: () => {
6904
+ scale = 1;
6905
+ applyTransform();
6369
6906
  },
6370
6907
  resetZoom: () => {
6371
6908
  scale = 1;
6372
- iframe.style.transform = "scale(1)";
6373
6909
  ctx.container.scrollTop = 0;
6374
6910
  ctx.container.scrollLeft = 0;
6911
+ applyTransform();
6375
6912
  },
6376
6913
  copy: () => {
6377
6914
  navigator.clipboard.writeText(rawHtml);
@@ -6481,6 +7018,14 @@ var OpenDocumentPlugin = class {
6481
7018
  group: "zoom",
6482
7019
  execute: () => instance.resetZoom?.()
6483
7020
  },
7021
+ {
7022
+ id: "fit-width",
7023
+ icon: "fit-width",
7024
+ label: "Fit to Width",
7025
+ type: "button",
7026
+ group: "zoom",
7027
+ execute: () => instance.fitToWidth?.()
7028
+ },
6484
7029
  {
6485
7030
  id: "rotate-cw",
6486
7031
  icon: "rotate-cw",
@@ -6546,10 +7091,26 @@ var OpenDocumentPlugin = class {
6546
7091
  container.className = "fp-odf-container";
6547
7092
  container.style.width = "100%";
6548
7093
  container.style.height = "100%";
6549
- container.style.overflowX = "hidden";
6550
- container.style.overflowY = "auto";
6551
- container.style.padding = "16px 8px";
7094
+ container.style.overflow = "auto";
6552
7095
  container.style.backgroundColor = "#f1f5f9";
7096
+ const scrollWrapper = document.createElement("div");
7097
+ scrollWrapper.className = "fp-odf-scroll-wrapper";
7098
+ scrollWrapper.style.minWidth = "100%";
7099
+ scrollWrapper.style.minHeight = "100%";
7100
+ scrollWrapper.style.width = "max-content";
7101
+ scrollWrapper.style.height = "max-content";
7102
+ scrollWrapper.style.display = "flex";
7103
+ scrollWrapper.style.justifyContent = "center";
7104
+ scrollWrapper.style.alignItems = "flex-start";
7105
+ scrollWrapper.style.padding = "16px 8px";
7106
+ scrollWrapper.style.boxSizing = "border-box";
7107
+ const sizer = document.createElement("div");
7108
+ sizer.className = "fp-odf-sizer";
7109
+ sizer.style.position = "relative";
7110
+ sizer.style.flexShrink = "0";
7111
+ sizer.style.display = "flex";
7112
+ sizer.style.justifyContent = "center";
7113
+ sizer.style.alignItems = "center";
6553
7114
  const wrapper = document.createElement("div");
6554
7115
  wrapper.className = "fp-odf-wrapper";
6555
7116
  wrapper.style.margin = "0 auto";
@@ -6558,9 +7119,12 @@ var OpenDocumentPlugin = class {
6558
7119
  wrapper.style.backgroundColor = "#ffffff";
6559
7120
  wrapper.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
6560
7121
  wrapper.style.borderRadius = "4px";
6561
- wrapper.style.transformOrigin = "top center";
7122
+ wrapper.style.transformOrigin = "center center";
6562
7123
  wrapper.style.transition = "transform 0.15s ease";
6563
- container.appendChild(wrapper);
7124
+ wrapper.style.flexShrink = "0";
7125
+ sizer.appendChild(wrapper);
7126
+ scrollWrapper.appendChild(sizer);
7127
+ container.appendChild(scrollWrapper);
6564
7128
  ctx.container.appendChild(container);
6565
7129
  let scale = 1;
6566
7130
  let rotation = 0;
@@ -6578,7 +7142,10 @@ var OpenDocumentPlugin = class {
6578
7142
  const sW = availW / elW;
6579
7143
  const sH = availH / (isPresentation ? 540 : singlePageH);
6580
7144
  if (isPresentation) {
6581
- return Math.min(2.5, Math.min(sW, sH));
7145
+ if (mode === "page") {
7146
+ return Math.min(2.5, Math.min(sW, sH));
7147
+ }
7148
+ return Math.min(2.5, sW);
6582
7149
  }
6583
7150
  if (mode === "page") {
6584
7151
  return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
@@ -6586,13 +7153,18 @@ var OpenDocumentPlugin = class {
6586
7153
  return Math.max(0.4, Math.min(3, sW));
6587
7154
  };
6588
7155
  const applyTransform = () => {
6589
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6590
- wrapper.style.transformOrigin = "top center";
6591
7156
  const activeSlide = slides[currentPage - 1];
7157
+ const elW = isPresentation ? 960 : 816;
6592
7158
  const baseH = activeSlide?.offsetHeight || wrapper.offsetHeight || 1056;
6593
- const scaledH = baseH * scale;
6594
- const extraH = Math.max(0, scaledH - baseH);
6595
- wrapper.style.marginBottom = `${extraH + 32}px`;
7159
+ const isRotated90 = rotation % 180 !== 0;
7160
+ const boxW = Math.round((isRotated90 ? baseH : elW) * scale);
7161
+ const boxH = Math.round((isRotated90 ? elW : baseH) * scale);
7162
+ sizer.style.width = `${boxW}px`;
7163
+ sizer.style.height = `${boxH}px`;
7164
+ wrapper.style.width = `${elW}px`;
7165
+ wrapper.style.height = `${baseH}px`;
7166
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7167
+ wrapper.style.transformOrigin = "center center";
6596
7168
  };
6597
7169
  const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6598
7170
  if (!isUserZoomed) {
@@ -6742,6 +7314,13 @@ var OpenDocumentPlugin = class {
6742
7314
  applyTransform();
6743
7315
  },
6744
7316
  fitToPage: () => {
7317
+ isUserZoomed = false;
7318
+ fitMode = "page";
7319
+ scale = calculateFitScale("page");
7320
+ rotation = 0;
7321
+ applyTransform();
7322
+ },
7323
+ fitToWidth: () => {
6745
7324
  isUserZoomed = false;
6746
7325
  fitMode = "width";
6747
7326
  scale = calculateFitScale("width");
@@ -6750,8 +7329,8 @@ var OpenDocumentPlugin = class {
6750
7329
  },
6751
7330
  resetZoom: () => {
6752
7331
  isUserZoomed = false;
6753
- fitMode = "width";
6754
- scale = calculateFitScale("width");
7332
+ fitMode = ctx?.options?.fitMode || "width";
7333
+ scale = calculateFitScale(fitMode);
6755
7334
  rotation = 0;
6756
7335
  container.scrollTop = 0;
6757
7336
  container.scrollLeft = 0;
@@ -7108,6 +7687,14 @@ var DocPlugin = class {
7108
7687
  group: "zoom",
7109
7688
  execute: () => instance.resetZoom?.()
7110
7689
  },
7690
+ {
7691
+ id: "fit-width",
7692
+ icon: "fit-width",
7693
+ label: "Fit to Width",
7694
+ type: "button",
7695
+ group: "zoom",
7696
+ execute: () => instance.fitToWidth?.()
7697
+ },
7111
7698
  {
7112
7699
  id: "rotate-cw",
7113
7700
  icon: "rotate-cw",
@@ -7156,13 +7743,28 @@ var DocPlugin = class {
7156
7743
  container.className = "fp-doc-container";
7157
7744
  container.style.width = "100%";
7158
7745
  container.style.height = "100%";
7159
- container.style.overflowX = "hidden";
7160
- container.style.overflowY = "auto";
7161
- container.style.padding = "16px 8px";
7746
+ container.style.overflow = "auto";
7162
7747
  container.style.backgroundColor = "#f1f5f9";
7163
- container.style.display = "flex";
7164
- container.style.justifyContent = "center";
7165
- container.style.alignItems = "flex-start";
7748
+ const scrollWrapper = document.createElement("div");
7749
+ scrollWrapper.className = "fp-doc-scroll-wrapper";
7750
+ scrollWrapper.style.minWidth = "100%";
7751
+ scrollWrapper.style.minHeight = "100%";
7752
+ scrollWrapper.style.width = "max-content";
7753
+ scrollWrapper.style.height = "max-content";
7754
+ scrollWrapper.style.display = "flex";
7755
+ scrollWrapper.style.justifyContent = "center";
7756
+ scrollWrapper.style.alignItems = "flex-start";
7757
+ scrollWrapper.style.padding = "16px 8px";
7758
+ scrollWrapper.style.boxSizing = "border-box";
7759
+ const sizer = document.createElement("div");
7760
+ sizer.className = "fp-doc-sizer";
7761
+ sizer.style.position = "relative";
7762
+ sizer.style.flexShrink = "0";
7763
+ sizer.style.display = "flex";
7764
+ sizer.style.justifyContent = "center";
7765
+ sizer.style.alignItems = "center";
7766
+ scrollWrapper.appendChild(sizer);
7767
+ container.appendChild(scrollWrapper);
7166
7768
  ctx.container.appendChild(container);
7167
7769
  let scale = 1;
7168
7770
  let extractedRawText = "";
@@ -7210,10 +7812,11 @@ var DocPlugin = class {
7210
7812
  pageCard.style.padding = "72px 56px";
7211
7813
  pageCard.style.boxSizing = "border-box";
7212
7814
  pageCard.style.display = i === 0 ? "block" : "none";
7213
- pageCard.style.transformOrigin = "top center";
7815
+ pageCard.style.transformOrigin = "center center";
7214
7816
  pageCard.style.transition = "transform 0.15s ease";
7215
7817
  pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
7216
7818
  pageCard.style.color = "#1e293b";
7819
+ pageCard.style.flexShrink = "0";
7217
7820
  if (isFallback && i === 0) {
7218
7821
  pageCard.innerHTML = `
7219
7822
  <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
@@ -7225,18 +7828,23 @@ var DocPlugin = class {
7225
7828
  } else {
7226
7829
  pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
7227
7830
  }
7228
- container.appendChild(pageCard);
7831
+ sizer.appendChild(pageCard);
7229
7832
  pageCards.push(pageCard);
7230
7833
  }
7231
7834
  let rotation = 0;
7232
7835
  const applyTransform = () => {
7233
7836
  const activeCard = pageCards[currentPage - 1];
7234
7837
  if (activeCard) {
7235
- activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7838
+ const baseW = 816;
7236
7839
  const baseH = activeCard.offsetHeight || 1056;
7237
- const scaledH = baseH * scale;
7238
- const extraH = Math.max(0, scaledH - baseH);
7239
- activeCard.style.marginBottom = `${extraH + 32}px`;
7840
+ const isRotated90 = rotation % 180 !== 0;
7841
+ const boxW = Math.round((isRotated90 ? baseH : baseW) * scale);
7842
+ const boxH = Math.round((isRotated90 ? baseW : baseH) * scale);
7843
+ sizer.style.width = `${boxW}px`;
7844
+ sizer.style.height = `${boxH}px`;
7845
+ activeCard.style.width = `${baseW}px`;
7846
+ activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7847
+ activeCard.style.transformOrigin = "center center";
7240
7848
  }
7241
7849
  };
7242
7850
  const showPage = (pageNum) => {
@@ -7244,11 +7852,11 @@ var DocPlugin = class {
7244
7852
  pageCards.forEach((card, idx) => {
7245
7853
  if (idx + 1 === currentPage) {
7246
7854
  card.style.display = "block";
7247
- card.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7248
7855
  } else {
7249
7856
  card.style.display = "none";
7250
7857
  }
7251
7858
  });
7859
+ applyTransform();
7252
7860
  container.scrollTop = 0;
7253
7861
  ctx.emit("page-change", { page: currentPage, total: totalPages });
7254
7862
  };
@@ -7308,6 +7916,13 @@ var DocPlugin = class {
7308
7916
  applyTransform();
7309
7917
  },
7310
7918
  fitToPage: () => {
7919
+ isUserZoomed = false;
7920
+ fitMode = "page";
7921
+ scale = calculateFitScale("page");
7922
+ rotation = 0;
7923
+ applyTransform();
7924
+ },
7925
+ fitToWidth: () => {
7311
7926
  isUserZoomed = false;
7312
7927
  fitMode = "width";
7313
7928
  scale = calculateFitScale("width");
@@ -7316,9 +7931,11 @@ var DocPlugin = class {
7316
7931
  },
7317
7932
  resetZoom: () => {
7318
7933
  isUserZoomed = false;
7319
- fitMode = "width";
7320
- scale = calculateFitScale("width");
7934
+ fitMode = ctx?.options?.fitMode || "width";
7935
+ scale = calculateFitScale(fitMode);
7321
7936
  rotation = 0;
7937
+ container.scrollTop = 0;
7938
+ container.scrollLeft = 0;
7322
7939
  ctx.container.scrollTop = 0;
7323
7940
  ctx.container.scrollLeft = 0;
7324
7941
  applyTransform();
@@ -7811,6 +8428,14 @@ var PptPlugin = class {
7811
8428
  group: "zoom",
7812
8429
  execute: () => instance.resetZoom?.()
7813
8430
  },
8431
+ {
8432
+ id: "fit-width",
8433
+ icon: "fit-width",
8434
+ label: "Fit to Width",
8435
+ type: "button",
8436
+ group: "zoom",
8437
+ execute: () => instance.fitToWidth?.()
8438
+ },
7814
8439
  {
7815
8440
  id: "rotate-cw",
7816
8441
  icon: "rotate-cw",
@@ -7843,17 +8468,30 @@ var PptPlugin = class {
7843
8468
  container.style.width = "100%";
7844
8469
  container.style.height = "100%";
7845
8470
  container.style.overflow = "auto";
7846
- container.style.display = "flex";
7847
- container.style.justifyContent = "center";
7848
- container.style.alignItems = "center";
7849
- container.style.padding = "32px 16px";
7850
8471
  container.style.backgroundColor = "#0f172a";
7851
8472
  container.style.boxSizing = "border-box";
8473
+ const scrollWrapper = document.createElement("div");
8474
+ scrollWrapper.className = "fp-ppt-scroll-wrapper";
8475
+ scrollWrapper.style.minWidth = "100%";
8476
+ scrollWrapper.style.minHeight = "100%";
8477
+ scrollWrapper.style.width = "max-content";
8478
+ scrollWrapper.style.height = "max-content";
8479
+ scrollWrapper.style.display = "flex";
8480
+ scrollWrapper.style.justifyContent = "center";
8481
+ scrollWrapper.style.alignItems = "center";
8482
+ scrollWrapper.style.padding = "32px 16px";
8483
+ scrollWrapper.style.boxSizing = "border-box";
8484
+ const sizer = document.createElement("div");
8485
+ sizer.className = "fp-ppt-sizer";
8486
+ sizer.style.position = "relative";
8487
+ sizer.style.flexShrink = "0";
8488
+ sizer.style.display = "flex";
8489
+ sizer.style.justifyContent = "center";
8490
+ sizer.style.alignItems = "center";
7852
8491
  const slideCard = document.createElement("div");
7853
8492
  slideCard.className = "fp-ppt-slide-card";
7854
8493
  slideCard.style.width = "960px";
7855
- slideCard.style.maxWidth = "calc(100% - 32px)";
7856
- slideCard.style.maxHeight = "calc(100% - 48px)";
8494
+ slideCard.style.height = "540px";
7857
8495
  slideCard.style.aspectRatio = "16 / 9";
7858
8496
  slideCard.style.backgroundColor = "#ffffff";
7859
8497
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
@@ -7863,23 +8501,47 @@ var PptPlugin = class {
7863
8501
  slideCard.style.transition = "transform 0.2s ease";
7864
8502
  slideCard.style.transformOrigin = "center center";
7865
8503
  slideCard.style.flexShrink = "0";
7866
- container.appendChild(slideCard);
8504
+ sizer.appendChild(slideCard);
8505
+ scrollWrapper.appendChild(sizer);
8506
+ container.appendChild(scrollWrapper);
7867
8507
  ctx.container.appendChild(container);
7868
8508
  let scale = 1;
7869
8509
  let rotation = 0;
7870
8510
  let currentSlide = 1;
7871
8511
  let slides = [];
7872
8512
  const createdBlobUrls = [];
7873
- const calculateFitScale = () => {
7874
- const availW = Math.max(200, container.clientWidth - 48);
7875
- const availH = Math.max(200, container.clientHeight - 72);
7876
- return Math.min(1, Math.min(availW / 960, availH / 540));
8513
+ let fitMode = ctx?.options?.fitMode || "width";
8514
+ let isUserZoomed = false;
8515
+ const calculateFitScale = (mode = fitMode) => {
8516
+ const availW = Math.max(200, container.clientWidth - 32);
8517
+ const availH = Math.max(200, container.clientHeight - 32);
8518
+ if (mode === "page") {
8519
+ return Math.min(2.5, Math.min(availW / 960, availH / 540));
8520
+ }
8521
+ return Math.min(2.5, availW / 960);
7877
8522
  };
7878
8523
  const applyTransform = () => {
8524
+ const cW = 960;
8525
+ const cH = 540;
8526
+ const isRotated90 = rotation % 180 !== 0;
8527
+ const boxW = Math.round((isRotated90 ? cH : cW) * scale);
8528
+ const boxH = Math.round((isRotated90 ? cW : cH) * scale);
8529
+ sizer.style.width = `${boxW}px`;
8530
+ sizer.style.height = `${boxH}px`;
8531
+ slideCard.style.width = `${cW}px`;
8532
+ slideCard.style.height = `${cH}px`;
7879
8533
  slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
8534
+ slideCard.style.transformOrigin = "center center";
7880
8535
  };
8536
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
8537
+ if (!isUserZoomed) {
8538
+ scale = calculateFitScale(fitMode);
8539
+ applyTransform();
8540
+ }
8541
+ }) : null;
8542
+ ro?.observe(container);
7881
8543
  setTimeout(() => {
7882
- scale = calculateFitScale();
8544
+ scale = calculateFitScale(fitMode);
7883
8545
  applyTransform();
7884
8546
  }, 60);
7885
8547
  try {
@@ -7972,6 +8634,7 @@ var PptPlugin = class {
7972
8634
  };
7973
8635
  renderSlide(1);
7974
8636
  const cleanup = () => {
8637
+ ro?.disconnect();
7975
8638
  for (const u of createdBlobUrls) {
7976
8639
  URL.revokeObjectURL(u);
7977
8640
  }
@@ -7982,28 +8645,44 @@ var PptPlugin = class {
7982
8645
  return {
7983
8646
  destroy: cleanup,
7984
8647
  zoomIn: () => {
8648
+ isUserZoomed = true;
7985
8649
  scale += 0.15;
7986
8650
  applyTransform();
7987
8651
  },
7988
8652
  zoomOut: () => {
8653
+ isUserZoomed = true;
7989
8654
  scale = Math.max(0.2, scale - 0.15);
7990
8655
  applyTransform();
7991
8656
  },
7992
8657
  getZoom: () => scale,
7993
8658
  setZoom: (level) => {
8659
+ isUserZoomed = true;
7994
8660
  scale = level;
7995
8661
  applyTransform();
7996
8662
  },
7997
8663
  fitToPage: () => {
7998
- scale = calculateFitScale();
8664
+ isUserZoomed = false;
8665
+ fitMode = "page";
8666
+ scale = calculateFitScale("page");
8667
+ rotation = 0;
8668
+ applyTransform();
8669
+ },
8670
+ fitToWidth: () => {
8671
+ isUserZoomed = false;
8672
+ fitMode = "width";
8673
+ scale = calculateFitScale("width");
7999
8674
  rotation = 0;
8000
8675
  applyTransform();
8001
8676
  },
8002
8677
  resetZoom: () => {
8003
- scale = calculateFitScale();
8678
+ isUserZoomed = false;
8679
+ fitMode = ctx?.options?.fitMode || "width";
8680
+ scale = calculateFitScale(fitMode);
8004
8681
  rotation = 0;
8005
8682
  container.scrollTop = 0;
8006
8683
  container.scrollLeft = 0;
8684
+ ctx.container.scrollTop = 0;
8685
+ ctx.container.scrollLeft = 0;
8007
8686
  applyTransform();
8008
8687
  },
8009
8688
  rotateCW: () => {