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

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