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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.js CHANGED
@@ -1057,6 +1057,10 @@ var FilePreviewViewer = class _FilePreviewViewer {
1057
1057
  * Preview a file in the given container element.
1058
1058
  */
1059
1059
  async preview(container, source, options = {}) {
1060
+ options = {
1061
+ ...options,
1062
+ fitMode: options.fitMode ?? "width"
1063
+ };
1060
1064
  this.currentOptions = options;
1061
1065
  this.abort();
1062
1066
  this.abortController = new AbortController();
@@ -1105,8 +1109,8 @@ var FilePreviewViewer = class _FilePreviewViewer {
1105
1109
  this.activeInstance = instance;
1106
1110
  instance.openInSeparateWindow = () => this.openInSeparateWindow();
1107
1111
  instance.toggleThumbnails = () => this.toggleThumbnails();
1108
- if (!instance.resetZoom && instance.fitToPage) {
1109
- instance.resetZoom = () => instance.fitToPage?.();
1112
+ if (!instance.resetZoom) {
1113
+ instance.resetZoom = () => instance.fitToWidth ? instance.fitToWidth() : instance.fitToPage?.();
1110
1114
  }
1111
1115
  this.hideLoading();
1112
1116
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
@@ -1503,7 +1507,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1503
1507
  this.wrapperEl?.classList.toggle("fp-fullscreen-active");
1504
1508
  }
1505
1509
  setTimeout(() => {
1506
- this.activeInstance?.fitToPage?.();
1510
+ this.triggerAutoFit();
1507
1511
  }, 120);
1508
1512
  }
1509
1513
  /**
@@ -1630,18 +1634,29 @@ var FilePreviewViewer = class _FilePreviewViewer {
1630
1634
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl, (isOpen) => {
1631
1635
  this.toolbar?.setActionActive("thumbnails", isOpen);
1632
1636
  setTimeout(() => {
1633
- this.activeInstance?.fitToPage?.();
1637
+ this.triggerAutoFit();
1634
1638
  }, 260);
1635
1639
  });
1636
1640
  this.setupKeyboardShortcuts();
1637
1641
  this.setupDragAndDrop(container, options);
1638
1642
  this.setupResizeAndFullscreenListeners();
1639
1643
  }
1644
+ triggerAutoFit() {
1645
+ if (!this.activeInstance) return;
1646
+ const mode = this.currentOptions?.fitMode ?? "width";
1647
+ if (mode === "width" && this.activeInstance.fitToWidth) {
1648
+ this.activeInstance.fitToWidth();
1649
+ } else if (this.activeInstance.fitToPage) {
1650
+ this.activeInstance.fitToPage();
1651
+ } else if (this.activeInstance.resetZoom) {
1652
+ this.activeInstance.resetZoom();
1653
+ }
1654
+ }
1640
1655
  setupResizeAndFullscreenListeners() {
1641
1656
  if (this.fullscreenHandler) return;
1642
1657
  this.fullscreenHandler = () => {
1643
1658
  setTimeout(() => {
1644
- this.activeInstance?.fitToPage?.();
1659
+ this.triggerAutoFit();
1645
1660
  }, 100);
1646
1661
  };
1647
1662
  document.addEventListener("fullscreenchange", this.fullscreenHandler);
@@ -1649,7 +1664,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1649
1664
  if (typeof ResizeObserver !== "undefined" && this.contentEl) {
1650
1665
  this.resizeObserver = new ResizeObserver(() => {
1651
1666
  if (this.activeInstance && (!this.activeInstance.getZoom || Math.abs((this.activeInstance.getZoom?.() ?? 1) - 1) < 0.05)) {
1652
- this.activeInstance.fitToPage?.();
1667
+ this.triggerAutoFit();
1653
1668
  }
1654
1669
  });
1655
1670
  this.resizeObserver.observe(this.contentEl);
@@ -1677,7 +1692,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1677
1692
  } else if (e.key === "-" || e.key === "_") {
1678
1693
  this.activeInstance.zoomOut?.();
1679
1694
  } else if (e.key === "0") {
1680
- this.activeInstance.fitToPage?.();
1695
+ this.resetZoom();
1681
1696
  } else if (e.key === "r" || e.key === "R") {
1682
1697
  this.activeInstance.rotateCW?.();
1683
1698
  } else if (e.key === "f" || e.key === "F") {
@@ -2085,16 +2100,22 @@ var PdfPlugin = class {
2085
2100
  container.className = "fp-pdf-container";
2086
2101
  container.style.width = "100%";
2087
2102
  container.style.height = "100%";
2088
- container.style.overflowX = "hidden";
2089
- container.style.overflowY = "auto";
2090
- container.style.display = "flex";
2091
- container.style.flexDirection = "column";
2092
- container.style.alignItems = "center";
2093
- container.style.justifyContent = "flex-start";
2094
- container.style.padding = "16px 8px";
2103
+ container.style.overflow = "auto";
2095
2104
  container.style.backgroundColor = "#0f172a";
2096
2105
  container.style.boxSizing = "border-box";
2097
2106
  container.style.position = "relative";
2107
+ const scrollWrapper = document.createElement("div");
2108
+ scrollWrapper.className = "fp-pdf-scroll-wrapper";
2109
+ scrollWrapper.style.minWidth = "100%";
2110
+ scrollWrapper.style.minHeight = "100%";
2111
+ scrollWrapper.style.width = "max-content";
2112
+ scrollWrapper.style.height = "max-content";
2113
+ scrollWrapper.style.display = "flex";
2114
+ scrollWrapper.style.flexDirection = "column";
2115
+ scrollWrapper.style.alignItems = "center";
2116
+ scrollWrapper.style.justifyContent = "flex-start";
2117
+ scrollWrapper.style.padding = "16px 8px";
2118
+ scrollWrapper.style.boxSizing = "border-box";
2098
2119
  const pageCard = document.createElement("div");
2099
2120
  pageCard.className = "fp-pdf-page-card";
2100
2121
  pageCard.style.boxShadow = "0 10px 35px rgba(0, 0, 0, 0.5)";
@@ -2107,7 +2128,8 @@ var PdfPlugin = class {
2107
2128
  pageCard.style.transition = "box-shadow 0.2s ease";
2108
2129
  let canvas = document.createElement("canvas");
2109
2130
  pageCard.appendChild(canvas);
2110
- container.appendChild(pageCard);
2131
+ scrollWrapper.appendChild(pageCard);
2132
+ container.appendChild(scrollWrapper);
2111
2133
  ctx.container.appendChild(container);
2112
2134
  const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
2113
2135
  const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
@@ -2293,7 +2315,7 @@ var PdfPlugin = class {
2293
2315
  renderPage(currentPage);
2294
2316
  },
2295
2317
  resetZoom: () => {
2296
- fitMode = "page";
2318
+ fitMode = ctx?.options?.fitMode || "width";
2297
2319
  zoomScale = 1;
2298
2320
  rotation = 0;
2299
2321
  container.scrollTop = 0;
@@ -2477,6 +2499,16 @@ var MediaPlugin = class {
2477
2499
  instance.resetZoom?.();
2478
2500
  }
2479
2501
  },
2502
+ {
2503
+ id: "fit-width",
2504
+ icon: "fit-width",
2505
+ label: "Fit to Width",
2506
+ type: "button",
2507
+ group: "zoom",
2508
+ execute: () => {
2509
+ instance.fitToWidth?.();
2510
+ }
2511
+ },
2480
2512
  {
2481
2513
  id: "rotate-cw",
2482
2514
  icon: "rotate-cw",
@@ -2573,9 +2605,30 @@ var MediaPlugin = class {
2573
2605
  const isVideo = mimeType.startsWith("video/") || VIDEO_EXTS.includes(ctx.metadata.extension || "");
2574
2606
  const isAudio = mimeType.startsWith("audio/") || AUDIO_EXTS.includes(ctx.metadata.extension || "");
2575
2607
  let url = "";
2608
+ let naturalW = 800;
2609
+ let naturalH = 600;
2576
2610
  if (ctx.metadata.extension === ".svg" || mimeType === "image/svg+xml") {
2577
2611
  const decoder = new TextDecoder("utf-8");
2578
2612
  const svgText = decoder.decode(ctx.buffer);
2613
+ try {
2614
+ const vbMatch = svgText.match(/viewBox=["']\s*([0-9.-]+)\s+([0-9.-]+)\s+([0-9.-]+)\s+([0-9.-]+)\s*["']/i);
2615
+ if (vbMatch) {
2616
+ const vw = parseFloat(vbMatch[3]);
2617
+ const vh = parseFloat(vbMatch[4]);
2618
+ if (vw > 0 && vh > 0) {
2619
+ naturalW = vw;
2620
+ naturalH = vh;
2621
+ }
2622
+ } else {
2623
+ const wMatch = svgText.match(/width=["']([0-9.]+)(?:px)?["']/i);
2624
+ const hMatch = svgText.match(/height=["']([0-9.]+)(?:px)?["']/i);
2625
+ if (wMatch && hMatch) {
2626
+ naturalW = parseFloat(wMatch[1]) || naturalW;
2627
+ naturalH = parseFloat(hMatch[1]) || naturalH;
2628
+ }
2629
+ }
2630
+ } catch {
2631
+ }
2579
2632
  const blob = new Blob([svgText], { type: "image/svg+xml" });
2580
2633
  url = URL.createObjectURL(blob);
2581
2634
  } else {
@@ -2616,45 +2669,150 @@ var MediaPlugin = class {
2616
2669
  element = document.createElement("div");
2617
2670
  element.textContent = "Unsupported media type";
2618
2671
  }
2619
- ctx.container.style.display = "flex";
2620
- ctx.container.style.alignItems = "center";
2621
- ctx.container.style.justifyContent = "center";
2622
- ctx.container.style.overflow = "hidden";
2623
- ctx.container.appendChild(element);
2672
+ const scrollWrapper = document.createElement("div");
2673
+ scrollWrapper.className = "fp-media-scroll-wrapper";
2674
+ scrollWrapper.style.minWidth = "100%";
2675
+ scrollWrapper.style.minHeight = "100%";
2676
+ scrollWrapper.style.width = "max-content";
2677
+ scrollWrapper.style.height = "max-content";
2678
+ scrollWrapper.style.display = "flex";
2679
+ scrollWrapper.style.flexDirection = "column";
2680
+ scrollWrapper.style.alignItems = "center";
2681
+ scrollWrapper.style.padding = "16px";
2682
+ scrollWrapper.style.boxSizing = "border-box";
2683
+ const sizer = document.createElement("div");
2684
+ sizer.className = "fp-media-sizer";
2685
+ sizer.style.position = "relative";
2686
+ sizer.style.flexShrink = "0";
2687
+ sizer.style.display = "flex";
2688
+ sizer.style.justifyContent = "center";
2689
+ sizer.style.alignItems = "center";
2690
+ sizer.style.margin = "auto 0";
2691
+ sizer.appendChild(element);
2692
+ scrollWrapper.appendChild(sizer);
2693
+ ctx.container.style.overflow = "auto";
2694
+ ctx.container.style.padding = "0";
2695
+ ctx.container.innerHTML = "";
2696
+ ctx.container.appendChild(scrollWrapper);
2697
+ let baseW = naturalW;
2698
+ let baseH = naturalH;
2699
+ let fitMode = ctx?.options?.fitMode || "width";
2700
+ let isUserZoomed = false;
2701
+ const updateBaseDimensions = () => {
2702
+ if (!isImage) return;
2703
+ const img = element;
2704
+ if (img.naturalWidth > 0 && img.naturalHeight > 0) {
2705
+ naturalW = img.naturalWidth;
2706
+ naturalH = img.naturalHeight;
2707
+ }
2708
+ const availW = Math.max(100, ctx.container.clientWidth - 32);
2709
+ const availH = Math.max(100, ctx.container.clientHeight - 32);
2710
+ let fitRatio;
2711
+ if (fitMode === "page") {
2712
+ fitRatio = Math.min(availW / naturalW, availH / naturalH);
2713
+ } else {
2714
+ fitRatio = availW / naturalW;
2715
+ }
2716
+ baseW = Math.max(1, Math.round(naturalW * fitRatio));
2717
+ baseH = Math.max(1, Math.round(naturalH * fitRatio));
2718
+ };
2624
2719
  const applyTransform = () => {
2625
- element.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
2720
+ if (isImage) {
2721
+ if (!isUserZoomed) {
2722
+ updateBaseDimensions();
2723
+ }
2724
+ const availH = Math.max(100, ctx.container.clientHeight - 32);
2725
+ const isRotated90 = rotation % 180 !== 0;
2726
+ const boxW = Math.round((isRotated90 ? baseH : baseW) * currentZoom);
2727
+ const boxH = Math.round((isRotated90 ? baseW : baseH) * currentZoom);
2728
+ sizer.style.width = `${boxW}px`;
2729
+ sizer.style.height = `${boxH}px`;
2730
+ sizer.style.margin = boxH < availH ? "auto 0" : "0";
2731
+ const imgW = isRotated90 ? boxH : boxW;
2732
+ const imgH = isRotated90 ? boxW : boxH;
2733
+ element.style.width = `${imgW}px`;
2734
+ element.style.height = `${imgH}px`;
2735
+ element.style.maxWidth = "none";
2736
+ element.style.maxHeight = "none";
2737
+ element.style.transform = rotation ? `rotate(${rotation}deg)` : "none";
2738
+ element.style.transformOrigin = "center center";
2739
+ element.style.flexShrink = "0";
2740
+ } else {
2741
+ element.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
2742
+ }
2626
2743
  };
2744
+ if (isImage) {
2745
+ const img = element;
2746
+ if (img.complete && img.naturalWidth > 0) {
2747
+ updateBaseDimensions();
2748
+ applyTransform();
2749
+ } else {
2750
+ img.onload = () => {
2751
+ updateBaseDimensions();
2752
+ applyTransform();
2753
+ };
2754
+ }
2755
+ }
2756
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
2757
+ if (!isUserZoomed && currentZoom === 1) {
2758
+ updateBaseDimensions();
2759
+ applyTransform();
2760
+ }
2761
+ }) : null;
2762
+ ro?.observe(ctx.container);
2627
2763
  const cleanup = () => {
2764
+ ro?.disconnect();
2628
2765
  if (url) URL.revokeObjectURL(url);
2629
- element.remove();
2766
+ scrollWrapper.remove();
2630
2767
  ctx.container.innerHTML = "";
2631
2768
  };
2632
2769
  ctx.signal.addEventListener("abort", cleanup);
2633
2770
  return {
2634
2771
  destroy: cleanup,
2635
2772
  zoomIn: isImage ? () => {
2636
- currentZoom += 0.1;
2773
+ isUserZoomed = true;
2774
+ currentZoom = Math.min(5, Math.round((currentZoom + 0.15) * 100) / 100);
2637
2775
  applyTransform();
2638
2776
  } : void 0,
2639
2777
  zoomOut: isImage ? () => {
2640
- currentZoom = Math.max(0.1, currentZoom - 0.1);
2778
+ isUserZoomed = true;
2779
+ currentZoom = Math.max(0.1, Math.round((currentZoom - 0.15) * 100) / 100);
2641
2780
  applyTransform();
2642
2781
  } : void 0,
2643
2782
  getZoom: isImage ? () => currentZoom : void 0,
2644
2783
  setZoom: isImage ? (level) => {
2784
+ isUserZoomed = true;
2645
2785
  currentZoom = level;
2646
2786
  applyTransform();
2647
2787
  } : void 0,
2648
2788
  fitToPage: isImage ? () => {
2789
+ isUserZoomed = false;
2790
+ fitMode = "page";
2791
+ currentZoom = 1;
2792
+ rotation = 0;
2793
+ ctx.container.scrollTop = 0;
2794
+ ctx.container.scrollLeft = 0;
2795
+ updateBaseDimensions();
2796
+ applyTransform();
2797
+ } : void 0,
2798
+ fitToWidth: isImage ? () => {
2799
+ isUserZoomed = false;
2800
+ fitMode = "width";
2649
2801
  currentZoom = 1;
2650
2802
  rotation = 0;
2803
+ ctx.container.scrollTop = 0;
2804
+ ctx.container.scrollLeft = 0;
2805
+ updateBaseDimensions();
2651
2806
  applyTransform();
2652
2807
  } : void 0,
2653
2808
  resetZoom: isImage ? () => {
2809
+ isUserZoomed = false;
2810
+ fitMode = ctx?.options?.fitMode || "width";
2654
2811
  currentZoom = 1;
2655
2812
  rotation = 0;
2656
2813
  ctx.container.scrollTop = 0;
2657
2814
  ctx.container.scrollLeft = 0;
2815
+ updateBaseDimensions();
2658
2816
  applyTransform();
2659
2817
  } : void 0,
2660
2818
  rotateCW: isImage || isVideo ? () => {
@@ -2781,6 +2939,14 @@ var DocxPlugin = class {
2781
2939
  group: "zoom",
2782
2940
  execute: () => instance.resetZoom?.()
2783
2941
  },
2942
+ {
2943
+ id: "fit-width",
2944
+ icon: "fit-width",
2945
+ label: "Fit to Width",
2946
+ type: "button",
2947
+ group: "zoom",
2948
+ execute: () => instance.fitToWidth?.()
2949
+ },
2784
2950
  {
2785
2951
  id: "rotate-cw",
2786
2952
  icon: "rotate-cw",
@@ -2819,7 +2985,7 @@ var DocxPlugin = class {
2819
2985
  async render(ctx) {
2820
2986
  const wrapper = document.createElement("div");
2821
2987
  wrapper.className = "fp-docx-wrapper";
2822
- wrapper.style.transformOrigin = "top center";
2988
+ wrapper.style.transformOrigin = "center center";
2823
2989
  wrapper.style.transition = "transform 0.2s ease";
2824
2990
  wrapper.style.padding = "0";
2825
2991
  wrapper.style.maxWidth = "none";
@@ -2829,11 +2995,31 @@ var DocxPlugin = class {
2829
2995
  wrapper.style.flexDirection = "column";
2830
2996
  wrapper.style.alignItems = "center";
2831
2997
  wrapper.style.boxSizing = "border-box";
2832
- ctx.container.style.overflowX = "hidden";
2833
- ctx.container.style.overflowY = "auto";
2834
- ctx.container.style.padding = "16px 8px";
2998
+ wrapper.style.flexShrink = "0";
2999
+ const sizer = document.createElement("div");
3000
+ sizer.className = "fp-docx-sizer";
3001
+ sizer.style.position = "relative";
3002
+ sizer.style.flexShrink = "0";
3003
+ sizer.style.display = "flex";
3004
+ sizer.style.justifyContent = "center";
3005
+ sizer.style.alignItems = "center";
3006
+ const scrollWrapper = document.createElement("div");
3007
+ scrollWrapper.className = "fp-docx-scroll-wrapper";
3008
+ scrollWrapper.style.minWidth = "100%";
3009
+ scrollWrapper.style.minHeight = "100%";
3010
+ scrollWrapper.style.width = "max-content";
3011
+ scrollWrapper.style.height = "max-content";
3012
+ scrollWrapper.style.display = "flex";
3013
+ scrollWrapper.style.justifyContent = "center";
3014
+ scrollWrapper.style.alignItems = "flex-start";
3015
+ scrollWrapper.style.padding = "16px 8px";
3016
+ scrollWrapper.style.boxSizing = "border-box";
3017
+ sizer.appendChild(wrapper);
3018
+ scrollWrapper.appendChild(sizer);
3019
+ ctx.container.style.overflow = "auto";
3020
+ ctx.container.style.padding = "0";
2835
3021
  ctx.container.style.backgroundColor = "#f1f5f9";
2836
- ctx.container.appendChild(wrapper);
3022
+ ctx.container.appendChild(scrollWrapper);
2837
3023
  const styleOverride = document.createElement("style");
2838
3024
  styleOverride.textContent = `
2839
3025
  .fp-docx-wrapper .docx-wrapper {
@@ -2872,8 +3058,8 @@ var DocxPlugin = class {
2872
3058
  renderEndnotes: true,
2873
3059
  useBase64URL: true
2874
3060
  });
2875
- if (!ctx.container.contains(wrapper)) {
2876
- ctx.container.appendChild(wrapper);
3061
+ if (!sizer.contains(wrapper)) {
3062
+ sizer.appendChild(wrapper);
2877
3063
  }
2878
3064
  if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
2879
3065
  renderedSuccessfully = true;
@@ -2910,8 +3096,8 @@ var DocxPlugin = class {
2910
3096
  try {
2911
3097
  wrapper.innerHTML = "";
2912
3098
  this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
2913
- if (!ctx.container.contains(wrapper)) {
2914
- ctx.container.appendChild(wrapper);
3099
+ if (!sizer.contains(wrapper)) {
3100
+ sizer.appendChild(wrapper);
2915
3101
  }
2916
3102
  renderedSuccessfully = true;
2917
3103
  } catch (fallbackErr) {
@@ -2926,8 +3112,8 @@ var DocxPlugin = class {
2926
3112
  </p>
2927
3113
  </div>
2928
3114
  `;
2929
- if (!ctx.container.contains(wrapper)) {
2930
- ctx.container.appendChild(wrapper);
3115
+ if (!sizer.contains(wrapper)) {
3116
+ sizer.appendChild(wrapper);
2931
3117
  }
2932
3118
  }
2933
3119
  }
@@ -3020,31 +3206,40 @@ var DocxPlugin = class {
3020
3206
  }
3021
3207
  scale = 1;
3022
3208
  let rotation = 0;
3023
- let fitMode = "page";
3209
+ let fitMode = ctx?.options?.fitMode || "width";
3024
3210
  let isUserZoomed = false;
3025
- const calculateFitScale = (_mode = fitMode) => {
3211
+ const calculateFitScale = (mode = fitMode) => {
3026
3212
  const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3027
3213
  const elW = activeEl.offsetWidth || 816;
3214
+ const elH = activeEl.offsetHeight || 1056;
3028
3215
  const availW = Math.max(280, ctx.container.clientWidth - 32);
3216
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
3029
3217
  const sW = availW / elW;
3218
+ const sH = availH / elH;
3219
+ if (mode === "page") {
3220
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
3221
+ }
3030
3222
  return Math.max(0.35, Math.min(3, sW));
3031
3223
  };
3032
3224
  const applyTransform = () => {
3033
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3034
- wrapper.style.transformOrigin = "top center";
3035
3225
  const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3226
+ const elW = activeEl.offsetWidth || 816;
3036
3227
  const elH = activeEl.offsetHeight || 1056;
3037
- if (scale > 1) {
3038
- wrapper.style.marginBottom = `${Math.round(elH * scale - elH + 32)}px`;
3039
- } else {
3040
- wrapper.style.marginBottom = "32px";
3041
- }
3228
+ const isRotated90 = rotation % 180 !== 0;
3229
+ const boxW = Math.round((isRotated90 ? elH : elW) * scale);
3230
+ const boxH = Math.round((isRotated90 ? elW : elH) * scale);
3231
+ sizer.style.width = `${boxW}px`;
3232
+ sizer.style.height = `${boxH}px`;
3233
+ wrapper.style.width = `${elW}px`;
3234
+ wrapper.style.height = `${elH}px`;
3235
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3236
+ wrapper.style.transformOrigin = "center center";
3042
3237
  };
3043
3238
  let resizeObserver = null;
3044
3239
  if (typeof ResizeObserver !== "undefined") {
3045
3240
  resizeObserver = new ResizeObserver(() => {
3046
3241
  if (!isUserZoomed) {
3047
- const newFit = calculateFitScale("page");
3242
+ const newFit = calculateFitScale(fitMode);
3048
3243
  if (Math.abs(scale - newFit) > 0.015) {
3049
3244
  scale = newFit;
3050
3245
  applyTransform();
@@ -3054,7 +3249,7 @@ var DocxPlugin = class {
3054
3249
  resizeObserver.observe(ctx.container);
3055
3250
  }
3056
3251
  setTimeout(() => {
3057
- scale = calculateFitScale("page");
3252
+ scale = calculateFitScale(fitMode);
3058
3253
  applyTransform();
3059
3254
  }, 40);
3060
3255
  const cleanup = () => {
@@ -3063,7 +3258,7 @@ var DocxPlugin = class {
3063
3258
  for (const url of createdBlobUrls) {
3064
3259
  URL.revokeObjectURL(url);
3065
3260
  }
3066
- wrapper.remove();
3261
+ scrollWrapper.remove();
3067
3262
  styleOverride.remove();
3068
3263
  ctx.container.innerHTML = "";
3069
3264
  };
@@ -3093,13 +3288,22 @@ var DocxPlugin = class {
3093
3288
  },
3094
3289
  fitToPage: () => {
3095
3290
  isUserZoomed = false;
3291
+ fitMode = "page";
3096
3292
  scale = calculateFitScale("page");
3097
3293
  rotation = 0;
3098
3294
  applyTransform();
3099
3295
  },
3296
+ fitToWidth: () => {
3297
+ isUserZoomed = false;
3298
+ fitMode = "width";
3299
+ scale = calculateFitScale("width");
3300
+ rotation = 0;
3301
+ applyTransform();
3302
+ },
3100
3303
  resetZoom: () => {
3101
3304
  isUserZoomed = false;
3102
- scale = calculateFitScale("page");
3305
+ fitMode = ctx?.options?.fitMode || "width";
3306
+ scale = calculateFitScale(fitMode);
3103
3307
  rotation = 0;
3104
3308
  ctx.container.scrollTop = 0;
3105
3309
  ctx.container.scrollLeft = 0;
@@ -3564,6 +3768,16 @@ var ExcelPlugin = class {
3564
3768
  instance.resetZoom?.();
3565
3769
  }
3566
3770
  },
3771
+ {
3772
+ id: "fit-width",
3773
+ icon: "fit-width",
3774
+ label: "Fit to Width",
3775
+ type: "button",
3776
+ group: "zoom",
3777
+ execute: () => {
3778
+ instance.fitToWidth?.();
3779
+ }
3780
+ },
3567
3781
  {
3568
3782
  id: "download",
3569
3783
  icon: "download",
@@ -3610,7 +3824,7 @@ var ExcelPlugin = class {
3610
3824
  contentArea.style.flex = "1";
3611
3825
  contentArea.style.overflow = "auto";
3612
3826
  contentArea.style.padding = "16px";
3613
- contentArea.style.transformOrigin = "top left";
3827
+ contentArea.style.boxSizing = "border-box";
3614
3828
  const tabsArea = document.createElement("div");
3615
3829
  tabsArea.className = "fp-excel-tabs";
3616
3830
  tabsArea.style.display = "flex";
@@ -3624,18 +3838,35 @@ var ExcelPlugin = class {
3624
3838
  container.appendChild(tabsArea);
3625
3839
  ctx.container.appendChild(container);
3626
3840
  let scale = 1;
3841
+ let isUserZoomed = false;
3627
3842
  let currentSheetIndex = 1;
3628
3843
  let sheetNames = [];
3629
3844
  let wb = null;
3630
3845
  const applyTransform = () => {
3631
- contentArea.style.transform = `scale(${scale})`;
3632
- contentArea.style.transformOrigin = "top left";
3846
+ const sizer = contentArea.querySelector(".fp-excel-sizer");
3847
+ const table = contentArea.querySelector("table");
3848
+ if (!sizer || !table) return;
3849
+ if (!table._baseWidth && table.offsetWidth > 0) {
3850
+ table._baseWidth = table.offsetWidth;
3851
+ table._baseHeight = table.offsetHeight;
3852
+ }
3853
+ const baseW = table._baseWidth || table.offsetWidth || 800;
3854
+ const baseH = table._baseHeight || table.offsetHeight || 600;
3855
+ const scaledW = Math.round(baseW * scale);
3856
+ const scaledH = Math.round(baseH * scale);
3857
+ sizer.style.width = `${scaledW}px`;
3858
+ sizer.style.height = `${scaledH}px`;
3859
+ table.style.position = "absolute";
3860
+ table.style.top = "0";
3861
+ table.style.left = "0";
3862
+ table.style.transform = `scale(${scale})`;
3863
+ table.style.transformOrigin = "top left";
3633
3864
  };
3634
3865
  const calculateFitScale = () => {
3635
3866
  const table = contentArea.querySelector("table");
3636
3867
  if (!table) return 1;
3637
3868
  const availW = Math.max(280, container.clientWidth - 48);
3638
- const tW = table.offsetWidth || 800;
3869
+ const tW = table._baseWidth || table.offsetWidth || 800;
3639
3870
  return Math.max(0.4, Math.min(1, availW / tW));
3640
3871
  };
3641
3872
  try {
@@ -3655,9 +3886,13 @@ var ExcelPlugin = class {
3655
3886
  contentArea.innerHTML = '<div style="padding: 24px; color: #888;">Empty sheet</div>';
3656
3887
  return;
3657
3888
  }
3889
+ const sizer = document.createElement("div");
3890
+ sizer.className = "fp-excel-sizer";
3891
+ sizer.style.position = "relative";
3658
3892
  const html = XLSX.utils.sheet_to_html(ws, { id: "fp-sheet-table", editable: false });
3659
- contentArea.innerHTML = html;
3660
- const table = contentArea.querySelector("table");
3893
+ sizer.innerHTML = html;
3894
+ contentArea.appendChild(sizer);
3895
+ const table = sizer.querySelector("table");
3661
3896
  if (table) {
3662
3897
  table.style.borderCollapse = "collapse";
3663
3898
  table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
@@ -3692,13 +3927,17 @@ var ExcelPlugin = class {
3692
3927
  });
3693
3928
  ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
3694
3929
  requestAnimationFrame(() => {
3695
- scale = calculateFitScale();
3930
+ if (!isUserZoomed) {
3931
+ scale = calculateFitScale();
3932
+ }
3696
3933
  applyTransform();
3697
3934
  });
3698
3935
  };
3699
3936
  const ro = new ResizeObserver(() => {
3700
- scale = calculateFitScale();
3701
- applyTransform();
3937
+ if (!isUserZoomed) {
3938
+ scale = calculateFitScale();
3939
+ applyTransform();
3940
+ }
3702
3941
  });
3703
3942
  ro.observe(container);
3704
3943
  if (sheetNames.length > 0) {
@@ -3735,23 +3974,33 @@ var ExcelPlugin = class {
3735
3974
  return {
3736
3975
  destroy: cleanup,
3737
3976
  zoomIn: () => {
3977
+ isUserZoomed = true;
3738
3978
  scale += 0.15;
3739
3979
  applyTransform();
3740
3980
  },
3741
3981
  zoomOut: () => {
3982
+ isUserZoomed = true;
3742
3983
  scale = Math.max(0.2, scale - 0.15);
3743
3984
  applyTransform();
3744
3985
  },
3745
3986
  getZoom: () => scale,
3746
3987
  setZoom: (level) => {
3988
+ isUserZoomed = true;
3747
3989
  scale = level;
3748
3990
  applyTransform();
3749
3991
  },
3750
3992
  fitToPage: () => {
3993
+ isUserZoomed = false;
3994
+ scale = calculateFitScale();
3995
+ applyTransform();
3996
+ },
3997
+ fitToWidth: () => {
3998
+ isUserZoomed = false;
3751
3999
  scale = calculateFitScale();
3752
4000
  applyTransform();
3753
4001
  },
3754
4002
  resetZoom: () => {
4003
+ isUserZoomed = false;
3755
4004
  scale = calculateFitScale();
3756
4005
  contentArea.scrollTop = 0;
3757
4006
  contentArea.scrollLeft = 0;
@@ -3897,6 +4146,16 @@ var CsvPlugin = class {
3897
4146
  instance.resetZoom?.();
3898
4147
  }
3899
4148
  },
4149
+ {
4150
+ id: "fit-width",
4151
+ icon: "fit-width",
4152
+ label: "Fit to Width",
4153
+ type: "button",
4154
+ group: "zoom",
4155
+ execute: () => {
4156
+ instance.fitToWidth?.();
4157
+ }
4158
+ },
3900
4159
  {
3901
4160
  id: "download",
3902
4161
  icon: "download",
@@ -3983,21 +4242,41 @@ var CsvPlugin = class {
3983
4242
  container.appendChild(tableWrapper);
3984
4243
  ctx.container.appendChild(container);
3985
4244
  let scale = 1;
4245
+ let isUserZoomed = false;
3986
4246
  const calculateFitScale = () => {
3987
4247
  const availW = Math.max(280, container.clientWidth - 48);
3988
- const tW = table.offsetWidth || 800;
4248
+ const tW = table._baseWidth || table.offsetWidth || 800;
3989
4249
  return Math.max(0.4, Math.min(1, availW / tW));
3990
4250
  };
3991
4251
  const applyScale = () => {
3992
- tableWrapper.style.transform = `scale(${scale})`;
4252
+ if (!table._baseWidth && table.offsetWidth > 0) {
4253
+ table._baseWidth = table.offsetWidth;
4254
+ table._baseHeight = table.offsetHeight;
4255
+ }
4256
+ const baseW = table._baseWidth || table.offsetWidth || 800;
4257
+ const baseH = table._baseHeight || table.offsetHeight || 600;
4258
+ const scaledW = Math.round(baseW * scale);
4259
+ const scaledH = Math.round(baseH * scale);
4260
+ tableWrapper.style.position = "relative";
4261
+ tableWrapper.style.width = `${scaledW}px`;
4262
+ tableWrapper.style.height = `${scaledH}px`;
4263
+ table.style.position = "absolute";
4264
+ table.style.top = "0";
4265
+ table.style.left = "0";
4266
+ table.style.transform = `scale(${scale})`;
4267
+ table.style.transformOrigin = "top left";
3993
4268
  };
3994
4269
  requestAnimationFrame(() => {
3995
- scale = calculateFitScale();
4270
+ if (!isUserZoomed) {
4271
+ scale = calculateFitScale();
4272
+ }
3996
4273
  applyScale();
3997
4274
  });
3998
4275
  const ro = new ResizeObserver(() => {
3999
- scale = calculateFitScale();
4000
- applyScale();
4276
+ if (!isUserZoomed) {
4277
+ scale = calculateFitScale();
4278
+ applyScale();
4279
+ }
4001
4280
  });
4002
4281
  ro.observe(container);
4003
4282
  const cleanup = () => {
@@ -4070,23 +4349,33 @@ var CsvPlugin = class {
4070
4349
  }];
4071
4350
  },
4072
4351
  zoomIn: () => {
4352
+ isUserZoomed = true;
4073
4353
  scale += 0.1;
4074
4354
  applyScale();
4075
4355
  },
4076
4356
  zoomOut: () => {
4357
+ isUserZoomed = true;
4077
4358
  scale = Math.max(0.2, scale - 0.1);
4078
4359
  applyScale();
4079
4360
  },
4080
4361
  getZoom: () => scale,
4081
4362
  setZoom: (level) => {
4363
+ isUserZoomed = true;
4082
4364
  scale = level;
4083
4365
  applyScale();
4084
4366
  },
4085
4367
  fitToPage: () => {
4368
+ isUserZoomed = false;
4369
+ scale = calculateFitScale();
4370
+ applyScale();
4371
+ },
4372
+ fitToWidth: () => {
4373
+ isUserZoomed = false;
4086
4374
  scale = calculateFitScale();
4087
4375
  applyScale();
4088
4376
  },
4089
4377
  resetZoom: () => {
4378
+ isUserZoomed = false;
4090
4379
  scale = calculateFitScale();
4091
4380
  container.scrollTop = 0;
4092
4381
  container.scrollLeft = 0;
@@ -4217,6 +4506,16 @@ var CodePlugin = class {
4217
4506
  instance.resetZoom?.();
4218
4507
  }
4219
4508
  },
4509
+ {
4510
+ id: "fit-width",
4511
+ icon: "fit-width",
4512
+ label: "Fit to Width",
4513
+ type: "button",
4514
+ group: "zoom",
4515
+ execute: () => {
4516
+ instance.fitToWidth?.();
4517
+ }
4518
+ },
4220
4519
  {
4221
4520
  id: "copy",
4222
4521
  icon: "copy",
@@ -4322,15 +4621,27 @@ var CodePlugin = class {
4322
4621
  let isUserZoomed = false;
4323
4622
  const pre = document.createElement("pre");
4324
4623
  const code = document.createElement("code");
4624
+ const sizer = document.createElement("div");
4625
+ const scrollWrapper = document.createElement("div");
4325
4626
  if (isTxt) {
4326
- container.style.overflowX = "hidden";
4327
- container.style.overflowY = "auto";
4627
+ container.style.overflow = "auto";
4328
4628
  container.style.backgroundColor = "#f1f5f9";
4329
- container.style.padding = "16px 8px";
4330
- container.style.boxSizing = "border-box";
4331
- container.style.display = "flex";
4332
- container.style.flexDirection = "column";
4333
- container.style.alignItems = "center";
4629
+ scrollWrapper.className = "fp-code-scroll-wrapper";
4630
+ scrollWrapper.style.minWidth = "100%";
4631
+ scrollWrapper.style.minHeight = "100%";
4632
+ scrollWrapper.style.width = "max-content";
4633
+ scrollWrapper.style.height = "max-content";
4634
+ scrollWrapper.style.display = "flex";
4635
+ scrollWrapper.style.justifyContent = "center";
4636
+ scrollWrapper.style.alignItems = "flex-start";
4637
+ scrollWrapper.style.padding = "16px 8px";
4638
+ scrollWrapper.style.boxSizing = "border-box";
4639
+ sizer.className = "fp-code-sizer";
4640
+ sizer.style.position = "relative";
4641
+ sizer.style.flexShrink = "0";
4642
+ sizer.style.display = "flex";
4643
+ sizer.style.justifyContent = "center";
4644
+ sizer.style.alignItems = "center";
4334
4645
  pre.style.margin = "0 auto";
4335
4646
  pre.style.fontFamily = "Consolas, 'Courier New', monospace";
4336
4647
  pre.style.fontSize = "13px";
@@ -4345,8 +4656,9 @@ var CodePlugin = class {
4345
4656
  pre.style.boxSizing = "border-box";
4346
4657
  pre.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
4347
4658
  pre.style.borderRadius = "4px";
4348
- pre.style.transformOrigin = "top center";
4659
+ pre.style.transformOrigin = "center center";
4349
4660
  pre.style.transition = "transform 0.15s ease";
4661
+ pre.style.flexShrink = "0";
4350
4662
  } else {
4351
4663
  container.style.overflow = "auto";
4352
4664
  container.style.backgroundColor = "#1e1e1e";
@@ -4379,7 +4691,13 @@ var CodePlugin = class {
4379
4691
  };
4380
4692
  renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
4381
4693
  pre.appendChild(code);
4382
- container.appendChild(pre);
4694
+ if (isTxt) {
4695
+ sizer.appendChild(pre);
4696
+ scrollWrapper.appendChild(sizer);
4697
+ container.appendChild(scrollWrapper);
4698
+ } else {
4699
+ container.appendChild(pre);
4700
+ }
4383
4701
  const showPage = (pageNum) => {
4384
4702
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4385
4703
  renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
@@ -4396,10 +4714,16 @@ var CodePlugin = class {
4396
4714
  };
4397
4715
  const updateTransform = () => {
4398
4716
  if (isTxt) {
4717
+ const elW = 816;
4718
+ const baseH = pre.offsetHeight || 1056;
4719
+ const isRotated90 = rotation % 180 !== 0;
4720
+ const boxW = Math.round((isRotated90 ? baseH : elW) * zoomLevel);
4721
+ const boxH = Math.round((isRotated90 ? elW : baseH) * zoomLevel);
4722
+ sizer.style.width = `${boxW}px`;
4723
+ sizer.style.height = `${boxH}px`;
4724
+ pre.style.width = `${elW}px`;
4399
4725
  pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
4400
- const scaledH = 1056 * zoomLevel;
4401
- const extraH = Math.max(0, scaledH - 1056);
4402
- pre.style.marginBottom = `${extraH + 32}px`;
4726
+ pre.style.transformOrigin = "center center";
4403
4727
  } else {
4404
4728
  pre.style.transform = `rotate(${rotation}deg)`;
4405
4729
  pre.style.fontSize = `${fontSize}px`;
@@ -4496,6 +4820,13 @@ var CodePlugin = class {
4496
4820
  zoomLevel = isTxt ? calculateTxtFit() : 1;
4497
4821
  updateTransform();
4498
4822
  },
4823
+ fitToWidth: () => {
4824
+ isUserZoomed = false;
4825
+ fontSize = 13;
4826
+ rotation = 0;
4827
+ zoomLevel = isTxt ? calculateTxtFit() : 1;
4828
+ updateTransform();
4829
+ },
4499
4830
  resetZoom: () => {
4500
4831
  isUserZoomed = false;
4501
4832
  fontSize = 13;
@@ -4580,6 +4911,14 @@ var ArchivePlugin = class {
4580
4911
  group: "zoom",
4581
4912
  execute: () => instance.resetZoom?.()
4582
4913
  },
4914
+ {
4915
+ id: "fit-width",
4916
+ icon: "fit-width",
4917
+ label: "Fit to Width",
4918
+ type: "button",
4919
+ group: "zoom",
4920
+ execute: () => instance.fitToWidth?.()
4921
+ },
4583
4922
  {
4584
4923
  id: "download",
4585
4924
  icon: "download",
@@ -4605,16 +4944,56 @@ var ArchivePlugin = class {
4605
4944
  width: 100%;
4606
4945
  height: 100%;
4607
4946
  overflow: auto;
4608
- padding: 24px;
4609
4947
  box-sizing: border-box;
4610
4948
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
4611
- transform-origin: top left;
4612
- transition: transform 0.2s ease;
4613
4949
  `;
4950
+ const scrollWrapper = document.createElement("div");
4951
+ scrollWrapper.className = "fp-archive-scroll-wrapper";
4952
+ scrollWrapper.style.cssText = `
4953
+ min-width: 100%;
4954
+ min-height: 100%;
4955
+ width: max-content;
4956
+ height: max-content;
4957
+ display: flex;
4958
+ justify-content: center;
4959
+ align-items: flex-start;
4960
+ padding: 24px;
4961
+ box-sizing: border-box;
4962
+ `;
4963
+ const sizer = document.createElement("div");
4964
+ sizer.className = "fp-archive-sizer";
4965
+ sizer.style.cssText = `
4966
+ position: relative;
4967
+ flex-shrink: 0;
4968
+ display: flex;
4969
+ justify-content: center;
4970
+ align-items: flex-start;
4971
+ `;
4972
+ const card = document.createElement("div");
4973
+ card.className = "fp-archive-card";
4974
+ card.style.cssText = `
4975
+ width: 900px;
4976
+ box-sizing: border-box;
4977
+ transform-origin: top center;
4978
+ transition: transform 0.15s ease;
4979
+ flex-shrink: 0;
4980
+ `;
4981
+ sizer.appendChild(card);
4982
+ scrollWrapper.appendChild(sizer);
4983
+ wrapper.appendChild(scrollWrapper);
4614
4984
  ctx.container.innerHTML = "";
4615
4985
  ctx.container.style.overflow = "auto";
4616
4986
  ctx.container.appendChild(wrapper);
4617
4987
  let scale = 1;
4988
+ const applyTransform = () => {
4989
+ const boxW = Math.round(900 * scale);
4990
+ const cardH = card.offsetHeight || 600;
4991
+ const boxH = Math.round(cardH * scale);
4992
+ sizer.style.width = `${boxW}px`;
4993
+ sizer.style.height = `${boxH}px`;
4994
+ card.style.transform = `scale(${scale})`;
4995
+ card.style.transformOrigin = "top center";
4996
+ };
4618
4997
  const entries = await new Promise((resolve, reject) => {
4619
4998
  unzip(new Uint8Array(ctx.buffer), (err, unzipped) => {
4620
4999
  if (err) {
@@ -4638,7 +5017,7 @@ var ArchivePlugin = class {
4638
5017
  });
4639
5018
  const totalUncompressedSize = entries.reduce((acc, e) => acc + e.size, 0);
4640
5019
  const renderUI = (filteredEntries) => {
4641
- wrapper.innerHTML = `
5020
+ card.innerHTML = `
4642
5021
  <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;">
4643
5022
  <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;">
4644
5023
  <div>
@@ -4668,7 +5047,7 @@ var ArchivePlugin = class {
4668
5047
  </div>
4669
5048
  </div>
4670
5049
  `;
4671
- const tbody = wrapper.querySelector("#fp-archive-body");
5050
+ const tbody = card.querySelector("#fp-archive-body");
4672
5051
  if (filteredEntries.length === 0) {
4673
5052
  tbody.innerHTML = `<tr><td colspan="3" style="padding: 24px; text-align: center; color: #9ca3af;">No matching files found</td></tr>`;
4674
5053
  } else {
@@ -4697,19 +5076,20 @@ var ArchivePlugin = class {
4697
5076
  }
4698
5077
  });
4699
5078
  });
4700
- const searchInput = wrapper.querySelector("#fp-archive-search");
5079
+ const searchInput = card.querySelector("#fp-archive-search");
4701
5080
  if (searchInput) {
4702
5081
  searchInput.addEventListener("input", () => {
4703
5082
  const q = searchInput.value.toLowerCase().trim();
4704
5083
  const filtered = q ? entries.filter((e) => e.path.toLowerCase().includes(q)) : entries;
4705
5084
  renderUI(filtered);
4706
- const newSearch = wrapper.querySelector("#fp-archive-search");
5085
+ const newSearch = card.querySelector("#fp-archive-search");
4707
5086
  if (newSearch) {
4708
5087
  newSearch.value = q;
4709
5088
  newSearch.focus();
4710
5089
  }
4711
5090
  });
4712
5091
  }
5092
+ applyTransform();
4713
5093
  };
4714
5094
  renderUI(entries);
4715
5095
  const cleanup = () => {
@@ -4721,26 +5101,30 @@ var ArchivePlugin = class {
4721
5101
  destroy: cleanup,
4722
5102
  zoomIn: () => {
4723
5103
  scale += 0.1;
4724
- wrapper.style.transform = `scale(${scale})`;
5104
+ applyTransform();
4725
5105
  },
4726
5106
  zoomOut: () => {
4727
5107
  scale = Math.max(0.3, scale - 0.1);
4728
- wrapper.style.transform = `scale(${scale})`;
5108
+ applyTransform();
4729
5109
  },
4730
5110
  getZoom: () => scale,
4731
5111
  setZoom: (level) => {
4732
5112
  scale = level;
4733
- wrapper.style.transform = `scale(${scale})`;
5113
+ applyTransform();
4734
5114
  },
4735
5115
  fitToPage: () => {
4736
5116
  scale = 1;
4737
- wrapper.style.transform = "scale(1)";
5117
+ applyTransform();
5118
+ },
5119
+ fitToWidth: () => {
5120
+ scale = 1;
5121
+ applyTransform();
4738
5122
  },
4739
5123
  resetZoom: () => {
4740
5124
  scale = 1;
4741
- wrapper.style.transform = "scale(1)";
4742
- ctx.container.scrollTop = 0;
4743
- ctx.container.scrollLeft = 0;
5125
+ wrapper.scrollTop = 0;
5126
+ wrapper.scrollLeft = 0;
5127
+ applyTransform();
4744
5128
  },
4745
5129
  download: () => {
4746
5130
  downloadFile(ctx.buffer, ctx.metadata.name || "archive.zip", "application/zip");
@@ -4800,6 +5184,14 @@ var MarkdownPlugin = class {
4800
5184
  group: "zoom",
4801
5185
  execute: () => instance.resetZoom?.()
4802
5186
  },
5187
+ {
5188
+ id: "fit-width",
5189
+ icon: "fit-width",
5190
+ label: "Fit to Width",
5191
+ type: "button",
5192
+ group: "zoom",
5193
+ execute: () => instance.fitToWidth?.()
5194
+ },
4803
5195
  {
4804
5196
  id: "copy",
4805
5197
  icon: "copy",
@@ -4953,6 +5345,10 @@ var MarkdownPlugin = class {
4953
5345
  fontSize = 15;
4954
5346
  wrapper.style.fontSize = "15px";
4955
5347
  },
5348
+ fitToWidth: () => {
5349
+ fontSize = 15;
5350
+ wrapper.style.fontSize = "15px";
5351
+ },
4956
5352
  resetZoom: () => {
4957
5353
  fontSize = 15;
4958
5354
  wrapper.style.fontSize = "15px";
@@ -5060,6 +5456,14 @@ var PptxPlugin = class {
5060
5456
  group: "zoom",
5061
5457
  execute: () => instance.resetZoom?.()
5062
5458
  },
5459
+ {
5460
+ id: "fit-width",
5461
+ icon: "fit-width",
5462
+ label: "Fit to Width",
5463
+ type: "button",
5464
+ group: "zoom",
5465
+ execute: () => instance.fitToWidth?.()
5466
+ },
5063
5467
  {
5064
5468
  id: "rotate-cw",
5065
5469
  icon: "rotate-cw",
@@ -5099,12 +5503,30 @@ var PptxPlugin = class {
5099
5503
  width: 100%;
5100
5504
  height: 100%;
5101
5505
  overflow: auto;
5506
+ box-sizing: border-box;
5507
+ background: var(--fp-bg-canvas, #525659);
5508
+ `;
5509
+ const scrollWrapper = document.createElement("div");
5510
+ scrollWrapper.className = "fp-pptx-scroll-wrapper";
5511
+ scrollWrapper.style.cssText = `
5512
+ min-width: 100%;
5513
+ min-height: 100%;
5514
+ width: max-content;
5515
+ height: max-content;
5102
5516
  display: flex;
5103
5517
  justify-content: center;
5104
- align-items: flex-start;
5518
+ align-items: center;
5105
5519
  padding: 24px;
5106
5520
  box-sizing: border-box;
5107
- background: var(--fp-bg-canvas, #525659);
5521
+ `;
5522
+ const sizer = document.createElement("div");
5523
+ sizer.className = "fp-pptx-sizer";
5524
+ sizer.style.cssText = `
5525
+ position: relative;
5526
+ flex-shrink: 0;
5527
+ display: flex;
5528
+ justify-content: center;
5529
+ align-items: center;
5108
5530
  `;
5109
5531
  const slideContainer = document.createElement("div");
5110
5532
  slideContainer.className = "fp-slide-container";
@@ -5113,39 +5535,65 @@ var PptxPlugin = class {
5113
5535
  border-radius: 4px;
5114
5536
  overflow: hidden;
5115
5537
  background: #ffffff;
5116
- transform-origin: top center;
5538
+ transform-origin: center center;
5117
5539
  transition: transform 0.2s ease;
5118
- max-width: calc(100% - 32px);
5119
- max-height: calc(100% - 48px);
5540
+ flex-shrink: 0;
5120
5541
  `;
5121
5542
  const canvas = document.createElement("canvas");
5122
5543
  slideContainer.appendChild(canvas);
5123
- wrapper.appendChild(slideContainer);
5544
+ sizer.appendChild(slideContainer);
5545
+ scrollWrapper.appendChild(sizer);
5546
+ wrapper.appendChild(scrollWrapper);
5124
5547
  ctx.container.innerHTML = "";
5125
5548
  ctx.container.style.overflow = "auto";
5126
5549
  ctx.container.appendChild(wrapper);
5127
- const calculateFitScale = () => {
5128
- const availW = Math.max(200, ctx.container.clientWidth - 48);
5129
- const availH = Math.max(200, ctx.container.clientHeight - 72);
5550
+ let fitMode = ctx?.options?.fitMode || "width";
5551
+ let isUserZoomed = false;
5552
+ const calculateFitScale = (mode = fitMode) => {
5553
+ const availW = Math.max(200, ctx.container.clientWidth - 32);
5554
+ const availH = Math.max(200, ctx.container.clientHeight - 32);
5130
5555
  const cW = canvas.offsetWidth || 1280;
5131
5556
  const cH = canvas.offsetHeight || 720;
5132
- return Math.min(1, Math.min(availW / cW, availH / cH));
5557
+ if (mode === "page") {
5558
+ return Math.min(2.5, Math.min(availW / cW, availH / cH));
5559
+ }
5560
+ return Math.min(2.5, availW / cW);
5133
5561
  };
5134
5562
  const applyTransform = () => {
5563
+ const cW = canvas.offsetWidth || 1280;
5564
+ const cH = canvas.offsetHeight || 720;
5565
+ const isRotated90 = rotation % 180 !== 0;
5566
+ const boxW = Math.round((isRotated90 ? cH : cW) * scale);
5567
+ const boxH = Math.round((isRotated90 ? cW : cH) * scale);
5568
+ sizer.style.width = `${boxW}px`;
5569
+ sizer.style.height = `${boxH}px`;
5570
+ slideContainer.style.width = `${cW}px`;
5571
+ slideContainer.style.height = `${cH}px`;
5135
5572
  slideContainer.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5573
+ slideContainer.style.transformOrigin = "center center";
5136
5574
  };
5137
5575
  const renderCurrentSlide = async () => {
5138
5576
  try {
5139
5577
  await renderer.renderSlide(currentSlide - 1, canvas, 1280);
5140
5578
  ctx.emit("page-change", { page: currentSlide, totalPages: slideCount });
5141
- scale = calculateFitScale();
5579
+ if (!isUserZoomed) {
5580
+ scale = calculateFitScale(fitMode);
5581
+ }
5142
5582
  applyTransform();
5143
5583
  } catch (err) {
5144
5584
  console.error("[PptxPlugin] Failed to render slide:", err);
5145
5585
  }
5146
5586
  };
5147
5587
  await renderCurrentSlide();
5588
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5589
+ if (!isUserZoomed) {
5590
+ scale = calculateFitScale(fitMode);
5591
+ applyTransform();
5592
+ }
5593
+ }) : null;
5594
+ ro?.observe(ctx.container);
5148
5595
  const cleanup = () => {
5596
+ ro?.disconnect();
5149
5597
  renderer.destroy();
5150
5598
  wrapper.remove();
5151
5599
  ctx.container.innerHTML = "";
@@ -5162,28 +5610,44 @@ var PptxPlugin = class {
5162
5610
  getPageCount: () => slideCount,
5163
5611
  getCurrentPage: () => currentSlide,
5164
5612
  zoomIn: () => {
5613
+ isUserZoomed = true;
5165
5614
  scale += 0.15;
5166
5615
  applyTransform();
5167
5616
  },
5168
5617
  zoomOut: () => {
5618
+ isUserZoomed = true;
5169
5619
  scale = Math.max(0.2, scale - 0.15);
5170
5620
  applyTransform();
5171
5621
  },
5172
5622
  getZoom: () => scale,
5173
5623
  setZoom: (level) => {
5624
+ isUserZoomed = true;
5174
5625
  scale = level;
5175
5626
  applyTransform();
5176
5627
  },
5177
5628
  fitToPage: () => {
5178
- scale = calculateFitScale();
5629
+ isUserZoomed = false;
5630
+ fitMode = "page";
5631
+ scale = calculateFitScale("page");
5632
+ rotation = 0;
5633
+ applyTransform();
5634
+ },
5635
+ fitToWidth: () => {
5636
+ isUserZoomed = false;
5637
+ fitMode = "width";
5638
+ scale = calculateFitScale("width");
5179
5639
  rotation = 0;
5180
5640
  applyTransform();
5181
5641
  },
5182
5642
  resetZoom: () => {
5183
- scale = calculateFitScale();
5643
+ isUserZoomed = false;
5644
+ fitMode = ctx?.options?.fitMode || "width";
5645
+ scale = calculateFitScale(fitMode);
5184
5646
  rotation = 0;
5185
5647
  wrapper.scrollTop = 0;
5186
5648
  wrapper.scrollLeft = 0;
5649
+ ctx.container.scrollTop = 0;
5650
+ ctx.container.scrollLeft = 0;
5187
5651
  applyTransform();
5188
5652
  },
5189
5653
  rotateCW: () => {
@@ -5628,6 +6092,14 @@ var RtfPlugin = class {
5628
6092
  group: "zoom",
5629
6093
  execute: () => instance.resetZoom?.()
5630
6094
  },
6095
+ {
6096
+ id: "fit-width",
6097
+ icon: "fit-width",
6098
+ label: "Fit to Width",
6099
+ type: "button",
6100
+ group: "zoom",
6101
+ execute: () => instance.fitToWidth?.()
6102
+ },
5631
6103
  {
5632
6104
  id: "rotate-cw",
5633
6105
  icon: "rotate-cw",
@@ -5738,13 +6210,33 @@ var RtfPlugin = class {
5738
6210
  wrapper.style.flexDirection = "column";
5739
6211
  wrapper.style.alignItems = "center";
5740
6212
  wrapper.style.backgroundColor = "transparent";
5741
- wrapper.style.transformOrigin = "top center";
6213
+ wrapper.style.transformOrigin = "center center";
5742
6214
  wrapper.style.transition = "transform 0.15s ease";
5743
- ctx.container.style.overflowX = "hidden";
5744
- ctx.container.style.overflowY = "auto";
5745
- ctx.container.style.padding = "16px 8px";
6215
+ wrapper.style.flexShrink = "0";
6216
+ const sizer = document.createElement("div");
6217
+ sizer.className = "fp-rtf-sizer";
6218
+ sizer.style.position = "relative";
6219
+ sizer.style.flexShrink = "0";
6220
+ sizer.style.display = "flex";
6221
+ sizer.style.justifyContent = "center";
6222
+ sizer.style.alignItems = "center";
6223
+ const scrollWrapper = document.createElement("div");
6224
+ scrollWrapper.className = "fp-rtf-scroll-wrapper";
6225
+ scrollWrapper.style.minWidth = "100%";
6226
+ scrollWrapper.style.minHeight = "100%";
6227
+ scrollWrapper.style.width = "max-content";
6228
+ scrollWrapper.style.height = "max-content";
6229
+ scrollWrapper.style.display = "flex";
6230
+ scrollWrapper.style.justifyContent = "center";
6231
+ scrollWrapper.style.alignItems = "flex-start";
6232
+ scrollWrapper.style.padding = "16px 8px";
6233
+ scrollWrapper.style.boxSizing = "border-box";
6234
+ sizer.appendChild(wrapper);
6235
+ scrollWrapper.appendChild(sizer);
6236
+ ctx.container.style.overflow = "auto";
6237
+ ctx.container.style.padding = "0";
5746
6238
  ctx.container.style.backgroundColor = "#f1f5f9";
5747
- ctx.container.appendChild(wrapper);
6239
+ ctx.container.appendChild(scrollWrapper);
5748
6240
  let scale = 1;
5749
6241
  let rotation = 0;
5750
6242
  let pageElements = [];
@@ -5763,12 +6255,17 @@ var RtfPlugin = class {
5763
6255
  return Math.max(0.4, Math.min(3, sW));
5764
6256
  };
5765
6257
  const applyTransform = () => {
5766
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5767
- wrapper.style.transformOrigin = "top center";
6258
+ const elW = pageWidth;
5768
6259
  const baseH = pageHeight;
5769
- const scaledH = baseH * scale;
5770
- const extraH = Math.max(0, scaledH - baseH);
5771
- wrapper.style.marginBottom = `${extraH + 32}px`;
6260
+ const isRotated90 = rotation % 180 !== 0;
6261
+ const boxW = Math.round((isRotated90 ? baseH : elW) * scale);
6262
+ const boxH = Math.round((isRotated90 ? elW : baseH) * scale);
6263
+ sizer.style.width = `${boxW}px`;
6264
+ sizer.style.height = `${boxH}px`;
6265
+ wrapper.style.width = `${elW}px`;
6266
+ wrapper.style.height = `${baseH}px`;
6267
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6268
+ wrapper.style.transformOrigin = "center center";
5772
6269
  };
5773
6270
  const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5774
6271
  if (!isUserZoomed) {
@@ -6039,7 +6536,7 @@ var RtfPlugin = class {
6039
6536
  }
6040
6537
  const cleanup = () => {
6041
6538
  resizeObserver?.disconnect();
6042
- wrapper.remove();
6539
+ scrollWrapper.remove();
6043
6540
  ctx.container.innerHTML = "";
6044
6541
  };
6045
6542
  ctx.signal.addEventListener("abort", cleanup);
@@ -6091,6 +6588,13 @@ var RtfPlugin = class {
6091
6588
  applyTransform();
6092
6589
  },
6093
6590
  fitToPage: () => {
6591
+ isUserZoomed = false;
6592
+ fitMode = "page";
6593
+ scale = calculateFitScale("page");
6594
+ rotation = 0;
6595
+ applyTransform();
6596
+ },
6597
+ fitToWidth: () => {
6094
6598
  isUserZoomed = false;
6095
6599
  fitMode = "width";
6096
6600
  scale = calculateFitScale("width");
@@ -6099,8 +6603,8 @@ var RtfPlugin = class {
6099
6603
  },
6100
6604
  resetZoom: () => {
6101
6605
  isUserZoomed = false;
6102
- fitMode = "width";
6103
- scale = calculateFitScale("width");
6606
+ fitMode = ctx?.options?.fitMode || "width";
6607
+ scale = calculateFitScale(fitMode);
6104
6608
  rotation = 0;
6105
6609
  ctx.container.scrollTop = 0;
6106
6610
  ctx.container.scrollLeft = 0;
@@ -6181,6 +6685,14 @@ var HtmlPreviewPlugin = class {
6181
6685
  group: "zoom",
6182
6686
  execute: () => instance.resetZoom?.()
6183
6687
  },
6688
+ {
6689
+ id: "fit-width",
6690
+ icon: "fit-width",
6691
+ label: "Fit to Width",
6692
+ type: "button",
6693
+ group: "zoom",
6694
+ execute: () => instance.fitToWidth?.()
6695
+ },
6184
6696
  {
6185
6697
  id: "copy",
6186
6698
  icon: "copy",
@@ -6214,23 +6726,44 @@ var HtmlPreviewPlugin = class {
6214
6726
  ADD_TAGS: ["style", "link"],
6215
6727
  ADD_ATTR: ["target", "rel"]
6216
6728
  });
6729
+ const sizer = document.createElement("div");
6730
+ sizer.className = "fp-html-sizer";
6731
+ sizer.style.position = "relative";
6217
6732
  const iframe = document.createElement("iframe");
6218
6733
  iframe.className = "fp-html-iframe";
6219
- iframe.style.width = "100%";
6220
- iframe.style.height = "100%";
6221
6734
  iframe.style.border = "none";
6222
6735
  iframe.style.backgroundColor = "#ffffff";
6223
6736
  iframe.style.transformOrigin = "top left";
6224
6737
  iframe.style.transition = "transform 0.2s ease";
6225
6738
  iframe.sandbox.add("allow-same-origin");
6739
+ sizer.appendChild(iframe);
6226
6740
  ctx.container.style.overflow = "auto";
6227
6741
  ctx.container.style.width = "100%";
6228
6742
  ctx.container.style.height = "100%";
6229
- ctx.container.appendChild(iframe);
6743
+ ctx.container.innerHTML = "";
6744
+ ctx.container.appendChild(sizer);
6230
6745
  iframe.srcdoc = sanitized;
6231
6746
  let scale = 1;
6747
+ const applyTransform = () => {
6748
+ const baseW = Math.max(600, ctx.container.clientWidth || 800);
6749
+ const baseH = Math.max(400, ctx.container.clientHeight || 600);
6750
+ const scaledW = Math.round(baseW * scale);
6751
+ const scaledH = Math.round(baseH * scale);
6752
+ sizer.style.width = `${scaledW}px`;
6753
+ sizer.style.height = `${scaledH}px`;
6754
+ iframe.style.position = "absolute";
6755
+ iframe.style.top = "0";
6756
+ iframe.style.left = "0";
6757
+ iframe.style.width = `${baseW}px`;
6758
+ iframe.style.height = `${baseH}px`;
6759
+ iframe.style.transform = `scale(${scale})`;
6760
+ iframe.style.transformOrigin = "top left";
6761
+ };
6762
+ requestAnimationFrame(() => {
6763
+ applyTransform();
6764
+ });
6232
6765
  const cleanup = () => {
6233
- iframe.remove();
6766
+ sizer.remove();
6234
6767
  ctx.container.innerHTML = "";
6235
6768
  };
6236
6769
  ctx.signal.addEventListener("abort", cleanup);
@@ -6238,26 +6771,30 @@ var HtmlPreviewPlugin = class {
6238
6771
  destroy: cleanup,
6239
6772
  zoomIn: () => {
6240
6773
  scale += 0.1;
6241
- iframe.style.transform = `scale(${scale})`;
6774
+ applyTransform();
6242
6775
  },
6243
6776
  zoomOut: () => {
6244
6777
  scale = Math.max(0.2, scale - 0.1);
6245
- iframe.style.transform = `scale(${scale})`;
6778
+ applyTransform();
6246
6779
  },
6247
6780
  getZoom: () => scale,
6248
6781
  setZoom: (level) => {
6249
6782
  scale = level;
6250
- iframe.style.transform = `scale(${scale})`;
6783
+ applyTransform();
6251
6784
  },
6252
6785
  fitToPage: () => {
6253
6786
  scale = 1;
6254
- iframe.style.transform = "scale(1)";
6787
+ applyTransform();
6788
+ },
6789
+ fitToWidth: () => {
6790
+ scale = 1;
6791
+ applyTransform();
6255
6792
  },
6256
6793
  resetZoom: () => {
6257
6794
  scale = 1;
6258
- iframe.style.transform = "scale(1)";
6259
6795
  ctx.container.scrollTop = 0;
6260
6796
  ctx.container.scrollLeft = 0;
6797
+ applyTransform();
6261
6798
  },
6262
6799
  copy: () => {
6263
6800
  navigator.clipboard.writeText(rawHtml);
@@ -6367,6 +6904,14 @@ var OpenDocumentPlugin = class {
6367
6904
  group: "zoom",
6368
6905
  execute: () => instance.resetZoom?.()
6369
6906
  },
6907
+ {
6908
+ id: "fit-width",
6909
+ icon: "fit-width",
6910
+ label: "Fit to Width",
6911
+ type: "button",
6912
+ group: "zoom",
6913
+ execute: () => instance.fitToWidth?.()
6914
+ },
6370
6915
  {
6371
6916
  id: "rotate-cw",
6372
6917
  icon: "rotate-cw",
@@ -6432,10 +6977,26 @@ var OpenDocumentPlugin = class {
6432
6977
  container.className = "fp-odf-container";
6433
6978
  container.style.width = "100%";
6434
6979
  container.style.height = "100%";
6435
- container.style.overflowX = "hidden";
6436
- container.style.overflowY = "auto";
6437
- container.style.padding = "16px 8px";
6980
+ container.style.overflow = "auto";
6438
6981
  container.style.backgroundColor = "#f1f5f9";
6982
+ const scrollWrapper = document.createElement("div");
6983
+ scrollWrapper.className = "fp-odf-scroll-wrapper";
6984
+ scrollWrapper.style.minWidth = "100%";
6985
+ scrollWrapper.style.minHeight = "100%";
6986
+ scrollWrapper.style.width = "max-content";
6987
+ scrollWrapper.style.height = "max-content";
6988
+ scrollWrapper.style.display = "flex";
6989
+ scrollWrapper.style.justifyContent = "center";
6990
+ scrollWrapper.style.alignItems = "flex-start";
6991
+ scrollWrapper.style.padding = "16px 8px";
6992
+ scrollWrapper.style.boxSizing = "border-box";
6993
+ const sizer = document.createElement("div");
6994
+ sizer.className = "fp-odf-sizer";
6995
+ sizer.style.position = "relative";
6996
+ sizer.style.flexShrink = "0";
6997
+ sizer.style.display = "flex";
6998
+ sizer.style.justifyContent = "center";
6999
+ sizer.style.alignItems = "center";
6439
7000
  const wrapper = document.createElement("div");
6440
7001
  wrapper.className = "fp-odf-wrapper";
6441
7002
  wrapper.style.margin = "0 auto";
@@ -6444,9 +7005,12 @@ var OpenDocumentPlugin = class {
6444
7005
  wrapper.style.backgroundColor = "#ffffff";
6445
7006
  wrapper.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
6446
7007
  wrapper.style.borderRadius = "4px";
6447
- wrapper.style.transformOrigin = "top center";
7008
+ wrapper.style.transformOrigin = "center center";
6448
7009
  wrapper.style.transition = "transform 0.15s ease";
6449
- container.appendChild(wrapper);
7010
+ wrapper.style.flexShrink = "0";
7011
+ sizer.appendChild(wrapper);
7012
+ scrollWrapper.appendChild(sizer);
7013
+ container.appendChild(scrollWrapper);
6450
7014
  ctx.container.appendChild(container);
6451
7015
  let scale = 1;
6452
7016
  let rotation = 0;
@@ -6464,7 +7028,10 @@ var OpenDocumentPlugin = class {
6464
7028
  const sW = availW / elW;
6465
7029
  const sH = availH / (isPresentation ? 540 : singlePageH);
6466
7030
  if (isPresentation) {
6467
- return Math.min(2.5, Math.min(sW, sH));
7031
+ if (mode === "page") {
7032
+ return Math.min(2.5, Math.min(sW, sH));
7033
+ }
7034
+ return Math.min(2.5, sW);
6468
7035
  }
6469
7036
  if (mode === "page") {
6470
7037
  return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
@@ -6472,13 +7039,18 @@ var OpenDocumentPlugin = class {
6472
7039
  return Math.max(0.4, Math.min(3, sW));
6473
7040
  };
6474
7041
  const applyTransform = () => {
6475
- wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6476
- wrapper.style.transformOrigin = "top center";
6477
7042
  const activeSlide = slides[currentPage - 1];
7043
+ const elW = isPresentation ? 960 : 816;
6478
7044
  const baseH = activeSlide?.offsetHeight || wrapper.offsetHeight || 1056;
6479
- const scaledH = baseH * scale;
6480
- const extraH = Math.max(0, scaledH - baseH);
6481
- wrapper.style.marginBottom = `${extraH + 32}px`;
7045
+ const isRotated90 = rotation % 180 !== 0;
7046
+ const boxW = Math.round((isRotated90 ? baseH : elW) * scale);
7047
+ const boxH = Math.round((isRotated90 ? elW : baseH) * scale);
7048
+ sizer.style.width = `${boxW}px`;
7049
+ sizer.style.height = `${boxH}px`;
7050
+ wrapper.style.width = `${elW}px`;
7051
+ wrapper.style.height = `${baseH}px`;
7052
+ wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7053
+ wrapper.style.transformOrigin = "center center";
6482
7054
  };
6483
7055
  const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6484
7056
  if (!isUserZoomed) {
@@ -6628,6 +7200,13 @@ var OpenDocumentPlugin = class {
6628
7200
  applyTransform();
6629
7201
  },
6630
7202
  fitToPage: () => {
7203
+ isUserZoomed = false;
7204
+ fitMode = "page";
7205
+ scale = calculateFitScale("page");
7206
+ rotation = 0;
7207
+ applyTransform();
7208
+ },
7209
+ fitToWidth: () => {
6631
7210
  isUserZoomed = false;
6632
7211
  fitMode = "width";
6633
7212
  scale = calculateFitScale("width");
@@ -6636,8 +7215,8 @@ var OpenDocumentPlugin = class {
6636
7215
  },
6637
7216
  resetZoom: () => {
6638
7217
  isUserZoomed = false;
6639
- fitMode = "width";
6640
- scale = calculateFitScale("width");
7218
+ fitMode = ctx?.options?.fitMode || "width";
7219
+ scale = calculateFitScale(fitMode);
6641
7220
  rotation = 0;
6642
7221
  container.scrollTop = 0;
6643
7222
  container.scrollLeft = 0;
@@ -6994,6 +7573,14 @@ var DocPlugin = class {
6994
7573
  group: "zoom",
6995
7574
  execute: () => instance.resetZoom?.()
6996
7575
  },
7576
+ {
7577
+ id: "fit-width",
7578
+ icon: "fit-width",
7579
+ label: "Fit to Width",
7580
+ type: "button",
7581
+ group: "zoom",
7582
+ execute: () => instance.fitToWidth?.()
7583
+ },
6997
7584
  {
6998
7585
  id: "rotate-cw",
6999
7586
  icon: "rotate-cw",
@@ -7042,13 +7629,28 @@ var DocPlugin = class {
7042
7629
  container.className = "fp-doc-container";
7043
7630
  container.style.width = "100%";
7044
7631
  container.style.height = "100%";
7045
- container.style.overflowX = "hidden";
7046
- container.style.overflowY = "auto";
7047
- container.style.padding = "16px 8px";
7632
+ container.style.overflow = "auto";
7048
7633
  container.style.backgroundColor = "#f1f5f9";
7049
- container.style.display = "flex";
7050
- container.style.justifyContent = "center";
7051
- container.style.alignItems = "flex-start";
7634
+ const scrollWrapper = document.createElement("div");
7635
+ scrollWrapper.className = "fp-doc-scroll-wrapper";
7636
+ scrollWrapper.style.minWidth = "100%";
7637
+ scrollWrapper.style.minHeight = "100%";
7638
+ scrollWrapper.style.width = "max-content";
7639
+ scrollWrapper.style.height = "max-content";
7640
+ scrollWrapper.style.display = "flex";
7641
+ scrollWrapper.style.justifyContent = "center";
7642
+ scrollWrapper.style.alignItems = "flex-start";
7643
+ scrollWrapper.style.padding = "16px 8px";
7644
+ scrollWrapper.style.boxSizing = "border-box";
7645
+ const sizer = document.createElement("div");
7646
+ sizer.className = "fp-doc-sizer";
7647
+ sizer.style.position = "relative";
7648
+ sizer.style.flexShrink = "0";
7649
+ sizer.style.display = "flex";
7650
+ sizer.style.justifyContent = "center";
7651
+ sizer.style.alignItems = "center";
7652
+ scrollWrapper.appendChild(sizer);
7653
+ container.appendChild(scrollWrapper);
7052
7654
  ctx.container.appendChild(container);
7053
7655
  let scale = 1;
7054
7656
  let extractedRawText = "";
@@ -7096,10 +7698,11 @@ var DocPlugin = class {
7096
7698
  pageCard.style.padding = "72px 56px";
7097
7699
  pageCard.style.boxSizing = "border-box";
7098
7700
  pageCard.style.display = i === 0 ? "block" : "none";
7099
- pageCard.style.transformOrigin = "top center";
7701
+ pageCard.style.transformOrigin = "center center";
7100
7702
  pageCard.style.transition = "transform 0.15s ease";
7101
7703
  pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
7102
7704
  pageCard.style.color = "#1e293b";
7705
+ pageCard.style.flexShrink = "0";
7103
7706
  if (isFallback && i === 0) {
7104
7707
  pageCard.innerHTML = `
7105
7708
  <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
@@ -7111,18 +7714,23 @@ var DocPlugin = class {
7111
7714
  } else {
7112
7715
  pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
7113
7716
  }
7114
- container.appendChild(pageCard);
7717
+ sizer.appendChild(pageCard);
7115
7718
  pageCards.push(pageCard);
7116
7719
  }
7117
7720
  let rotation = 0;
7118
7721
  const applyTransform = () => {
7119
7722
  const activeCard = pageCards[currentPage - 1];
7120
7723
  if (activeCard) {
7121
- activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7724
+ const baseW = 816;
7122
7725
  const baseH = activeCard.offsetHeight || 1056;
7123
- const scaledH = baseH * scale;
7124
- const extraH = Math.max(0, scaledH - baseH);
7125
- activeCard.style.marginBottom = `${extraH + 32}px`;
7726
+ const isRotated90 = rotation % 180 !== 0;
7727
+ const boxW = Math.round((isRotated90 ? baseH : baseW) * scale);
7728
+ const boxH = Math.round((isRotated90 ? baseW : baseH) * scale);
7729
+ sizer.style.width = `${boxW}px`;
7730
+ sizer.style.height = `${boxH}px`;
7731
+ activeCard.style.width = `${baseW}px`;
7732
+ activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7733
+ activeCard.style.transformOrigin = "center center";
7126
7734
  }
7127
7735
  };
7128
7736
  const showPage = (pageNum) => {
@@ -7130,11 +7738,11 @@ var DocPlugin = class {
7130
7738
  pageCards.forEach((card, idx) => {
7131
7739
  if (idx + 1 === currentPage) {
7132
7740
  card.style.display = "block";
7133
- card.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
7134
7741
  } else {
7135
7742
  card.style.display = "none";
7136
7743
  }
7137
7744
  });
7745
+ applyTransform();
7138
7746
  container.scrollTop = 0;
7139
7747
  ctx.emit("page-change", { page: currentPage, total: totalPages });
7140
7748
  };
@@ -7194,6 +7802,13 @@ var DocPlugin = class {
7194
7802
  applyTransform();
7195
7803
  },
7196
7804
  fitToPage: () => {
7805
+ isUserZoomed = false;
7806
+ fitMode = "page";
7807
+ scale = calculateFitScale("page");
7808
+ rotation = 0;
7809
+ applyTransform();
7810
+ },
7811
+ fitToWidth: () => {
7197
7812
  isUserZoomed = false;
7198
7813
  fitMode = "width";
7199
7814
  scale = calculateFitScale("width");
@@ -7202,9 +7817,11 @@ var DocPlugin = class {
7202
7817
  },
7203
7818
  resetZoom: () => {
7204
7819
  isUserZoomed = false;
7205
- fitMode = "width";
7206
- scale = calculateFitScale("width");
7820
+ fitMode = ctx?.options?.fitMode || "width";
7821
+ scale = calculateFitScale(fitMode);
7207
7822
  rotation = 0;
7823
+ container.scrollTop = 0;
7824
+ container.scrollLeft = 0;
7208
7825
  ctx.container.scrollTop = 0;
7209
7826
  ctx.container.scrollLeft = 0;
7210
7827
  applyTransform();
@@ -7697,6 +8314,14 @@ var PptPlugin = class {
7697
8314
  group: "zoom",
7698
8315
  execute: () => instance.resetZoom?.()
7699
8316
  },
8317
+ {
8318
+ id: "fit-width",
8319
+ icon: "fit-width",
8320
+ label: "Fit to Width",
8321
+ type: "button",
8322
+ group: "zoom",
8323
+ execute: () => instance.fitToWidth?.()
8324
+ },
7700
8325
  {
7701
8326
  id: "rotate-cw",
7702
8327
  icon: "rotate-cw",
@@ -7729,17 +8354,30 @@ var PptPlugin = class {
7729
8354
  container.style.width = "100%";
7730
8355
  container.style.height = "100%";
7731
8356
  container.style.overflow = "auto";
7732
- container.style.display = "flex";
7733
- container.style.justifyContent = "center";
7734
- container.style.alignItems = "center";
7735
- container.style.padding = "32px 16px";
7736
8357
  container.style.backgroundColor = "#0f172a";
7737
8358
  container.style.boxSizing = "border-box";
8359
+ const scrollWrapper = document.createElement("div");
8360
+ scrollWrapper.className = "fp-ppt-scroll-wrapper";
8361
+ scrollWrapper.style.minWidth = "100%";
8362
+ scrollWrapper.style.minHeight = "100%";
8363
+ scrollWrapper.style.width = "max-content";
8364
+ scrollWrapper.style.height = "max-content";
8365
+ scrollWrapper.style.display = "flex";
8366
+ scrollWrapper.style.justifyContent = "center";
8367
+ scrollWrapper.style.alignItems = "center";
8368
+ scrollWrapper.style.padding = "32px 16px";
8369
+ scrollWrapper.style.boxSizing = "border-box";
8370
+ const sizer = document.createElement("div");
8371
+ sizer.className = "fp-ppt-sizer";
8372
+ sizer.style.position = "relative";
8373
+ sizer.style.flexShrink = "0";
8374
+ sizer.style.display = "flex";
8375
+ sizer.style.justifyContent = "center";
8376
+ sizer.style.alignItems = "center";
7738
8377
  const slideCard = document.createElement("div");
7739
8378
  slideCard.className = "fp-ppt-slide-card";
7740
8379
  slideCard.style.width = "960px";
7741
- slideCard.style.maxWidth = "calc(100% - 32px)";
7742
- slideCard.style.maxHeight = "calc(100% - 48px)";
8380
+ slideCard.style.height = "540px";
7743
8381
  slideCard.style.aspectRatio = "16 / 9";
7744
8382
  slideCard.style.backgroundColor = "#ffffff";
7745
8383
  slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
@@ -7749,23 +8387,47 @@ var PptPlugin = class {
7749
8387
  slideCard.style.transition = "transform 0.2s ease";
7750
8388
  slideCard.style.transformOrigin = "center center";
7751
8389
  slideCard.style.flexShrink = "0";
7752
- container.appendChild(slideCard);
8390
+ sizer.appendChild(slideCard);
8391
+ scrollWrapper.appendChild(sizer);
8392
+ container.appendChild(scrollWrapper);
7753
8393
  ctx.container.appendChild(container);
7754
8394
  let scale = 1;
7755
8395
  let rotation = 0;
7756
8396
  let currentSlide = 1;
7757
8397
  let slides = [];
7758
8398
  const createdBlobUrls = [];
7759
- const calculateFitScale = () => {
7760
- const availW = Math.max(200, container.clientWidth - 48);
7761
- const availH = Math.max(200, container.clientHeight - 72);
7762
- return Math.min(1, Math.min(availW / 960, availH / 540));
8399
+ let fitMode = ctx?.options?.fitMode || "width";
8400
+ let isUserZoomed = false;
8401
+ const calculateFitScale = (mode = fitMode) => {
8402
+ const availW = Math.max(200, container.clientWidth - 32);
8403
+ const availH = Math.max(200, container.clientHeight - 32);
8404
+ if (mode === "page") {
8405
+ return Math.min(2.5, Math.min(availW / 960, availH / 540));
8406
+ }
8407
+ return Math.min(2.5, availW / 960);
7763
8408
  };
7764
8409
  const applyTransform = () => {
8410
+ const cW = 960;
8411
+ const cH = 540;
8412
+ const isRotated90 = rotation % 180 !== 0;
8413
+ const boxW = Math.round((isRotated90 ? cH : cW) * scale);
8414
+ const boxH = Math.round((isRotated90 ? cW : cH) * scale);
8415
+ sizer.style.width = `${boxW}px`;
8416
+ sizer.style.height = `${boxH}px`;
8417
+ slideCard.style.width = `${cW}px`;
8418
+ slideCard.style.height = `${cH}px`;
7765
8419
  slideCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
8420
+ slideCard.style.transformOrigin = "center center";
7766
8421
  };
8422
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
8423
+ if (!isUserZoomed) {
8424
+ scale = calculateFitScale(fitMode);
8425
+ applyTransform();
8426
+ }
8427
+ }) : null;
8428
+ ro?.observe(container);
7767
8429
  setTimeout(() => {
7768
- scale = calculateFitScale();
8430
+ scale = calculateFitScale(fitMode);
7769
8431
  applyTransform();
7770
8432
  }, 60);
7771
8433
  try {
@@ -7858,6 +8520,7 @@ var PptPlugin = class {
7858
8520
  };
7859
8521
  renderSlide(1);
7860
8522
  const cleanup = () => {
8523
+ ro?.disconnect();
7861
8524
  for (const u of createdBlobUrls) {
7862
8525
  URL.revokeObjectURL(u);
7863
8526
  }
@@ -7868,28 +8531,44 @@ var PptPlugin = class {
7868
8531
  return {
7869
8532
  destroy: cleanup,
7870
8533
  zoomIn: () => {
8534
+ isUserZoomed = true;
7871
8535
  scale += 0.15;
7872
8536
  applyTransform();
7873
8537
  },
7874
8538
  zoomOut: () => {
8539
+ isUserZoomed = true;
7875
8540
  scale = Math.max(0.2, scale - 0.15);
7876
8541
  applyTransform();
7877
8542
  },
7878
8543
  getZoom: () => scale,
7879
8544
  setZoom: (level) => {
8545
+ isUserZoomed = true;
7880
8546
  scale = level;
7881
8547
  applyTransform();
7882
8548
  },
7883
8549
  fitToPage: () => {
7884
- scale = calculateFitScale();
8550
+ isUserZoomed = false;
8551
+ fitMode = "page";
8552
+ scale = calculateFitScale("page");
8553
+ rotation = 0;
8554
+ applyTransform();
8555
+ },
8556
+ fitToWidth: () => {
8557
+ isUserZoomed = false;
8558
+ fitMode = "width";
8559
+ scale = calculateFitScale("width");
7885
8560
  rotation = 0;
7886
8561
  applyTransform();
7887
8562
  },
7888
8563
  resetZoom: () => {
7889
- scale = calculateFitScale();
8564
+ isUserZoomed = false;
8565
+ fitMode = ctx?.options?.fitMode || "width";
8566
+ scale = calculateFitScale(fitMode);
7890
8567
  rotation = 0;
7891
8568
  container.scrollTop = 0;
7892
8569
  container.scrollLeft = 0;
8570
+ ctx.container.scrollTop = 0;
8571
+ ctx.container.scrollLeft = 0;
7893
8572
  applyTransform();
7894
8573
  },
7895
8574
  rotateCW: () => {