@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.js CHANGED
@@ -1140,6 +1140,10 @@ var FilePreviewViewer = class _FilePreviewViewer {
1140
1140
  * Preview a file in the given container element.
1141
1141
  */
1142
1142
  async preview(container, source, options = {}) {
1143
+ options = {
1144
+ ...options,
1145
+ fitMode: options.fitMode ?? "width"
1146
+ };
1143
1147
  this.currentOptions = options;
1144
1148
  this.abort();
1145
1149
  this.abortController = new AbortController();
@@ -1188,8 +1192,8 @@ var FilePreviewViewer = class _FilePreviewViewer {
1188
1192
  this.activeInstance = instance;
1189
1193
  instance.openInSeparateWindow = () => this.openInSeparateWindow();
1190
1194
  instance.toggleThumbnails = () => this.toggleThumbnails();
1191
- if (!instance.resetZoom && instance.fitToPage) {
1192
- instance.resetZoom = () => instance.fitToPage?.();
1195
+ if (!instance.resetZoom) {
1196
+ instance.resetZoom = () => instance.fitToWidth ? instance.fitToWidth() : instance.fitToPage?.();
1193
1197
  }
1194
1198
  this.hideLoading();
1195
1199
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
@@ -1586,7 +1590,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1586
1590
  this.wrapperEl?.classList.toggle("fp-fullscreen-active");
1587
1591
  }
1588
1592
  setTimeout(() => {
1589
- this.activeInstance?.fitToPage?.();
1593
+ this.triggerAutoFit();
1590
1594
  }, 120);
1591
1595
  }
1592
1596
  /**
@@ -1713,18 +1717,29 @@ var FilePreviewViewer = class _FilePreviewViewer {
1713
1717
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl, (isOpen) => {
1714
1718
  this.toolbar?.setActionActive("thumbnails", isOpen);
1715
1719
  setTimeout(() => {
1716
- this.activeInstance?.fitToPage?.();
1720
+ this.triggerAutoFit();
1717
1721
  }, 260);
1718
1722
  });
1719
1723
  this.setupKeyboardShortcuts();
1720
1724
  this.setupDragAndDrop(container, options);
1721
1725
  this.setupResizeAndFullscreenListeners();
1722
1726
  }
1727
+ triggerAutoFit() {
1728
+ if (!this.activeInstance) return;
1729
+ const mode = this.currentOptions?.fitMode ?? "width";
1730
+ if (mode === "width" && this.activeInstance.fitToWidth) {
1731
+ this.activeInstance.fitToWidth();
1732
+ } else if (this.activeInstance.fitToPage) {
1733
+ this.activeInstance.fitToPage();
1734
+ } else if (this.activeInstance.resetZoom) {
1735
+ this.activeInstance.resetZoom();
1736
+ }
1737
+ }
1723
1738
  setupResizeAndFullscreenListeners() {
1724
1739
  if (this.fullscreenHandler) return;
1725
1740
  this.fullscreenHandler = () => {
1726
1741
  setTimeout(() => {
1727
- this.activeInstance?.fitToPage?.();
1742
+ this.triggerAutoFit();
1728
1743
  }, 100);
1729
1744
  };
1730
1745
  document.addEventListener("fullscreenchange", this.fullscreenHandler);
@@ -1732,7 +1747,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1732
1747
  if (typeof ResizeObserver !== "undefined" && this.contentEl) {
1733
1748
  this.resizeObserver = new ResizeObserver(() => {
1734
1749
  if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
1735
- this.activeInstance.fitToPage?.();
1750
+ this.triggerAutoFit();
1736
1751
  }
1737
1752
  });
1738
1753
  this.resizeObserver.observe(this.contentEl);
@@ -1760,7 +1775,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1760
1775
  } else if (e.key === "-" || e.key === "_") {
1761
1776
  this.activeInstance.zoomOut?.();
1762
1777
  } else if (e.key === "0") {
1763
- this.activeInstance.fitToPage?.();
1778
+ this.resetZoom();
1764
1779
  } else if (e.key === "r" || e.key === "R") {
1765
1780
  this.activeInstance.rotateCW?.();
1766
1781
  } else if (e.key === "f" || e.key === "F") {
@@ -2168,16 +2183,22 @@ var PdfPlugin = class {
2168
2183
  container.className = "fp-pdf-container";
2169
2184
  container.style.width = "100%";
2170
2185
  container.style.height = "100%";
2171
- container.style.overflowX = "hidden";
2172
- container.style.overflowY = "auto";
2173
- container.style.display = "flex";
2174
- container.style.flexDirection = "column";
2175
- container.style.alignItems = "center";
2176
- container.style.justifyContent = "flex-start";
2177
- container.style.padding = "16px 8px";
2186
+ container.style.overflow = "auto";
2178
2187
  container.style.backgroundColor = "#0f172a";
2179
2188
  container.style.boxSizing = "border-box";
2180
2189
  container.style.position = "relative";
2190
+ const scrollWrapper = document.createElement("div");
2191
+ scrollWrapper.className = "fp-pdf-scroll-wrapper";
2192
+ scrollWrapper.style.minWidth = "100%";
2193
+ scrollWrapper.style.minHeight = "100%";
2194
+ scrollWrapper.style.width = "max-content";
2195
+ scrollWrapper.style.height = "max-content";
2196
+ scrollWrapper.style.display = "flex";
2197
+ scrollWrapper.style.flexDirection = "column";
2198
+ scrollWrapper.style.alignItems = "center";
2199
+ scrollWrapper.style.justifyContent = "flex-start";
2200
+ scrollWrapper.style.padding = "16px 8px";
2201
+ scrollWrapper.style.boxSizing = "border-box";
2181
2202
  const pageCard = document.createElement("div");
2182
2203
  pageCard.className = "fp-pdf-page-card";
2183
2204
  pageCard.style.boxShadow = "0 10px 35px rgba(0, 0, 0, 0.5)";
@@ -2190,7 +2211,8 @@ var PdfPlugin = class {
2190
2211
  pageCard.style.transition = "box-shadow 0.2s ease";
2191
2212
  let canvas = document.createElement("canvas");
2192
2213
  pageCard.appendChild(canvas);
2193
- container.appendChild(pageCard);
2214
+ scrollWrapper.appendChild(pageCard);
2215
+ container.appendChild(scrollWrapper);
2194
2216
  ctx.container.appendChild(container);
2195
2217
  const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
2196
2218
  const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
@@ -2376,7 +2398,7 @@ var PdfPlugin = class {
2376
2398
  renderPage(currentPage);
2377
2399
  },
2378
2400
  resetZoom: () => {
2379
- fitMode = "page";
2401
+ fitMode = ctx?.options?.fitMode || "width";
2380
2402
  zoomScale = 1;
2381
2403
  rotation = 0;
2382
2404
  container.scrollTop = 0;
@@ -2560,6 +2582,16 @@ var MediaPlugin = class {
2560
2582
  instance.resetZoom?.();
2561
2583
  }
2562
2584
  },
2585
+ {
2586
+ id: "fit-width",
2587
+ icon: "fit-width",
2588
+ label: "Fit to Width",
2589
+ type: "button",
2590
+ group: "zoom",
2591
+ execute: () => {
2592
+ instance.fitToWidth?.();
2593
+ }
2594
+ },
2563
2595
  {
2564
2596
  id: "rotate-cw",
2565
2597
  icon: "rotate-cw",
@@ -2656,9 +2688,30 @@ var MediaPlugin = class {
2656
2688
  const isVideo = mimeType.startsWith("video/") || VIDEO_EXTS.includes(ctx.metadata.extension || "");
2657
2689
  const isAudio = mimeType.startsWith("audio/") || AUDIO_EXTS.includes(ctx.metadata.extension || "");
2658
2690
  let url = "";
2691
+ let naturalW = 800;
2692
+ let naturalH = 600;
2659
2693
  if (ctx.metadata.extension === ".svg" || mimeType === "image/svg+xml") {
2660
2694
  const decoder = new TextDecoder("utf-8");
2661
2695
  const svgText = decoder.decode(ctx.buffer);
2696
+ try {
2697
+ const vbMatch = svgText.match(/viewBox=["']\s*([0-9.-]+)\s+([0-9.-]+)\s+([0-9.-]+)\s+([0-9.-]+)\s*["']/i);
2698
+ if (vbMatch) {
2699
+ const vw = parseFloat(vbMatch[3]);
2700
+ const vh = parseFloat(vbMatch[4]);
2701
+ if (vw > 0 && vh > 0) {
2702
+ naturalW = vw;
2703
+ naturalH = vh;
2704
+ }
2705
+ } else {
2706
+ const wMatch = svgText.match(/width=["']([0-9.]+)(?:px)?["']/i);
2707
+ const hMatch = svgText.match(/height=["']([0-9.]+)(?:px)?["']/i);
2708
+ if (wMatch && hMatch) {
2709
+ naturalW = parseFloat(wMatch[1]) || naturalW;
2710
+ naturalH = parseFloat(hMatch[1]) || naturalH;
2711
+ }
2712
+ }
2713
+ } catch {
2714
+ }
2662
2715
  const blob = new Blob([svgText], { type: "image/svg+xml" });
2663
2716
  url = URL.createObjectURL(blob);
2664
2717
  } else {
@@ -2699,45 +2752,150 @@ var MediaPlugin = class {
2699
2752
  element = document.createElement("div");
2700
2753
  element.textContent = "Unsupported media type";
2701
2754
  }
2702
- ctx.container.style.display = "flex";
2703
- ctx.container.style.alignItems = "center";
2704
- ctx.container.style.justifyContent = "center";
2705
- ctx.container.style.overflow = "hidden";
2706
- ctx.container.appendChild(element);
2755
+ const scrollWrapper = document.createElement("div");
2756
+ scrollWrapper.className = "fp-media-scroll-wrapper";
2757
+ scrollWrapper.style.minWidth = "100%";
2758
+ scrollWrapper.style.minHeight = "100%";
2759
+ scrollWrapper.style.width = "max-content";
2760
+ scrollWrapper.style.height = "max-content";
2761
+ scrollWrapper.style.display = "flex";
2762
+ scrollWrapper.style.flexDirection = "column";
2763
+ scrollWrapper.style.alignItems = "center";
2764
+ scrollWrapper.style.padding = "16px";
2765
+ scrollWrapper.style.boxSizing = "border-box";
2766
+ const sizer = document.createElement("div");
2767
+ sizer.className = "fp-media-sizer";
2768
+ sizer.style.position = "relative";
2769
+ sizer.style.flexShrink = "0";
2770
+ sizer.style.display = "flex";
2771
+ sizer.style.justifyContent = "center";
2772
+ sizer.style.alignItems = "center";
2773
+ sizer.style.margin = "auto 0";
2774
+ sizer.appendChild(element);
2775
+ scrollWrapper.appendChild(sizer);
2776
+ ctx.container.style.overflow = "auto";
2777
+ ctx.container.style.padding = "0";
2778
+ ctx.container.innerHTML = "";
2779
+ ctx.container.appendChild(scrollWrapper);
2780
+ let baseW = naturalW;
2781
+ let baseH = naturalH;
2782
+ let fitMode = ctx?.options?.fitMode || "width";
2783
+ let isUserZoomed = false;
2784
+ const updateBaseDimensions = () => {
2785
+ if (!isImage) return;
2786
+ const img = element;
2787
+ if (img.naturalWidth > 0 && img.naturalHeight > 0) {
2788
+ naturalW = img.naturalWidth;
2789
+ naturalH = img.naturalHeight;
2790
+ }
2791
+ const availW = Math.max(100, ctx.container.clientWidth - 32);
2792
+ const availH = Math.max(100, ctx.container.clientHeight - 32);
2793
+ let fitRatio;
2794
+ if (fitMode === "page") {
2795
+ fitRatio = Math.min(availW / naturalW, availH / naturalH);
2796
+ } else {
2797
+ fitRatio = availW / naturalW;
2798
+ }
2799
+ baseW = Math.max(1, Math.round(naturalW * fitRatio));
2800
+ baseH = Math.max(1, Math.round(naturalH * fitRatio));
2801
+ };
2707
2802
  const applyTransform = () => {
2708
- element.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
2803
+ if (isImage) {
2804
+ if (!isUserZoomed) {
2805
+ updateBaseDimensions();
2806
+ }
2807
+ const availH = Math.max(100, ctx.container.clientHeight - 32);
2808
+ const isRotated90 = rotation % 180 !== 0;
2809
+ const boxW = Math.round((isRotated90 ? baseH : baseW) * currentZoom);
2810
+ const boxH = Math.round((isRotated90 ? baseW : baseH) * currentZoom);
2811
+ sizer.style.width = `${boxW}px`;
2812
+ sizer.style.height = `${boxH}px`;
2813
+ sizer.style.margin = boxH < availH ? "auto 0" : "0";
2814
+ const imgW = isRotated90 ? boxH : boxW;
2815
+ const imgH = isRotated90 ? boxW : boxH;
2816
+ element.style.width = `${imgW}px`;
2817
+ element.style.height = `${imgH}px`;
2818
+ element.style.maxWidth = "none";
2819
+ element.style.maxHeight = "none";
2820
+ element.style.transform = rotation ? `rotate(${rotation}deg)` : "none";
2821
+ element.style.transformOrigin = "center center";
2822
+ element.style.flexShrink = "0";
2823
+ } else {
2824
+ element.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
2825
+ }
2709
2826
  };
2827
+ if (isImage) {
2828
+ const img = element;
2829
+ if (img.complete && img.naturalWidth > 0) {
2830
+ updateBaseDimensions();
2831
+ applyTransform();
2832
+ } else {
2833
+ img.onload = () => {
2834
+ updateBaseDimensions();
2835
+ applyTransform();
2836
+ };
2837
+ }
2838
+ }
2839
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
2840
+ if (!isUserZoomed && currentZoom === 1) {
2841
+ updateBaseDimensions();
2842
+ applyTransform();
2843
+ }
2844
+ }) : null;
2845
+ ro?.observe(ctx.container);
2710
2846
  const cleanup = () => {
2847
+ ro?.disconnect();
2711
2848
  if (url) URL.revokeObjectURL(url);
2712
- element.remove();
2849
+ scrollWrapper.remove();
2713
2850
  ctx.container.innerHTML = "";
2714
2851
  };
2715
2852
  ctx.signal.addEventListener("abort", cleanup);
2716
2853
  return {
2717
2854
  destroy: cleanup,
2718
2855
  zoomIn: isImage ? () => {
2719
- currentZoom += 0.1;
2856
+ isUserZoomed = true;
2857
+ currentZoom = Math.min(5, Math.round((currentZoom + 0.15) * 100) / 100);
2720
2858
  applyTransform();
2721
2859
  } : void 0,
2722
2860
  zoomOut: isImage ? () => {
2723
- currentZoom = Math.max(0.1, currentZoom - 0.1);
2861
+ isUserZoomed = true;
2862
+ currentZoom = Math.max(0.1, Math.round((currentZoom - 0.15) * 100) / 100);
2724
2863
  applyTransform();
2725
2864
  } : void 0,
2726
2865
  getZoom: isImage ? () => currentZoom : void 0,
2727
2866
  setZoom: isImage ? (level) => {
2867
+ isUserZoomed = true;
2728
2868
  currentZoom = level;
2729
2869
  applyTransform();
2730
2870
  } : void 0,
2731
2871
  fitToPage: isImage ? () => {
2872
+ isUserZoomed = false;
2873
+ fitMode = "page";
2874
+ currentZoom = 1;
2875
+ rotation = 0;
2876
+ ctx.container.scrollTop = 0;
2877
+ ctx.container.scrollLeft = 0;
2878
+ updateBaseDimensions();
2879
+ applyTransform();
2880
+ } : void 0,
2881
+ fitToWidth: isImage ? () => {
2882
+ isUserZoomed = false;
2883
+ fitMode = "width";
2732
2884
  currentZoom = 1;
2733
2885
  rotation = 0;
2886
+ ctx.container.scrollTop = 0;
2887
+ ctx.container.scrollLeft = 0;
2888
+ updateBaseDimensions();
2734
2889
  applyTransform();
2735
2890
  } : void 0,
2736
2891
  resetZoom: isImage ? () => {
2892
+ isUserZoomed = false;
2893
+ fitMode = ctx?.options?.fitMode || "width";
2737
2894
  currentZoom = 1;
2738
2895
  rotation = 0;
2739
2896
  ctx.container.scrollTop = 0;
2740
2897
  ctx.container.scrollLeft = 0;
2898
+ updateBaseDimensions();
2741
2899
  applyTransform();
2742
2900
  } : void 0,
2743
2901
  rotateCW: isImage || isVideo ? () => {
@@ -2864,6 +3022,14 @@ var DocxPlugin = class {
2864
3022
  group: "zoom",
2865
3023
  execute: () => instance.resetZoom?.()
2866
3024
  },
3025
+ {
3026
+ id: "fit-width",
3027
+ icon: "fit-width",
3028
+ label: "Fit to Width",
3029
+ type: "button",
3030
+ group: "zoom",
3031
+ execute: () => instance.fitToWidth?.()
3032
+ },
2867
3033
  {
2868
3034
  id: "rotate-cw",
2869
3035
  icon: "rotate-cw",
@@ -2902,7 +3068,7 @@ var DocxPlugin = class {
2902
3068
  async render(ctx) {
2903
3069
  const wrapper = document.createElement("div");
2904
3070
  wrapper.className = "fp-docx-wrapper";
2905
- wrapper.style.transformOrigin = "top center";
3071
+ wrapper.style.transformOrigin = "center center";
2906
3072
  wrapper.style.transition = "transform 0.2s ease";
2907
3073
  wrapper.style.padding = "0";
2908
3074
  wrapper.style.maxWidth = "none";
@@ -2912,11 +3078,31 @@ var DocxPlugin = class {
2912
3078
  wrapper.style.flexDirection = "column";
2913
3079
  wrapper.style.alignItems = "center";
2914
3080
  wrapper.style.boxSizing = "border-box";
2915
- ctx.container.style.overflowX = "hidden";
2916
- ctx.container.style.overflowY = "auto";
2917
- ctx.container.style.padding = "16px 8px";
3081
+ wrapper.style.flexShrink = "0";
3082
+ const sizer = document.createElement("div");
3083
+ sizer.className = "fp-docx-sizer";
3084
+ sizer.style.position = "relative";
3085
+ sizer.style.flexShrink = "0";
3086
+ sizer.style.display = "flex";
3087
+ sizer.style.justifyContent = "center";
3088
+ sizer.style.alignItems = "center";
3089
+ const scrollWrapper = document.createElement("div");
3090
+ scrollWrapper.className = "fp-docx-scroll-wrapper";
3091
+ scrollWrapper.style.minWidth = "100%";
3092
+ scrollWrapper.style.minHeight = "100%";
3093
+ scrollWrapper.style.width = "max-content";
3094
+ scrollWrapper.style.height = "max-content";
3095
+ scrollWrapper.style.display = "flex";
3096
+ scrollWrapper.style.justifyContent = "center";
3097
+ scrollWrapper.style.alignItems = "flex-start";
3098
+ scrollWrapper.style.padding = "16px 8px";
3099
+ scrollWrapper.style.boxSizing = "border-box";
3100
+ sizer.appendChild(wrapper);
3101
+ scrollWrapper.appendChild(sizer);
3102
+ ctx.container.style.overflow = "auto";
3103
+ ctx.container.style.padding = "0";
2918
3104
  ctx.container.style.backgroundColor = "#f1f5f9";
2919
- ctx.container.appendChild(wrapper);
3105
+ ctx.container.appendChild(scrollWrapper);
2920
3106
  const styleOverride = document.createElement("style");
2921
3107
  styleOverride.textContent = `
2922
3108
  .fp-docx-wrapper .docx-wrapper {
@@ -2955,8 +3141,8 @@ var DocxPlugin = class {
2955
3141
  renderEndnotes: true,
2956
3142
  useBase64URL: true
2957
3143
  });
2958
- if (!ctx.container.contains(wrapper)) {
2959
- ctx.container.appendChild(wrapper);
3144
+ if (!sizer.contains(wrapper)) {
3145
+ sizer.appendChild(wrapper);
2960
3146
  }
2961
3147
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
2962
3148
  renderedSuccessfully = true;
@@ -2993,8 +3179,8 @@ var DocxPlugin = class {
2993
3179
  try {
2994
3180
  wrapper.innerHTML = "";
2995
3181
  this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
2996
- if (!ctx.container.contains(wrapper)) {
2997
- ctx.container.appendChild(wrapper);
3182
+ if (!sizer.contains(wrapper)) {
3183
+ sizer.appendChild(wrapper);
2998
3184
  }
2999
3185
  renderedSuccessfully = true;
3000
3186
  } catch (fallbackErr) {
@@ -3009,8 +3195,8 @@ var DocxPlugin = class {
3009
3195
  </p>
3010
3196
  </div>
3011
3197
  `;
3012
- if (!ctx.container.contains(wrapper)) {
3013
- ctx.container.appendChild(wrapper);
3198
+ if (!sizer.contains(wrapper)) {
3199
+ sizer.appendChild(wrapper);
3014
3200
  }
3015
3201
  }
3016
3202
  }
@@ -3103,31 +3289,40 @@ var DocxPlugin = class {
3103
3289
  }
3104
3290
  scale = 1;
3105
3291
  let rotation = 0;
3106
- let fitMode = "page";
3292
+ let fitMode = ctx?.options?.fitMode || "width";
3107
3293
  let isUserZoomed = false;
3108
- const calculateFitScale = (_mode = fitMode) => {
3294
+ const calculateFitScale = (mode = fitMode) => {
3109
3295
  const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3110
3296
  const elW = activeEl.offsetWidth || 816;
3297
+ const elH = activeEl.offsetHeight || 1056;
3111
3298
  const availW = Math.max(280, ctx.container.clientWidth - 32);
3299
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
3112
3300
  const sW = availW / elW;
3301
+ const sH = availH / elH;
3302
+ if (mode === "page") {
3303
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
3304
+ }
3113
3305
  return Math.max(0.35, Math.min(3, sW));
3114
3306
  };
3115
3307
  const applyTransform = () => {
3116
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3117
- wrapper.style.transformOrigin = "top center";
3118
3308
  const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3309
+ const elW = activeEl.offsetWidth || 816;
3119
3310
  const elH = activeEl.offsetHeight || 1056;
3120
- if (scale > 1) {
3121
- wrapper.style.marginBottom = `${Math.round(elH * scale - elH + 32)}px`;
3122
- } else {
3123
- wrapper.style.marginBottom = "32px";
3124
- }
3311
+ const isRotated90 = rotation % 180 !== 0;
3312
+ const boxW = Math.round((isRotated90 ? elH : elW) * scale);
3313
+ const boxH = Math.round((isRotated90 ? elW : elH) * scale);
3314
+ sizer.style.width = `${boxW}px`;
3315
+ sizer.style.height = `${boxH}px`;
3316
+ wrapper.style.width = `${elW}px`;
3317
+ wrapper.style.height = `${elH}px`;
3318
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3319
+ wrapper.style.transformOrigin = "center center";
3125
3320
  };
3126
3321
  let resizeObserver = null;
3127
3322
  if (typeof ResizeObserver !== "undefined") {
3128
3323
  resizeObserver = new ResizeObserver(() => {
3129
3324
  if (!isUserZoomed) {
3130
- const newFit = calculateFitScale("page");
3325
+ const newFit = calculateFitScale(fitMode);
3131
3326
  if (Math.abs(scale - newFit) > 0.015) {
3132
3327
  scale = newFit;
3133
3328
  applyTransform();
@@ -3137,7 +3332,7 @@ var DocxPlugin = class {
3137
3332
  resizeObserver.observe(ctx.container);
3138
3333
  }
3139
3334
  setTimeout(() => {
3140
- scale = calculateFitScale("page");
3335
+ scale = calculateFitScale(fitMode);
3141
3336
  applyTransform();
3142
3337
  }, 40);
3143
3338
  const cleanup = () => {
@@ -3146,7 +3341,7 @@ var DocxPlugin = class {
3146
3341
  for (const url of createdBlobUrls) {
3147
3342
  URL.revokeObjectURL(url);
3148
3343
  }
3149
- wrapper.remove();
3344
+ scrollWrapper.remove();
3150
3345
  styleOverride.remove();
3151
3346
  ctx.container.innerHTML = "";
3152
3347
  };
@@ -3176,13 +3371,22 @@ var DocxPlugin = class {
3176
3371
  },
3177
3372
  fitToPage: () => {
3178
3373
  isUserZoomed = false;
3374
+ fitMode = "page";
3179
3375
  scale = calculateFitScale("page");
3180
3376
  rotation = 0;
3181
3377
  applyTransform();
3182
3378
  },
3379
+ fitToWidth: () => {
3380
+ isUserZoomed = false;
3381
+ fitMode = "width";
3382
+ scale = calculateFitScale("width");
3383
+ rotation = 0;
3384
+ applyTransform();
3385
+ },
3183
3386
  resetZoom: () => {
3184
3387
  isUserZoomed = false;
3185
- scale = calculateFitScale("page");
3388
+ fitMode = ctx?.options?.fitMode || "width";
3389
+ scale = calculateFitScale(fitMode);
3186
3390
  rotation = 0;
3187
3391
  ctx.container.scrollTop = 0;
3188
3392
  ctx.container.scrollLeft = 0;
@@ -3647,6 +3851,16 @@ var ExcelPlugin = class {
3647
3851
  instance.resetZoom?.();
3648
3852
  }
3649
3853
  },
3854
+ {
3855
+ id: "fit-width",
3856
+ icon: "fit-width",
3857
+ label: "Fit to Width",
3858
+ type: "button",
3859
+ group: "zoom",
3860
+ execute: () => {
3861
+ instance.fitToWidth?.();
3862
+ }
3863
+ },
3650
3864
  {
3651
3865
  id: "download",
3652
3866
  icon: "download",
@@ -3693,7 +3907,7 @@ var ExcelPlugin = class {
3693
3907
  contentArea.style.flex = "1";
3694
3908
  contentArea.style.overflow = "auto";
3695
3909
  contentArea.style.padding = "16px";
3696
- contentArea.style.transformOrigin = "top left";
3910
+ contentArea.style.boxSizing = "border-box";
3697
3911
  const tabsArea = document.createElement("div");
3698
3912
  tabsArea.className = "fp-excel-tabs";
3699
3913
  tabsArea.style.display = "flex";
@@ -3707,18 +3921,35 @@ var ExcelPlugin = class {
3707
3921
  container.appendChild(tabsArea);
3708
3922
  ctx.container.appendChild(container);
3709
3923
  let scale = 1;
3924
+ let isUserZoomed = false;
3710
3925
  let currentSheetIndex = 1;
3711
3926
  let sheetNames = [];
3712
3927
  let wb = null;
3713
3928
  const applyTransform = () => {
3714
- contentArea.style.transform = `scale(${scale})`;
3715
- contentArea.style.transformOrigin = "top left";
3929
+ const sizer = contentArea.querySelector(".fp-excel-sizer");
3930
+ const table = contentArea.querySelector("table");
3931
+ if (!sizer || !table) return;
3932
+ if (!table._baseWidth && table.offsetWidth > 0) {
3933
+ table._baseWidth = table.offsetWidth;
3934
+ table._baseHeight = table.offsetHeight;
3935
+ }
3936
+ const baseW = table._baseWidth || table.offsetWidth || 800;
3937
+ const baseH = table._baseHeight || table.offsetHeight || 600;
3938
+ const scaledW = Math.round(baseW * scale);
3939
+ const scaledH = Math.round(baseH * scale);
3940
+ sizer.style.width = `${scaledW}px`;
3941
+ sizer.style.height = `${scaledH}px`;
3942
+ table.style.position = "absolute";
3943
+ table.style.top = "0";
3944
+ table.style.left = "0";
3945
+ table.style.transform = `scale(${scale})`;
3946
+ table.style.transformOrigin = "top left";
3716
3947
  };
3717
3948
  const calculateFitScale = () => {
3718
3949
  const table = contentArea.querySelector("table");
3719
3950
  if (!table) return 1;
3720
3951
  const availW = Math.max(280, container.clientWidth - 48);
3721
- const tW = table.offsetWidth || 800;
3952
+ const tW = table._baseWidth || table.offsetWidth || 800;
3722
3953
  return Math.max(0.4, Math.min(1, availW / tW));
3723
3954
  };
3724
3955
  try {
@@ -3738,9 +3969,13 @@ var ExcelPlugin = class {
3738
3969
  contentArea.innerHTML = '<div style="padding: 24px; color: #888;">Empty sheet</div>';
3739
3970
  return;
3740
3971
  }
3972
+ const sizer = document.createElement("div");
3973
+ sizer.className = "fp-excel-sizer";
3974
+ sizer.style.position = "relative";
3741
3975
  const html = XLSX.utils.sheet_to_html(ws, { id: "fp-sheet-table", editable: false });
3742
- contentArea.innerHTML = html;
3743
- const table = contentArea.querySelector("table");
3976
+ sizer.innerHTML = html;
3977
+ contentArea.appendChild(sizer);
3978
+ const table = sizer.querySelector("table");
3744
3979
  if (table) {
3745
3980
  table.style.borderCollapse = "collapse";
3746
3981
  table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
@@ -3775,13 +4010,17 @@ var ExcelPlugin = class {
3775
4010
  });
3776
4011
  ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
3777
4012
  requestAnimationFrame(() => {
3778
- scale = calculateFitScale();
4013
+ if (!isUserZoomed) {
4014
+ scale = calculateFitScale();
4015
+ }
3779
4016
  applyTransform();
3780
4017
  });
3781
4018
  };
3782
4019
  const ro = new ResizeObserver(() => {
3783
- scale = calculateFitScale();
3784
- applyTransform();
4020
+ if (!isUserZoomed) {
4021
+ scale = calculateFitScale();
4022
+ applyTransform();
4023
+ }
3785
4024
  });
3786
4025
  ro.observe(container);
3787
4026
  if (sheetNames.length > 0) {
@@ -3818,23 +4057,33 @@ var ExcelPlugin = class {
3818
4057
  return {
3819
4058
  destroy: cleanup,
3820
4059
  zoomIn: () => {
4060
+ isUserZoomed = true;
3821
4061
  scale += 0.15;
3822
4062
  applyTransform();
3823
4063
  },
3824
4064
  zoomOut: () => {
4065
+ isUserZoomed = true;
3825
4066
  scale = Math.max(0.2, scale - 0.15);
3826
4067
  applyTransform();
3827
4068
  },
3828
4069
  getZoom: () => scale,
3829
4070
  setZoom: (level) => {
4071
+ isUserZoomed = true;
3830
4072
  scale = level;
3831
4073
  applyTransform();
3832
4074
  },
3833
4075
  fitToPage: () => {
4076
+ isUserZoomed = false;
4077
+ scale = calculateFitScale();
4078
+ applyTransform();
4079
+ },
4080
+ fitToWidth: () => {
4081
+ isUserZoomed = false;
3834
4082
  scale = calculateFitScale();
3835
4083
  applyTransform();
3836
4084
  },
3837
4085
  resetZoom: () => {
4086
+ isUserZoomed = false;
3838
4087
  scale = calculateFitScale();
3839
4088
  contentArea.scrollTop = 0;
3840
4089
  contentArea.scrollLeft = 0;
@@ -3980,6 +4229,16 @@ var CsvPlugin = class {
3980
4229
  instance.resetZoom?.();
3981
4230
  }
3982
4231
  },
4232
+ {
4233
+ id: "fit-width",
4234
+ icon: "fit-width",
4235
+ label: "Fit to Width",
4236
+ type: "button",
4237
+ group: "zoom",
4238
+ execute: () => {
4239
+ instance.fitToWidth?.();
4240
+ }
4241
+ },
3983
4242
  {
3984
4243
  id: "download",
3985
4244
  icon: "download",
@@ -4066,21 +4325,41 @@ var CsvPlugin = class {
4066
4325
  container.appendChild(tableWrapper);
4067
4326
  ctx.container.appendChild(container);
4068
4327
  let scale = 1;
4328
+ let isUserZoomed = false;
4069
4329
  const calculateFitScale = () => {
4070
4330
  const availW = Math.max(280, container.clientWidth - 48);
4071
- const tW = table.offsetWidth || 800;
4331
+ const tW = table._baseWidth || table.offsetWidth || 800;
4072
4332
  return Math.max(0.4, Math.min(1, availW / tW));
4073
4333
  };
4074
4334
  const applyScale = () => {
4075
- tableWrapper.style.transform = `scale(${scale})`;
4335
+ if (!table._baseWidth && table.offsetWidth > 0) {
4336
+ table._baseWidth = table.offsetWidth;
4337
+ table._baseHeight = table.offsetHeight;
4338
+ }
4339
+ const baseW = table._baseWidth || table.offsetWidth || 800;
4340
+ const baseH = table._baseHeight || table.offsetHeight || 600;
4341
+ const scaledW = Math.round(baseW * scale);
4342
+ const scaledH = Math.round(baseH * scale);
4343
+ tableWrapper.style.position = "relative";
4344
+ tableWrapper.style.width = `${scaledW}px`;
4345
+ tableWrapper.style.height = `${scaledH}px`;
4346
+ table.style.position = "absolute";
4347
+ table.style.top = "0";
4348
+ table.style.left = "0";
4349
+ table.style.transform = `scale(${scale})`;
4350
+ table.style.transformOrigin = "top left";
4076
4351
  };
4077
4352
  requestAnimationFrame(() => {
4078
- scale = calculateFitScale();
4353
+ if (!isUserZoomed) {
4354
+ scale = calculateFitScale();
4355
+ }
4079
4356
  applyScale();
4080
4357
  });
4081
4358
  const ro = new ResizeObserver(() => {
4082
- scale = calculateFitScale();
4083
- applyScale();
4359
+ if (!isUserZoomed) {
4360
+ scale = calculateFitScale();
4361
+ applyScale();
4362
+ }
4084
4363
  });
4085
4364
  ro.observe(container);
4086
4365
  const cleanup = () => {
@@ -4153,23 +4432,33 @@ var CsvPlugin = class {
4153
4432
  }];
4154
4433
  },
4155
4434
  zoomIn: () => {
4435
+ isUserZoomed = true;
4156
4436
  scale += 0.1;
4157
4437
  applyScale();
4158
4438
  },
4159
4439
  zoomOut: () => {
4440
+ isUserZoomed = true;
4160
4441
  scale = Math.max(0.2, scale - 0.1);
4161
4442
  applyScale();
4162
4443
  },
4163
4444
  getZoom: () => scale,
4164
4445
  setZoom: (level) => {
4446
+ isUserZoomed = true;
4165
4447
  scale = level;
4166
4448
  applyScale();
4167
4449
  },
4168
4450
  fitToPage: () => {
4451
+ isUserZoomed = false;
4452
+ scale = calculateFitScale();
4453
+ applyScale();
4454
+ },
4455
+ fitToWidth: () => {
4456
+ isUserZoomed = false;
4169
4457
  scale = calculateFitScale();
4170
4458
  applyScale();
4171
4459
  },
4172
4460
  resetZoom: () => {
4461
+ isUserZoomed = false;
4173
4462
  scale = calculateFitScale();
4174
4463
  container.scrollTop = 0;
4175
4464
  container.scrollLeft = 0;
@@ -4300,6 +4589,16 @@ var CodePlugin = class {
4300
4589
  instance.resetZoom?.();
4301
4590
  }
4302
4591
  },
4592
+ {
4593
+ id: "fit-width",
4594
+ icon: "fit-width",
4595
+ label: "Fit to Width",
4596
+ type: "button",
4597
+ group: "zoom",
4598
+ execute: () => {
4599
+ instance.fitToWidth?.();
4600
+ }
4601
+ },
4303
4602
  {
4304
4603
  id: "copy",
4305
4604
  icon: "copy",
@@ -4405,15 +4704,27 @@ var CodePlugin = class {
4405
4704
  let isUserZoomed = false;
4406
4705
  const pre = document.createElement("pre");
4407
4706
  const code = document.createElement("code");
4707
+ const sizer = document.createElement("div");
4708
+ const scrollWrapper = document.createElement("div");
4408
4709
  if (isTxt) {
4409
- container.style.overflowX = "hidden";
4410
- container.style.overflowY = "auto";
4710
+ container.style.overflow = "auto";
4411
4711
  container.style.backgroundColor = "#f1f5f9";
4412
- container.style.padding = "16px 8px";
4413
- container.style.boxSizing = "border-box";
4414
- container.style.display = "flex";
4415
- container.style.flexDirection = "column";
4416
- container.style.alignItems = "center";
4712
+ scrollWrapper.className = "fp-code-scroll-wrapper";
4713
+ scrollWrapper.style.minWidth = "100%";
4714
+ scrollWrapper.style.minHeight = "100%";
4715
+ scrollWrapper.style.width = "max-content";
4716
+ scrollWrapper.style.height = "max-content";
4717
+ scrollWrapper.style.display = "flex";
4718
+ scrollWrapper.style.justifyContent = "center";
4719
+ scrollWrapper.style.alignItems = "flex-start";
4720
+ scrollWrapper.style.padding = "16px 8px";
4721
+ scrollWrapper.style.boxSizing = "border-box";
4722
+ sizer.className = "fp-code-sizer";
4723
+ sizer.style.position = "relative";
4724
+ sizer.style.flexShrink = "0";
4725
+ sizer.style.display = "flex";
4726
+ sizer.style.justifyContent = "center";
4727
+ sizer.style.alignItems = "center";
4417
4728
  pre.style.margin = "0 auto";
4418
4729
  pre.style.fontFamily = "Consolas, 'Courier New', monospace";
4419
4730
  pre.style.fontSize = "13px";
@@ -4428,8 +4739,9 @@ var CodePlugin = class {
4428
4739
  pre.style.boxSizing = "border-box";
4429
4740
  pre.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
4430
4741
  pre.style.borderRadius = "4px";
4431
- pre.style.transformOrigin = "top center";
4742
+ pre.style.transformOrigin = "center center";
4432
4743
  pre.style.transition = "transform 0.15s ease";
4744
+ pre.style.flexShrink = "0";
4433
4745
  } else {
4434
4746
  container.style.overflow = "auto";
4435
4747
  container.style.backgroundColor = "#1e1e1e";
@@ -4462,7 +4774,13 @@ var CodePlugin = class {
4462
4774
  };
4463
4775
  renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
4464
4776
  pre.appendChild(code);
4465
- container.appendChild(pre);
4777
+ if (isTxt) {
4778
+ sizer.appendChild(pre);
4779
+ scrollWrapper.appendChild(sizer);
4780
+ container.appendChild(scrollWrapper);
4781
+ } else {
4782
+ container.appendChild(pre);
4783
+ }
4466
4784
  const showPage = (pageNum) => {
4467
4785
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4468
4786
  renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
@@ -4479,10 +4797,16 @@ var CodePlugin = class {
4479
4797
  };
4480
4798
  const updateTransform = () => {
4481
4799
  if (isTxt) {
4800
+ const elW = 816;
4801
+ const baseH = pre.offsetHeight || 1056;
4802
+ const isRotated90 = rotation % 180 !== 0;
4803
+ const boxW = Math.round((isRotated90 ? baseH : elW) * zoomLevel);
4804
+ const boxH = Math.round((isRotated90 ? elW : baseH) * zoomLevel);
4805
+ sizer.style.width = `${boxW}px`;
4806
+ sizer.style.height = `${boxH}px`;
4807
+ pre.style.width = `${elW}px`;
4482
4808
  pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
4483
- const scaledH = 1056 * zoomLevel;
4484
- const extraH = Math.max(0, scaledH - 1056);
4485
- pre.style.marginBottom = `${extraH + 32}px`;
4809
+ pre.style.transformOrigin = "center center";
4486
4810
  } else {
4487
4811
  pre.style.transform = `rotate(${rotation}deg)`;
4488
4812
  pre.style.fontSize = `${fontSize}px`;
@@ -4579,6 +4903,13 @@ var CodePlugin = class {
4579
4903
  zoomLevel = isTxt ? calculateTxtFit() : 1;
4580
4904
  updateTransform();
4581
4905
  },
4906
+ fitToWidth: () => {
4907
+ isUserZoomed = false;
4908
+ fontSize = 13;
4909
+ rotation = 0;
4910
+ zoomLevel = isTxt ? calculateTxtFit() : 1;
4911
+ updateTransform();
4912
+ },
4582
4913
  resetZoom: () => {
4583
4914
  isUserZoomed = false;
4584
4915
  fontSize = 13;
@@ -4663,6 +4994,14 @@ var ArchivePlugin = class {
4663
4994
  group: "zoom",
4664
4995
  execute: () => instance.resetZoom?.()
4665
4996
  },
4997
+ {
4998
+ id: "fit-width",
4999
+ icon: "fit-width",
5000
+ label: "Fit to Width",
5001
+ type: "button",
5002
+ group: "zoom",
5003
+ execute: () => instance.fitToWidth?.()
5004
+ },
4666
5005
  {
4667
5006
  id: "download",
4668
5007
  icon: "download",
@@ -4688,16 +5027,56 @@ var ArchivePlugin = class {
4688
5027
  width: 100%;
4689
5028
  height: 100%;
4690
5029
  overflow: auto;
4691
- padding: 24px;
4692
5030
  box-sizing: border-box;
4693
5031
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
4694
- transform-origin: top left;
4695
- transition: transform 0.2s ease;
4696
5032
  `;
5033
+ const scrollWrapper = document.createElement("div");
5034
+ scrollWrapper.className = "fp-archive-scroll-wrapper";
5035
+ scrollWrapper.style.cssText = `
5036
+ min-width: 100%;
5037
+ min-height: 100%;
5038
+ width: max-content;
5039
+ height: max-content;
5040
+ display: flex;
5041
+ justify-content: center;
5042
+ align-items: flex-start;
5043
+ padding: 24px;
5044
+ box-sizing: border-box;
5045
+ `;
5046
+ const sizer = document.createElement("div");
5047
+ sizer.className = "fp-archive-sizer";
5048
+ sizer.style.cssText = `
5049
+ position: relative;
5050
+ flex-shrink: 0;
5051
+ display: flex;
5052
+ justify-content: center;
5053
+ align-items: flex-start;
5054
+ `;
5055
+ const card = document.createElement("div");
5056
+ card.className = "fp-archive-card";
5057
+ card.style.cssText = `
5058
+ width: 900px;
5059
+ box-sizing: border-box;
5060
+ transform-origin: top center;
5061
+ transition: transform 0.15s ease;
5062
+ flex-shrink: 0;
5063
+ `;
5064
+ sizer.appendChild(card);
5065
+ scrollWrapper.appendChild(sizer);
5066
+ wrapper.appendChild(scrollWrapper);
4697
5067
  ctx.container.innerHTML = "";
4698
5068
  ctx.container.style.overflow = "auto";
4699
5069
  ctx.container.appendChild(wrapper);
4700
5070
  let scale = 1;
5071
+ const applyTransform = () => {
5072
+ const boxW = Math.round(900 * scale);
5073
+ const cardH = card.offsetHeight || 600;
5074
+ const boxH = Math.round(cardH * scale);
5075
+ sizer.style.width = `${boxW}px`;
5076
+ sizer.style.height = `${boxH}px`;
5077
+ card.style.transform = `scale(${scale})`;
5078
+ card.style.transformOrigin = "top center";
5079
+ };
4701
5080
  const entries = await new Promise((resolve, reject) => {
4702
5081
  unzip(new Uint8Array(ctx.buffer), (err, unzipped) => {
4703
5082
  if (err) {
@@ -4721,7 +5100,7 @@ var ArchivePlugin = class {
4721
5100
  });
4722
5101
  const totalUncompressedSize = entries.reduce((acc, e) => acc + e.size, 0);
4723
5102
  const renderUI = (filteredEntries) => {
4724
- wrapper.innerHTML = `
5103
+ card.innerHTML = `
4725
5104
  <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;">
4726
5105
  <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;">
4727
5106
  <div>
@@ -4751,7 +5130,7 @@ var ArchivePlugin = class {
4751
5130
  </div>
4752
5131
  </div>
4753
5132
  `;
4754
- const tbody = wrapper.querySelector("#fp-archive-body");
5133
+ const tbody = card.querySelector("#fp-archive-body");
4755
5134
  if (filteredEntries.length === 0) {
4756
5135
  tbody.innerHTML = `<tr><td colspan="3" style="padding: 24px; text-align: center; color: #9ca3af;">No matching files found</td></tr>`;
4757
5136
  } else {
@@ -4780,19 +5159,20 @@ var ArchivePlugin = class {
4780
5159
  }
4781
5160
  });
4782
5161
  });
4783
- const searchInput = wrapper.querySelector("#fp-archive-search");
5162
+ const searchInput = card.querySelector("#fp-archive-search");
4784
5163
  if (searchInput) {
4785
5164
  searchInput.addEventListener("input", () => {
4786
5165
  const q = searchInput.value.toLowerCase().trim();
4787
5166
  const filtered = q ? entries.filter((e) => e.path.toLowerCase().includes(q)) : entries;
4788
5167
  renderUI(filtered);
4789
- const newSearch = wrapper.querySelector("#fp-archive-search");
5168
+ const newSearch = card.querySelector("#fp-archive-search");
4790
5169
  if (newSearch) {
4791
5170
  newSearch.value = q;
4792
5171
  newSearch.focus();
4793
5172
  }
4794
5173
  });
4795
5174
  }
5175
+ applyTransform();
4796
5176
  };
4797
5177
  renderUI(entries);
4798
5178
  const cleanup = () => {
@@ -4804,26 +5184,30 @@ var ArchivePlugin = class {
4804
5184
  destroy: cleanup,
4805
5185
  zoomIn: () => {
4806
5186
  scale += 0.1;
4807
- wrapper.style.transform = `scale(${scale})`;
5187
+ applyTransform();
4808
5188
  },
4809
5189
  zoomOut: () => {
4810
5190
  scale = Math.max(0.3, scale - 0.1);
4811
- wrapper.style.transform = `scale(${scale})`;
5191
+ applyTransform();
4812
5192
  },
4813
5193
  getZoom: () => scale,
4814
5194
  setZoom: (level) => {
4815
5195
  scale = level;
4816
- wrapper.style.transform = `scale(${scale})`;
5196
+ applyTransform();
4817
5197
  },
4818
5198
  fitToPage: () => {
4819
5199
  scale = 1;
4820
- wrapper.style.transform = "scale(1)";
5200
+ applyTransform();
5201
+ },
5202
+ fitToWidth: () => {
5203
+ scale = 1;
5204
+ applyTransform();
4821
5205
  },
4822
5206
  resetZoom: () => {
4823
5207
  scale = 1;
4824
- wrapper.style.transform = "scale(1)";
4825
- ctx.container.scrollTop = 0;
4826
- ctx.container.scrollLeft = 0;
5208
+ wrapper.scrollTop = 0;
5209
+ wrapper.scrollLeft = 0;
5210
+ applyTransform();
4827
5211
  },
4828
5212
  download: () => {
4829
5213
  downloadFile(ctx.buffer, ctx.metadata.name || "archive.zip", "application/zip");
@@ -4883,6 +5267,14 @@ var MarkdownPlugin = class {
4883
5267
  group: "zoom",
4884
5268
  execute: () => instance.resetZoom?.()
4885
5269
  },
5270
+ {
5271
+ id: "fit-width",
5272
+ icon: "fit-width",
5273
+ label: "Fit to Width",
5274
+ type: "button",
5275
+ group: "zoom",
5276
+ execute: () => instance.fitToWidth?.()
5277
+ },
4886
5278
  {
4887
5279
  id: "copy",
4888
5280
  icon: "copy",
@@ -5036,6 +5428,10 @@ var MarkdownPlugin = class {
5036
5428
  fontSize = 15;
5037
5429
  wrapper.style.fontSize = "15px";
5038
5430
  },
5431
+ fitToWidth: () => {
5432
+ fontSize = 15;
5433
+ wrapper.style.fontSize = "15px";
5434
+ },
5039
5435
  resetZoom: () => {
5040
5436
  fontSize = 15;
5041
5437
  wrapper.style.fontSize = "15px";
@@ -5143,6 +5539,14 @@ var PptxPlugin = class {
5143
5539
  group: "zoom",
5144
5540
  execute: () => instance.resetZoom?.()
5145
5541
  },
5542
+ {
5543
+ id: "fit-width",
5544
+ icon: "fit-width",
5545
+ label: "Fit to Width",
5546
+ type: "button",
5547
+ group: "zoom",
5548
+ execute: () => instance.fitToWidth?.()
5549
+ },
5146
5550
  {
5147
5551
  id: "rotate-cw",
5148
5552
  icon: "rotate-cw",
@@ -5182,12 +5586,30 @@ var PptxPlugin = class {
5182
5586
  width: 100%;
5183
5587
  height: 100%;
5184
5588
  overflow: auto;
5589
+ box-sizing: border-box;
5590
+ background: var(--fp-bg-canvas, #525659);
5591
+ `;
5592
+ const scrollWrapper = document.createElement("div");
5593
+ scrollWrapper.className = "fp-pptx-scroll-wrapper";
5594
+ scrollWrapper.style.cssText = `
5595
+ min-width: 100%;
5596
+ min-height: 100%;
5597
+ width: max-content;
5598
+ height: max-content;
5185
5599
  display: flex;
5186
5600
  justify-content: center;
5187
- align-items: flex-start;
5601
+ align-items: center;
5188
5602
  padding: 24px;
5189
5603
  box-sizing: border-box;
5190
- background: var(--fp-bg-canvas, #525659);
5604
+ `;
5605
+ const sizer = document.createElement("div");
5606
+ sizer.className = "fp-pptx-sizer";
5607
+ sizer.style.cssText = `
5608
+ position: relative;
5609
+ flex-shrink: 0;
5610
+ display: flex;
5611
+ justify-content: center;
5612
+ align-items: center;
5191
5613
  `;
5192
5614
  const slideContainer = document.createElement("div");
5193
5615
  slideContainer.className = "fp-slide-container";
@@ -5196,39 +5618,65 @@ var PptxPlugin = class {
5196
5618
  border-radius: 4px;
5197
5619
  overflow: hidden;
5198
5620
  background: #ffffff;
5199
- transform-origin: top center;
5621
+ transform-origin: center center;
5200
5622
  transition: transform 0.2s ease;
5201
- max-width: calc(100% - 32px);
5202
- max-height: calc(100% - 48px);
5623
+ flex-shrink: 0;
5203
5624
  `;
5204
5625
  const canvas = document.createElement("canvas");
5205
5626
  slideContainer.appendChild(canvas);
5206
- wrapper.appendChild(slideContainer);
5627
+ sizer.appendChild(slideContainer);
5628
+ scrollWrapper.appendChild(sizer);
5629
+ wrapper.appendChild(scrollWrapper);
5207
5630
  ctx.container.innerHTML = "";
5208
5631
  ctx.container.style.overflow = "auto";
5209
5632
  ctx.container.appendChild(wrapper);
5210
- const calculateFitScale = () => {
5211
- const availW = Math.max(200, ctx.container.clientWidth - 48);
5212
- const availH = Math.max(200, ctx.container.clientHeight - 72);
5633
+ let fitMode = ctx?.options?.fitMode || "width";
5634
+ let isUserZoomed = false;
5635
+ const calculateFitScale = (mode = fitMode) => {
5636
+ const availW = Math.max(200, ctx.container.clientWidth - 32);
5637
+ const availH = Math.max(200, ctx.container.clientHeight - 32);
5213
5638
  const cW = canvas.offsetWidth || 1280;
5214
5639
  const cH = canvas.offsetHeight || 720;
5215
- return Math.min(1, Math.min(availW / cW, availH / cH));
5640
+ if (mode === "page") {
5641
+ return Math.min(2.5, Math.min(availW / cW, availH / cH));
5642
+ }
5643
+ return Math.min(2.5, availW / cW);
5216
5644
  };
5217
5645
  const applyTransform = () => {
5646
+ const cW = canvas.offsetWidth || 1280;
5647
+ const cH = canvas.offsetHeight || 720;
5648
+ const isRotated90 = rotation % 180 !== 0;
5649
+ const boxW = Math.round((isRotated90 ? cH : cW) * scale);
5650
+ const boxH = Math.round((isRotated90 ? cW : cH) * scale);
5651
+ sizer.style.width = `${boxW}px`;
5652
+ sizer.style.height = `${boxH}px`;
5653
+ slideContainer.style.width = `${cW}px`;
5654
+ slideContainer.style.height = `${cH}px`;
5218
5655
  slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5656
+ slideContainer.style.transformOrigin = "center center";
5219
5657
  };
5220
5658
  const renderCurrentSlide = async () => {
5221
5659
  try {
5222
5660
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
5223
5661
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
5224
- scale = calculateFitScale();
5662
+ if (!isUserZoomed) {
5663
+ scale = calculateFitScale(fitMode);
5664
+ }
5225
5665
  applyTransform();
5226
5666
  } catch (err) {
5227
5667
  console.error("[PptxPlugin] Failed to render slide:", err);
5228
5668
  }
5229
5669
  };
5230
5670
  await renderCurrentSlide();
5671
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5672
+ if (!isUserZoomed) {
5673
+ scale = calculateFitScale(fitMode);
5674
+ applyTransform();
5675
+ }
5676
+ }) : null;
5677
+ ro?.observe(ctx.container);
5231
5678
  const cleanup = () => {
5679
+ ro?.disconnect();
5232
5680
  renderer.destroy();
5233
5681
  wrapper.remove();
5234
5682
  ctx.container.innerHTML = "";
@@ -5245,28 +5693,44 @@ var PptxPlugin = class {
5245
5693
  getPageCount: () => slideCount,
5246
5694
  getCurrentPage: () => currentSlide,
5247
5695
  zoomIn: () => {
5696
+ isUserZoomed = true;
5248
5697
  scale += 0.15;
5249
5698
  applyTransform();
5250
5699
  },
5251
5700
  zoomOut: () => {
5701
+ isUserZoomed = true;
5252
5702
  scale = Math.max(0.2, scale - 0.15);
5253
5703
  applyTransform();
5254
5704
  },
5255
5705
  getZoom: () => scale,
5256
5706
  setZoom: (level) => {
5707
+ isUserZoomed = true;
5257
5708
  scale = level;
5258
5709
  applyTransform();
5259
5710
  },
5260
5711
  fitToPage: () => {
5261
- scale = calculateFitScale();
5712
+ isUserZoomed = false;
5713
+ fitMode = "page";
5714
+ scale = calculateFitScale("page");
5715
+ rotation = 0;
5716
+ applyTransform();
5717
+ },
5718
+ fitToWidth: () => {
5719
+ isUserZoomed = false;
5720
+ fitMode = "width";
5721
+ scale = calculateFitScale("width");
5262
5722
  rotation = 0;
5263
5723
  applyTransform();
5264
5724
  },
5265
5725
  resetZoom: () => {
5266
- scale = calculateFitScale();
5726
+ isUserZoomed = false;
5727
+ fitMode = ctx?.options?.fitMode || "width";
5728
+ scale = calculateFitScale(fitMode);
5267
5729
  rotation = 0;
5268
5730
  wrapper.scrollTop = 0;
5269
5731
  wrapper.scrollLeft = 0;
5732
+ ctx.container.scrollTop = 0;
5733
+ ctx.container.scrollLeft = 0;
5270
5734
  applyTransform();
5271
5735
  },
5272
5736
  rotateCW: () => {
@@ -5711,6 +6175,14 @@ var RtfPlugin = class {
5711
6175
  group: "zoom",
5712
6176
  execute: () => instance.resetZoom?.()
5713
6177
  },
6178
+ {
6179
+ id: "fit-width",
6180
+ icon: "fit-width",
6181
+ label: "Fit to Width",
6182
+ type: "button",
6183
+ group: "zoom",
6184
+ execute: () => instance.fitToWidth?.()
6185
+ },
5714
6186
  {
5715
6187
  id: "rotate-cw",
5716
6188
  icon: "rotate-cw",
@@ -5821,13 +6293,33 @@ var RtfPlugin = class {
5821
6293
  wrapper.style.flexDirection = "column";
5822
6294
  wrapper.style.alignItems = "center";
5823
6295
  wrapper.style.backgroundColor = "transparent";
5824
- wrapper.style.transformOrigin = "top center";
6296
+ wrapper.style.transformOrigin = "center center";
5825
6297
  wrapper.style.transition = "transform 0.15s ease";
5826
- ctx.container.style.overflowX = "hidden";
5827
- ctx.container.style.overflowY = "auto";
5828
- ctx.container.style.padding = "16px 8px";
6298
+ wrapper.style.flexShrink = "0";
6299
+ const sizer = document.createElement("div");
6300
+ sizer.className = "fp-rtf-sizer";
6301
+ sizer.style.position = "relative";
6302
+ sizer.style.flexShrink = "0";
6303
+ sizer.style.display = "flex";
6304
+ sizer.style.justifyContent = "center";
6305
+ sizer.style.alignItems = "center";
6306
+ const scrollWrapper = document.createElement("div");
6307
+ scrollWrapper.className = "fp-rtf-scroll-wrapper";
6308
+ scrollWrapper.style.minWidth = "100%";
6309
+ scrollWrapper.style.minHeight = "100%";
6310
+ scrollWrapper.style.width = "max-content";
6311
+ scrollWrapper.style.height = "max-content";
6312
+ scrollWrapper.style.display = "flex";
6313
+ scrollWrapper.style.justifyContent = "center";
6314
+ scrollWrapper.style.alignItems = "flex-start";
6315
+ scrollWrapper.style.padding = "16px 8px";
6316
+ scrollWrapper.style.boxSizing = "border-box";
6317
+ sizer.appendChild(wrapper);
6318
+ scrollWrapper.appendChild(sizer);
6319
+ ctx.container.style.overflow = "auto";
6320
+ ctx.container.style.padding = "0";
5829
6321
  ctx.container.style.backgroundColor = "#f1f5f9";
5830
- ctx.container.appendChild(wrapper);
6322
+ ctx.container.appendChild(scrollWrapper);
5831
6323
  let scale = 1;
5832
6324
  let rotation = 0;
5833
6325
  let pageElements = [];
@@ -5846,12 +6338,17 @@ var RtfPlugin = class {
5846
6338
  return Math.max(0.4, Math.min(3, sW));
5847
6339
  };
5848
6340
  const applyTransform = () => {
5849
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5850
- wrapper.style.transformOrigin = "top center";
6341
+ const elW = pageWidth;
5851
6342
  const baseH = pageHeight;
5852
- const scaledH = baseH * scale;
5853
- const extraH = Math.max(0, scaledH - baseH);
5854
- wrapper.style.marginBottom = `${extraH + 32}px`;
6343
+ const isRotated90 = rotation % 180 !== 0;
6344
+ const boxW = Math.round((isRotated90 ? baseH : elW) * scale);
6345
+ const boxH = Math.round((isRotated90 ? elW : baseH) * scale);
6346
+ sizer.style.width = `${boxW}px`;
6347
+ sizer.style.height = `${boxH}px`;
6348
+ wrapper.style.width = `${elW}px`;
6349
+ wrapper.style.height = `${baseH}px`;
6350
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6351
+ wrapper.style.transformOrigin = "center center";
5855
6352
  };
5856
6353
  const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5857
6354
  if (!isUserZoomed) {
@@ -6122,7 +6619,7 @@ var RtfPlugin = class {
6122
6619
  }
6123
6620
  const cleanup = () => {
6124
6621
  resizeObserver?.disconnect();
6125
- wrapper.remove();
6622
+ scrollWrapper.remove();
6126
6623
  ctx.container.innerHTML = "";
6127
6624
  };
6128
6625
  ctx.signal.addEventListener("abort", cleanup);
@@ -6174,6 +6671,13 @@ var RtfPlugin = class {
6174
6671
  applyTransform();
6175
6672
  },
6176
6673
  fitToPage: () => {
6674
+ isUserZoomed = false;
6675
+ fitMode = "page";
6676
+ scale = calculateFitScale("page");
6677
+ rotation = 0;
6678
+ applyTransform();
6679
+ },
6680
+ fitToWidth: () => {
6177
6681
  isUserZoomed = false;
6178
6682
  fitMode = "width";
6179
6683
  scale = calculateFitScale("width");
@@ -6182,8 +6686,8 @@ var RtfPlugin = class {
6182
6686
  },
6183
6687
  resetZoom: () => {
6184
6688
  isUserZoomed = false;
6185
- fitMode = "width";
6186
- scale = calculateFitScale("width");
6689
+ fitMode = ctx?.options?.fitMode || "width";
6690
+ scale = calculateFitScale(fitMode);
6187
6691
  rotation = 0;
6188
6692
  ctx.container.scrollTop = 0;
6189
6693
  ctx.container.scrollLeft = 0;
@@ -6264,6 +6768,14 @@ var HtmlPreviewPlugin = class {
6264
6768
  group: "zoom",
6265
6769
  execute: () => instance.resetZoom?.()
6266
6770
  },
6771
+ {
6772
+ id: "fit-width",
6773
+ icon: "fit-width",
6774
+ label: "Fit to Width",
6775
+ type: "button",
6776
+ group: "zoom",
6777
+ execute: () => instance.fitToWidth?.()
6778
+ },
6267
6779
  {
6268
6780
  id: "copy",
6269
6781
  icon: "copy",
@@ -6297,23 +6809,44 @@ var HtmlPreviewPlugin = class {
6297
6809
  ADD_TAGS: ["style", "link"],
6298
6810
  ADD_ATTR: ["target", "rel"]
6299
6811
  });
6812
+ const sizer = document.createElement("div");
6813
+ sizer.className = "fp-html-sizer";
6814
+ sizer.style.position = "relative";
6300
6815
  const iframe = document.createElement("iframe");
6301
6816
  iframe.className = "fp-html-iframe";
6302
- iframe.style.width = "100%";
6303
- iframe.style.height = "100%";
6304
6817
  iframe.style.border = "none";
6305
6818
  iframe.style.backgroundColor = "#ffffff";
6306
6819
  iframe.style.transformOrigin = "top left";
6307
6820
  iframe.style.transition = "transform 0.2s ease";
6308
6821
  iframe.sandbox.add("allow-same-origin");
6822
+ sizer.appendChild(iframe);
6309
6823
  ctx.container.style.overflow = "auto";
6310
6824
  ctx.container.style.width = "100%";
6311
6825
  ctx.container.style.height = "100%";
6312
- ctx.container.appendChild(iframe);
6826
+ ctx.container.innerHTML = "";
6827
+ ctx.container.appendChild(sizer);
6313
6828
  iframe.srcdoc = sanitized;
6314
6829
  let scale = 1;
6830
+ const applyTransform = () => {
6831
+ const baseW = Math.max(600, ctx.container.clientWidth || 800);
6832
+ const baseH = Math.max(400, ctx.container.clientHeight || 600);
6833
+ const scaledW = Math.round(baseW * scale);
6834
+ const scaledH = Math.round(baseH * scale);
6835
+ sizer.style.width = `${scaledW}px`;
6836
+ sizer.style.height = `${scaledH}px`;
6837
+ iframe.style.position = "absolute";
6838
+ iframe.style.top = "0";
6839
+ iframe.style.left = "0";
6840
+ iframe.style.width = `${baseW}px`;
6841
+ iframe.style.height = `${baseH}px`;
6842
+ iframe.style.transform = `scale(${scale})`;
6843
+ iframe.style.transformOrigin = "top left";
6844
+ };
6845
+ requestAnimationFrame(() => {
6846
+ applyTransform();
6847
+ });
6315
6848
  const cleanup = () => {
6316
- iframe.remove();
6849
+ sizer.remove();
6317
6850
  ctx.container.innerHTML = "";
6318
6851
  };
6319
6852
  ctx.signal.addEventListener("abort", cleanup);
@@ -6321,26 +6854,30 @@ var HtmlPreviewPlugin = class {
6321
6854
  destroy: cleanup,
6322
6855
  zoomIn: () => {
6323
6856
  scale += 0.1;
6324
- iframe.style.transform = `scale(${scale})`;
6857
+ applyTransform();
6325
6858
  },
6326
6859
  zoomOut: () => {
6327
6860
  scale = Math.max(0.2, scale - 0.1);
6328
- iframe.style.transform = `scale(${scale})`;
6861
+ applyTransform();
6329
6862
  },
6330
6863
  getZoom: () => scale,
6331
6864
  setZoom: (level) => {
6332
6865
  scale = level;
6333
- iframe.style.transform = `scale(${scale})`;
6866
+ applyTransform();
6334
6867
  },
6335
6868
  fitToPage: () => {
6336
6869
  scale = 1;
6337
- iframe.style.transform = "scale(1)";
6870
+ applyTransform();
6871
+ },
6872
+ fitToWidth: () => {
6873
+ scale = 1;
6874
+ applyTransform();
6338
6875
  },
6339
6876
  resetZoom: () => {
6340
6877
  scale = 1;
6341
- iframe.style.transform = "scale(1)";
6342
6878
  ctx.container.scrollTop = 0;
6343
6879
  ctx.container.scrollLeft = 0;
6880
+ applyTransform();
6344
6881
  },
6345
6882
  copy: () => {
6346
6883
  navigator.clipboard.writeText(rawHtml);
@@ -6450,6 +6987,14 @@ var OpenDocumentPlugin = class {
6450
6987
  group: "zoom",
6451
6988
  execute: () => instance.resetZoom?.()
6452
6989
  },
6990
+ {
6991
+ id: "fit-width",
6992
+ icon: "fit-width",
6993
+ label: "Fit to Width",
6994
+ type: "button",
6995
+ group: "zoom",
6996
+ execute: () => instance.fitToWidth?.()
6997
+ },
6453
6998
  {
6454
6999
  id: "rotate-cw",
6455
7000
  icon: "rotate-cw",
@@ -6515,10 +7060,26 @@ var OpenDocumentPlugin = class {
6515
7060
  container.className = "fp-odf-container";
6516
7061
  container.style.width = "100%";
6517
7062
  container.style.height = "100%";
6518
- container.style.overflowX = "hidden";
6519
- container.style.overflowY = "auto";
6520
- container.style.padding = "16px 8px";
7063
+ container.style.overflow = "auto";
6521
7064
  container.style.backgroundColor = "#f1f5f9";
7065
+ const scrollWrapper = document.createElement("div");
7066
+ scrollWrapper.className = "fp-odf-scroll-wrapper";
7067
+ scrollWrapper.style.minWidth = "100%";
7068
+ scrollWrapper.style.minHeight = "100%";
7069
+ scrollWrapper.style.width = "max-content";
7070
+ scrollWrapper.style.height = "max-content";
7071
+ scrollWrapper.style.display = "flex";
7072
+ scrollWrapper.style.justifyContent = "center";
7073
+ scrollWrapper.style.alignItems = "flex-start";
7074
+ scrollWrapper.style.padding = "16px 8px";
7075
+ scrollWrapper.style.boxSizing = "border-box";
7076
+ const sizer = document.createElement("div");
7077
+ sizer.className = "fp-odf-sizer";
7078
+ sizer.style.position = "relative";
7079
+ sizer.style.flexShrink = "0";
7080
+ sizer.style.display = "flex";
7081
+ sizer.style.justifyContent = "center";
7082
+ sizer.style.alignItems = "center";
6522
7083
  const wrapper = document.createElement("div");
6523
7084
  wrapper.className = "fp-odf-wrapper";
6524
7085
  wrapper.style.margin = "0 auto";
@@ -6527,9 +7088,12 @@ var OpenDocumentPlugin = class {
6527
7088
  wrapper.style.backgroundColor = "#ffffff";
6528
7089
  wrapper.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
6529
7090
  wrapper.style.borderRadius = "4px";
6530
- wrapper.style.transformOrigin = "top center";
7091
+ wrapper.style.transformOrigin = "center center";
6531
7092
  wrapper.style.transition = "transform 0.15s ease";
6532
- container.appendChild(wrapper);
7093
+ wrapper.style.flexShrink = "0";
7094
+ sizer.appendChild(wrapper);
7095
+ scrollWrapper.appendChild(sizer);
7096
+ container.appendChild(scrollWrapper);
6533
7097
  ctx.container.appendChild(container);
6534
7098
  let scale = 1;
6535
7099
  let rotation = 0;
@@ -6547,7 +7111,10 @@ var OpenDocumentPlugin = class {
6547
7111
  const sW = availW / elW;
6548
7112
  const sH = availH / (isPresentation ? 540 : singlePageH);
6549
7113
  if (isPresentation) {
6550
- return Math.min(2.5, Math.min(sW, sH));
7114
+ if (mode === "page") {
7115
+ return Math.min(2.5, Math.min(sW, sH));
7116
+ }
7117
+ return Math.min(2.5, sW);
6551
7118
  }
6552
7119
  if (mode === "page") {
6553
7120
  return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
@@ -6555,13 +7122,18 @@ var OpenDocumentPlugin = class {
6555
7122
  return Math.max(0.4, Math.min(3, sW));
6556
7123
  };
6557
7124
  const applyTransform = () => {
6558
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6559
- wrapper.style.transformOrigin = "top center";
6560
7125
  const activeSlide = slides[currentPage - 1];
7126
+ const elW = isPresentation ? 960 : 816;
6561
7127
  const baseH = activeSlide?.offsetHeight || wrapper.offsetHeight || 1056;
6562
- const scaledH = baseH * scale;
6563
- const extraH = Math.max(0, scaledH - baseH);
6564
- wrapper.style.marginBottom = `${extraH + 32}px`;
7128
+ const isRotated90 = rotation % 180 !== 0;
7129
+ const boxW = Math.round((isRotated90 ? baseH : elW) * scale);
7130
+ const boxH = Math.round((isRotated90 ? elW : baseH) * scale);
7131
+ sizer.style.width = `${boxW}px`;
7132
+ sizer.style.height = `${boxH}px`;
7133
+ wrapper.style.width = `${elW}px`;
7134
+ wrapper.style.height = `${baseH}px`;
7135
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7136
+ wrapper.style.transformOrigin = "center center";
6565
7137
  };
6566
7138
  const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6567
7139
  if (!isUserZoomed) {
@@ -6711,6 +7283,13 @@ var OpenDocumentPlugin = class {
6711
7283
  applyTransform();
6712
7284
  },
6713
7285
  fitToPage: () => {
7286
+ isUserZoomed = false;
7287
+ fitMode = "page";
7288
+ scale = calculateFitScale("page");
7289
+ rotation = 0;
7290
+ applyTransform();
7291
+ },
7292
+ fitToWidth: () => {
6714
7293
  isUserZoomed = false;
6715
7294
  fitMode = "width";
6716
7295
  scale = calculateFitScale("width");
@@ -6719,8 +7298,8 @@ var OpenDocumentPlugin = class {
6719
7298
  },
6720
7299
  resetZoom: () => {
6721
7300
  isUserZoomed = false;
6722
- fitMode = "width";
6723
- scale = calculateFitScale("width");
7301
+ fitMode = ctx?.options?.fitMode || "width";
7302
+ scale = calculateFitScale(fitMode);
6724
7303
  rotation = 0;
6725
7304
  container.scrollTop = 0;
6726
7305
  container.scrollLeft = 0;
@@ -7077,6 +7656,14 @@ var DocPlugin = class {
7077
7656
  group: "zoom",
7078
7657
  execute: () => instance.resetZoom?.()
7079
7658
  },
7659
+ {
7660
+ id: "fit-width",
7661
+ icon: "fit-width",
7662
+ label: "Fit to Width",
7663
+ type: "button",
7664
+ group: "zoom",
7665
+ execute: () => instance.fitToWidth?.()
7666
+ },
7080
7667
  {
7081
7668
  id: "rotate-cw",
7082
7669
  icon: "rotate-cw",
@@ -7125,13 +7712,28 @@ var DocPlugin = class {
7125
7712
  container.className = "fp-doc-container";
7126
7713
  container.style.width = "100%";
7127
7714
  container.style.height = "100%";
7128
- container.style.overflowX = "hidden";
7129
- container.style.overflowY = "auto";
7130
- container.style.padding = "16px 8px";
7715
+ container.style.overflow = "auto";
7131
7716
  container.style.backgroundColor = "#f1f5f9";
7132
- container.style.display = "flex";
7133
- container.style.justifyContent = "center";
7134
- container.style.alignItems = "flex-start";
7717
+ const scrollWrapper = document.createElement("div");
7718
+ scrollWrapper.className = "fp-doc-scroll-wrapper";
7719
+ scrollWrapper.style.minWidth = "100%";
7720
+ scrollWrapper.style.minHeight = "100%";
7721
+ scrollWrapper.style.width = "max-content";
7722
+ scrollWrapper.style.height = "max-content";
7723
+ scrollWrapper.style.display = "flex";
7724
+ scrollWrapper.style.justifyContent = "center";
7725
+ scrollWrapper.style.alignItems = "flex-start";
7726
+ scrollWrapper.style.padding = "16px 8px";
7727
+ scrollWrapper.style.boxSizing = "border-box";
7728
+ const sizer = document.createElement("div");
7729
+ sizer.className = "fp-doc-sizer";
7730
+ sizer.style.position = "relative";
7731
+ sizer.style.flexShrink = "0";
7732
+ sizer.style.display = "flex";
7733
+ sizer.style.justifyContent = "center";
7734
+ sizer.style.alignItems = "center";
7735
+ scrollWrapper.appendChild(sizer);
7736
+ container.appendChild(scrollWrapper);
7135
7737
  ctx.container.appendChild(container);
7136
7738
  let scale = 1;
7137
7739
  let extractedRawText = "";
@@ -7179,10 +7781,11 @@ var DocPlugin = class {
7179
7781
  pageCard.style.padding = "72px 56px";
7180
7782
  pageCard.style.boxSizing = "border-box";
7181
7783
  pageCard.style.display = i === 0 ? "block" : "none";
7182
- pageCard.style.transformOrigin = "top center";
7784
+ pageCard.style.transformOrigin = "center center";
7183
7785
  pageCard.style.transition = "transform 0.15s ease";
7184
7786
  pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
7185
7787
  pageCard.style.color = "#1e293b";
7788
+ pageCard.style.flexShrink = "0";
7186
7789
  if (isFallback && i === 0) {
7187
7790
  pageCard.innerHTML = `
7188
7791
  <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
@@ -7194,18 +7797,23 @@ var DocPlugin = class {
7194
7797
  } else {
7195
7798
  pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
7196
7799
  }
7197
- container.appendChild(pageCard);
7800
+ sizer.appendChild(pageCard);
7198
7801
  pageCards.push(pageCard);
7199
7802
  }
7200
7803
  let rotation = 0;
7201
7804
  const applyTransform = () => {
7202
7805
  const activeCard = pageCards[currentPage - 1];
7203
7806
  if (activeCard) {
7204
- activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7807
+ const baseW = 816;
7205
7808
  const baseH = activeCard.offsetHeight || 1056;
7206
- const scaledH = baseH * scale;
7207
- const extraH = Math.max(0, scaledH - baseH);
7208
- activeCard.style.marginBottom = `${extraH + 32}px`;
7809
+ const isRotated90 = rotation % 180 !== 0;
7810
+ const boxW = Math.round((isRotated90 ? baseH : baseW) * scale);
7811
+ const boxH = Math.round((isRotated90 ? baseW : baseH) * scale);
7812
+ sizer.style.width = `${boxW}px`;
7813
+ sizer.style.height = `${boxH}px`;
7814
+ activeCard.style.width = `${baseW}px`;
7815
+ activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7816
+ activeCard.style.transformOrigin = "center center";
7209
7817
  }
7210
7818
  };
7211
7819
  const showPage = (pageNum) => {
@@ -7213,11 +7821,11 @@ var DocPlugin = class {
7213
7821
  pageCards.forEach((card, idx) => {
7214
7822
  if (idx + 1 === currentPage) {
7215
7823
  card.style.display = "block";
7216
- card.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7217
7824
  } else {
7218
7825
  card.style.display = "none";
7219
7826
  }
7220
7827
  });
7828
+ applyTransform();
7221
7829
  container.scrollTop = 0;
7222
7830
  ctx.emit("page-change", { page: currentPage, total: totalPages });
7223
7831
  };
@@ -7277,6 +7885,13 @@ var DocPlugin = class {
7277
7885
  applyTransform();
7278
7886
  },
7279
7887
  fitToPage: () => {
7888
+ isUserZoomed = false;
7889
+ fitMode = "page";
7890
+ scale = calculateFitScale("page");
7891
+ rotation = 0;
7892
+ applyTransform();
7893
+ },
7894
+ fitToWidth: () => {
7280
7895
  isUserZoomed = false;
7281
7896
  fitMode = "width";
7282
7897
  scale = calculateFitScale("width");
@@ -7285,9 +7900,11 @@ var DocPlugin = class {
7285
7900
  },
7286
7901
  resetZoom: () => {
7287
7902
  isUserZoomed = false;
7288
- fitMode = "width";
7289
- scale = calculateFitScale("width");
7903
+ fitMode = ctx?.options?.fitMode || "width";
7904
+ scale = calculateFitScale(fitMode);
7290
7905
  rotation = 0;
7906
+ container.scrollTop = 0;
7907
+ container.scrollLeft = 0;
7291
7908
  ctx.container.scrollTop = 0;
7292
7909
  ctx.container.scrollLeft = 0;
7293
7910
  applyTransform();
@@ -7780,6 +8397,14 @@ var PptPlugin = class {
7780
8397
  group: "zoom",
7781
8398
  execute: () => instance.resetZoom?.()
7782
8399
  },
8400
+ {
8401
+ id: "fit-width",
8402
+ icon: "fit-width",
8403
+ label: "Fit to Width",
8404
+ type: "button",
8405
+ group: "zoom",
8406
+ execute: () => instance.fitToWidth?.()
8407
+ },
7783
8408
  {
7784
8409
  id: "rotate-cw",
7785
8410
  icon: "rotate-cw",
@@ -7812,17 +8437,30 @@ var PptPlugin = class {
7812
8437
  container.style.width = "100%";
7813
8438
  container.style.height = "100%";
7814
8439
  container.style.overflow = "auto";
7815
- container.style.display = "flex";
7816
- container.style.justifyContent = "center";
7817
- container.style.alignItems = "center";
7818
- container.style.padding = "32px 16px";
7819
8440
  container.style.backgroundColor = "#0f172a";
7820
8441
  container.style.boxSizing = "border-box";
8442
+ const scrollWrapper = document.createElement("div");
8443
+ scrollWrapper.className = "fp-ppt-scroll-wrapper";
8444
+ scrollWrapper.style.minWidth = "100%";
8445
+ scrollWrapper.style.minHeight = "100%";
8446
+ scrollWrapper.style.width = "max-content";
8447
+ scrollWrapper.style.height = "max-content";
8448
+ scrollWrapper.style.display = "flex";
8449
+ scrollWrapper.style.justifyContent = "center";
8450
+ scrollWrapper.style.alignItems = "center";
8451
+ scrollWrapper.style.padding = "32px 16px";
8452
+ scrollWrapper.style.boxSizing = "border-box";
8453
+ const sizer = document.createElement("div");
8454
+ sizer.className = "fp-ppt-sizer";
8455
+ sizer.style.position = "relative";
8456
+ sizer.style.flexShrink = "0";
8457
+ sizer.style.display = "flex";
8458
+ sizer.style.justifyContent = "center";
8459
+ sizer.style.alignItems = "center";
7821
8460
  const slideCard = document.createElement("div");
7822
8461
  slideCard.className = "fp-ppt-slide-card";
7823
8462
  slideCard.style.width = "960px";
7824
- slideCard.style.maxWidth = "calc(100% - 32px)";
7825
- slideCard.style.maxHeight = "calc(100% - 48px)";
8463
+ slideCard.style.height = "540px";
7826
8464
  slideCard.style.aspectRatio = "16 / 9";
7827
8465
  slideCard.style.backgroundColor = "#ffffff";
7828
8466
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
@@ -7832,23 +8470,47 @@ var PptPlugin = class {
7832
8470
  slideCard.style.transition = "transform 0.2s ease";
7833
8471
  slideCard.style.transformOrigin = "center center";
7834
8472
  slideCard.style.flexShrink = "0";
7835
- container.appendChild(slideCard);
8473
+ sizer.appendChild(slideCard);
8474
+ scrollWrapper.appendChild(sizer);
8475
+ container.appendChild(scrollWrapper);
7836
8476
  ctx.container.appendChild(container);
7837
8477
  let scale = 1;
7838
8478
  let rotation = 0;
7839
8479
  let currentSlide = 1;
7840
8480
  let slides = [];
7841
8481
  const createdBlobUrls = [];
7842
- const calculateFitScale = () => {
7843
- const availW = Math.max(200, container.clientWidth - 48);
7844
- const availH = Math.max(200, container.clientHeight - 72);
7845
- return Math.min(1, Math.min(availW / 960, availH / 540));
8482
+ let fitMode = ctx?.options?.fitMode || "width";
8483
+ let isUserZoomed = false;
8484
+ const calculateFitScale = (mode = fitMode) => {
8485
+ const availW = Math.max(200, container.clientWidth - 32);
8486
+ const availH = Math.max(200, container.clientHeight - 32);
8487
+ if (mode === "page") {
8488
+ return Math.min(2.5, Math.min(availW / 960, availH / 540));
8489
+ }
8490
+ return Math.min(2.5, availW / 960);
7846
8491
  };
7847
8492
  const applyTransform = () => {
8493
+ const cW = 960;
8494
+ const cH = 540;
8495
+ const isRotated90 = rotation % 180 !== 0;
8496
+ const boxW = Math.round((isRotated90 ? cH : cW) * scale);
8497
+ const boxH = Math.round((isRotated90 ? cW : cH) * scale);
8498
+ sizer.style.width = `${boxW}px`;
8499
+ sizer.style.height = `${boxH}px`;
8500
+ slideCard.style.width = `${cW}px`;
8501
+ slideCard.style.height = `${cH}px`;
7848
8502
  slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
8503
+ slideCard.style.transformOrigin = "center center";
7849
8504
  };
8505
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
8506
+ if (!isUserZoomed) {
8507
+ scale = calculateFitScale(fitMode);
8508
+ applyTransform();
8509
+ }
8510
+ }) : null;
8511
+ ro?.observe(container);
7850
8512
  setTimeout(() => {
7851
- scale = calculateFitScale();
8513
+ scale = calculateFitScale(fitMode);
7852
8514
  applyTransform();
7853
8515
  }, 60);
7854
8516
  try {
@@ -7941,6 +8603,7 @@ var PptPlugin = class {
7941
8603
  };
7942
8604
  renderSlide(1);
7943
8605
  const cleanup = () => {
8606
+ ro?.disconnect();
7944
8607
  for (const u of createdBlobUrls) {
7945
8608
  URL.revokeObjectURL(u);
7946
8609
  }
@@ -7951,28 +8614,44 @@ var PptPlugin = class {
7951
8614
  return {
7952
8615
  destroy: cleanup,
7953
8616
  zoomIn: () => {
8617
+ isUserZoomed = true;
7954
8618
  scale += 0.15;
7955
8619
  applyTransform();
7956
8620
  },
7957
8621
  zoomOut: () => {
8622
+ isUserZoomed = true;
7958
8623
  scale = Math.max(0.2, scale - 0.15);
7959
8624
  applyTransform();
7960
8625
  },
7961
8626
  getZoom: () => scale,
7962
8627
  setZoom: (level) => {
8628
+ isUserZoomed = true;
7963
8629
  scale = level;
7964
8630
  applyTransform();
7965
8631
  },
7966
8632
  fitToPage: () => {
7967
- scale = calculateFitScale();
8633
+ isUserZoomed = false;
8634
+ fitMode = "page";
8635
+ scale = calculateFitScale("page");
8636
+ rotation = 0;
8637
+ applyTransform();
8638
+ },
8639
+ fitToWidth: () => {
8640
+ isUserZoomed = false;
8641
+ fitMode = "width";
8642
+ scale = calculateFitScale("width");
7968
8643
  rotation = 0;
7969
8644
  applyTransform();
7970
8645
  },
7971
8646
  resetZoom: () => {
7972
- scale = calculateFitScale();
8647
+ isUserZoomed = false;
8648
+ fitMode = ctx?.options?.fitMode || "width";
8649
+ scale = calculateFitScale(fitMode);
7973
8650
  rotation = 0;
7974
8651
  container.scrollTop = 0;
7975
8652
  container.scrollLeft = 0;
8653
+ ctx.container.scrollTop = 0;
8654
+ ctx.container.scrollLeft = 0;
7976
8655
  applyTransform();
7977
8656
  },
7978
8657
  rotateCW: () => {