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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/angular.cjs CHANGED
@@ -1137,6 +1137,10 @@ var FilePreviewViewer = class _FilePreviewViewer {
1137
1137
  * Preview a file in the given container element.
1138
1138
  */
1139
1139
  async preview(container, source, options = {}) {
1140
+ options = {
1141
+ ...options,
1142
+ fitMode: options.fitMode ?? "width"
1143
+ };
1140
1144
  this.currentOptions = options;
1141
1145
  this.abort();
1142
1146
  this.abortController = new AbortController();
@@ -1185,8 +1189,8 @@ var FilePreviewViewer = class _FilePreviewViewer {
1185
1189
  this.activeInstance = instance;
1186
1190
  instance.openInSeparateWindow = () => this.openInSeparateWindow();
1187
1191
  instance.toggleThumbnails = () => this.toggleThumbnails();
1188
- if (!instance.resetZoom && instance.fitToPage) {
1189
- instance.resetZoom = () => instance.fitToPage?.();
1192
+ if (!instance.resetZoom) {
1193
+ instance.resetZoom = () => instance.fitToWidth ? instance.fitToWidth() : instance.fitToPage?.();
1190
1194
  }
1191
1195
  this.hideLoading();
1192
1196
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
@@ -1583,7 +1587,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1583
1587
  this.wrapperEl?.classList.toggle("fp-fullscreen-active");
1584
1588
  }
1585
1589
  setTimeout(() => {
1586
- this.activeInstance?.fitToPage?.();
1590
+ this.triggerAutoFit();
1587
1591
  }, 120);
1588
1592
  }
1589
1593
  /**
@@ -1710,18 +1714,29 @@ var FilePreviewViewer = class _FilePreviewViewer {
1710
1714
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl, (isOpen) => {
1711
1715
  this.toolbar?.setActionActive("thumbnails", isOpen);
1712
1716
  setTimeout(() => {
1713
- this.activeInstance?.fitToPage?.();
1717
+ this.triggerAutoFit();
1714
1718
  }, 260);
1715
1719
  });
1716
1720
  this.setupKeyboardShortcuts();
1717
1721
  this.setupDragAndDrop(container, options);
1718
1722
  this.setupResizeAndFullscreenListeners();
1719
1723
  }
1724
+ triggerAutoFit() {
1725
+ if (!this.activeInstance) return;
1726
+ const mode = this.currentOptions?.fitMode ?? "width";
1727
+ if (mode === "width" && this.activeInstance.fitToWidth) {
1728
+ this.activeInstance.fitToWidth();
1729
+ } else if (this.activeInstance.fitToPage) {
1730
+ this.activeInstance.fitToPage();
1731
+ } else if (this.activeInstance.resetZoom) {
1732
+ this.activeInstance.resetZoom();
1733
+ }
1734
+ }
1720
1735
  setupResizeAndFullscreenListeners() {
1721
1736
  if (this.fullscreenHandler) return;
1722
1737
  this.fullscreenHandler = () => {
1723
1738
  setTimeout(() => {
1724
- this.activeInstance?.fitToPage?.();
1739
+ this.triggerAutoFit();
1725
1740
  }, 100);
1726
1741
  };
1727
1742
  document.addEventListener("fullscreenchange", this.fullscreenHandler);
@@ -1729,7 +1744,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1729
1744
  if (typeof ResizeObserver !== "undefined" && this.contentEl) {
1730
1745
  this.resizeObserver = new ResizeObserver(() => {
1731
1746
  if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
1732
- this.activeInstance.fitToPage?.();
1747
+ this.triggerAutoFit();
1733
1748
  }
1734
1749
  });
1735
1750
  this.resizeObserver.observe(this.contentEl);
@@ -1757,7 +1772,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1757
1772
  } else if (e.key === "-" || e.key === "_") {
1758
1773
  this.activeInstance.zoomOut?.();
1759
1774
  } else if (e.key === "0") {
1760
- this.activeInstance.fitToPage?.();
1775
+ this.resetZoom();
1761
1776
  } else if (e.key === "r" || e.key === "R") {
1762
1777
  this.activeInstance.rotateCW?.();
1763
1778
  } else if (e.key === "f" || e.key === "F") {
@@ -2165,16 +2180,22 @@ var PdfPlugin = class {
2165
2180
  container.className = "fp-pdf-container";
2166
2181
  container.style.width = "100%";
2167
2182
  container.style.height = "100%";
2168
- container.style.overflowX = "hidden";
2169
- container.style.overflowY = "auto";
2170
- container.style.display = "flex";
2171
- container.style.flexDirection = "column";
2172
- container.style.alignItems = "center";
2173
- container.style.justifyContent = "flex-start";
2174
- container.style.padding = "16px 8px";
2183
+ container.style.overflow = "auto";
2175
2184
  container.style.backgroundColor = "#0f172a";
2176
2185
  container.style.boxSizing = "border-box";
2177
2186
  container.style.position = "relative";
2187
+ const scrollWrapper = document.createElement("div");
2188
+ scrollWrapper.className = "fp-pdf-scroll-wrapper";
2189
+ scrollWrapper.style.minWidth = "100%";
2190
+ scrollWrapper.style.minHeight = "100%";
2191
+ scrollWrapper.style.width = "max-content";
2192
+ scrollWrapper.style.height = "max-content";
2193
+ scrollWrapper.style.display = "flex";
2194
+ scrollWrapper.style.flexDirection = "column";
2195
+ scrollWrapper.style.alignItems = "center";
2196
+ scrollWrapper.style.justifyContent = "flex-start";
2197
+ scrollWrapper.style.padding = "16px 8px";
2198
+ scrollWrapper.style.boxSizing = "border-box";
2178
2199
  const pageCard = document.createElement("div");
2179
2200
  pageCard.className = "fp-pdf-page-card";
2180
2201
  pageCard.style.boxShadow = "0 10px 35px rgba(0, 0, 0, 0.5)";
@@ -2187,7 +2208,8 @@ var PdfPlugin = class {
2187
2208
  pageCard.style.transition = "box-shadow 0.2s ease";
2188
2209
  let canvas = document.createElement("canvas");
2189
2210
  pageCard.appendChild(canvas);
2190
- container.appendChild(pageCard);
2211
+ scrollWrapper.appendChild(pageCard);
2212
+ container.appendChild(scrollWrapper);
2191
2213
  ctx.container.appendChild(container);
2192
2214
  const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
2193
2215
  const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
@@ -2373,7 +2395,7 @@ var PdfPlugin = class {
2373
2395
  renderPage(currentPage);
2374
2396
  },
2375
2397
  resetZoom: () => {
2376
- fitMode = "page";
2398
+ fitMode = ctx?.options?.fitMode || "width";
2377
2399
  zoomScale = 1;
2378
2400
  rotation = 0;
2379
2401
  container.scrollTop = 0;
@@ -2557,6 +2579,16 @@ var MediaPlugin = class {
2557
2579
  instance.resetZoom?.();
2558
2580
  }
2559
2581
  },
2582
+ {
2583
+ id: "fit-width",
2584
+ icon: "fit-width",
2585
+ label: "Fit to Width",
2586
+ type: "button",
2587
+ group: "zoom",
2588
+ execute: () => {
2589
+ instance.fitToWidth?.();
2590
+ }
2591
+ },
2560
2592
  {
2561
2593
  id: "rotate-cw",
2562
2594
  icon: "rotate-cw",
@@ -2653,9 +2685,30 @@ var MediaPlugin = class {
2653
2685
  const isVideo = mimeType.startsWith("video/") || VIDEO_EXTS.includes(ctx.metadata.extension || "");
2654
2686
  const isAudio = mimeType.startsWith("audio/") || AUDIO_EXTS.includes(ctx.metadata.extension || "");
2655
2687
  let url = "";
2688
+ let naturalW = 800;
2689
+ let naturalH = 600;
2656
2690
  if (ctx.metadata.extension === ".svg" || mimeType === "image/svg+xml") {
2657
2691
  const decoder = new TextDecoder("utf-8");
2658
2692
  const svgText = decoder.decode(ctx.buffer);
2693
+ try {
2694
+ const vbMatch = svgText.match(/viewBox=["']\s*([0-9.-]+)\s+([0-9.-]+)\s+([0-9.-]+)\s+([0-9.-]+)\s*["']/i);
2695
+ if (vbMatch) {
2696
+ const vw = parseFloat(vbMatch[3]);
2697
+ const vh = parseFloat(vbMatch[4]);
2698
+ if (vw > 0 && vh > 0) {
2699
+ naturalW = vw;
2700
+ naturalH = vh;
2701
+ }
2702
+ } else {
2703
+ const wMatch = svgText.match(/width=["']([0-9.]+)(?:px)?["']/i);
2704
+ const hMatch = svgText.match(/height=["']([0-9.]+)(?:px)?["']/i);
2705
+ if (wMatch && hMatch) {
2706
+ naturalW = parseFloat(wMatch[1]) || naturalW;
2707
+ naturalH = parseFloat(hMatch[1]) || naturalH;
2708
+ }
2709
+ }
2710
+ } catch {
2711
+ }
2659
2712
  const blob = new Blob([svgText], { type: "image/svg+xml" });
2660
2713
  url = URL.createObjectURL(blob);
2661
2714
  } else {
@@ -2696,45 +2749,150 @@ var MediaPlugin = class {
2696
2749
  element = document.createElement("div");
2697
2750
  element.textContent = "Unsupported media type";
2698
2751
  }
2699
- ctx.container.style.display = "flex";
2700
- ctx.container.style.alignItems = "center";
2701
- ctx.container.style.justifyContent = "center";
2702
- ctx.container.style.overflow = "hidden";
2703
- ctx.container.appendChild(element);
2752
+ const scrollWrapper = document.createElement("div");
2753
+ scrollWrapper.className = "fp-media-scroll-wrapper";
2754
+ scrollWrapper.style.minWidth = "100%";
2755
+ scrollWrapper.style.minHeight = "100%";
2756
+ scrollWrapper.style.width = "max-content";
2757
+ scrollWrapper.style.height = "max-content";
2758
+ scrollWrapper.style.display = "flex";
2759
+ scrollWrapper.style.flexDirection = "column";
2760
+ scrollWrapper.style.alignItems = "center";
2761
+ scrollWrapper.style.padding = "16px";
2762
+ scrollWrapper.style.boxSizing = "border-box";
2763
+ const sizer = document.createElement("div");
2764
+ sizer.className = "fp-media-sizer";
2765
+ sizer.style.position = "relative";
2766
+ sizer.style.flexShrink = "0";
2767
+ sizer.style.display = "flex";
2768
+ sizer.style.justifyContent = "center";
2769
+ sizer.style.alignItems = "center";
2770
+ sizer.style.margin = "auto 0";
2771
+ sizer.appendChild(element);
2772
+ scrollWrapper.appendChild(sizer);
2773
+ ctx.container.style.overflow = "auto";
2774
+ ctx.container.style.padding = "0";
2775
+ ctx.container.innerHTML = "";
2776
+ ctx.container.appendChild(scrollWrapper);
2777
+ let baseW = naturalW;
2778
+ let baseH = naturalH;
2779
+ let fitMode = ctx?.options?.fitMode || "width";
2780
+ let isUserZoomed = false;
2781
+ const updateBaseDimensions = () => {
2782
+ if (!isImage) return;
2783
+ const img = element;
2784
+ if (img.naturalWidth > 0 && img.naturalHeight > 0) {
2785
+ naturalW = img.naturalWidth;
2786
+ naturalH = img.naturalHeight;
2787
+ }
2788
+ const availW = Math.max(100, ctx.container.clientWidth - 32);
2789
+ const availH = Math.max(100, ctx.container.clientHeight - 32);
2790
+ let fitRatio;
2791
+ if (fitMode === "page") {
2792
+ fitRatio = Math.min(availW / naturalW, availH / naturalH);
2793
+ } else {
2794
+ fitRatio = availW / naturalW;
2795
+ }
2796
+ baseW = Math.max(1, Math.round(naturalW * fitRatio));
2797
+ baseH = Math.max(1, Math.round(naturalH * fitRatio));
2798
+ };
2704
2799
  const applyTransform = () => {
2705
- element.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
2800
+ if (isImage) {
2801
+ if (!isUserZoomed) {
2802
+ updateBaseDimensions();
2803
+ }
2804
+ const availH = Math.max(100, ctx.container.clientHeight - 32);
2805
+ const isRotated90 = rotation % 180 !== 0;
2806
+ const boxW = Math.round((isRotated90 ? baseH : baseW) * currentZoom);
2807
+ const boxH = Math.round((isRotated90 ? baseW : baseH) * currentZoom);
2808
+ sizer.style.width = `${boxW}px`;
2809
+ sizer.style.height = `${boxH}px`;
2810
+ sizer.style.margin = boxH < availH ? "auto 0" : "0";
2811
+ const imgW = isRotated90 ? boxH : boxW;
2812
+ const imgH = isRotated90 ? boxW : boxH;
2813
+ element.style.width = `${imgW}px`;
2814
+ element.style.height = `${imgH}px`;
2815
+ element.style.maxWidth = "none";
2816
+ element.style.maxHeight = "none";
2817
+ element.style.transform = rotation ? `rotate(${rotation}deg)` : "none";
2818
+ element.style.transformOrigin = "center center";
2819
+ element.style.flexShrink = "0";
2820
+ } else {
2821
+ element.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
2822
+ }
2706
2823
  };
2824
+ if (isImage) {
2825
+ const img = element;
2826
+ if (img.complete && img.naturalWidth > 0) {
2827
+ updateBaseDimensions();
2828
+ applyTransform();
2829
+ } else {
2830
+ img.onload = () => {
2831
+ updateBaseDimensions();
2832
+ applyTransform();
2833
+ };
2834
+ }
2835
+ }
2836
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
2837
+ if (!isUserZoomed && currentZoom === 1) {
2838
+ updateBaseDimensions();
2839
+ applyTransform();
2840
+ }
2841
+ }) : null;
2842
+ ro?.observe(ctx.container);
2707
2843
  const cleanup = () => {
2844
+ ro?.disconnect();
2708
2845
  if (url) URL.revokeObjectURL(url);
2709
- element.remove();
2846
+ scrollWrapper.remove();
2710
2847
  ctx.container.innerHTML = "";
2711
2848
  };
2712
2849
  ctx.signal.addEventListener("abort", cleanup);
2713
2850
  return {
2714
2851
  destroy: cleanup,
2715
2852
  zoomIn: isImage ? () => {
2716
- currentZoom += 0.1;
2853
+ isUserZoomed = true;
2854
+ currentZoom = Math.min(5, Math.round((currentZoom + 0.15) * 100) / 100);
2717
2855
  applyTransform();
2718
2856
  } : void 0,
2719
2857
  zoomOut: isImage ? () => {
2720
- currentZoom = Math.max(0.1, currentZoom - 0.1);
2858
+ isUserZoomed = true;
2859
+ currentZoom = Math.max(0.1, Math.round((currentZoom - 0.15) * 100) / 100);
2721
2860
  applyTransform();
2722
2861
  } : void 0,
2723
2862
  getZoom: isImage ? () => currentZoom : void 0,
2724
2863
  setZoom: isImage ? (level) => {
2864
+ isUserZoomed = true;
2725
2865
  currentZoom = level;
2726
2866
  applyTransform();
2727
2867
  } : void 0,
2728
2868
  fitToPage: isImage ? () => {
2869
+ isUserZoomed = false;
2870
+ fitMode = "page";
2871
+ currentZoom = 1;
2872
+ rotation = 0;
2873
+ ctx.container.scrollTop = 0;
2874
+ ctx.container.scrollLeft = 0;
2875
+ updateBaseDimensions();
2876
+ applyTransform();
2877
+ } : void 0,
2878
+ fitToWidth: isImage ? () => {
2879
+ isUserZoomed = false;
2880
+ fitMode = "width";
2729
2881
  currentZoom = 1;
2730
2882
  rotation = 0;
2883
+ ctx.container.scrollTop = 0;
2884
+ ctx.container.scrollLeft = 0;
2885
+ updateBaseDimensions();
2731
2886
  applyTransform();
2732
2887
  } : void 0,
2733
2888
  resetZoom: isImage ? () => {
2889
+ isUserZoomed = false;
2890
+ fitMode = ctx?.options?.fitMode || "width";
2734
2891
  currentZoom = 1;
2735
2892
  rotation = 0;
2736
2893
  ctx.container.scrollTop = 0;
2737
2894
  ctx.container.scrollLeft = 0;
2895
+ updateBaseDimensions();
2738
2896
  applyTransform();
2739
2897
  } : void 0,
2740
2898
  rotateCW: isImage || isVideo ? () => {
@@ -2861,6 +3019,14 @@ var DocxPlugin = class {
2861
3019
  group: "zoom",
2862
3020
  execute: () => instance.resetZoom?.()
2863
3021
  },
3022
+ {
3023
+ id: "fit-width",
3024
+ icon: "fit-width",
3025
+ label: "Fit to Width",
3026
+ type: "button",
3027
+ group: "zoom",
3028
+ execute: () => instance.fitToWidth?.()
3029
+ },
2864
3030
  {
2865
3031
  id: "rotate-cw",
2866
3032
  icon: "rotate-cw",
@@ -2899,7 +3065,7 @@ var DocxPlugin = class {
2899
3065
  async render(ctx) {
2900
3066
  const wrapper = document.createElement("div");
2901
3067
  wrapper.className = "fp-docx-wrapper";
2902
- wrapper.style.transformOrigin = "top center";
3068
+ wrapper.style.transformOrigin = "center center";
2903
3069
  wrapper.style.transition = "transform 0.2s ease";
2904
3070
  wrapper.style.padding = "0";
2905
3071
  wrapper.style.maxWidth = "none";
@@ -2909,11 +3075,31 @@ var DocxPlugin = class {
2909
3075
  wrapper.style.flexDirection = "column";
2910
3076
  wrapper.style.alignItems = "center";
2911
3077
  wrapper.style.boxSizing = "border-box";
2912
- ctx.container.style.overflowX = "hidden";
2913
- ctx.container.style.overflowY = "auto";
2914
- ctx.container.style.padding = "16px 8px";
3078
+ wrapper.style.flexShrink = "0";
3079
+ const sizer = document.createElement("div");
3080
+ sizer.className = "fp-docx-sizer";
3081
+ sizer.style.position = "relative";
3082
+ sizer.style.flexShrink = "0";
3083
+ sizer.style.display = "flex";
3084
+ sizer.style.justifyContent = "center";
3085
+ sizer.style.alignItems = "center";
3086
+ const scrollWrapper = document.createElement("div");
3087
+ scrollWrapper.className = "fp-docx-scroll-wrapper";
3088
+ scrollWrapper.style.minWidth = "100%";
3089
+ scrollWrapper.style.minHeight = "100%";
3090
+ scrollWrapper.style.width = "max-content";
3091
+ scrollWrapper.style.height = "max-content";
3092
+ scrollWrapper.style.display = "flex";
3093
+ scrollWrapper.style.justifyContent = "center";
3094
+ scrollWrapper.style.alignItems = "flex-start";
3095
+ scrollWrapper.style.padding = "16px 8px";
3096
+ scrollWrapper.style.boxSizing = "border-box";
3097
+ sizer.appendChild(wrapper);
3098
+ scrollWrapper.appendChild(sizer);
3099
+ ctx.container.style.overflow = "auto";
3100
+ ctx.container.style.padding = "0";
2915
3101
  ctx.container.style.backgroundColor = "#f1f5f9";
2916
- ctx.container.appendChild(wrapper);
3102
+ ctx.container.appendChild(scrollWrapper);
2917
3103
  const styleOverride = document.createElement("style");
2918
3104
  styleOverride.textContent = `
2919
3105
  .fp-docx-wrapper .docx-wrapper {
@@ -2952,8 +3138,8 @@ var DocxPlugin = class {
2952
3138
  renderEndnotes: true,
2953
3139
  useBase64URL: true
2954
3140
  });
2955
- if (!ctx.container.contains(wrapper)) {
2956
- ctx.container.appendChild(wrapper);
3141
+ if (!sizer.contains(wrapper)) {
3142
+ sizer.appendChild(wrapper);
2957
3143
  }
2958
3144
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
2959
3145
  renderedSuccessfully = true;
@@ -2990,8 +3176,8 @@ var DocxPlugin = class {
2990
3176
  try {
2991
3177
  wrapper.innerHTML = "";
2992
3178
  this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
2993
- if (!ctx.container.contains(wrapper)) {
2994
- ctx.container.appendChild(wrapper);
3179
+ if (!sizer.contains(wrapper)) {
3180
+ sizer.appendChild(wrapper);
2995
3181
  }
2996
3182
  renderedSuccessfully = true;
2997
3183
  } catch (fallbackErr) {
@@ -3006,8 +3192,8 @@ var DocxPlugin = class {
3006
3192
  </p>
3007
3193
  </div>
3008
3194
  `;
3009
- if (!ctx.container.contains(wrapper)) {
3010
- ctx.container.appendChild(wrapper);
3195
+ if (!sizer.contains(wrapper)) {
3196
+ sizer.appendChild(wrapper);
3011
3197
  }
3012
3198
  }
3013
3199
  }
@@ -3100,31 +3286,40 @@ var DocxPlugin = class {
3100
3286
  }
3101
3287
  scale = 1;
3102
3288
  let rotation = 0;
3103
- let fitMode = "page";
3289
+ let fitMode = ctx?.options?.fitMode || "width";
3104
3290
  let isUserZoomed = false;
3105
- const calculateFitScale = (_mode = fitMode) => {
3291
+ const calculateFitScale = (mode = fitMode) => {
3106
3292
  const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3107
3293
  const elW = activeEl.offsetWidth || 816;
3294
+ const elH = activeEl.offsetHeight || 1056;
3108
3295
  const availW = Math.max(280, ctx.container.clientWidth - 32);
3296
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
3109
3297
  const sW = availW / elW;
3298
+ const sH = availH / elH;
3299
+ if (mode === "page") {
3300
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
3301
+ }
3110
3302
  return Math.max(0.35, Math.min(3, sW));
3111
3303
  };
3112
3304
  const applyTransform = () => {
3113
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3114
- wrapper.style.transformOrigin = "top center";
3115
3305
  const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3306
+ const elW = activeEl.offsetWidth || 816;
3116
3307
  const elH = activeEl.offsetHeight || 1056;
3117
- if (scale > 1) {
3118
- wrapper.style.marginBottom = `${Math.round(elH * scale - elH + 32)}px`;
3119
- } else {
3120
- wrapper.style.marginBottom = "32px";
3121
- }
3308
+ const isRotated90 = rotation % 180 !== 0;
3309
+ const boxW = Math.round((isRotated90 ? elH : elW) * scale);
3310
+ const boxH = Math.round((isRotated90 ? elW : elH) * scale);
3311
+ sizer.style.width = `${boxW}px`;
3312
+ sizer.style.height = `${boxH}px`;
3313
+ wrapper.style.width = `${elW}px`;
3314
+ wrapper.style.height = `${elH}px`;
3315
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3316
+ wrapper.style.transformOrigin = "center center";
3122
3317
  };
3123
3318
  let resizeObserver = null;
3124
3319
  if (typeof ResizeObserver !== "undefined") {
3125
3320
  resizeObserver = new ResizeObserver(() => {
3126
3321
  if (!isUserZoomed) {
3127
- const newFit = calculateFitScale("page");
3322
+ const newFit = calculateFitScale(fitMode);
3128
3323
  if (Math.abs(scale - newFit) > 0.015) {
3129
3324
  scale = newFit;
3130
3325
  applyTransform();
@@ -3134,7 +3329,7 @@ var DocxPlugin = class {
3134
3329
  resizeObserver.observe(ctx.container);
3135
3330
  }
3136
3331
  setTimeout(() => {
3137
- scale = calculateFitScale("page");
3332
+ scale = calculateFitScale(fitMode);
3138
3333
  applyTransform();
3139
3334
  }, 40);
3140
3335
  const cleanup = () => {
@@ -3143,7 +3338,7 @@ var DocxPlugin = class {
3143
3338
  for (const url of createdBlobUrls) {
3144
3339
  URL.revokeObjectURL(url);
3145
3340
  }
3146
- wrapper.remove();
3341
+ scrollWrapper.remove();
3147
3342
  styleOverride.remove();
3148
3343
  ctx.container.innerHTML = "";
3149
3344
  };
@@ -3173,13 +3368,22 @@ var DocxPlugin = class {
3173
3368
  },
3174
3369
  fitToPage: () => {
3175
3370
  isUserZoomed = false;
3371
+ fitMode = "page";
3176
3372
  scale = calculateFitScale("page");
3177
3373
  rotation = 0;
3178
3374
  applyTransform();
3179
3375
  },
3376
+ fitToWidth: () => {
3377
+ isUserZoomed = false;
3378
+ fitMode = "width";
3379
+ scale = calculateFitScale("width");
3380
+ rotation = 0;
3381
+ applyTransform();
3382
+ },
3180
3383
  resetZoom: () => {
3181
3384
  isUserZoomed = false;
3182
- scale = calculateFitScale("page");
3385
+ fitMode = ctx?.options?.fitMode || "width";
3386
+ scale = calculateFitScale(fitMode);
3183
3387
  rotation = 0;
3184
3388
  ctx.container.scrollTop = 0;
3185
3389
  ctx.container.scrollLeft = 0;
@@ -3644,6 +3848,16 @@ var ExcelPlugin = class {
3644
3848
  instance.resetZoom?.();
3645
3849
  }
3646
3850
  },
3851
+ {
3852
+ id: "fit-width",
3853
+ icon: "fit-width",
3854
+ label: "Fit to Width",
3855
+ type: "button",
3856
+ group: "zoom",
3857
+ execute: () => {
3858
+ instance.fitToWidth?.();
3859
+ }
3860
+ },
3647
3861
  {
3648
3862
  id: "download",
3649
3863
  icon: "download",
@@ -3690,7 +3904,7 @@ var ExcelPlugin = class {
3690
3904
  contentArea.style.flex = "1";
3691
3905
  contentArea.style.overflow = "auto";
3692
3906
  contentArea.style.padding = "16px";
3693
- contentArea.style.transformOrigin = "top left";
3907
+ contentArea.style.boxSizing = "border-box";
3694
3908
  const tabsArea = document.createElement("div");
3695
3909
  tabsArea.className = "fp-excel-tabs";
3696
3910
  tabsArea.style.display = "flex";
@@ -3704,18 +3918,35 @@ var ExcelPlugin = class {
3704
3918
  container.appendChild(tabsArea);
3705
3919
  ctx.container.appendChild(container);
3706
3920
  let scale = 1;
3921
+ let isUserZoomed = false;
3707
3922
  let currentSheetIndex = 1;
3708
3923
  let sheetNames = [];
3709
3924
  let wb = null;
3710
3925
  const applyTransform = () => {
3711
- contentArea.style.transform = `scale(${scale})`;
3712
- contentArea.style.transformOrigin = "top left";
3926
+ const sizer = contentArea.querySelector(".fp-excel-sizer");
3927
+ const table = contentArea.querySelector("table");
3928
+ if (!sizer || !table) return;
3929
+ if (!table._baseWidth && table.offsetWidth > 0) {
3930
+ table._baseWidth = table.offsetWidth;
3931
+ table._baseHeight = table.offsetHeight;
3932
+ }
3933
+ const baseW = table._baseWidth || table.offsetWidth || 800;
3934
+ const baseH = table._baseHeight || table.offsetHeight || 600;
3935
+ const scaledW = Math.round(baseW * scale);
3936
+ const scaledH = Math.round(baseH * scale);
3937
+ sizer.style.width = `${scaledW}px`;
3938
+ sizer.style.height = `${scaledH}px`;
3939
+ table.style.position = "absolute";
3940
+ table.style.top = "0";
3941
+ table.style.left = "0";
3942
+ table.style.transform = `scale(${scale})`;
3943
+ table.style.transformOrigin = "top left";
3713
3944
  };
3714
3945
  const calculateFitScale = () => {
3715
3946
  const table = contentArea.querySelector("table");
3716
3947
  if (!table) return 1;
3717
3948
  const availW = Math.max(280, container.clientWidth - 48);
3718
- const tW = table.offsetWidth || 800;
3949
+ const tW = table._baseWidth || table.offsetWidth || 800;
3719
3950
  return Math.max(0.4, Math.min(1, availW / tW));
3720
3951
  };
3721
3952
  try {
@@ -3735,9 +3966,13 @@ var ExcelPlugin = class {
3735
3966
  contentArea.innerHTML = '<div style="padding: 24px; color: #888;">Empty sheet</div>';
3736
3967
  return;
3737
3968
  }
3969
+ const sizer = document.createElement("div");
3970
+ sizer.className = "fp-excel-sizer";
3971
+ sizer.style.position = "relative";
3738
3972
  const html = XLSX__namespace.utils.sheet_to_html(ws, { id: "fp-sheet-table", editable: false });
3739
- contentArea.innerHTML = html;
3740
- const table = contentArea.querySelector("table");
3973
+ sizer.innerHTML = html;
3974
+ contentArea.appendChild(sizer);
3975
+ const table = sizer.querySelector("table");
3741
3976
  if (table) {
3742
3977
  table.style.borderCollapse = "collapse";
3743
3978
  table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
@@ -3772,13 +4007,17 @@ var ExcelPlugin = class {
3772
4007
  });
3773
4008
  ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
3774
4009
  requestAnimationFrame(() => {
3775
- scale = calculateFitScale();
4010
+ if (!isUserZoomed) {
4011
+ scale = calculateFitScale();
4012
+ }
3776
4013
  applyTransform();
3777
4014
  });
3778
4015
  };
3779
4016
  const ro = new ResizeObserver(() => {
3780
- scale = calculateFitScale();
3781
- applyTransform();
4017
+ if (!isUserZoomed) {
4018
+ scale = calculateFitScale();
4019
+ applyTransform();
4020
+ }
3782
4021
  });
3783
4022
  ro.observe(container);
3784
4023
  if (sheetNames.length > 0) {
@@ -3815,23 +4054,33 @@ var ExcelPlugin = class {
3815
4054
  return {
3816
4055
  destroy: cleanup,
3817
4056
  zoomIn: () => {
4057
+ isUserZoomed = true;
3818
4058
  scale += 0.15;
3819
4059
  applyTransform();
3820
4060
  },
3821
4061
  zoomOut: () => {
4062
+ isUserZoomed = true;
3822
4063
  scale = Math.max(0.2, scale - 0.15);
3823
4064
  applyTransform();
3824
4065
  },
3825
4066
  getZoom: () => scale,
3826
4067
  setZoom: (level) => {
4068
+ isUserZoomed = true;
3827
4069
  scale = level;
3828
4070
  applyTransform();
3829
4071
  },
3830
4072
  fitToPage: () => {
4073
+ isUserZoomed = false;
4074
+ scale = calculateFitScale();
4075
+ applyTransform();
4076
+ },
4077
+ fitToWidth: () => {
4078
+ isUserZoomed = false;
3831
4079
  scale = calculateFitScale();
3832
4080
  applyTransform();
3833
4081
  },
3834
4082
  resetZoom: () => {
4083
+ isUserZoomed = false;
3835
4084
  scale = calculateFitScale();
3836
4085
  contentArea.scrollTop = 0;
3837
4086
  contentArea.scrollLeft = 0;
@@ -3977,6 +4226,16 @@ var CsvPlugin = class {
3977
4226
  instance.resetZoom?.();
3978
4227
  }
3979
4228
  },
4229
+ {
4230
+ id: "fit-width",
4231
+ icon: "fit-width",
4232
+ label: "Fit to Width",
4233
+ type: "button",
4234
+ group: "zoom",
4235
+ execute: () => {
4236
+ instance.fitToWidth?.();
4237
+ }
4238
+ },
3980
4239
  {
3981
4240
  id: "download",
3982
4241
  icon: "download",
@@ -4063,21 +4322,41 @@ var CsvPlugin = class {
4063
4322
  container.appendChild(tableWrapper);
4064
4323
  ctx.container.appendChild(container);
4065
4324
  let scale = 1;
4325
+ let isUserZoomed = false;
4066
4326
  const calculateFitScale = () => {
4067
4327
  const availW = Math.max(280, container.clientWidth - 48);
4068
- const tW = table.offsetWidth || 800;
4328
+ const tW = table._baseWidth || table.offsetWidth || 800;
4069
4329
  return Math.max(0.4, Math.min(1, availW / tW));
4070
4330
  };
4071
4331
  const applyScale = () => {
4072
- tableWrapper.style.transform = `scale(${scale})`;
4332
+ if (!table._baseWidth && table.offsetWidth > 0) {
4333
+ table._baseWidth = table.offsetWidth;
4334
+ table._baseHeight = table.offsetHeight;
4335
+ }
4336
+ const baseW = table._baseWidth || table.offsetWidth || 800;
4337
+ const baseH = table._baseHeight || table.offsetHeight || 600;
4338
+ const scaledW = Math.round(baseW * scale);
4339
+ const scaledH = Math.round(baseH * scale);
4340
+ tableWrapper.style.position = "relative";
4341
+ tableWrapper.style.width = `${scaledW}px`;
4342
+ tableWrapper.style.height = `${scaledH}px`;
4343
+ table.style.position = "absolute";
4344
+ table.style.top = "0";
4345
+ table.style.left = "0";
4346
+ table.style.transform = `scale(${scale})`;
4347
+ table.style.transformOrigin = "top left";
4073
4348
  };
4074
4349
  requestAnimationFrame(() => {
4075
- scale = calculateFitScale();
4350
+ if (!isUserZoomed) {
4351
+ scale = calculateFitScale();
4352
+ }
4076
4353
  applyScale();
4077
4354
  });
4078
4355
  const ro = new ResizeObserver(() => {
4079
- scale = calculateFitScale();
4080
- applyScale();
4356
+ if (!isUserZoomed) {
4357
+ scale = calculateFitScale();
4358
+ applyScale();
4359
+ }
4081
4360
  });
4082
4361
  ro.observe(container);
4083
4362
  const cleanup = () => {
@@ -4150,23 +4429,33 @@ var CsvPlugin = class {
4150
4429
  }];
4151
4430
  },
4152
4431
  zoomIn: () => {
4432
+ isUserZoomed = true;
4153
4433
  scale += 0.1;
4154
4434
  applyScale();
4155
4435
  },
4156
4436
  zoomOut: () => {
4437
+ isUserZoomed = true;
4157
4438
  scale = Math.max(0.2, scale - 0.1);
4158
4439
  applyScale();
4159
4440
  },
4160
4441
  getZoom: () => scale,
4161
4442
  setZoom: (level) => {
4443
+ isUserZoomed = true;
4162
4444
  scale = level;
4163
4445
  applyScale();
4164
4446
  },
4165
4447
  fitToPage: () => {
4448
+ isUserZoomed = false;
4449
+ scale = calculateFitScale();
4450
+ applyScale();
4451
+ },
4452
+ fitToWidth: () => {
4453
+ isUserZoomed = false;
4166
4454
  scale = calculateFitScale();
4167
4455
  applyScale();
4168
4456
  },
4169
4457
  resetZoom: () => {
4458
+ isUserZoomed = false;
4170
4459
  scale = calculateFitScale();
4171
4460
  container.scrollTop = 0;
4172
4461
  container.scrollLeft = 0;
@@ -4297,6 +4586,16 @@ var CodePlugin = class {
4297
4586
  instance.resetZoom?.();
4298
4587
  }
4299
4588
  },
4589
+ {
4590
+ id: "fit-width",
4591
+ icon: "fit-width",
4592
+ label: "Fit to Width",
4593
+ type: "button",
4594
+ group: "zoom",
4595
+ execute: () => {
4596
+ instance.fitToWidth?.();
4597
+ }
4598
+ },
4300
4599
  {
4301
4600
  id: "copy",
4302
4601
  icon: "copy",
@@ -4402,15 +4701,27 @@ var CodePlugin = class {
4402
4701
  let isUserZoomed = false;
4403
4702
  const pre = document.createElement("pre");
4404
4703
  const code = document.createElement("code");
4704
+ const sizer = document.createElement("div");
4705
+ const scrollWrapper = document.createElement("div");
4405
4706
  if (isTxt) {
4406
- container.style.overflowX = "hidden";
4407
- container.style.overflowY = "auto";
4707
+ container.style.overflow = "auto";
4408
4708
  container.style.backgroundColor = "#f1f5f9";
4409
- container.style.padding = "16px 8px";
4410
- container.style.boxSizing = "border-box";
4411
- container.style.display = "flex";
4412
- container.style.flexDirection = "column";
4413
- container.style.alignItems = "center";
4709
+ scrollWrapper.className = "fp-code-scroll-wrapper";
4710
+ scrollWrapper.style.minWidth = "100%";
4711
+ scrollWrapper.style.minHeight = "100%";
4712
+ scrollWrapper.style.width = "max-content";
4713
+ scrollWrapper.style.height = "max-content";
4714
+ scrollWrapper.style.display = "flex";
4715
+ scrollWrapper.style.justifyContent = "center";
4716
+ scrollWrapper.style.alignItems = "flex-start";
4717
+ scrollWrapper.style.padding = "16px 8px";
4718
+ scrollWrapper.style.boxSizing = "border-box";
4719
+ sizer.className = "fp-code-sizer";
4720
+ sizer.style.position = "relative";
4721
+ sizer.style.flexShrink = "0";
4722
+ sizer.style.display = "flex";
4723
+ sizer.style.justifyContent = "center";
4724
+ sizer.style.alignItems = "center";
4414
4725
  pre.style.margin = "0 auto";
4415
4726
  pre.style.fontFamily = "Consolas, 'Courier New', monospace";
4416
4727
  pre.style.fontSize = "13px";
@@ -4425,8 +4736,9 @@ var CodePlugin = class {
4425
4736
  pre.style.boxSizing = "border-box";
4426
4737
  pre.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
4427
4738
  pre.style.borderRadius = "4px";
4428
- pre.style.transformOrigin = "top center";
4739
+ pre.style.transformOrigin = "center center";
4429
4740
  pre.style.transition = "transform 0.15s ease";
4741
+ pre.style.flexShrink = "0";
4430
4742
  } else {
4431
4743
  container.style.overflow = "auto";
4432
4744
  container.style.backgroundColor = "#1e1e1e";
@@ -4459,7 +4771,13 @@ var CodePlugin = class {
4459
4771
  };
4460
4772
  renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
4461
4773
  pre.appendChild(code);
4462
- container.appendChild(pre);
4774
+ if (isTxt) {
4775
+ sizer.appendChild(pre);
4776
+ scrollWrapper.appendChild(sizer);
4777
+ container.appendChild(scrollWrapper);
4778
+ } else {
4779
+ container.appendChild(pre);
4780
+ }
4463
4781
  const showPage = (pageNum) => {
4464
4782
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4465
4783
  renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
@@ -4476,10 +4794,16 @@ var CodePlugin = class {
4476
4794
  };
4477
4795
  const updateTransform = () => {
4478
4796
  if (isTxt) {
4797
+ const elW = 816;
4798
+ const baseH = pre.offsetHeight || 1056;
4799
+ const isRotated90 = rotation % 180 !== 0;
4800
+ const boxW = Math.round((isRotated90 ? baseH : elW) * zoomLevel);
4801
+ const boxH = Math.round((isRotated90 ? elW : baseH) * zoomLevel);
4802
+ sizer.style.width = `${boxW}px`;
4803
+ sizer.style.height = `${boxH}px`;
4804
+ pre.style.width = `${elW}px`;
4479
4805
  pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
4480
- const scaledH = 1056 * zoomLevel;
4481
- const extraH = Math.max(0, scaledH - 1056);
4482
- pre.style.marginBottom = `${extraH + 32}px`;
4806
+ pre.style.transformOrigin = "center center";
4483
4807
  } else {
4484
4808
  pre.style.transform = `rotate(${rotation}deg)`;
4485
4809
  pre.style.fontSize = `${fontSize}px`;
@@ -4576,6 +4900,13 @@ var CodePlugin = class {
4576
4900
  zoomLevel = isTxt ? calculateTxtFit() : 1;
4577
4901
  updateTransform();
4578
4902
  },
4903
+ fitToWidth: () => {
4904
+ isUserZoomed = false;
4905
+ fontSize = 13;
4906
+ rotation = 0;
4907
+ zoomLevel = isTxt ? calculateTxtFit() : 1;
4908
+ updateTransform();
4909
+ },
4579
4910
  resetZoom: () => {
4580
4911
  isUserZoomed = false;
4581
4912
  fontSize = 13;
@@ -4660,6 +4991,14 @@ var ArchivePlugin = class {
4660
4991
  group: "zoom",
4661
4992
  execute: () => instance.resetZoom?.()
4662
4993
  },
4994
+ {
4995
+ id: "fit-width",
4996
+ icon: "fit-width",
4997
+ label: "Fit to Width",
4998
+ type: "button",
4999
+ group: "zoom",
5000
+ execute: () => instance.fitToWidth?.()
5001
+ },
4663
5002
  {
4664
5003
  id: "download",
4665
5004
  icon: "download",
@@ -4685,16 +5024,56 @@ var ArchivePlugin = class {
4685
5024
  width: 100%;
4686
5025
  height: 100%;
4687
5026
  overflow: auto;
4688
- padding: 24px;
4689
5027
  box-sizing: border-box;
4690
5028
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
4691
- transform-origin: top left;
4692
- transition: transform 0.2s ease;
4693
5029
  `;
5030
+ const scrollWrapper = document.createElement("div");
5031
+ scrollWrapper.className = "fp-archive-scroll-wrapper";
5032
+ scrollWrapper.style.cssText = `
5033
+ min-width: 100%;
5034
+ min-height: 100%;
5035
+ width: max-content;
5036
+ height: max-content;
5037
+ display: flex;
5038
+ justify-content: center;
5039
+ align-items: flex-start;
5040
+ padding: 24px;
5041
+ box-sizing: border-box;
5042
+ `;
5043
+ const sizer = document.createElement("div");
5044
+ sizer.className = "fp-archive-sizer";
5045
+ sizer.style.cssText = `
5046
+ position: relative;
5047
+ flex-shrink: 0;
5048
+ display: flex;
5049
+ justify-content: center;
5050
+ align-items: flex-start;
5051
+ `;
5052
+ const card = document.createElement("div");
5053
+ card.className = "fp-archive-card";
5054
+ card.style.cssText = `
5055
+ width: 900px;
5056
+ box-sizing: border-box;
5057
+ transform-origin: top center;
5058
+ transition: transform 0.15s ease;
5059
+ flex-shrink: 0;
5060
+ `;
5061
+ sizer.appendChild(card);
5062
+ scrollWrapper.appendChild(sizer);
5063
+ wrapper.appendChild(scrollWrapper);
4694
5064
  ctx.container.innerHTML = "";
4695
5065
  ctx.container.style.overflow = "auto";
4696
5066
  ctx.container.appendChild(wrapper);
4697
5067
  let scale = 1;
5068
+ const applyTransform = () => {
5069
+ const boxW = Math.round(900 * scale);
5070
+ const cardH = card.offsetHeight || 600;
5071
+ const boxH = Math.round(cardH * scale);
5072
+ sizer.style.width = `${boxW}px`;
5073
+ sizer.style.height = `${boxH}px`;
5074
+ card.style.transform = `scale(${scale})`;
5075
+ card.style.transformOrigin = "top center";
5076
+ };
4698
5077
  const entries = await new Promise((resolve, reject) => {
4699
5078
  fflate.unzip(new Uint8Array(ctx.buffer), (err, unzipped) => {
4700
5079
  if (err) {
@@ -4718,7 +5097,7 @@ var ArchivePlugin = class {
4718
5097
  });
4719
5098
  const totalUncompressedSize = entries.reduce((acc, e) => acc + e.size, 0);
4720
5099
  const renderUI = (filteredEntries) => {
4721
- wrapper.innerHTML = `
5100
+ card.innerHTML = `
4722
5101
  <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;">
4723
5102
  <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;">
4724
5103
  <div>
@@ -4748,7 +5127,7 @@ var ArchivePlugin = class {
4748
5127
  </div>
4749
5128
  </div>
4750
5129
  `;
4751
- const tbody = wrapper.querySelector("#fp-archive-body");
5130
+ const tbody = card.querySelector("#fp-archive-body");
4752
5131
  if (filteredEntries.length === 0) {
4753
5132
  tbody.innerHTML = `<tr><td colspan="3" style="padding: 24px; text-align: center; color: #9ca3af;">No matching files found</td></tr>`;
4754
5133
  } else {
@@ -4777,19 +5156,20 @@ var ArchivePlugin = class {
4777
5156
  }
4778
5157
  });
4779
5158
  });
4780
- const searchInput = wrapper.querySelector("#fp-archive-search");
5159
+ const searchInput = card.querySelector("#fp-archive-search");
4781
5160
  if (searchInput) {
4782
5161
  searchInput.addEventListener("input", () => {
4783
5162
  const q = searchInput.value.toLowerCase().trim();
4784
5163
  const filtered = q ? entries.filter((e) => e.path.toLowerCase().includes(q)) : entries;
4785
5164
  renderUI(filtered);
4786
- const newSearch = wrapper.querySelector("#fp-archive-search");
5165
+ const newSearch = card.querySelector("#fp-archive-search");
4787
5166
  if (newSearch) {
4788
5167
  newSearch.value = q;
4789
5168
  newSearch.focus();
4790
5169
  }
4791
5170
  });
4792
5171
  }
5172
+ applyTransform();
4793
5173
  };
4794
5174
  renderUI(entries);
4795
5175
  const cleanup = () => {
@@ -4801,26 +5181,30 @@ var ArchivePlugin = class {
4801
5181
  destroy: cleanup,
4802
5182
  zoomIn: () => {
4803
5183
  scale += 0.1;
4804
- wrapper.style.transform = `scale(${scale})`;
5184
+ applyTransform();
4805
5185
  },
4806
5186
  zoomOut: () => {
4807
5187
  scale = Math.max(0.3, scale - 0.1);
4808
- wrapper.style.transform = `scale(${scale})`;
5188
+ applyTransform();
4809
5189
  },
4810
5190
  getZoom: () => scale,
4811
5191
  setZoom: (level) => {
4812
5192
  scale = level;
4813
- wrapper.style.transform = `scale(${scale})`;
5193
+ applyTransform();
4814
5194
  },
4815
5195
  fitToPage: () => {
4816
5196
  scale = 1;
4817
- wrapper.style.transform = "scale(1)";
5197
+ applyTransform();
5198
+ },
5199
+ fitToWidth: () => {
5200
+ scale = 1;
5201
+ applyTransform();
4818
5202
  },
4819
5203
  resetZoom: () => {
4820
5204
  scale = 1;
4821
- wrapper.style.transform = "scale(1)";
4822
- ctx.container.scrollTop = 0;
4823
- ctx.container.scrollLeft = 0;
5205
+ wrapper.scrollTop = 0;
5206
+ wrapper.scrollLeft = 0;
5207
+ applyTransform();
4824
5208
  },
4825
5209
  download: () => {
4826
5210
  downloadFile(ctx.buffer, ctx.metadata.name || "archive.zip", "application/zip");
@@ -4880,6 +5264,14 @@ var MarkdownPlugin = class {
4880
5264
  group: "zoom",
4881
5265
  execute: () => instance.resetZoom?.()
4882
5266
  },
5267
+ {
5268
+ id: "fit-width",
5269
+ icon: "fit-width",
5270
+ label: "Fit to Width",
5271
+ type: "button",
5272
+ group: "zoom",
5273
+ execute: () => instance.fitToWidth?.()
5274
+ },
4883
5275
  {
4884
5276
  id: "copy",
4885
5277
  icon: "copy",
@@ -5033,6 +5425,10 @@ var MarkdownPlugin = class {
5033
5425
  fontSize = 15;
5034
5426
  wrapper.style.fontSize = "15px";
5035
5427
  },
5428
+ fitToWidth: () => {
5429
+ fontSize = 15;
5430
+ wrapper.style.fontSize = "15px";
5431
+ },
5036
5432
  resetZoom: () => {
5037
5433
  fontSize = 15;
5038
5434
  wrapper.style.fontSize = "15px";
@@ -5140,6 +5536,14 @@ var PptxPlugin = class {
5140
5536
  group: "zoom",
5141
5537
  execute: () => instance.resetZoom?.()
5142
5538
  },
5539
+ {
5540
+ id: "fit-width",
5541
+ icon: "fit-width",
5542
+ label: "Fit to Width",
5543
+ type: "button",
5544
+ group: "zoom",
5545
+ execute: () => instance.fitToWidth?.()
5546
+ },
5143
5547
  {
5144
5548
  id: "rotate-cw",
5145
5549
  icon: "rotate-cw",
@@ -5179,12 +5583,30 @@ var PptxPlugin = class {
5179
5583
  width: 100%;
5180
5584
  height: 100%;
5181
5585
  overflow: auto;
5586
+ box-sizing: border-box;
5587
+ background: var(--fp-bg-canvas, #525659);
5588
+ `;
5589
+ const scrollWrapper = document.createElement("div");
5590
+ scrollWrapper.className = "fp-pptx-scroll-wrapper";
5591
+ scrollWrapper.style.cssText = `
5592
+ min-width: 100%;
5593
+ min-height: 100%;
5594
+ width: max-content;
5595
+ height: max-content;
5182
5596
  display: flex;
5183
5597
  justify-content: center;
5184
- align-items: flex-start;
5598
+ align-items: center;
5185
5599
  padding: 24px;
5186
5600
  box-sizing: border-box;
5187
- background: var(--fp-bg-canvas, #525659);
5601
+ `;
5602
+ const sizer = document.createElement("div");
5603
+ sizer.className = "fp-pptx-sizer";
5604
+ sizer.style.cssText = `
5605
+ position: relative;
5606
+ flex-shrink: 0;
5607
+ display: flex;
5608
+ justify-content: center;
5609
+ align-items: center;
5188
5610
  `;
5189
5611
  const slideContainer = document.createElement("div");
5190
5612
  slideContainer.className = "fp-slide-container";
@@ -5193,39 +5615,65 @@ var PptxPlugin = class {
5193
5615
  border-radius: 4px;
5194
5616
  overflow: hidden;
5195
5617
  background: #ffffff;
5196
- transform-origin: top center;
5618
+ transform-origin: center center;
5197
5619
  transition: transform 0.2s ease;
5198
- max-width: calc(100% - 32px);
5199
- max-height: calc(100% - 48px);
5620
+ flex-shrink: 0;
5200
5621
  `;
5201
5622
  const canvas = document.createElement("canvas");
5202
5623
  slideContainer.appendChild(canvas);
5203
- wrapper.appendChild(slideContainer);
5624
+ sizer.appendChild(slideContainer);
5625
+ scrollWrapper.appendChild(sizer);
5626
+ wrapper.appendChild(scrollWrapper);
5204
5627
  ctx.container.innerHTML = "";
5205
5628
  ctx.container.style.overflow = "auto";
5206
5629
  ctx.container.appendChild(wrapper);
5207
- const calculateFitScale = () => {
5208
- const availW = Math.max(200, ctx.container.clientWidth - 48);
5209
- const availH = Math.max(200, ctx.container.clientHeight - 72);
5630
+ let fitMode = ctx?.options?.fitMode || "width";
5631
+ let isUserZoomed = false;
5632
+ const calculateFitScale = (mode = fitMode) => {
5633
+ const availW = Math.max(200, ctx.container.clientWidth - 32);
5634
+ const availH = Math.max(200, ctx.container.clientHeight - 32);
5210
5635
  const cW = canvas.offsetWidth || 1280;
5211
5636
  const cH = canvas.offsetHeight || 720;
5212
- return Math.min(1, Math.min(availW / cW, availH / cH));
5637
+ if (mode === "page") {
5638
+ return Math.min(2.5, Math.min(availW / cW, availH / cH));
5639
+ }
5640
+ return Math.min(2.5, availW / cW);
5213
5641
  };
5214
5642
  const applyTransform = () => {
5643
+ const cW = canvas.offsetWidth || 1280;
5644
+ const cH = canvas.offsetHeight || 720;
5645
+ const isRotated90 = rotation % 180 !== 0;
5646
+ const boxW = Math.round((isRotated90 ? cH : cW) * scale);
5647
+ const boxH = Math.round((isRotated90 ? cW : cH) * scale);
5648
+ sizer.style.width = `${boxW}px`;
5649
+ sizer.style.height = `${boxH}px`;
5650
+ slideContainer.style.width = `${cW}px`;
5651
+ slideContainer.style.height = `${cH}px`;
5215
5652
  slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5653
+ slideContainer.style.transformOrigin = "center center";
5216
5654
  };
5217
5655
  const renderCurrentSlide = async () => {
5218
5656
  try {
5219
5657
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
5220
5658
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
5221
- scale = calculateFitScale();
5659
+ if (!isUserZoomed) {
5660
+ scale = calculateFitScale(fitMode);
5661
+ }
5222
5662
  applyTransform();
5223
5663
  } catch (err) {
5224
5664
  console.error("[PptxPlugin] Failed to render slide:", err);
5225
5665
  }
5226
5666
  };
5227
5667
  await renderCurrentSlide();
5668
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5669
+ if (!isUserZoomed) {
5670
+ scale = calculateFitScale(fitMode);
5671
+ applyTransform();
5672
+ }
5673
+ }) : null;
5674
+ ro?.observe(ctx.container);
5228
5675
  const cleanup = () => {
5676
+ ro?.disconnect();
5229
5677
  renderer.destroy();
5230
5678
  wrapper.remove();
5231
5679
  ctx.container.innerHTML = "";
@@ -5242,28 +5690,44 @@ var PptxPlugin = class {
5242
5690
  getPageCount: () => slideCount,
5243
5691
  getCurrentPage: () => currentSlide,
5244
5692
  zoomIn: () => {
5693
+ isUserZoomed = true;
5245
5694
  scale += 0.15;
5246
5695
  applyTransform();
5247
5696
  },
5248
5697
  zoomOut: () => {
5698
+ isUserZoomed = true;
5249
5699
  scale = Math.max(0.2, scale - 0.15);
5250
5700
  applyTransform();
5251
5701
  },
5252
5702
  getZoom: () => scale,
5253
5703
  setZoom: (level) => {
5704
+ isUserZoomed = true;
5254
5705
  scale = level;
5255
5706
  applyTransform();
5256
5707
  },
5257
5708
  fitToPage: () => {
5258
- scale = calculateFitScale();
5709
+ isUserZoomed = false;
5710
+ fitMode = "page";
5711
+ scale = calculateFitScale("page");
5712
+ rotation = 0;
5713
+ applyTransform();
5714
+ },
5715
+ fitToWidth: () => {
5716
+ isUserZoomed = false;
5717
+ fitMode = "width";
5718
+ scale = calculateFitScale("width");
5259
5719
  rotation = 0;
5260
5720
  applyTransform();
5261
5721
  },
5262
5722
  resetZoom: () => {
5263
- scale = calculateFitScale();
5723
+ isUserZoomed = false;
5724
+ fitMode = ctx?.options?.fitMode || "width";
5725
+ scale = calculateFitScale(fitMode);
5264
5726
  rotation = 0;
5265
5727
  wrapper.scrollTop = 0;
5266
5728
  wrapper.scrollLeft = 0;
5729
+ ctx.container.scrollTop = 0;
5730
+ ctx.container.scrollLeft = 0;
5267
5731
  applyTransform();
5268
5732
  },
5269
5733
  rotateCW: () => {
@@ -5708,6 +6172,14 @@ var RtfPlugin = class {
5708
6172
  group: "zoom",
5709
6173
  execute: () => instance.resetZoom?.()
5710
6174
  },
6175
+ {
6176
+ id: "fit-width",
6177
+ icon: "fit-width",
6178
+ label: "Fit to Width",
6179
+ type: "button",
6180
+ group: "zoom",
6181
+ execute: () => instance.fitToWidth?.()
6182
+ },
5711
6183
  {
5712
6184
  id: "rotate-cw",
5713
6185
  icon: "rotate-cw",
@@ -5818,13 +6290,33 @@ var RtfPlugin = class {
5818
6290
  wrapper.style.flexDirection = "column";
5819
6291
  wrapper.style.alignItems = "center";
5820
6292
  wrapper.style.backgroundColor = "transparent";
5821
- wrapper.style.transformOrigin = "top center";
6293
+ wrapper.style.transformOrigin = "center center";
5822
6294
  wrapper.style.transition = "transform 0.15s ease";
5823
- ctx.container.style.overflowX = "hidden";
5824
- ctx.container.style.overflowY = "auto";
5825
- ctx.container.style.padding = "16px 8px";
6295
+ wrapper.style.flexShrink = "0";
6296
+ const sizer = document.createElement("div");
6297
+ sizer.className = "fp-rtf-sizer";
6298
+ sizer.style.position = "relative";
6299
+ sizer.style.flexShrink = "0";
6300
+ sizer.style.display = "flex";
6301
+ sizer.style.justifyContent = "center";
6302
+ sizer.style.alignItems = "center";
6303
+ const scrollWrapper = document.createElement("div");
6304
+ scrollWrapper.className = "fp-rtf-scroll-wrapper";
6305
+ scrollWrapper.style.minWidth = "100%";
6306
+ scrollWrapper.style.minHeight = "100%";
6307
+ scrollWrapper.style.width = "max-content";
6308
+ scrollWrapper.style.height = "max-content";
6309
+ scrollWrapper.style.display = "flex";
6310
+ scrollWrapper.style.justifyContent = "center";
6311
+ scrollWrapper.style.alignItems = "flex-start";
6312
+ scrollWrapper.style.padding = "16px 8px";
6313
+ scrollWrapper.style.boxSizing = "border-box";
6314
+ sizer.appendChild(wrapper);
6315
+ scrollWrapper.appendChild(sizer);
6316
+ ctx.container.style.overflow = "auto";
6317
+ ctx.container.style.padding = "0";
5826
6318
  ctx.container.style.backgroundColor = "#f1f5f9";
5827
- ctx.container.appendChild(wrapper);
6319
+ ctx.container.appendChild(scrollWrapper);
5828
6320
  let scale = 1;
5829
6321
  let rotation = 0;
5830
6322
  let pageElements = [];
@@ -5843,12 +6335,17 @@ var RtfPlugin = class {
5843
6335
  return Math.max(0.4, Math.min(3, sW));
5844
6336
  };
5845
6337
  const applyTransform = () => {
5846
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5847
- wrapper.style.transformOrigin = "top center";
6338
+ const elW = pageWidth;
5848
6339
  const baseH = pageHeight;
5849
- const scaledH = baseH * scale;
5850
- const extraH = Math.max(0, scaledH - baseH);
5851
- wrapper.style.marginBottom = `${extraH + 32}px`;
6340
+ const isRotated90 = rotation % 180 !== 0;
6341
+ const boxW = Math.round((isRotated90 ? baseH : elW) * scale);
6342
+ const boxH = Math.round((isRotated90 ? elW : baseH) * scale);
6343
+ sizer.style.width = `${boxW}px`;
6344
+ sizer.style.height = `${boxH}px`;
6345
+ wrapper.style.width = `${elW}px`;
6346
+ wrapper.style.height = `${baseH}px`;
6347
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6348
+ wrapper.style.transformOrigin = "center center";
5852
6349
  };
5853
6350
  const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5854
6351
  if (!isUserZoomed) {
@@ -6119,7 +6616,7 @@ var RtfPlugin = class {
6119
6616
  }
6120
6617
  const cleanup = () => {
6121
6618
  resizeObserver?.disconnect();
6122
- wrapper.remove();
6619
+ scrollWrapper.remove();
6123
6620
  ctx.container.innerHTML = "";
6124
6621
  };
6125
6622
  ctx.signal.addEventListener("abort", cleanup);
@@ -6171,6 +6668,13 @@ var RtfPlugin = class {
6171
6668
  applyTransform();
6172
6669
  },
6173
6670
  fitToPage: () => {
6671
+ isUserZoomed = false;
6672
+ fitMode = "page";
6673
+ scale = calculateFitScale("page");
6674
+ rotation = 0;
6675
+ applyTransform();
6676
+ },
6677
+ fitToWidth: () => {
6174
6678
  isUserZoomed = false;
6175
6679
  fitMode = "width";
6176
6680
  scale = calculateFitScale("width");
@@ -6179,8 +6683,8 @@ var RtfPlugin = class {
6179
6683
  },
6180
6684
  resetZoom: () => {
6181
6685
  isUserZoomed = false;
6182
- fitMode = "width";
6183
- scale = calculateFitScale("width");
6686
+ fitMode = ctx?.options?.fitMode || "width";
6687
+ scale = calculateFitScale(fitMode);
6184
6688
  rotation = 0;
6185
6689
  ctx.container.scrollTop = 0;
6186
6690
  ctx.container.scrollLeft = 0;
@@ -6261,6 +6765,14 @@ var HtmlPreviewPlugin = class {
6261
6765
  group: "zoom",
6262
6766
  execute: () => instance.resetZoom?.()
6263
6767
  },
6768
+ {
6769
+ id: "fit-width",
6770
+ icon: "fit-width",
6771
+ label: "Fit to Width",
6772
+ type: "button",
6773
+ group: "zoom",
6774
+ execute: () => instance.fitToWidth?.()
6775
+ },
6264
6776
  {
6265
6777
  id: "copy",
6266
6778
  icon: "copy",
@@ -6294,23 +6806,44 @@ var HtmlPreviewPlugin = class {
6294
6806
  ADD_TAGS: ["style", "link"],
6295
6807
  ADD_ATTR: ["target", "rel"]
6296
6808
  });
6809
+ const sizer = document.createElement("div");
6810
+ sizer.className = "fp-html-sizer";
6811
+ sizer.style.position = "relative";
6297
6812
  const iframe = document.createElement("iframe");
6298
6813
  iframe.className = "fp-html-iframe";
6299
- iframe.style.width = "100%";
6300
- iframe.style.height = "100%";
6301
6814
  iframe.style.border = "none";
6302
6815
  iframe.style.backgroundColor = "#ffffff";
6303
6816
  iframe.style.transformOrigin = "top left";
6304
6817
  iframe.style.transition = "transform 0.2s ease";
6305
6818
  iframe.sandbox.add("allow-same-origin");
6819
+ sizer.appendChild(iframe);
6306
6820
  ctx.container.style.overflow = "auto";
6307
6821
  ctx.container.style.width = "100%";
6308
6822
  ctx.container.style.height = "100%";
6309
- ctx.container.appendChild(iframe);
6823
+ ctx.container.innerHTML = "";
6824
+ ctx.container.appendChild(sizer);
6310
6825
  iframe.srcdoc = sanitized;
6311
6826
  let scale = 1;
6827
+ const applyTransform = () => {
6828
+ const baseW = Math.max(600, ctx.container.clientWidth || 800);
6829
+ const baseH = Math.max(400, ctx.container.clientHeight || 600);
6830
+ const scaledW = Math.round(baseW * scale);
6831
+ const scaledH = Math.round(baseH * scale);
6832
+ sizer.style.width = `${scaledW}px`;
6833
+ sizer.style.height = `${scaledH}px`;
6834
+ iframe.style.position = "absolute";
6835
+ iframe.style.top = "0";
6836
+ iframe.style.left = "0";
6837
+ iframe.style.width = `${baseW}px`;
6838
+ iframe.style.height = `${baseH}px`;
6839
+ iframe.style.transform = `scale(${scale})`;
6840
+ iframe.style.transformOrigin = "top left";
6841
+ };
6842
+ requestAnimationFrame(() => {
6843
+ applyTransform();
6844
+ });
6312
6845
  const cleanup = () => {
6313
- iframe.remove();
6846
+ sizer.remove();
6314
6847
  ctx.container.innerHTML = "";
6315
6848
  };
6316
6849
  ctx.signal.addEventListener("abort", cleanup);
@@ -6318,26 +6851,30 @@ var HtmlPreviewPlugin = class {
6318
6851
  destroy: cleanup,
6319
6852
  zoomIn: () => {
6320
6853
  scale += 0.1;
6321
- iframe.style.transform = `scale(${scale})`;
6854
+ applyTransform();
6322
6855
  },
6323
6856
  zoomOut: () => {
6324
6857
  scale = Math.max(0.2, scale - 0.1);
6325
- iframe.style.transform = `scale(${scale})`;
6858
+ applyTransform();
6326
6859
  },
6327
6860
  getZoom: () => scale,
6328
6861
  setZoom: (level) => {
6329
6862
  scale = level;
6330
- iframe.style.transform = `scale(${scale})`;
6863
+ applyTransform();
6331
6864
  },
6332
6865
  fitToPage: () => {
6333
6866
  scale = 1;
6334
- iframe.style.transform = "scale(1)";
6867
+ applyTransform();
6868
+ },
6869
+ fitToWidth: () => {
6870
+ scale = 1;
6871
+ applyTransform();
6335
6872
  },
6336
6873
  resetZoom: () => {
6337
6874
  scale = 1;
6338
- iframe.style.transform = "scale(1)";
6339
6875
  ctx.container.scrollTop = 0;
6340
6876
  ctx.container.scrollLeft = 0;
6877
+ applyTransform();
6341
6878
  },
6342
6879
  copy: () => {
6343
6880
  navigator.clipboard.writeText(rawHtml);
@@ -6447,6 +6984,14 @@ var OpenDocumentPlugin = class {
6447
6984
  group: "zoom",
6448
6985
  execute: () => instance.resetZoom?.()
6449
6986
  },
6987
+ {
6988
+ id: "fit-width",
6989
+ icon: "fit-width",
6990
+ label: "Fit to Width",
6991
+ type: "button",
6992
+ group: "zoom",
6993
+ execute: () => instance.fitToWidth?.()
6994
+ },
6450
6995
  {
6451
6996
  id: "rotate-cw",
6452
6997
  icon: "rotate-cw",
@@ -6512,10 +7057,26 @@ var OpenDocumentPlugin = class {
6512
7057
  container.className = "fp-odf-container";
6513
7058
  container.style.width = "100%";
6514
7059
  container.style.height = "100%";
6515
- container.style.overflowX = "hidden";
6516
- container.style.overflowY = "auto";
6517
- container.style.padding = "16px 8px";
7060
+ container.style.overflow = "auto";
6518
7061
  container.style.backgroundColor = "#f1f5f9";
7062
+ const scrollWrapper = document.createElement("div");
7063
+ scrollWrapper.className = "fp-odf-scroll-wrapper";
7064
+ scrollWrapper.style.minWidth = "100%";
7065
+ scrollWrapper.style.minHeight = "100%";
7066
+ scrollWrapper.style.width = "max-content";
7067
+ scrollWrapper.style.height = "max-content";
7068
+ scrollWrapper.style.display = "flex";
7069
+ scrollWrapper.style.justifyContent = "center";
7070
+ scrollWrapper.style.alignItems = "flex-start";
7071
+ scrollWrapper.style.padding = "16px 8px";
7072
+ scrollWrapper.style.boxSizing = "border-box";
7073
+ const sizer = document.createElement("div");
7074
+ sizer.className = "fp-odf-sizer";
7075
+ sizer.style.position = "relative";
7076
+ sizer.style.flexShrink = "0";
7077
+ sizer.style.display = "flex";
7078
+ sizer.style.justifyContent = "center";
7079
+ sizer.style.alignItems = "center";
6519
7080
  const wrapper = document.createElement("div");
6520
7081
  wrapper.className = "fp-odf-wrapper";
6521
7082
  wrapper.style.margin = "0 auto";
@@ -6524,9 +7085,12 @@ var OpenDocumentPlugin = class {
6524
7085
  wrapper.style.backgroundColor = "#ffffff";
6525
7086
  wrapper.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
6526
7087
  wrapper.style.borderRadius = "4px";
6527
- wrapper.style.transformOrigin = "top center";
7088
+ wrapper.style.transformOrigin = "center center";
6528
7089
  wrapper.style.transition = "transform 0.15s ease";
6529
- container.appendChild(wrapper);
7090
+ wrapper.style.flexShrink = "0";
7091
+ sizer.appendChild(wrapper);
7092
+ scrollWrapper.appendChild(sizer);
7093
+ container.appendChild(scrollWrapper);
6530
7094
  ctx.container.appendChild(container);
6531
7095
  let scale = 1;
6532
7096
  let rotation = 0;
@@ -6544,7 +7108,10 @@ var OpenDocumentPlugin = class {
6544
7108
  const sW = availW / elW;
6545
7109
  const sH = availH / (isPresentation ? 540 : singlePageH);
6546
7110
  if (isPresentation) {
6547
- return Math.min(2.5, Math.min(sW, sH));
7111
+ if (mode === "page") {
7112
+ return Math.min(2.5, Math.min(sW, sH));
7113
+ }
7114
+ return Math.min(2.5, sW);
6548
7115
  }
6549
7116
  if (mode === "page") {
6550
7117
  return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
@@ -6552,13 +7119,18 @@ var OpenDocumentPlugin = class {
6552
7119
  return Math.max(0.4, Math.min(3, sW));
6553
7120
  };
6554
7121
  const applyTransform = () => {
6555
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6556
- wrapper.style.transformOrigin = "top center";
6557
7122
  const activeSlide = slides[currentPage - 1];
7123
+ const elW = isPresentation ? 960 : 816;
6558
7124
  const baseH = activeSlide?.offsetHeight || wrapper.offsetHeight || 1056;
6559
- const scaledH = baseH * scale;
6560
- const extraH = Math.max(0, scaledH - baseH);
6561
- wrapper.style.marginBottom = `${extraH + 32}px`;
7125
+ const isRotated90 = rotation % 180 !== 0;
7126
+ const boxW = Math.round((isRotated90 ? baseH : elW) * scale);
7127
+ const boxH = Math.round((isRotated90 ? elW : baseH) * scale);
7128
+ sizer.style.width = `${boxW}px`;
7129
+ sizer.style.height = `${boxH}px`;
7130
+ wrapper.style.width = `${elW}px`;
7131
+ wrapper.style.height = `${baseH}px`;
7132
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7133
+ wrapper.style.transformOrigin = "center center";
6562
7134
  };
6563
7135
  const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6564
7136
  if (!isUserZoomed) {
@@ -6708,6 +7280,13 @@ var OpenDocumentPlugin = class {
6708
7280
  applyTransform();
6709
7281
  },
6710
7282
  fitToPage: () => {
7283
+ isUserZoomed = false;
7284
+ fitMode = "page";
7285
+ scale = calculateFitScale("page");
7286
+ rotation = 0;
7287
+ applyTransform();
7288
+ },
7289
+ fitToWidth: () => {
6711
7290
  isUserZoomed = false;
6712
7291
  fitMode = "width";
6713
7292
  scale = calculateFitScale("width");
@@ -6716,8 +7295,8 @@ var OpenDocumentPlugin = class {
6716
7295
  },
6717
7296
  resetZoom: () => {
6718
7297
  isUserZoomed = false;
6719
- fitMode = "width";
6720
- scale = calculateFitScale("width");
7298
+ fitMode = ctx?.options?.fitMode || "width";
7299
+ scale = calculateFitScale(fitMode);
6721
7300
  rotation = 0;
6722
7301
  container.scrollTop = 0;
6723
7302
  container.scrollLeft = 0;
@@ -7074,6 +7653,14 @@ var DocPlugin = class {
7074
7653
  group: "zoom",
7075
7654
  execute: () => instance.resetZoom?.()
7076
7655
  },
7656
+ {
7657
+ id: "fit-width",
7658
+ icon: "fit-width",
7659
+ label: "Fit to Width",
7660
+ type: "button",
7661
+ group: "zoom",
7662
+ execute: () => instance.fitToWidth?.()
7663
+ },
7077
7664
  {
7078
7665
  id: "rotate-cw",
7079
7666
  icon: "rotate-cw",
@@ -7122,13 +7709,28 @@ var DocPlugin = class {
7122
7709
  container.className = "fp-doc-container";
7123
7710
  container.style.width = "100%";
7124
7711
  container.style.height = "100%";
7125
- container.style.overflowX = "hidden";
7126
- container.style.overflowY = "auto";
7127
- container.style.padding = "16px 8px";
7712
+ container.style.overflow = "auto";
7128
7713
  container.style.backgroundColor = "#f1f5f9";
7129
- container.style.display = "flex";
7130
- container.style.justifyContent = "center";
7131
- container.style.alignItems = "flex-start";
7714
+ const scrollWrapper = document.createElement("div");
7715
+ scrollWrapper.className = "fp-doc-scroll-wrapper";
7716
+ scrollWrapper.style.minWidth = "100%";
7717
+ scrollWrapper.style.minHeight = "100%";
7718
+ scrollWrapper.style.width = "max-content";
7719
+ scrollWrapper.style.height = "max-content";
7720
+ scrollWrapper.style.display = "flex";
7721
+ scrollWrapper.style.justifyContent = "center";
7722
+ scrollWrapper.style.alignItems = "flex-start";
7723
+ scrollWrapper.style.padding = "16px 8px";
7724
+ scrollWrapper.style.boxSizing = "border-box";
7725
+ const sizer = document.createElement("div");
7726
+ sizer.className = "fp-doc-sizer";
7727
+ sizer.style.position = "relative";
7728
+ sizer.style.flexShrink = "0";
7729
+ sizer.style.display = "flex";
7730
+ sizer.style.justifyContent = "center";
7731
+ sizer.style.alignItems = "center";
7732
+ scrollWrapper.appendChild(sizer);
7733
+ container.appendChild(scrollWrapper);
7132
7734
  ctx.container.appendChild(container);
7133
7735
  let scale = 1;
7134
7736
  let extractedRawText = "";
@@ -7176,10 +7778,11 @@ var DocPlugin = class {
7176
7778
  pageCard.style.padding = "72px 56px";
7177
7779
  pageCard.style.boxSizing = "border-box";
7178
7780
  pageCard.style.display = i === 0 ? "block" : "none";
7179
- pageCard.style.transformOrigin = "top center";
7781
+ pageCard.style.transformOrigin = "center center";
7180
7782
  pageCard.style.transition = "transform 0.15s ease";
7181
7783
  pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
7182
7784
  pageCard.style.color = "#1e293b";
7785
+ pageCard.style.flexShrink = "0";
7183
7786
  if (isFallback && i === 0) {
7184
7787
  pageCard.innerHTML = `
7185
7788
  <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
@@ -7191,18 +7794,23 @@ var DocPlugin = class {
7191
7794
  } else {
7192
7795
  pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
7193
7796
  }
7194
- container.appendChild(pageCard);
7797
+ sizer.appendChild(pageCard);
7195
7798
  pageCards.push(pageCard);
7196
7799
  }
7197
7800
  let rotation = 0;
7198
7801
  const applyTransform = () => {
7199
7802
  const activeCard = pageCards[currentPage - 1];
7200
7803
  if (activeCard) {
7201
- activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7804
+ const baseW = 816;
7202
7805
  const baseH = activeCard.offsetHeight || 1056;
7203
- const scaledH = baseH * scale;
7204
- const extraH = Math.max(0, scaledH - baseH);
7205
- activeCard.style.marginBottom = `${extraH + 32}px`;
7806
+ const isRotated90 = rotation % 180 !== 0;
7807
+ const boxW = Math.round((isRotated90 ? baseH : baseW) * scale);
7808
+ const boxH = Math.round((isRotated90 ? baseW : baseH) * scale);
7809
+ sizer.style.width = `${boxW}px`;
7810
+ sizer.style.height = `${boxH}px`;
7811
+ activeCard.style.width = `${baseW}px`;
7812
+ activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7813
+ activeCard.style.transformOrigin = "center center";
7206
7814
  }
7207
7815
  };
7208
7816
  const showPage = (pageNum) => {
@@ -7210,11 +7818,11 @@ var DocPlugin = class {
7210
7818
  pageCards.forEach((card, idx) => {
7211
7819
  if (idx + 1 === currentPage) {
7212
7820
  card.style.display = "block";
7213
- card.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7214
7821
  } else {
7215
7822
  card.style.display = "none";
7216
7823
  }
7217
7824
  });
7825
+ applyTransform();
7218
7826
  container.scrollTop = 0;
7219
7827
  ctx.emit("page-change", { page: currentPage, total: totalPages });
7220
7828
  };
@@ -7274,6 +7882,13 @@ var DocPlugin = class {
7274
7882
  applyTransform();
7275
7883
  },
7276
7884
  fitToPage: () => {
7885
+ isUserZoomed = false;
7886
+ fitMode = "page";
7887
+ scale = calculateFitScale("page");
7888
+ rotation = 0;
7889
+ applyTransform();
7890
+ },
7891
+ fitToWidth: () => {
7277
7892
  isUserZoomed = false;
7278
7893
  fitMode = "width";
7279
7894
  scale = calculateFitScale("width");
@@ -7282,9 +7897,11 @@ var DocPlugin = class {
7282
7897
  },
7283
7898
  resetZoom: () => {
7284
7899
  isUserZoomed = false;
7285
- fitMode = "width";
7286
- scale = calculateFitScale("width");
7900
+ fitMode = ctx?.options?.fitMode || "width";
7901
+ scale = calculateFitScale(fitMode);
7287
7902
  rotation = 0;
7903
+ container.scrollTop = 0;
7904
+ container.scrollLeft = 0;
7288
7905
  ctx.container.scrollTop = 0;
7289
7906
  ctx.container.scrollLeft = 0;
7290
7907
  applyTransform();
@@ -7777,6 +8394,14 @@ var PptPlugin = class {
7777
8394
  group: "zoom",
7778
8395
  execute: () => instance.resetZoom?.()
7779
8396
  },
8397
+ {
8398
+ id: "fit-width",
8399
+ icon: "fit-width",
8400
+ label: "Fit to Width",
8401
+ type: "button",
8402
+ group: "zoom",
8403
+ execute: () => instance.fitToWidth?.()
8404
+ },
7780
8405
  {
7781
8406
  id: "rotate-cw",
7782
8407
  icon: "rotate-cw",
@@ -7809,17 +8434,30 @@ var PptPlugin = class {
7809
8434
  container.style.width = "100%";
7810
8435
  container.style.height = "100%";
7811
8436
  container.style.overflow = "auto";
7812
- container.style.display = "flex";
7813
- container.style.justifyContent = "center";
7814
- container.style.alignItems = "center";
7815
- container.style.padding = "32px 16px";
7816
8437
  container.style.backgroundColor = "#0f172a";
7817
8438
  container.style.boxSizing = "border-box";
8439
+ const scrollWrapper = document.createElement("div");
8440
+ scrollWrapper.className = "fp-ppt-scroll-wrapper";
8441
+ scrollWrapper.style.minWidth = "100%";
8442
+ scrollWrapper.style.minHeight = "100%";
8443
+ scrollWrapper.style.width = "max-content";
8444
+ scrollWrapper.style.height = "max-content";
8445
+ scrollWrapper.style.display = "flex";
8446
+ scrollWrapper.style.justifyContent = "center";
8447
+ scrollWrapper.style.alignItems = "center";
8448
+ scrollWrapper.style.padding = "32px 16px";
8449
+ scrollWrapper.style.boxSizing = "border-box";
8450
+ const sizer = document.createElement("div");
8451
+ sizer.className = "fp-ppt-sizer";
8452
+ sizer.style.position = "relative";
8453
+ sizer.style.flexShrink = "0";
8454
+ sizer.style.display = "flex";
8455
+ sizer.style.justifyContent = "center";
8456
+ sizer.style.alignItems = "center";
7818
8457
  const slideCard = document.createElement("div");
7819
8458
  slideCard.className = "fp-ppt-slide-card";
7820
8459
  slideCard.style.width = "960px";
7821
- slideCard.style.maxWidth = "calc(100% - 32px)";
7822
- slideCard.style.maxHeight = "calc(100% - 48px)";
8460
+ slideCard.style.height = "540px";
7823
8461
  slideCard.style.aspectRatio = "16 / 9";
7824
8462
  slideCard.style.backgroundColor = "#ffffff";
7825
8463
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
@@ -7829,23 +8467,47 @@ var PptPlugin = class {
7829
8467
  slideCard.style.transition = "transform 0.2s ease";
7830
8468
  slideCard.style.transformOrigin = "center center";
7831
8469
  slideCard.style.flexShrink = "0";
7832
- container.appendChild(slideCard);
8470
+ sizer.appendChild(slideCard);
8471
+ scrollWrapper.appendChild(sizer);
8472
+ container.appendChild(scrollWrapper);
7833
8473
  ctx.container.appendChild(container);
7834
8474
  let scale = 1;
7835
8475
  let rotation = 0;
7836
8476
  let currentSlide = 1;
7837
8477
  let slides = [];
7838
8478
  const createdBlobUrls = [];
7839
- const calculateFitScale = () => {
7840
- const availW = Math.max(200, container.clientWidth - 48);
7841
- const availH = Math.max(200, container.clientHeight - 72);
7842
- return Math.min(1, Math.min(availW / 960, availH / 540));
8479
+ let fitMode = ctx?.options?.fitMode || "width";
8480
+ let isUserZoomed = false;
8481
+ const calculateFitScale = (mode = fitMode) => {
8482
+ const availW = Math.max(200, container.clientWidth - 32);
8483
+ const availH = Math.max(200, container.clientHeight - 32);
8484
+ if (mode === "page") {
8485
+ return Math.min(2.5, Math.min(availW / 960, availH / 540));
8486
+ }
8487
+ return Math.min(2.5, availW / 960);
7843
8488
  };
7844
8489
  const applyTransform = () => {
8490
+ const cW = 960;
8491
+ const cH = 540;
8492
+ const isRotated90 = rotation % 180 !== 0;
8493
+ const boxW = Math.round((isRotated90 ? cH : cW) * scale);
8494
+ const boxH = Math.round((isRotated90 ? cW : cH) * scale);
8495
+ sizer.style.width = `${boxW}px`;
8496
+ sizer.style.height = `${boxH}px`;
8497
+ slideCard.style.width = `${cW}px`;
8498
+ slideCard.style.height = `${cH}px`;
7845
8499
  slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
8500
+ slideCard.style.transformOrigin = "center center";
7846
8501
  };
8502
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
8503
+ if (!isUserZoomed) {
8504
+ scale = calculateFitScale(fitMode);
8505
+ applyTransform();
8506
+ }
8507
+ }) : null;
8508
+ ro?.observe(container);
7847
8509
  setTimeout(() => {
7848
- scale = calculateFitScale();
8510
+ scale = calculateFitScale(fitMode);
7849
8511
  applyTransform();
7850
8512
  }, 60);
7851
8513
  try {
@@ -7938,6 +8600,7 @@ var PptPlugin = class {
7938
8600
  };
7939
8601
  renderSlide(1);
7940
8602
  const cleanup = () => {
8603
+ ro?.disconnect();
7941
8604
  for (const u of createdBlobUrls) {
7942
8605
  URL.revokeObjectURL(u);
7943
8606
  }
@@ -7948,28 +8611,44 @@ var PptPlugin = class {
7948
8611
  return {
7949
8612
  destroy: cleanup,
7950
8613
  zoomIn: () => {
8614
+ isUserZoomed = true;
7951
8615
  scale += 0.15;
7952
8616
  applyTransform();
7953
8617
  },
7954
8618
  zoomOut: () => {
8619
+ isUserZoomed = true;
7955
8620
  scale = Math.max(0.2, scale - 0.15);
7956
8621
  applyTransform();
7957
8622
  },
7958
8623
  getZoom: () => scale,
7959
8624
  setZoom: (level) => {
8625
+ isUserZoomed = true;
7960
8626
  scale = level;
7961
8627
  applyTransform();
7962
8628
  },
7963
8629
  fitToPage: () => {
7964
- scale = calculateFitScale();
8630
+ isUserZoomed = false;
8631
+ fitMode = "page";
8632
+ scale = calculateFitScale("page");
8633
+ rotation = 0;
8634
+ applyTransform();
8635
+ },
8636
+ fitToWidth: () => {
8637
+ isUserZoomed = false;
8638
+ fitMode = "width";
8639
+ scale = calculateFitScale("width");
7965
8640
  rotation = 0;
7966
8641
  applyTransform();
7967
8642
  },
7968
8643
  resetZoom: () => {
7969
- scale = calculateFitScale();
8644
+ isUserZoomed = false;
8645
+ fitMode = ctx?.options?.fitMode || "width";
8646
+ scale = calculateFitScale(fitMode);
7970
8647
  rotation = 0;
7971
8648
  container.scrollTop = 0;
7972
8649
  container.scrollLeft = 0;
8650
+ ctx.container.scrollTop = 0;
8651
+ ctx.container.scrollLeft = 0;
7973
8652
  applyTransform();
7974
8653
  },
7975
8654
  rotateCW: () => {