@files-preview-app/preview-file 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/angular.cjs CHANGED
@@ -42,6 +42,7 @@ function _interopNamespace(e) {
42
42
  var DOMPurify6__default = /*#__PURE__*/_interopDefault(DOMPurify6);
43
43
  var pdfjsLib__namespace = /*#__PURE__*/_interopNamespace(pdfjsLib);
44
44
  var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
45
+ var fflate__namespace = /*#__PURE__*/_interopNamespace(fflate);
45
46
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
46
47
  var hljs__default = /*#__PURE__*/_interopDefault(hljs);
47
48
  var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
@@ -574,8 +575,7 @@ var ICON_FULLSCREEN = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
574
575
  var ICON_COPY = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
575
576
  var ICON_FAST_FORWARD = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 19 22 12 13 5 13 19"></polygon><polygon points="2 19 11 12 2 5 2 19"></polygon></svg>`;
576
577
  var ICON_REWIND = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 19 2 12 11 5 11 19"></polygon><polygon points="22 19 13 12 22 5 22 19"></polygon></svg>`;
577
- var ICON_SPEED = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>`;
578
- var ICON_EXTERNAL_WINDOW = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>`;
578
+ var ICON_EXTERNAL_WINDOW = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"></rect><line x1="3" y1="8" x2="21" y2="8"></line><circle cx="6" cy="5.5" r="0.75" fill="currentColor"></circle><circle cx="8.5" cy="5.5" r="0.75" fill="currentColor"></circle><polyline points="14 13 18 13 18 17"></polyline><line x1="11" y1="20" x2="18" y2="13"></line></svg>`;
579
579
  var ICON_MAP = {
580
580
  "zoom-in": ICON_ZOOM_IN,
581
581
  "zoom-out": ICON_ZOOM_OUT,
@@ -602,28 +602,177 @@ var ICON_MAP = {
602
602
  "forward-10": ICON_FAST_FORWARD,
603
603
  "rewind": ICON_REWIND,
604
604
  "replay-10": ICON_REWIND,
605
- "speed": ICON_SPEED,
606
605
  "open-window": ICON_EXTERNAL_WINDOW,
607
- "external-window": ICON_EXTERNAL_WINDOW
606
+ "external-window": ICON_EXTERNAL_WINDOW,
607
+ "openWindow": ICON_EXTERNAL_WINDOW,
608
+ "openSeparateWindow": ICON_EXTERNAL_WINDOW
608
609
  };
609
610
  var ToolbarController = class {
610
611
  el;
611
612
  toolbarEl;
612
613
  actions = [];
614
+ config = {};
613
615
  pageInputEl = null;
614
616
  pageLabelEl = null;
615
- constructor(container) {
617
+ prevPageBtn = null;
618
+ nextPageBtn = null;
619
+ constructor(container, config) {
616
620
  this.el = container;
621
+ if (config) this.config = { ...config };
617
622
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
618
623
  this.el.appendChild(this.toolbarEl);
619
624
  }
625
+ setConfig(config) {
626
+ this.config = { ...config };
627
+ this.render();
628
+ }
629
+ getConfig() {
630
+ return { ...this.config };
631
+ }
632
+ hideAction(actionId) {
633
+ this.config[actionId] = false;
634
+ if (actionId === "zoom-in" || actionId === "zoomIn") this.config.zoomIn = false;
635
+ if (actionId === "zoom-out" || actionId === "zoomOut") this.config.zoomOut = false;
636
+ if (actionId === "fit-page" || actionId === "fitPage" || actionId === "fitToPage" || actionId === "fit-width") {
637
+ this.config.fitPage = false;
638
+ this.config.fitToPage = false;
639
+ this.config.fitWidth = false;
640
+ }
641
+ if (actionId === "rotate-cw" || actionId === "rotate" || actionId === "rotateCW") this.config.rotate = false;
642
+ if (actionId === "fullscreen") this.config.fullscreen = false;
643
+ if (actionId === "download") this.config.download = false;
644
+ if (actionId === "print") this.config.print = false;
645
+ if (actionId === "open-window" || actionId === "openWindow" || actionId === "openSeparateWindow") {
646
+ this.config.openWindow = false;
647
+ this.config.openSeparateWindow = false;
648
+ }
649
+ if (actionId === "copy") this.config.copy = false;
650
+ if (actionId === "page-nav" || actionId === "pageNav" || actionId === "pagination") {
651
+ this.config.pageNav = false;
652
+ this.config.pagination = false;
653
+ }
654
+ this.render();
655
+ }
656
+ showAction(actionId) {
657
+ this.config[actionId] = true;
658
+ if (actionId === "zoom-in" || actionId === "zoomIn") this.config.zoomIn = true;
659
+ if (actionId === "zoom-out" || actionId === "zoomOut") this.config.zoomOut = true;
660
+ if (actionId === "fit-page" || actionId === "fitPage" || actionId === "fitToPage" || actionId === "fit-width") {
661
+ this.config.fitPage = true;
662
+ this.config.fitToPage = true;
663
+ this.config.fitWidth = true;
664
+ }
665
+ if (actionId === "rotate-cw" || actionId === "rotate" || actionId === "rotateCW") this.config.rotate = true;
666
+ if (actionId === "fullscreen") this.config.fullscreen = true;
667
+ if (actionId === "download") this.config.download = true;
668
+ if (actionId === "print") this.config.print = true;
669
+ if (actionId === "open-window" || actionId === "openWindow" || actionId === "openSeparateWindow") {
670
+ this.config.openWindow = true;
671
+ this.config.openSeparateWindow = true;
672
+ }
673
+ if (actionId === "copy") this.config.copy = true;
674
+ if (actionId === "page-nav" || actionId === "pageNav" || actionId === "pagination") {
675
+ this.config.pageNav = true;
676
+ this.config.pagination = true;
677
+ }
678
+ this.render();
679
+ }
680
+ normalizeActionId(actionId) {
681
+ if (actionId === "zoomIn") return "zoom-in";
682
+ if (actionId === "zoomOut") return "zoom-out";
683
+ if (actionId === "fitPage" || actionId === "fitToPage" || actionId === "fitWidth") return "fit-page";
684
+ if (actionId === "rotateCW") return "rotate-cw";
685
+ if (actionId === "rotateCCW") return "rotate-ccw";
686
+ if (actionId === "openWindow" || actionId === "openSeparateWindow") return "open-window";
687
+ if (actionId === "pageNav") return "page-nav";
688
+ return actionId;
689
+ }
690
+ enableAction(actionId) {
691
+ const norm = this.normalizeActionId(actionId);
692
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
693
+ if (btn) {
694
+ btn.disabled = false;
695
+ btn.classList.remove("disabled");
696
+ }
697
+ }
698
+ disableAction(actionId) {
699
+ const norm = this.normalizeActionId(actionId);
700
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
701
+ if (btn) {
702
+ btn.disabled = true;
703
+ btn.classList.add("disabled");
704
+ }
705
+ }
706
+ setActionActive(actionId, active) {
707
+ const norm = this.normalizeActionId(actionId);
708
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
709
+ if (btn) {
710
+ if (active) {
711
+ btn.classList.add("active");
712
+ } else {
713
+ btn.classList.remove("active");
714
+ }
715
+ }
716
+ }
717
+ isActionEnabled(action) {
718
+ const c = this.config;
719
+ const id = action.id;
720
+ if (c[id] === false) return false;
721
+ if (id === "zoom-in" && (c.zoomIn === false || c["zoom-in"] === false)) return false;
722
+ if (id === "zoom-out" && (c.zoomOut === false || c["zoom-out"] === false)) return false;
723
+ if ((id === "fit-page" || id === "fit-width" || id === "fit-slide") && (c.fitPage === false || c.fitToPage === false || c.fitWidth === false || c["fit-page"] === false || c["fit-width"] === false)) {
724
+ return false;
725
+ }
726
+ if ((id === "rotate-cw" || id === "rotate-ccw" || id === "rotate") && (c.rotate === false || c.rotateCW === false || c.rotateCCW === false || c["rotate-cw"] === false)) {
727
+ return false;
728
+ }
729
+ if (id === "fullscreen" && c.fullscreen === false) return false;
730
+ if (id === "download" && c.download === false) return false;
731
+ if (id === "print" && c.print === false) return false;
732
+ if ((id === "open-window" || id === "external-window") && (c.openWindow === false || c.openSeparateWindow === false || c["open-window"] === false)) {
733
+ return false;
734
+ }
735
+ if (id === "copy" && c.copy === false) return false;
736
+ if (id === "thumbnails" && c.thumbnails === false) return false;
737
+ if (id === "page-nav" && (c.pageNav === false || c.pagination === false || c["page-nav"] === false)) {
738
+ return false;
739
+ }
740
+ if ((id === "prev" || id === "page-prev") && (c.prevPage === false || c.prev === false || c["prev"] === false || c.pageNav === false || c.pagination === false)) {
741
+ return false;
742
+ }
743
+ if ((id === "next" || id === "page-next") && (c.nextPage === false || c.next === false || c["next"] === false || c.pageNav === false || c.pagination === false)) {
744
+ return false;
745
+ }
746
+ if (id === "play" && c.play === false) return false;
747
+ if (id === "pause" && c.pause === false) return false;
748
+ if ((id === "fast-forward" || id === "forward-10") && (c.fastForward === false || c["fast-forward"] === false)) {
749
+ return false;
750
+ }
751
+ if ((id === "rewind" || id === "replay-10") && (c.rewind === false || c["rewind"] === false)) {
752
+ return false;
753
+ }
754
+ if (id === "speed" && c.speed === false) return false;
755
+ return true;
756
+ }
620
757
  setPage(page, max) {
621
758
  if (this.pageInputEl) {
622
759
  this.pageInputEl.value = page.toString();
760
+ if (max !== void 0) {
761
+ this.pageInputEl.max = max.toString();
762
+ }
623
763
  }
624
764
  if (max !== void 0 && this.pageLabelEl) {
625
765
  this.pageLabelEl.textContent = ` / ${max}`;
626
766
  }
767
+ const currentMax = max !== void 0 ? max : parseInt(this.pageInputEl?.max || "1", 10) || 1;
768
+ if (this.prevPageBtn) {
769
+ this.prevPageBtn.disabled = page <= 1;
770
+ this.prevPageBtn.style.opacity = page <= 1 ? "0.4" : "1";
771
+ }
772
+ if (this.nextPageBtn) {
773
+ this.nextPageBtn.disabled = page >= currentMax;
774
+ this.nextPageBtn.style.opacity = page >= currentMax ? "0.4" : "1";
775
+ }
627
776
  }
628
777
  update(actions) {
629
778
  this.actions = actions;
@@ -646,8 +795,9 @@ var ToolbarController = class {
646
795
  view: [],
647
796
  actions: []
648
797
  };
649
- const hasPageNav = this.actions.some((a) => a.type === "page-nav");
650
- const effectiveActions = hasPageNav ? this.actions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : this.actions;
798
+ const enabledActions = this.actions.filter((a) => this.isActionEnabled(a));
799
+ const hasPageNav = enabledActions.some((a) => a.type === "page-nav");
800
+ const effectiveActions = hasPageNav ? enabledActions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : enabledActions;
651
801
  for (const action of effectiveActions) {
652
802
  if (groups[action.group]) {
653
803
  groups[action.group].push(action);
@@ -707,6 +857,13 @@ var ToolbarController = class {
707
857
  action.execute("go", val);
708
858
  });
709
859
  const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${max}`);
860
+ const curVal = action.value ?? 1;
861
+ prevBtn.disabled = curVal <= 1;
862
+ prevBtn.style.opacity = curVal <= 1 ? "0.4" : "1";
863
+ nextBtn.disabled = curVal >= max;
864
+ nextBtn.style.opacity = curVal >= max ? "0.4" : "1";
865
+ this.prevPageBtn = prevBtn;
866
+ this.nextPageBtn = nextBtn;
710
867
  this.pageInputEl = input;
711
868
  this.pageLabelEl = label;
712
869
  groupEl.appendChild(prevBtn);
@@ -747,9 +904,11 @@ var ToolbarController = class {
747
904
  type: "button",
748
905
  "data-action-id": id
749
906
  });
750
- const svg = ICON_MAP[iconHtml] || ICON_MAP[id] || (iconHtml && iconHtml.startsWith("<svg") ? iconHtml : null);
751
- if (svg) {
752
- btn.innerHTML = sanitizeSVG(svg);
907
+ const internalSvg = ICON_MAP[iconHtml] || ICON_MAP[id];
908
+ if (internalSvg) {
909
+ btn.innerHTML = internalSvg;
910
+ } else if (iconHtml && iconHtml.startsWith("<svg")) {
911
+ btn.innerHTML = sanitizeSVG(iconHtml);
753
912
  } else {
754
913
  btn.textContent = title || id;
755
914
  }
@@ -760,25 +919,63 @@ var ToolbarController = class {
760
919
  var ThumbnailPanel = class {
761
920
  el;
762
921
  panelEl;
922
+ listEl;
923
+ headerEl;
763
924
  thumbnails = [];
764
925
  onSelect;
926
+ onToggleCallback;
765
927
  activeIndex = 0;
766
- constructor(container) {
928
+ observer;
929
+ renderedIndices = /* @__PURE__ */ new Set();
930
+ constructor(container, onToggle) {
767
931
  this.el = container;
932
+ this.onToggleCallback = onToggle;
768
933
  this.panelEl = createElement("div", { className: "fp-thumbnail-panel hidden" });
934
+ this.panelEl.style.width = "0px";
935
+ this.panelEl.style.minWidth = "0px";
936
+ this.panelEl.style.opacity = "0";
937
+ this.headerEl = createElement("div", { className: "fp-thumbnail-header" });
938
+ const title = createElement("span", { className: "fp-thumbnail-title" }, "Pages");
939
+ const closeBtn = createElement("button", {
940
+ className: "fp-thumbnail-close-btn",
941
+ title: "Close Thumbnails",
942
+ type: "button"
943
+ });
944
+ closeBtn.innerHTML = ICON_CLOSE;
945
+ closeBtn.addEventListener("click", (e) => {
946
+ e.stopPropagation();
947
+ this.hide();
948
+ });
949
+ this.headerEl.appendChild(title);
950
+ this.headerEl.appendChild(closeBtn);
951
+ this.listEl = createElement("div", { className: "fp-thumbnail-list" });
952
+ this.panelEl.appendChild(this.headerEl);
953
+ this.panelEl.appendChild(this.listEl);
769
954
  this.el.appendChild(this.panelEl);
770
955
  }
956
+ isOpen() {
957
+ return !this.panelEl.classList.contains("hidden") && this.panelEl.style.width !== "0px";
958
+ }
771
959
  update(thumbnails, onSelect) {
772
960
  this.thumbnails = thumbnails;
773
961
  this.onSelect = onSelect;
962
+ this.renderedIndices.clear();
963
+ const titleEl = this.headerEl.querySelector(".fp-thumbnail-title");
964
+ if (titleEl) {
965
+ titleEl.textContent = `Pages (${thumbnails.length})`;
966
+ }
774
967
  this.render();
968
+ if (this.isOpen()) {
969
+ this.renderAllVisible();
970
+ }
775
971
  }
776
972
  setActive(index) {
777
973
  this.activeIndex = index;
778
- const items = this.panelEl.querySelectorAll(".fp-thumbnail-item");
974
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
779
975
  items.forEach((item, i) => {
780
976
  if (i === index) {
781
977
  item.classList.add("active");
978
+ item.scrollIntoView({ block: "nearest", behavior: "smooth" });
782
979
  } else {
783
980
  item.classList.remove("active");
784
981
  }
@@ -786,37 +983,102 @@ var ThumbnailPanel = class {
786
983
  }
787
984
  show() {
788
985
  this.panelEl.classList.remove("hidden");
986
+ this.panelEl.style.width = "200px";
987
+ this.panelEl.style.minWidth = "200px";
988
+ this.panelEl.style.opacity = "1";
989
+ this.panelEl.style.display = "flex";
990
+ this.onToggleCallback?.(true);
991
+ this.renderAllVisible();
789
992
  }
790
993
  hide() {
791
994
  this.panelEl.classList.add("hidden");
995
+ this.panelEl.style.width = "0px";
996
+ this.panelEl.style.minWidth = "0px";
997
+ this.panelEl.style.opacity = "0";
998
+ this.onToggleCallback?.(false);
792
999
  }
793
1000
  toggle() {
794
- this.panelEl.classList.toggle("hidden");
1001
+ if (this.isOpen()) {
1002
+ this.hide();
1003
+ } else {
1004
+ this.show();
1005
+ }
795
1006
  }
796
1007
  destroy() {
1008
+ if (this.observer) {
1009
+ this.observer.disconnect();
1010
+ this.observer = void 0;
1011
+ }
1012
+ this.renderedIndices.clear();
797
1013
  this.el.innerHTML = "";
798
1014
  }
799
- async render() {
800
- this.panelEl.innerHTML = "";
1015
+ renderAllVisible() {
1016
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
1017
+ items.forEach((item) => {
1018
+ const idx = parseInt(item.dataset.thumbIndex || "-1", 10);
1019
+ if (idx >= 0 && !this.renderedIndices.has(idx)) {
1020
+ this.renderThumbnailItem(idx, item);
1021
+ }
1022
+ });
1023
+ }
1024
+ renderThumbnailItem(idx, itemEl) {
1025
+ if (this.renderedIndices.has(idx)) return;
1026
+ this.renderedIndices.add(idx);
1027
+ const canvas = itemEl.querySelector("canvas");
1028
+ const thumb = this.thumbnails[idx];
1029
+ if (canvas && thumb) {
1030
+ Promise.resolve(thumb.render(canvas)).catch((err) => {
1031
+ console.warn(`[FilePreview] Error rendering thumbnail ${idx}:`, err);
1032
+ });
1033
+ }
1034
+ }
1035
+ render() {
1036
+ if (this.observer) {
1037
+ this.observer.disconnect();
1038
+ }
1039
+ this.listEl.innerHTML = "";
1040
+ const useObserver = typeof IntersectionObserver !== "undefined";
1041
+ if (useObserver) {
1042
+ this.observer = new IntersectionObserver(
1043
+ (entries) => {
1044
+ for (const entry of entries) {
1045
+ if (entry.isIntersecting) {
1046
+ const target = entry.target;
1047
+ const idx = parseInt(target.dataset.thumbIndex || "-1", 10);
1048
+ if (idx >= 0) {
1049
+ this.renderThumbnailItem(idx, target);
1050
+ }
1051
+ }
1052
+ }
1053
+ },
1054
+ {
1055
+ root: this.listEl,
1056
+ rootMargin: "120px 0px 120px 0px"
1057
+ }
1058
+ );
1059
+ }
801
1060
  for (let i = 0; i < this.thumbnails.length; i++) {
802
1061
  const thumb = this.thumbnails[i];
803
- const itemEl = createElement("div", { className: "fp-thumbnail-item" });
804
- if (i === this.activeIndex) {
805
- itemEl.classList.add("active");
806
- }
1062
+ const itemEl = createElement("div", {
1063
+ className: "fp-thumbnail-item" + (i === this.activeIndex ? " active" : "")
1064
+ });
1065
+ itemEl.dataset.thumbIndex = i.toString();
807
1066
  itemEl.addEventListener("click", () => {
808
1067
  this.setActive(i);
809
1068
  this.onSelect?.(i);
810
1069
  });
811
- const canvas = createElement("canvas", { width: "150", height: "200" });
812
- const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label);
813
- itemEl.appendChild(canvas);
1070
+ const canvasWrapper = createElement("div", { className: "fp-thumbnail-canvas-wrapper" });
1071
+ const canvas = createElement("canvas", { width: "130", height: "170" });
1072
+ canvasWrapper.appendChild(canvas);
1073
+ const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label || `Page ${i + 1}`);
1074
+ itemEl.appendChild(canvasWrapper);
814
1075
  itemEl.appendChild(label);
815
- this.panelEl.appendChild(itemEl);
816
- try {
817
- await thumb.render(canvas);
818
- } catch (err) {
819
- console.error(`[FilePreview] Error rendering thumbnail ${i}:`, err);
1076
+ this.listEl.appendChild(itemEl);
1077
+ if (this.observer) {
1078
+ this.observer.observe(itemEl);
1079
+ }
1080
+ if (i < 4) {
1081
+ this.renderThumbnailItem(i, itemEl);
820
1082
  }
821
1083
  }
822
1084
  }
@@ -837,6 +1099,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
837
1099
  currentOptions = {};
838
1100
  resizeObserver = null;
839
1101
  fullscreenHandler = null;
1102
+ titleBarEl = null;
840
1103
  /**
841
1104
  * Register a preview plugin.
842
1105
  */
@@ -873,6 +1136,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
873
1136
  }
874
1137
  this.currentBuffer = buffer.slice(0);
875
1138
  this.currentMetadata = metadata;
1139
+ this.updateTitleBar(options, metadata);
876
1140
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
877
1141
  const fileInfo = { metadata, buffer };
878
1142
  const matchedPlugin = await this.findPlugin(fileInfo);
@@ -896,15 +1160,21 @@ var FilePreviewViewer = class _FilePreviewViewer {
896
1160
  if (event === "page-change" && payload && typeof payload.page === "number") {
897
1161
  const total = payload.total ?? payload.totalPages;
898
1162
  this.toolbar?.setPage(payload.page, total);
1163
+ this.thumbnailPanel?.setActive(payload.page - 1);
899
1164
  }
900
1165
  this.eventEmitter.emit(event, payload);
901
- }
1166
+ },
1167
+ toggleThumbnails: () => this.toggleThumbnails()
902
1168
  });
903
1169
  this.activeInstance = instance;
904
1170
  instance.openInSeparateWindow = () => this.openInSeparateWindow();
1171
+ instance.toggleThumbnails = () => this.toggleThumbnails();
905
1172
  this.hideLoading();
906
1173
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
907
- if (options.showToolbar !== false && this.toolbar) {
1174
+ const isToolbarVisible = options.showToolbar !== false && options.toolbar !== false;
1175
+ if (isToolbarVisible && this.toolbar) {
1176
+ const toolbarConfig = this.extractToolbarConfig(options);
1177
+ this.toolbar.setConfig(toolbarConfig);
908
1178
  const actions = matchedPlugin.getToolbarActions(instance);
909
1179
  const hasFullscreen = actions.some((a) => a.id === "fullscreen");
910
1180
  if (!hasFullscreen && this.wrapperEl) {
@@ -954,6 +1224,12 @@ var FilePreviewViewer = class _FilePreviewViewer {
954
1224
  }
955
1225
  });
956
1226
  }
1227
+ const thumbAction = actions.find((a) => a.id === "thumbnails");
1228
+ if (thumbAction) {
1229
+ thumbAction.execute = () => {
1230
+ this.toggleThumbnails();
1231
+ };
1232
+ }
957
1233
  this.toolbar.update(actions);
958
1234
  this.toolbar.show();
959
1235
  }
@@ -1038,7 +1314,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1038
1314
  if (targetUrl) {
1039
1315
  const newWin2 = window.open(targetUrl, "_blank");
1040
1316
  if (!newWin2) {
1041
- alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1317
+ console.warn("[FilePreviewViewer] Popup blocker prevented opening the preview in a separate window.");
1042
1318
  return null;
1043
1319
  }
1044
1320
  return newWin2;
@@ -1046,7 +1322,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1046
1322
  const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
1047
1323
  const newWin = window.open("", "_blank");
1048
1324
  if (!newWin) {
1049
- alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1325
+ console.warn("[FilePreviewViewer] Popup blocker prevented opening the preview in a separate window.");
1050
1326
  return null;
1051
1327
  }
1052
1328
  newWin.document.title = title;
@@ -1123,6 +1399,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1123
1399
  this.currentBuffer = null;
1124
1400
  this.currentMetadata = null;
1125
1401
  this.currentContainer = null;
1402
+ this.titleBarEl = null;
1126
1403
  }
1127
1404
  /**
1128
1405
  * Get the currently active preview instance.
@@ -1130,7 +1407,218 @@ var FilePreviewViewer = class _FilePreviewViewer {
1130
1407
  getInstance() {
1131
1408
  return this.activeInstance;
1132
1409
  }
1410
+ // --- Toolbar Configuration & Feature Toggles ---
1411
+ /**
1412
+ * Configure toolbar features dynamically through options or methods.
1413
+ * e.g. viewer.setToolbarConfig({ zoomIn: false, print: false })
1414
+ */
1415
+ setToolbarConfig(config) {
1416
+ this.toolbar?.setConfig(config);
1417
+ }
1418
+ /**
1419
+ * Get current toolbar configuration.
1420
+ */
1421
+ getToolbarConfig() {
1422
+ return this.toolbar?.getConfig() ?? {};
1423
+ }
1424
+ /**
1425
+ * Hide a specific toolbar action by ID or alias (e.g. 'zoom-in', 'print', 'download').
1426
+ */
1427
+ hideToolbarAction(actionId) {
1428
+ this.toolbar?.hideAction(actionId);
1429
+ }
1430
+ /**
1431
+ * Show a specific toolbar action by ID or alias.
1432
+ */
1433
+ showToolbarAction(actionId) {
1434
+ this.toolbar?.showAction(actionId);
1435
+ }
1436
+ /**
1437
+ * Enable a specific toolbar action button.
1438
+ */
1439
+ enableToolbarAction(actionId) {
1440
+ this.toolbar?.enableAction(actionId);
1441
+ }
1442
+ /**
1443
+ * Disable a specific toolbar action button.
1444
+ */
1445
+ disableToolbarAction(actionId) {
1446
+ this.toolbar?.disableAction(actionId);
1447
+ }
1448
+ // --- Programmatic Toolbar Action Methods ---
1449
+ /**
1450
+ * Fit document to page so it fills the frame width with minimal margins.
1451
+ */
1452
+ fitToPage() {
1453
+ this.activeInstance?.fitToPage?.();
1454
+ }
1455
+ /**
1456
+ * Zoom in.
1457
+ */
1458
+ zoomIn() {
1459
+ this.activeInstance?.zoomIn?.();
1460
+ }
1461
+ /**
1462
+ * Zoom out.
1463
+ */
1464
+ zoomOut() {
1465
+ this.activeInstance?.zoomOut?.();
1466
+ }
1467
+ /**
1468
+ * Set specific zoom level.
1469
+ */
1470
+ setZoom(level) {
1471
+ this.activeInstance?.setZoom?.(level);
1472
+ }
1473
+ /**
1474
+ * Get current zoom level.
1475
+ */
1476
+ getZoom() {
1477
+ return this.activeInstance?.getZoom?.() ?? 1;
1478
+ }
1479
+ /**
1480
+ * Rotate 90 degrees clockwise.
1481
+ */
1482
+ rotateCW() {
1483
+ this.activeInstance?.rotateCW?.();
1484
+ }
1485
+ /**
1486
+ * Rotate 90 degrees counter-clockwise.
1487
+ */
1488
+ rotateCCW() {
1489
+ this.activeInstance?.rotateCCW?.();
1490
+ }
1491
+ /**
1492
+ * Navigate to a specific page number.
1493
+ */
1494
+ goToPage(page) {
1495
+ this.activeInstance?.goToPage?.(page);
1496
+ this.toolbar?.setPage(page);
1497
+ }
1498
+ /**
1499
+ * Navigate to next page.
1500
+ */
1501
+ nextPage() {
1502
+ const cur = this.getCurrentPage();
1503
+ const max = this.getPageCount();
1504
+ if (cur < max) {
1505
+ this.goToPage(cur + 1);
1506
+ }
1507
+ }
1508
+ /**
1509
+ * Navigate to previous page.
1510
+ */
1511
+ prevPage() {
1512
+ const cur = this.getCurrentPage();
1513
+ if (cur > 1) {
1514
+ this.goToPage(cur - 1);
1515
+ }
1516
+ }
1517
+ /**
1518
+ * Get total page count.
1519
+ */
1520
+ getPageCount() {
1521
+ return this.activeInstance?.getPageCount?.() ?? 1;
1522
+ }
1523
+ /**
1524
+ * Get current page number.
1525
+ */
1526
+ getCurrentPage() {
1527
+ return this.activeInstance?.getCurrentPage?.() ?? 1;
1528
+ }
1529
+ /**
1530
+ * Download the current document.
1531
+ */
1532
+ download() {
1533
+ this.activeInstance?.download?.();
1534
+ }
1535
+ /**
1536
+ * Print the current document.
1537
+ */
1538
+ print() {
1539
+ this.activeInstance?.print?.();
1540
+ }
1541
+ /**
1542
+ * Toggle fullscreen mode.
1543
+ */
1544
+ toggleFullscreen() {
1545
+ try {
1546
+ if (!document.fullscreenElement) {
1547
+ this.wrapperEl?.requestFullscreen?.();
1548
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
1549
+ } else {
1550
+ document.exitFullscreen?.();
1551
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
1552
+ }
1553
+ } catch {
1554
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
1555
+ }
1556
+ setTimeout(() => {
1557
+ this.activeInstance?.fitToPage?.();
1558
+ }, 120);
1559
+ }
1560
+ /**
1561
+ * Toggle thumbnails sidebar panel.
1562
+ */
1563
+ toggleThumbnails() {
1564
+ if (!this.thumbnailPanel) return;
1565
+ this.thumbnailPanel.toggle();
1566
+ const isOpen = this.thumbnailPanel.isOpen();
1567
+ this.toolbar?.setActionActive("thumbnails", isOpen);
1568
+ setTimeout(() => {
1569
+ this.activeInstance?.fitToPage?.();
1570
+ }, 260);
1571
+ }
1572
+ /**
1573
+ * Set whether to display the file name / title bar above the toolbar.
1574
+ */
1575
+ setShowFileName(show) {
1576
+ if (this.currentOptions) {
1577
+ this.currentOptions.showFileName = show;
1578
+ }
1579
+ this.updateTitleBar(this.currentOptions, this.currentMetadata);
1580
+ }
1581
+ /**
1582
+ * Set or override the displayed file name in the preview panel.
1583
+ */
1584
+ setFileName(name) {
1585
+ if (this.currentOptions) {
1586
+ this.currentOptions.fileName = name;
1587
+ }
1588
+ this.updateTitleBar(this.currentOptions, this.currentMetadata);
1589
+ }
1133
1590
  // --- Private methods ---
1591
+ updateTitleBar(options, metadata) {
1592
+ if (!this.titleBarEl) return;
1593
+ const show = options?.showFileName !== false;
1594
+ if (!show) {
1595
+ this.titleBarEl.style.display = "none";
1596
+ return;
1597
+ }
1598
+ const name = options?.fileName || metadata?.name || this.currentMetadata?.name || "";
1599
+ if (!name) {
1600
+ this.titleBarEl.style.display = "none";
1601
+ return;
1602
+ }
1603
+ this.titleBarEl.style.display = "flex";
1604
+ const ext = metadata?.extension || (name.includes(".") ? "." + name.split(".").pop() : "");
1605
+ const badge = ext ? ext.replace(".", "").toUpperCase() : "";
1606
+ this.titleBarEl.innerHTML = `
1607
+ <div class="fp-titlebar-info">
1608
+ <span class="fp-titlebar-icon">\u{1F4C4}</span>
1609
+ <span class="fp-titlebar-name" title="${name}">${name}</span>
1610
+ ${badge ? `<span class="fp-titlebar-badge">${badge}</span>` : ""}
1611
+ </div>
1612
+ `;
1613
+ }
1614
+ extractToolbarConfig(options) {
1615
+ if (!options) return {};
1616
+ const nested = typeof options.toolbar === "object" ? options.toolbar : {};
1617
+ return {
1618
+ ...options,
1619
+ ...nested
1620
+ };
1621
+ }
1134
1622
  abort() {
1135
1623
  if (this.abortController) {
1136
1624
  this.abortController.abort();
@@ -1173,17 +1661,29 @@ var FilePreviewViewer = class _FilePreviewViewer {
1173
1661
  bodyEl.className = "fp-body";
1174
1662
  bodyEl.appendChild(thumbnailEl);
1175
1663
  bodyEl.appendChild(this.contentEl);
1664
+ const titleBarEl = document.createElement("div");
1665
+ titleBarEl.className = "fp-titlebar";
1666
+ this.titleBarEl = titleBarEl;
1667
+ this.updateTitleBar(options, this.currentMetadata);
1176
1668
  if (toolbarPos === "top") {
1669
+ this.wrapperEl.appendChild(titleBarEl);
1177
1670
  this.wrapperEl.appendChild(toolbarEl);
1178
1671
  this.wrapperEl.appendChild(bodyEl);
1179
1672
  } else {
1673
+ this.wrapperEl.appendChild(titleBarEl);
1180
1674
  this.wrapperEl.appendChild(bodyEl);
1181
1675
  this.wrapperEl.appendChild(toolbarEl);
1182
1676
  }
1183
1677
  container.innerHTML = "";
1184
1678
  container.appendChild(this.wrapperEl);
1185
- this.toolbar = new ToolbarController(toolbarEl);
1186
- this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
1679
+ const initialToolbarConfig = this.extractToolbarConfig(options);
1680
+ this.toolbar = new ToolbarController(toolbarEl, initialToolbarConfig);
1681
+ this.thumbnailPanel = new ThumbnailPanel(thumbnailEl, (isOpen) => {
1682
+ this.toolbar?.setActionActive("thumbnails", isOpen);
1683
+ setTimeout(() => {
1684
+ this.activeInstance?.fitToPage?.();
1685
+ }, 260);
1686
+ });
1187
1687
  this.setupKeyboardShortcuts();
1188
1688
  this.setupDragAndDrop(container, options);
1189
1689
  this.setupResizeAndFullscreenListeners();
@@ -1573,6 +2073,14 @@ var PdfPlugin = class {
1573
2073
  group: "zoom",
1574
2074
  execute: () => instance.fitToPage?.()
1575
2075
  },
2076
+ {
2077
+ id: "fit-width",
2078
+ icon: "fit-width",
2079
+ label: "Fit to Width",
2080
+ type: "button",
2081
+ group: "zoom",
2082
+ execute: () => instance.fitToWidth?.()
2083
+ },
1576
2084
  {
1577
2085
  id: "rotate-cw",
1578
2086
  icon: "rotate-cw",
@@ -1581,6 +2089,14 @@ var PdfPlugin = class {
1581
2089
  group: "view",
1582
2090
  execute: () => instance.rotateCW?.()
1583
2091
  },
2092
+ {
2093
+ id: "rotate-ccw",
2094
+ icon: "rotate-ccw",
2095
+ label: "Rotate Counter-Clockwise",
2096
+ type: "button",
2097
+ group: "view",
2098
+ execute: () => instance.rotateCCW?.()
2099
+ },
1584
2100
  {
1585
2101
  id: "download",
1586
2102
  icon: "download",
@@ -1612,12 +2128,13 @@ var PdfPlugin = class {
1612
2128
  container.className = "fp-pdf-container";
1613
2129
  container.style.width = "100%";
1614
2130
  container.style.height = "100%";
1615
- container.style.overflow = "auto";
2131
+ container.style.overflowX = "hidden";
2132
+ container.style.overflowY = "auto";
1616
2133
  container.style.display = "flex";
1617
2134
  container.style.flexDirection = "column";
1618
2135
  container.style.alignItems = "center";
1619
2136
  container.style.justifyContent = "flex-start";
1620
- container.style.padding = "20px 16px";
2137
+ container.style.padding = "16px 8px";
1621
2138
  container.style.backgroundColor = "#0f172a";
1622
2139
  container.style.boxSizing = "border-box";
1623
2140
  container.style.position = "relative";
@@ -1628,30 +2145,12 @@ var PdfPlugin = class {
1628
2145
  pageCard.style.borderRadius = "4px";
1629
2146
  pageCard.style.overflow = "hidden";
1630
2147
  pageCard.style.lineHeight = "0";
1631
- pageCard.style.transition = "transform 0.15s ease";
1632
2148
  pageCard.style.position = "relative";
1633
2149
  pageCard.style.flexShrink = "0";
2150
+ pageCard.style.transition = "box-shadow 0.2s ease";
1634
2151
  let canvas = document.createElement("canvas");
1635
2152
  pageCard.appendChild(canvas);
1636
2153
  container.appendChild(pageCard);
1637
- const indicator = document.createElement("div");
1638
- indicator.className = "fp-pdf-page-indicator";
1639
- indicator.style.position = "sticky";
1640
- indicator.style.bottom = "16px";
1641
- indicator.style.marginTop = "16px";
1642
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1643
- indicator.style.backdropFilter = "blur(8px)";
1644
- indicator.style.color = "#f8fafc";
1645
- indicator.style.fontSize = "12px";
1646
- indicator.style.fontWeight = "600";
1647
- indicator.style.padding = "5px 14px";
1648
- indicator.style.borderRadius = "20px";
1649
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1650
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1651
- indicator.style.zIndex = "10";
1652
- indicator.style.userSelect = "none";
1653
- indicator.style.pointerEvents = "none";
1654
- container.appendChild(indicator);
1655
2154
  ctx.container.appendChild(container);
1656
2155
  const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
1657
2156
  const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
@@ -1667,8 +2166,9 @@ var PdfPlugin = class {
1667
2166
  let currentPage = 1;
1668
2167
  let zoomScale = 1;
1669
2168
  let rotation = 0;
1670
- let fitMode = "page";
2169
+ let fitMode = ctx?.options?.fitMode || "width";
1671
2170
  let currentRenderTask = null;
2171
+ let textLayerDiv = null;
1672
2172
  const renderPage = async (pageNum) => {
1673
2173
  if (currentRenderTask) {
1674
2174
  try {
@@ -1680,30 +2180,37 @@ var PdfPlugin = class {
1680
2180
  const newCanvas = document.createElement("canvas");
1681
2181
  pageCard.replaceChild(newCanvas, canvas);
1682
2182
  canvas = newCanvas;
2183
+ if (textLayerDiv) {
2184
+ textLayerDiv.remove();
2185
+ textLayerDiv = null;
2186
+ }
1683
2187
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
1684
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1685
2188
  ctx.emit("page-change", { page: currentPage, total: totalPages });
1686
2189
  const page = await pdfDoc.getPage(currentPage);
1687
2190
  const containerWidth = container.clientWidth || 900;
1688
2191
  const containerHeight = container.clientHeight || 700;
1689
2192
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1690
- const availWidth = Math.max(320, containerWidth - 48);
1691
- const availHeight = Math.max(550, containerHeight - 88);
2193
+ const availWidth = Math.max(280, containerWidth - 36);
2194
+ const availHeight = Math.max(280, containerHeight - 32);
1692
2195
  const scaleW = availWidth / unscaledVp.width;
1693
2196
  const scaleH = availHeight / unscaledVp.height;
1694
2197
  let fitScale;
1695
2198
  if (fitMode === "page") {
1696
- fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
2199
+ fitScale = Math.max(0.2, Math.min(4, Math.min(scaleW, scaleH)));
1697
2200
  } else {
1698
- fitScale = Math.max(0.65, Math.min(1.25, scaleW));
2201
+ fitScale = Math.max(0.2, Math.min(4, scaleW));
1699
2202
  }
1700
2203
  const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1701
2204
  const pixelRatio = window.devicePixelRatio || 1;
1702
2205
  const viewport = page.getViewport({ scale: effectiveScale, rotation });
2206
+ const displayWidth = Math.floor(viewport.width);
2207
+ const displayHeight = Math.floor(viewport.height);
1703
2208
  canvas.width = Math.floor(viewport.width * pixelRatio);
1704
2209
  canvas.height = Math.floor(viewport.height * pixelRatio);
1705
- canvas.style.width = `${Math.floor(viewport.width)}px`;
1706
- canvas.style.height = `${Math.floor(viewport.height)}px`;
2210
+ canvas.style.width = `${displayWidth}px`;
2211
+ canvas.style.height = `${displayHeight}px`;
2212
+ pageCard.style.width = `${displayWidth}px`;
2213
+ pageCard.style.height = `${displayHeight}px`;
1707
2214
  const canvasCtx = canvas.getContext("2d");
1708
2215
  if (!canvasCtx) return;
1709
2216
  canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
@@ -1717,9 +2224,31 @@ var PdfPlugin = class {
1717
2224
  if (err?.name !== "RenderingCancelledException") {
1718
2225
  console.warn("[PdfPlugin] Page render warning:", err);
1719
2226
  }
2227
+ return;
1720
2228
  } finally {
1721
2229
  currentRenderTask = null;
1722
2230
  }
2231
+ try {
2232
+ textLayerDiv = document.createElement("div");
2233
+ textLayerDiv.className = "textLayer";
2234
+ textLayerDiv.style.width = `${displayWidth}px`;
2235
+ textLayerDiv.style.height = `${displayHeight}px`;
2236
+ textLayerDiv.style.position = "absolute";
2237
+ textLayerDiv.style.top = "0";
2238
+ textLayerDiv.style.left = "0";
2239
+ textLayerDiv.style.lineHeight = "1";
2240
+ pageCard.appendChild(textLayerDiv);
2241
+ if (pdfjsLib__namespace.TextLayer) {
2242
+ const textLayer = new pdfjsLib__namespace.TextLayer({
2243
+ textContentSource: page.streamTextContent(),
2244
+ container: textLayerDiv,
2245
+ viewport
2246
+ });
2247
+ await textLayer.render();
2248
+ }
2249
+ } catch (err) {
2250
+ console.debug("[PdfPlugin] TextLayer notice:", err);
2251
+ }
1723
2252
  };
1724
2253
  renderPage(1);
1725
2254
  let resizeTimer = null;
@@ -1729,11 +2258,48 @@ var PdfPlugin = class {
1729
2258
  if (Math.abs(zoomScale - 1) < 0.05) {
1730
2259
  renderPage(currentPage);
1731
2260
  }
1732
- }, 150);
2261
+ }, 120);
1733
2262
  });
1734
2263
  resizeObserver.observe(container);
2264
+ const onWheel = (e) => {
2265
+ if (e.ctrlKey || e.metaKey) {
2266
+ e.preventDefault();
2267
+ const delta = e.deltaY > 0 ? -0.15 : 0.15;
2268
+ zoomScale = Math.max(0.25, Math.min(4, Math.round((zoomScale + delta) * 100) / 100));
2269
+ renderPage(currentPage);
2270
+ }
2271
+ };
2272
+ container.addEventListener("wheel", onWheel, { passive: false });
2273
+ const onKeyDown = (e) => {
2274
+ const tag = e.target?.tagName?.toLowerCase();
2275
+ if (tag === "input" || tag === "textarea" || tag === "select") return;
2276
+ if (e.key === "ArrowRight" || e.key === "PageDown") {
2277
+ if (currentPage < totalPages) {
2278
+ e.preventDefault();
2279
+ container.scrollTop = 0;
2280
+ renderPage(currentPage + 1);
2281
+ }
2282
+ } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
2283
+ if (currentPage > 1) {
2284
+ e.preventDefault();
2285
+ container.scrollTop = 0;
2286
+ renderPage(currentPage - 1);
2287
+ }
2288
+ } else if (e.key === "Home") {
2289
+ e.preventDefault();
2290
+ container.scrollTop = 0;
2291
+ renderPage(1);
2292
+ } else if (e.key === "End") {
2293
+ e.preventDefault();
2294
+ container.scrollTop = 0;
2295
+ renderPage(totalPages);
2296
+ }
2297
+ };
2298
+ window.addEventListener("keydown", onKeyDown);
1735
2299
  const cleanup = () => {
1736
2300
  resizeObserver.disconnect();
2301
+ window.removeEventListener("keydown", onKeyDown);
2302
+ container.removeEventListener("wheel", onWheel);
1737
2303
  if (resizeTimer) clearTimeout(resizeTimer);
1738
2304
  if (currentRenderTask) {
1739
2305
  try {
@@ -1752,22 +2318,26 @@ var PdfPlugin = class {
1752
2318
  const instance = {
1753
2319
  destroy: cleanup,
1754
2320
  zoomIn: () => {
1755
- zoomScale = Math.min(3.5, zoomScale + 0.2);
2321
+ zoomScale = Math.min(4, Math.round((zoomScale + 0.2) * 10) / 10);
1756
2322
  renderPage(currentPage);
1757
2323
  },
1758
2324
  zoomOut: () => {
1759
- zoomScale = Math.max(0.3, zoomScale - 0.2);
2325
+ zoomScale = Math.max(0.25, Math.round((zoomScale - 0.2) * 10) / 10);
1760
2326
  renderPage(currentPage);
1761
2327
  },
1762
2328
  getZoom: () => zoomScale,
1763
2329
  setZoom: (level) => {
1764
- zoomScale = Math.max(0.3, Math.min(3.5, level));
2330
+ zoomScale = Math.max(0.25, Math.min(4, level));
1765
2331
  renderPage(currentPage);
1766
2332
  },
1767
2333
  fitToPage: () => {
1768
- fitMode = fitMode === "page" ? "width" : "page";
2334
+ fitMode = "page";
2335
+ zoomScale = 1;
2336
+ renderPage(currentPage);
2337
+ },
2338
+ fitToWidth: () => {
2339
+ fitMode = "width";
1769
2340
  zoomScale = 1;
1770
- rotation = 0;
1771
2341
  renderPage(currentPage);
1772
2342
  },
1773
2343
  rotateCW: () => {
@@ -1785,6 +2355,9 @@ var PdfPlugin = class {
1785
2355
  container.scrollTop = 0;
1786
2356
  renderPage(page);
1787
2357
  },
2358
+ toggleThumbnails: () => {
2359
+ ctx?.toggleThumbnails?.();
2360
+ },
1788
2361
  download: () => {
1789
2362
  const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1790
2363
  const url = URL.createObjectURL(blob);
@@ -1818,7 +2391,7 @@ var PdfPlugin = class {
1818
2391
  },
1819
2392
  getThumbnails: async () => {
1820
2393
  const thumbnails = [];
1821
- const count = Math.min(totalPages, 50);
2394
+ const count = totalPages;
1822
2395
  for (let i = 1; i <= count; i++) {
1823
2396
  thumbnails.push({
1824
2397
  index: i,
@@ -1826,9 +2399,10 @@ var PdfPlugin = class {
1826
2399
  render: async (thumbCanvas) => {
1827
2400
  try {
1828
2401
  const p = await pdfDoc.getPage(i);
1829
- const baseVp = p.getViewport({ scale: 1 });
1830
- const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1831
- const thumbVp = p.getViewport({ scale: thumbScale });
2402
+ const baseVp = p.getViewport({ scale: 1, rotation });
2403
+ const targetW = thumbCanvas.width || 130;
2404
+ const thumbScale = targetW / baseVp.width;
2405
+ const thumbVp = p.getViewport({ scale: thumbScale, rotation });
1832
2406
  thumbCanvas.height = Math.floor(thumbVp.height);
1833
2407
  const tCtx = thumbCanvas.getContext("2d");
1834
2408
  if (tCtx) {
@@ -2172,28 +2746,26 @@ var DocxPlugin = class {
2172
2746
  getToolbarActions(instance) {
2173
2747
  const totalPages = instance.getPageCount?.() ?? 1;
2174
2748
  const actions = [];
2175
- if (totalPages > 1) {
2176
- actions.push({
2177
- id: "page-nav",
2178
- icon: "",
2179
- label: "Page Navigation",
2180
- type: "page-nav",
2181
- group: "navigation",
2182
- value: instance.getCurrentPage?.() ?? 1,
2183
- max: totalPages,
2184
- execute: (action, page) => {
2185
- const cur = instance.getCurrentPage?.() ?? 1;
2186
- const max = instance.getPageCount?.() ?? 1;
2187
- if (action === "prev") {
2188
- if (cur > 1) instance.goToPage?.(cur - 1);
2189
- } else if (action === "next") {
2190
- if (cur < max) instance.goToPage?.(cur + 1);
2191
- } else if (typeof page === "number") {
2192
- instance.goToPage?.(page);
2193
- }
2749
+ actions.push({
2750
+ id: "page-nav",
2751
+ icon: "",
2752
+ label: "Page Navigation",
2753
+ type: "page-nav",
2754
+ group: "navigation",
2755
+ value: instance.getCurrentPage?.() ?? 1,
2756
+ max: totalPages,
2757
+ execute: (action, page) => {
2758
+ const cur = instance.getCurrentPage?.() ?? 1;
2759
+ const max = instance.getPageCount?.() ?? 1;
2760
+ if (action === "prev") {
2761
+ if (cur > 1) instance.goToPage?.(cur - 1);
2762
+ } else if (action === "next") {
2763
+ if (cur < max) instance.goToPage?.(cur + 1);
2764
+ } else if (typeof page === "number") {
2765
+ instance.goToPage?.(page);
2194
2766
  }
2195
- });
2196
- }
2767
+ }
2768
+ });
2197
2769
  actions.push(
2198
2770
  {
2199
2771
  id: "zoom-out",
@@ -2259,11 +2831,17 @@ var DocxPlugin = class {
2259
2831
  wrapper.className = "fp-docx-wrapper";
2260
2832
  wrapper.style.transformOrigin = "top center";
2261
2833
  wrapper.style.transition = "transform 0.2s ease";
2262
- wrapper.style.padding = "24px 16px";
2263
- wrapper.style.maxWidth = "950px";
2834
+ wrapper.style.padding = "0";
2835
+ wrapper.style.maxWidth = "none";
2836
+ wrapper.style.width = "816px";
2264
2837
  wrapper.style.margin = "0 auto";
2838
+ wrapper.style.display = "flex";
2839
+ wrapper.style.flexDirection = "column";
2840
+ wrapper.style.alignItems = "center";
2265
2841
  wrapper.style.boxSizing = "border-box";
2266
- ctx.container.style.overflow = "auto";
2842
+ ctx.container.style.overflowX = "hidden";
2843
+ ctx.container.style.overflowY = "auto";
2844
+ ctx.container.style.padding = "16px 8px";
2267
2845
  ctx.container.style.backgroundColor = "#f1f5f9";
2268
2846
  ctx.container.appendChild(wrapper);
2269
2847
  const styleOverride = document.createElement("style");
@@ -2437,39 +3015,13 @@ var DocxPlugin = class {
2437
3015
  const pageElements = sections.length > 0 ? sections : cards;
2438
3016
  const totalPages = Math.max(1, pageElements.length);
2439
3017
  let currentPage = 1;
2440
- let indicator = null;
2441
- if (totalPages > 1) {
2442
- indicator = document.createElement("div");
2443
- indicator.className = "fp-docx-page-indicator";
2444
- indicator.style.position = "sticky";
2445
- indicator.style.bottom = "16px";
2446
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2447
- indicator.style.backdropFilter = "blur(8px)";
2448
- indicator.style.color = "#f8fafc";
2449
- indicator.style.fontSize = "12px";
2450
- indicator.style.fontWeight = "600";
2451
- indicator.style.padding = "5px 14px";
2452
- indicator.style.borderRadius = "20px";
2453
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2454
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2455
- indicator.style.zIndex = "10";
2456
- indicator.style.userSelect = "none";
2457
- indicator.style.pointerEvents = "none";
2458
- indicator.style.textAlign = "center";
2459
- indicator.style.width = "fit-content";
2460
- indicator.style.margin = "16px auto 0";
2461
- ctx.container.appendChild(indicator);
2462
- }
2463
3018
  const showPage = (pageNum) => {
2464
3019
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
2465
3020
  if (pageElements.length > 1) {
2466
3021
  pageElements.forEach((sec, idx) => {
2467
- sec.style.display = idx + 1 === currentPage ? "block" : "none";
3022
+ sec.style.display = idx + 1 === currentPage ? "" : "none";
2468
3023
  });
2469
3024
  }
2470
- if (indicator) {
2471
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2472
- }
2473
3025
  ctx.container.scrollTop = 0;
2474
3026
  ctx.emit("page-change", { page: currentPage, total: totalPages });
2475
3027
  };
@@ -2478,31 +3030,46 @@ var DocxPlugin = class {
2478
3030
  }
2479
3031
  scale = 1;
2480
3032
  let rotation = 0;
2481
- let fitMode = "width";
2482
- const calculateFitScale = (mode = fitMode) => {
2483
- const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
3033
+ let fitMode = "page";
3034
+ let isUserZoomed = false;
3035
+ const calculateFitScale = (_mode = fitMode) => {
3036
+ const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
2484
3037
  const elW = activeEl.offsetWidth || 816;
2485
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
2486
- const availW = Math.max(280, ctx.container.clientWidth - 48);
2487
- const availH = Math.max(280, ctx.container.clientHeight - 80);
3038
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
2488
3039
  const sW = availW / elW;
2489
- const sH = availH / singlePageH;
2490
- if (mode === "page") {
2491
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
2492
- }
2493
- return Math.max(0.65, Math.min(1.05, sW));
3040
+ return Math.max(0.35, Math.min(3, sW));
2494
3041
  };
2495
3042
  const applyTransform = () => {
2496
3043
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2497
3044
  wrapper.style.transformOrigin = "top center";
3045
+ const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3046
+ const elH = activeEl.offsetHeight || 1056;
3047
+ if (scale > 1) {
3048
+ wrapper.style.marginBottom = `${Math.round(elH * scale - elH + 32)}px`;
3049
+ } else {
3050
+ wrapper.style.marginBottom = "32px";
3051
+ }
2498
3052
  };
3053
+ let resizeObserver = null;
3054
+ if (typeof ResizeObserver !== "undefined") {
3055
+ resizeObserver = new ResizeObserver(() => {
3056
+ if (!isUserZoomed) {
3057
+ const newFit = calculateFitScale("page");
3058
+ if (Math.abs(scale - newFit) > 0.015) {
3059
+ scale = newFit;
3060
+ applyTransform();
3061
+ }
3062
+ }
3063
+ });
3064
+ resizeObserver.observe(ctx.container);
3065
+ }
2499
3066
  setTimeout(() => {
2500
- fitMode = "width";
2501
- scale = calculateFitScale("width");
3067
+ scale = calculateFitScale("page");
2502
3068
  applyTransform();
2503
- }, 60);
3069
+ }, 40);
2504
3070
  const cleanup = () => {
2505
- indicator?.remove();
3071
+ resizeObserver?.disconnect();
3072
+ resizeObserver = null;
2506
3073
  for (const url of createdBlobUrls) {
2507
3074
  URL.revokeObjectURL(url);
2508
3075
  }
@@ -2519,21 +3086,24 @@ var DocxPlugin = class {
2519
3086
  showPage(page);
2520
3087
  },
2521
3088
  zoomIn: () => {
2522
- scale += 0.15;
3089
+ isUserZoomed = true;
3090
+ scale = Math.min(3.5, scale + 0.15);
2523
3091
  applyTransform();
2524
3092
  },
2525
3093
  zoomOut: () => {
3094
+ isUserZoomed = true;
2526
3095
  scale = Math.max(0.2, scale - 0.15);
2527
3096
  applyTransform();
2528
3097
  },
2529
3098
  getZoom: () => scale,
2530
3099
  setZoom: (level) => {
3100
+ isUserZoomed = true;
2531
3101
  scale = level;
2532
3102
  applyTransform();
2533
3103
  },
2534
3104
  fitToPage: () => {
2535
- fitMode = fitMode === "width" ? "page" : "width";
2536
- scale = calculateFitScale(fitMode);
3105
+ isUserZoomed = false;
3106
+ scale = calculateFitScale("page");
2537
3107
  rotation = 0;
2538
3108
  applyTransform();
2539
3109
  },
@@ -2923,7 +3493,7 @@ var ExcelPlugin = class {
2923
3493
  "application/vnd.ms-excel.template.macroEnabled.12",
2924
3494
  "application/vnd.oasis.opendocument.spreadsheet"
2925
3495
  ];
2926
- weight = 80;
3496
+ weight = 85;
2927
3497
  supports(file) {
2928
3498
  const ext = file.metadata.extension?.toLowerCase();
2929
3499
  const mime = file.metadata.mimeType?.toLowerCase();
@@ -2935,6 +3505,14 @@ var ExcelPlugin = class {
2935
3505
  getToolbarActions(instance) {
2936
3506
  const totalSheets = instance.getPageCount?.() ?? 1;
2937
3507
  const actions = [];
3508
+ actions.push({
3509
+ id: "thumbnails",
3510
+ icon: "thumbnails",
3511
+ label: "Sheet Thumbnails",
3512
+ type: "button",
3513
+ group: "navigation",
3514
+ execute: () => instance.toggleThumbnails?.()
3515
+ });
2938
3516
  if (totalSheets > 1) {
2939
3517
  actions.push({
2940
3518
  id: "page-nav",
@@ -3017,6 +3595,16 @@ var ExcelPlugin = class {
3017
3595
  execute: () => {
3018
3596
  instance.print?.();
3019
3597
  }
3598
+ },
3599
+ {
3600
+ id: "open-window",
3601
+ icon: "open-window",
3602
+ label: "Open in Separate Full Window",
3603
+ type: "button",
3604
+ group: "actions",
3605
+ execute: () => {
3606
+ instance.openInSeparateWindow?.();
3607
+ }
3020
3608
  }
3021
3609
  );
3022
3610
  return actions;
@@ -3182,6 +3770,63 @@ var ExcelPlugin = class {
3182
3770
  },
3183
3771
  getPageCount: () => sheetNames.length,
3184
3772
  getCurrentPage: () => currentSheetIndex,
3773
+ getThumbnails: () => {
3774
+ return sheetNames.map((name, idx) => ({
3775
+ index: idx,
3776
+ label: name,
3777
+ render: async (canvas) => {
3778
+ canvas.width = 140;
3779
+ canvas.height = 100;
3780
+ const c = canvas.getContext("2d");
3781
+ if (!c) return;
3782
+ c.fillStyle = "#ffffff";
3783
+ c.fillRect(0, 0, canvas.width, canvas.height);
3784
+ c.fillStyle = "#107c41";
3785
+ c.fillRect(0, 0, canvas.width, 16);
3786
+ c.fillStyle = "#ffffff";
3787
+ c.font = 'bold 8px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
3788
+ c.fillText(`\u{1F4CA} ${name.slice(0, 16)}`, 6, 11);
3789
+ const startY = 17;
3790
+ const rowH = 13;
3791
+ const colW = 30;
3792
+ const colHeaders = ["A", "B", "C", "D"];
3793
+ c.fillStyle = "#f1f5f9";
3794
+ c.fillRect(0, startY, canvas.width, rowH);
3795
+ c.strokeStyle = "#cbd5e1";
3796
+ c.lineWidth = 0.8;
3797
+ c.strokeRect(0, startY, canvas.width, rowH);
3798
+ c.fillStyle = "#64748b";
3799
+ c.font = "bold 7px sans-serif";
3800
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3801
+ c.fillText(colHeaders[ci], 14 + ci * colW, startY + 9);
3802
+ }
3803
+ const ws = wb?.Sheets[name];
3804
+ for (let ri = 1; ri <= 5; ri++) {
3805
+ const y = startY + ri * rowH;
3806
+ if (y > canvas.height - 2) break;
3807
+ c.fillStyle = "#f8fafc";
3808
+ c.fillRect(0, y, 12, rowH);
3809
+ c.fillStyle = "#94a3b8";
3810
+ c.font = "6px sans-serif";
3811
+ c.fillText(String(ri), 3, y + 9);
3812
+ c.strokeStyle = "#e2e8f0";
3813
+ c.strokeRect(0, y, canvas.width, rowH);
3814
+ c.fillStyle = "#334155";
3815
+ c.font = "6px sans-serif";
3816
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3817
+ const cellRef = `${colHeaders[ci]}${ri}`;
3818
+ const cellVal = ws?.[cellRef]?.w || ws?.[cellRef]?.v;
3819
+ if (cellVal !== void 0) {
3820
+ c.fillText(String(cellVal).slice(0, 6), 14 + ci * colW, y + 9);
3821
+ }
3822
+ }
3823
+ }
3824
+ c.strokeStyle = "#cbd5e1";
3825
+ c.lineWidth = 1;
3826
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
3827
+ }
3828
+ }));
3829
+ },
3185
3830
  download: () => {
3186
3831
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
3187
3832
  const url = URL.createObjectURL(blob);
@@ -3214,7 +3859,39 @@ var CsvPlugin = class {
3214
3859
  return ext === ".csv" || ext === ".tsv" || mime === "text/csv" || mime === "text/tab-separated-values";
3215
3860
  }
3216
3861
  getToolbarActions(instance) {
3217
- return [
3862
+ const totalPages = instance.getPageCount?.() ?? 1;
3863
+ const actions = [];
3864
+ actions.push({
3865
+ id: "thumbnails",
3866
+ icon: "thumbnails",
3867
+ label: "Page Thumbnails",
3868
+ type: "button",
3869
+ group: "navigation",
3870
+ execute: () => instance.toggleThumbnails?.()
3871
+ });
3872
+ if (totalPages > 1) {
3873
+ actions.push({
3874
+ id: "page-nav",
3875
+ icon: "",
3876
+ label: "Page Navigation",
3877
+ type: "page-nav",
3878
+ group: "navigation",
3879
+ value: instance.getCurrentPage?.() ?? 1,
3880
+ max: totalPages,
3881
+ execute: (action, page) => {
3882
+ const cur = instance.getCurrentPage?.() ?? 1;
3883
+ const max = instance.getPageCount?.() ?? 1;
3884
+ if (action === "prev") {
3885
+ if (cur > 1) instance.goToPage?.(cur - 1);
3886
+ } else if (action === "next") {
3887
+ if (cur < max) instance.goToPage?.(cur + 1);
3888
+ } else if (typeof page === "number") {
3889
+ instance.goToPage?.(page);
3890
+ }
3891
+ }
3892
+ });
3893
+ }
3894
+ actions.push(
3218
3895
  {
3219
3896
  id: "zoom-out",
3220
3897
  icon: "zoom-out",
@@ -3235,6 +3912,16 @@ var CsvPlugin = class {
3235
3912
  instance.zoomIn?.();
3236
3913
  }
3237
3914
  },
3915
+ {
3916
+ id: "fit-page",
3917
+ icon: "fit-page",
3918
+ label: "Fit to View",
3919
+ type: "button",
3920
+ group: "zoom",
3921
+ execute: () => {
3922
+ instance.fitToPage?.();
3923
+ }
3924
+ },
3238
3925
  {
3239
3926
  id: "download",
3240
3927
  icon: "download",
@@ -3252,10 +3939,21 @@ var CsvPlugin = class {
3252
3939
  type: "button",
3253
3940
  group: "actions",
3254
3941
  execute: () => {
3255
- instance.print?.();
3942
+ instance.print?.();
3943
+ }
3944
+ },
3945
+ {
3946
+ id: "open-window",
3947
+ icon: "open-window",
3948
+ label: "Open in Separate Full Window",
3949
+ type: "button",
3950
+ group: "actions",
3951
+ execute: () => {
3952
+ instance.openInSeparateWindow?.();
3256
3953
  }
3257
3954
  }
3258
- ];
3955
+ );
3956
+ return actions;
3259
3957
  }
3260
3958
  async render(ctx) {
3261
3959
  const decoder = new TextDecoder("utf-8");
@@ -3267,33 +3965,62 @@ var CsvPlugin = class {
3267
3965
  container.style.padding = "16px";
3268
3966
  container.style.boxSizing = "border-box";
3269
3967
  container.style.transformOrigin = "top left";
3968
+ const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3969
+ const allLines = text.split(/\r?\n/).filter((r) => r.trim().length > 0);
3970
+ const headerLine = allLines[0] || "";
3971
+ const headerCells = headerLine.split(delimiter);
3972
+ const dataLines = allLines.slice(1);
3973
+ const ROWS_PER_PAGE = 100;
3974
+ const totalPages = Math.max(1, Math.ceil(dataLines.length / ROWS_PER_PAGE));
3975
+ let currentPage = 1;
3270
3976
  const table = document.createElement("table");
3271
3977
  table.style.borderCollapse = "collapse";
3272
3978
  table.style.width = "100%";
3273
- table.style.fontFamily = "monospace";
3979
+ table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace';
3274
3980
  table.style.fontSize = "13px";
3275
- const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3276
- const rows = text.split(/\r?\n/).slice(0, 500);
3277
- rows.forEach((row, rowIndex) => {
3278
- if (!row.trim()) return;
3279
- const tr = document.createElement("tr");
3280
- const cells = row.split(delimiter);
3281
- cells.forEach((cell) => {
3282
- const td = document.createElement(rowIndex === 0 ? "th" : "td");
3283
- td.textContent = cell.trim();
3284
- td.style.border = "1px solid #d0d7de";
3285
- td.style.padding = "6px 12px";
3286
- if (rowIndex === 0) {
3287
- td.style.backgroundColor = "#f6f8fa";
3288
- td.style.fontWeight = "bold";
3289
- }
3290
- tr.appendChild(td);
3981
+ const renderPage = (pageNum) => {
3982
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
3983
+ table.innerHTML = "";
3984
+ const headerTr = document.createElement("tr");
3985
+ headerCells.forEach((cell) => {
3986
+ const th = document.createElement("th");
3987
+ th.textContent = cell.trim();
3988
+ th.style.border = "1px solid #d0d7de";
3989
+ th.style.padding = "8px 12px";
3990
+ th.style.backgroundColor = "#f6f8fa";
3991
+ th.style.fontWeight = "600";
3992
+ th.style.whiteSpace = "nowrap";
3993
+ headerTr.appendChild(th);
3291
3994
  });
3292
- table.appendChild(tr);
3293
- });
3995
+ table.appendChild(headerTr);
3996
+ const start = (currentPage - 1) * ROWS_PER_PAGE;
3997
+ const end = Math.min(dataLines.length, start + ROWS_PER_PAGE);
3998
+ const pageRows = dataLines.slice(start, end);
3999
+ pageRows.forEach((row, idx) => {
4000
+ const tr = document.createElement("tr");
4001
+ if (idx % 2 === 1) tr.style.backgroundColor = "#f8fafc";
4002
+ const cells = row.split(delimiter);
4003
+ cells.forEach((cell) => {
4004
+ const td = document.createElement("td");
4005
+ td.textContent = cell.trim();
4006
+ td.style.border = "1px solid #d0d7de";
4007
+ td.style.padding = "6px 12px";
4008
+ td.style.whiteSpace = "nowrap";
4009
+ tr.appendChild(td);
4010
+ });
4011
+ table.appendChild(tr);
4012
+ });
4013
+ container.scrollTop = 0;
4014
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
4015
+ };
4016
+ renderPage(1);
3294
4017
  container.appendChild(table);
3295
4018
  ctx.container.appendChild(container);
3296
4019
  let scale = 1;
4020
+ const applyScale = () => {
4021
+ container.style.transform = `scale(${scale})`;
4022
+ container.style.transformOrigin = "top left";
4023
+ };
3297
4024
  const cleanup = () => {
3298
4025
  container.remove();
3299
4026
  ctx.container.innerHTML = "";
@@ -3301,22 +4028,86 @@ var CsvPlugin = class {
3301
4028
  ctx.signal.addEventListener("abort", cleanup);
3302
4029
  return {
3303
4030
  destroy: cleanup,
4031
+ getPageCount: () => totalPages,
4032
+ getCurrentPage: () => currentPage,
4033
+ goToPage: (page) => renderPage(page),
4034
+ getThumbnails: () => {
4035
+ const thumbnails = [];
4036
+ for (let i = 0; i < totalPages; i++) {
4037
+ const pageIdx = i;
4038
+ const startRow = pageIdx * ROWS_PER_PAGE + 1;
4039
+ const endRow = Math.min(dataLines.length, (pageIdx + 1) * ROWS_PER_PAGE);
4040
+ const label = totalPages === 1 ? "Table" : `Page ${pageIdx + 1} (${startRow}-${endRow})`;
4041
+ thumbnails.push({
4042
+ index: pageIdx,
4043
+ label,
4044
+ render: async (canvas) => {
4045
+ canvas.width = 140;
4046
+ canvas.height = 100;
4047
+ const c = canvas.getContext("2d");
4048
+ if (!c) return;
4049
+ c.fillStyle = "#ffffff";
4050
+ c.fillRect(0, 0, canvas.width, canvas.height);
4051
+ c.fillStyle = "#2563eb";
4052
+ c.fillRect(0, 0, canvas.width, 16);
4053
+ c.fillStyle = "#ffffff";
4054
+ c.font = "bold 8px sans-serif";
4055
+ c.fillText(`\u{1F4C8} ${label.slice(0, 18)}`, 6, 11);
4056
+ const startY = 17;
4057
+ const rowH = 13;
4058
+ const colW = 28;
4059
+ const cols = Math.min(4, headerCells.length || 4);
4060
+ c.fillStyle = "#f1f5f9";
4061
+ c.fillRect(0, startY, canvas.width, rowH);
4062
+ c.strokeStyle = "#cbd5e1";
4063
+ c.lineWidth = 0.8;
4064
+ c.strokeRect(0, startY, canvas.width, rowH);
4065
+ c.fillStyle = "#64748b";
4066
+ c.font = "bold 7px sans-serif";
4067
+ for (let ci = 0; ci < cols; ci++) {
4068
+ const hName = headerCells[ci] || `Col ${ci + 1}`;
4069
+ c.fillText(hName.slice(0, 5), 8 + ci * colW, startY + 9);
4070
+ }
4071
+ const rowsToPreview = dataLines.slice(pageIdx * ROWS_PER_PAGE, pageIdx * ROWS_PER_PAGE + 5);
4072
+ for (let ri = 0; ri < rowsToPreview.length; ri++) {
4073
+ const y = startY + (ri + 1) * rowH;
4074
+ if (y > canvas.height - 2) break;
4075
+ c.strokeStyle = "#e2e8f0";
4076
+ c.strokeRect(0, y, canvas.width, rowH);
4077
+ const cVals = rowsToPreview[ri].split(delimiter);
4078
+ c.fillStyle = "#334155";
4079
+ c.font = "6px sans-serif";
4080
+ for (let ci = 0; ci < cols; ci++) {
4081
+ const val = cVals[ci] || "";
4082
+ c.fillText(val.trim().slice(0, 6), 8 + ci * colW, y + 9);
4083
+ }
4084
+ }
4085
+ c.strokeStyle = "#cbd5e1";
4086
+ c.lineWidth = 1;
4087
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4088
+ }
4089
+ });
4090
+ }
4091
+ return thumbnails;
4092
+ },
3304
4093
  zoomIn: () => {
3305
4094
  scale += 0.1;
3306
- container.style.transform = `scale(${scale})`;
4095
+ applyScale();
3307
4096
  },
3308
4097
  zoomOut: () => {
3309
4098
  scale = Math.max(0.2, scale - 0.1);
3310
- container.style.transform = `scale(${scale})`;
4099
+ applyScale();
3311
4100
  },
3312
4101
  getZoom: () => scale,
3313
4102
  setZoom: (level) => {
3314
4103
  scale = level;
3315
- container.style.transform = `scale(${scale})`;
4104
+ applyScale();
3316
4105
  },
3317
4106
  fitToPage: () => {
3318
- scale = 1;
3319
- container.style.transform = `scale(1)`;
4107
+ const availW = Math.max(280, (ctx.container.clientWidth || window.innerWidth) - 48);
4108
+ const tW = table.offsetWidth || 600;
4109
+ scale = Math.max(0.35, Math.min(1, availW / tW));
4110
+ applyScale();
3320
4111
  },
3321
4112
  download: () => {
3322
4113
  const blob = new Blob([ctx.buffer], { type: "text/csv" });
@@ -3380,28 +4171,34 @@ var CodePlugin = class {
3380
4171
  getToolbarActions(instance) {
3381
4172
  const totalPages = instance.getPageCount?.() ?? 1;
3382
4173
  const actions = [];
3383
- if (totalPages > 1) {
3384
- actions.push({
3385
- id: "page-nav",
3386
- icon: "",
3387
- label: "Page Navigation",
3388
- type: "page-nav",
3389
- group: "navigation",
3390
- value: instance.getCurrentPage?.() ?? 1,
3391
- max: totalPages,
3392
- execute: (action, page) => {
3393
- const cur = instance.getCurrentPage?.() ?? 1;
3394
- const max = instance.getPageCount?.() ?? 1;
3395
- if (action === "prev") {
3396
- if (cur > 1) instance.goToPage?.(cur - 1);
3397
- } else if (action === "next") {
3398
- if (cur < max) instance.goToPage?.(cur + 1);
3399
- } else if (typeof page === "number") {
3400
- instance.goToPage?.(page);
3401
- }
4174
+ actions.push({
4175
+ id: "thumbnails",
4176
+ icon: "thumbnails",
4177
+ label: "Page Thumbnails",
4178
+ type: "button",
4179
+ group: "navigation",
4180
+ execute: () => instance.toggleThumbnails?.()
4181
+ });
4182
+ actions.push({
4183
+ id: "page-nav",
4184
+ icon: "",
4185
+ label: "Page Navigation",
4186
+ type: "page-nav",
4187
+ group: "navigation",
4188
+ value: instance.getCurrentPage?.() ?? 1,
4189
+ max: totalPages,
4190
+ execute: (action, page) => {
4191
+ const cur = instance.getCurrentPage?.() ?? 1;
4192
+ const max = instance.getPageCount?.() ?? 1;
4193
+ if (action === "prev") {
4194
+ if (cur > 1) instance.goToPage?.(cur - 1);
4195
+ } else if (action === "next") {
4196
+ if (cur < max) instance.goToPage?.(cur + 1);
4197
+ } else if (typeof page === "number") {
4198
+ instance.goToPage?.(page);
3402
4199
  }
3403
- });
3404
- }
4200
+ }
4201
+ });
3405
4202
  actions.push(
3406
4203
  {
3407
4204
  id: "zoom-out",
@@ -3545,16 +4342,19 @@ var CodePlugin = class {
3545
4342
  let fontSize = 13;
3546
4343
  let rotation = 0;
3547
4344
  let zoomLevel = 1;
4345
+ let isUserZoomed = false;
3548
4346
  const pre = document.createElement("pre");
3549
4347
  const code = document.createElement("code");
3550
4348
  if (isTxt) {
4349
+ container.style.overflowX = "hidden";
4350
+ container.style.overflowY = "auto";
3551
4351
  container.style.backgroundColor = "#f1f5f9";
3552
- container.style.padding = "32px";
4352
+ container.style.padding = "16px 8px";
3553
4353
  container.style.boxSizing = "border-box";
3554
4354
  container.style.display = "flex";
3555
4355
  container.style.flexDirection = "column";
3556
4356
  container.style.alignItems = "center";
3557
- pre.style.margin = "0";
4357
+ pre.style.margin = "0 auto";
3558
4358
  pre.style.fontFamily = "Consolas, 'Courier New', monospace";
3559
4359
  pre.style.fontSize = "13px";
3560
4360
  pre.style.color = "#1e293b";
@@ -3566,10 +4366,12 @@ var CodePlugin = class {
3566
4366
  pre.style.minHeight = "1056px";
3567
4367
  pre.style.padding = "72px 56px";
3568
4368
  pre.style.boxSizing = "border-box";
3569
- pre.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)";
4369
+ pre.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
3570
4370
  pre.style.borderRadius = "4px";
3571
4371
  pre.style.transformOrigin = "top center";
4372
+ pre.style.transition = "transform 0.15s ease";
3572
4373
  } else {
4374
+ container.style.overflow = "auto";
3573
4375
  container.style.backgroundColor = "#1e1e1e";
3574
4376
  container.style.color = "#d4d4d4";
3575
4377
  container.style.padding = "16px";
@@ -3601,42 +4403,9 @@ var CodePlugin = class {
3601
4403
  renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
3602
4404
  pre.appendChild(code);
3603
4405
  container.appendChild(pre);
3604
- let indicator = null;
3605
- if (totalPages > 1) {
3606
- indicator = document.createElement("div");
3607
- indicator.className = "fp-code-page-indicator";
3608
- indicator.style.position = "sticky";
3609
- indicator.style.bottom = "16px";
3610
- if (isTxt) {
3611
- indicator.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
3612
- indicator.style.color = "#334155";
3613
- indicator.style.border = "1px solid #e2e8f0";
3614
- indicator.style.boxShadow = "0 1px 3px rgba(0,0,0,0.1)";
3615
- } else {
3616
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3617
- indicator.style.color = "#f8fafc";
3618
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3619
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3620
- indicator.style.backdropFilter = "blur(8px)";
3621
- }
3622
- indicator.style.fontSize = "12px";
3623
- indicator.style.fontWeight = "600";
3624
- indicator.style.padding = "5px 14px";
3625
- indicator.style.borderRadius = "20px";
3626
- indicator.style.zIndex = "10";
3627
- indicator.style.userSelect = "none";
3628
- indicator.style.pointerEvents = "none";
3629
- indicator.style.textAlign = "center";
3630
- indicator.style.width = "fit-content";
3631
- indicator.style.margin = "16px auto 0";
3632
- container.appendChild(indicator);
3633
- }
3634
4406
  const showPage = (pageNum) => {
3635
4407
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
3636
4408
  renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
3637
- if (indicator) {
3638
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3639
- }
3640
4409
  container.scrollTop = 0;
3641
4410
  ctx.emit("page-change", { page: currentPage, total: totalPages });
3642
4411
  };
@@ -3644,36 +4413,90 @@ var CodePlugin = class {
3644
4413
  showPage(1);
3645
4414
  }
3646
4415
  ctx.container.appendChild(container);
3647
- const cleanup = () => {
3648
- indicator?.remove();
3649
- container.remove();
3650
- ctx.container.innerHTML = "";
4416
+ const calculateTxtFit = () => {
4417
+ const availW = Math.max(280, container.clientWidth - 32);
4418
+ return Math.max(0.4, Math.min(3, availW / 816));
3651
4419
  };
3652
- ctx.signal.addEventListener("abort", cleanup);
3653
4420
  const updateTransform = () => {
3654
4421
  if (isTxt) {
3655
4422
  pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
4423
+ const scaledH = 1056 * zoomLevel;
4424
+ const extraH = Math.max(0, scaledH - 1056);
4425
+ pre.style.marginBottom = `${extraH + 32}px`;
3656
4426
  } else {
3657
4427
  pre.style.transform = `rotate(${rotation}deg)`;
3658
4428
  pre.style.fontSize = `${fontSize}px`;
3659
4429
  }
3660
4430
  };
4431
+ let resizeObserver = null;
4432
+ if (isTxt) {
4433
+ setTimeout(() => {
4434
+ zoomLevel = calculateTxtFit();
4435
+ updateTransform();
4436
+ }, 60);
4437
+ resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
4438
+ if (!isUserZoomed) {
4439
+ zoomLevel = calculateTxtFit();
4440
+ updateTransform();
4441
+ }
4442
+ }) : null;
4443
+ resizeObserver?.observe(container);
4444
+ }
4445
+ const cleanup = () => {
4446
+ resizeObserver?.disconnect();
4447
+ container.remove();
4448
+ ctx.container.innerHTML = "";
4449
+ };
4450
+ ctx.signal.addEventListener("abort", cleanup);
3661
4451
  return {
3662
4452
  destroy: cleanup,
3663
4453
  getPageCount: () => totalPages,
3664
4454
  getCurrentPage: () => currentPage,
3665
4455
  goToPage: (page) => showPage(page),
4456
+ getThumbnails: () => {
4457
+ return rawPages.map((pageText, idx) => ({
4458
+ index: idx,
4459
+ label: `Page ${idx + 1}`,
4460
+ render: async (canvas) => {
4461
+ canvas.width = 140;
4462
+ canvas.height = 180;
4463
+ const c = canvas.getContext("2d");
4464
+ if (!c) return;
4465
+ c.fillStyle = "#ffffff";
4466
+ c.fillRect(0, 0, canvas.width, canvas.height);
4467
+ c.strokeStyle = "#cbd5e1";
4468
+ c.lineWidth = 1;
4469
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4470
+ c.fillStyle = "#64748b";
4471
+ const lines = (pageText || "").split("\n").slice(0, 24);
4472
+ const lineH = 6;
4473
+ const startY = 14;
4474
+ const startX = 12;
4475
+ const maxW = canvas.width - 24;
4476
+ c.font = "5px monospace";
4477
+ for (let li = 0; li < lines.length; li++) {
4478
+ const l = lines[li].trim();
4479
+ if (!l) continue;
4480
+ const y = startY + li * lineH;
4481
+ if (y > canvas.height - 12) break;
4482
+ c.fillText(l.slice(0, 30), startX, y, maxW);
4483
+ }
4484
+ }
4485
+ }));
4486
+ },
3666
4487
  zoomIn: () => {
4488
+ isUserZoomed = true;
3667
4489
  if (isTxt) {
3668
- zoomLevel = Math.min(3, zoomLevel + 0.1);
4490
+ zoomLevel = Math.min(3.5, zoomLevel + 0.15);
3669
4491
  } else {
3670
4492
  fontSize = Math.min(32, fontSize + 2);
3671
4493
  }
3672
4494
  updateTransform();
3673
4495
  },
3674
4496
  zoomOut: () => {
4497
+ isUserZoomed = true;
3675
4498
  if (isTxt) {
3676
- zoomLevel = Math.max(0.1, zoomLevel - 0.1);
4499
+ zoomLevel = Math.max(0.1, zoomLevel - 0.15);
3677
4500
  } else {
3678
4501
  fontSize = Math.max(8, fontSize - 2);
3679
4502
  }
@@ -3681,6 +4504,7 @@ var CodePlugin = class {
3681
4504
  },
3682
4505
  getZoom: () => isTxt ? zoomLevel : fontSize / 13,
3683
4506
  setZoom: (level) => {
4507
+ isUserZoomed = true;
3684
4508
  if (isTxt) {
3685
4509
  zoomLevel = level;
3686
4510
  } else {
@@ -3689,9 +4513,10 @@ var CodePlugin = class {
3689
4513
  updateTransform();
3690
4514
  },
3691
4515
  fitToPage: () => {
4516
+ isUserZoomed = false;
3692
4517
  fontSize = 13;
3693
4518
  rotation = 0;
3694
- zoomLevel = isTxt ? Math.max(0.65, Math.min(1.05, (container.clientWidth - 48) / 816)) : 1;
4519
+ zoomLevel = isTxt ? calculateTxtFit() : 1;
3695
4520
  updateTransform();
3696
4521
  },
3697
4522
  rotateCW: () => {
@@ -4572,28 +5397,26 @@ var RtfPlugin = class {
4572
5397
  getToolbarActions(instance) {
4573
5398
  const totalPages = instance.getPageCount?.() ?? 1;
4574
5399
  const actions = [];
4575
- if (totalPages > 1) {
4576
- actions.push({
4577
- id: "page-nav",
4578
- icon: "",
4579
- label: "Page Navigation",
4580
- type: "page-nav",
4581
- group: "navigation",
4582
- value: instance.getCurrentPage?.() ?? 1,
4583
- max: totalPages,
4584
- execute: (action, page) => {
4585
- const cur = instance.getCurrentPage?.() ?? 1;
4586
- const max = instance.getPageCount?.() ?? 1;
4587
- if (action === "prev") {
4588
- if (cur > 1) instance.goToPage?.(cur - 1);
4589
- } else if (action === "next") {
4590
- if (cur < max) instance.goToPage?.(cur + 1);
4591
- } else if (typeof page === "number") {
4592
- instance.goToPage?.(page);
4593
- }
5400
+ actions.push({
5401
+ id: "page-nav",
5402
+ icon: "",
5403
+ label: "Page Navigation",
5404
+ type: "page-nav",
5405
+ group: "navigation",
5406
+ value: instance.getCurrentPage?.() ?? 1,
5407
+ max: totalPages,
5408
+ execute: (action, page) => {
5409
+ const cur = instance.getCurrentPage?.() ?? 1;
5410
+ const max = instance.getPageCount?.() ?? 1;
5411
+ if (action === "prev") {
5412
+ if (cur > 1) instance.goToPage?.(cur - 1);
5413
+ } else if (action === "next") {
5414
+ if (cur < max) instance.goToPage?.(cur + 1);
5415
+ } else if (typeof page === "number") {
5416
+ instance.goToPage?.(page);
4594
5417
  }
4595
- });
4596
- }
5418
+ }
5419
+ });
4597
5420
  actions.push(
4598
5421
  {
4599
5422
  id: "zoom-out",
@@ -4665,43 +5488,57 @@ var RtfPlugin = class {
4665
5488
  async render(ctx) {
4666
5489
  const wrapper = document.createElement("div");
4667
5490
  wrapper.className = "fp-rtf-wrapper";
4668
- wrapper.style.padding = "32px";
4669
- wrapper.style.maxWidth = "850px";
5491
+ wrapper.style.padding = "0";
5492
+ wrapper.style.width = "816px";
4670
5493
  wrapper.style.margin = "0 auto";
4671
- wrapper.style.backgroundColor = "#fff";
4672
- wrapper.style.boxShadow = "0 2px 8px rgba(0,0,0,0.08)";
4673
- wrapper.style.borderRadius = "4px";
4674
- wrapper.style.minHeight = "100%";
5494
+ wrapper.style.boxSizing = "border-box";
5495
+ wrapper.style.display = "flex";
5496
+ wrapper.style.flexDirection = "column";
5497
+ wrapper.style.alignItems = "center";
5498
+ wrapper.style.backgroundColor = "transparent";
4675
5499
  wrapper.style.transformOrigin = "top center";
4676
- wrapper.style.transition = "transform 0.2s ease";
4677
- ctx.container.style.overflow = "auto";
4678
- ctx.container.style.padding = "24px";
5500
+ wrapper.style.transition = "transform 0.15s ease";
5501
+ ctx.container.style.overflowX = "hidden";
5502
+ ctx.container.style.overflowY = "auto";
5503
+ ctx.container.style.padding = "16px 8px";
4679
5504
  ctx.container.style.backgroundColor = "#f1f5f9";
4680
5505
  ctx.container.appendChild(wrapper);
4681
5506
  let scale = 1;
4682
5507
  let rotation = 0;
4683
5508
  let pageElements = [];
4684
- let fitMode = "width";
5509
+ let isUserZoomed = false;
5510
+ let fitMode = ctx?.options?.fitMode || "width";
4685
5511
  const calculateFitScale = (mode = fitMode) => {
4686
5512
  const activeEl = pageElements[currentPage - 1] || wrapper;
4687
- const elW = activeEl.offsetWidth || 850;
4688
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
4689
- const availW = Math.max(280, ctx.container.clientWidth - 48);
4690
- const availH = Math.max(280, ctx.container.clientHeight - 80);
5513
+ const elW = 816;
5514
+ const singlePageH = activeEl?.offsetHeight || 1056;
5515
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
5516
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
4691
5517
  const sW = availW / elW;
4692
5518
  const sH = availH / singlePageH;
4693
5519
  if (mode === "page") {
4694
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
5520
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
4695
5521
  }
4696
- return Math.max(0.65, Math.min(1.05, sW));
5522
+ return Math.max(0.4, Math.min(3, sW));
4697
5523
  };
4698
5524
  const applyTransform = () => {
4699
5525
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4700
5526
  wrapper.style.transformOrigin = "top center";
5527
+ const activeEl = pageElements[currentPage - 1];
5528
+ const baseH = activeEl?.offsetHeight || 1056;
5529
+ const scaledH = baseH * scale;
5530
+ const extraH = Math.max(0, scaledH - baseH);
5531
+ wrapper.style.marginBottom = `${extraH + 32}px`;
4701
5532
  };
5533
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5534
+ if (!isUserZoomed) {
5535
+ scale = calculateFitScale(fitMode);
5536
+ applyTransform();
5537
+ }
5538
+ }) : null;
5539
+ resizeObserver?.observe(ctx.container);
4702
5540
  setTimeout(() => {
4703
- fitMode = "width";
4704
- scale = calculateFitScale("width");
5541
+ scale = calculateFitScale(fitMode);
4705
5542
  applyTransform();
4706
5543
  }, 60);
4707
5544
  try {
@@ -4711,20 +5548,34 @@ var RtfPlugin = class {
4711
5548
  const doc = new RTFJS__namespace.Document(ctx.buffer, {});
4712
5549
  const htmlElements = await doc.render();
4713
5550
  const contentNodes = [];
4714
- for (const item of htmlElements) {
4715
- if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
4716
- contentNodes.push(...Array.from(item.children));
4717
- } else {
4718
- contentNodes.push(item);
5551
+ const extractBlocks = (nodes) => {
5552
+ for (const item of nodes) {
5553
+ if (!item || !(item instanceof HTMLElement)) continue;
5554
+ const tag = item.tagName.toLowerCase();
5555
+ const hasChildBlocks = Array.from(item.children).some((c) => {
5556
+ const ct = c.tagName.toLowerCase();
5557
+ return ["p", "div", "table", "h1", "h2", "h3", "h4", "h5", "h6", "section", "article", "ul", "ol"].includes(ct);
5558
+ });
5559
+ if (hasChildBlocks && !["table", "tr", "td", "th", "ul", "ol", "li"].includes(tag)) {
5560
+ extractBlocks(Array.from(item.children));
5561
+ } else {
5562
+ if (tag === "p" || tag === "div") {
5563
+ item.style.display = "block";
5564
+ item.style.marginBottom = "12px";
5565
+ item.style.lineHeight = "1.6";
5566
+ }
5567
+ contentNodes.push(item);
5568
+ }
4719
5569
  }
4720
- }
5570
+ };
5571
+ extractBlocks(htmlElements);
4721
5572
  wrapper.innerHTML = "";
4722
5573
  contentNodes.forEach((node) => wrapper.appendChild(node));
4723
5574
  const childHeights = contentNodes.map((c) => {
4724
5575
  const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
4725
5576
  const offH = c.offsetHeight || 0;
4726
5577
  const textLen = c.textContent?.trim().length || 0;
4727
- const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
5578
+ const estH = Math.max(28, Math.ceil(textLen / 75) * 24 + 16);
4728
5579
  return Math.max(rectH, offH, estH);
4729
5580
  });
4730
5581
  wrapper.innerHTML = "";
@@ -4741,6 +5592,7 @@ var RtfPlugin = class {
4741
5592
  card.style.marginBottom = "24px";
4742
5593
  card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4743
5594
  card.style.lineHeight = "1.6";
5595
+ card.style.color = "#1e293b";
4744
5596
  return card;
4745
5597
  };
4746
5598
  let curCard = createRtfCard();
@@ -4776,9 +5628,8 @@ var RtfPlugin = class {
4776
5628
  pageCard.style.padding = "72px 56px";
4777
5629
  pageCard.style.width = "816px";
4778
5630
  pageCard.style.minHeight = "1056px";
4779
- pageCard.style.maxWidth = "100%";
4780
5631
  pageCard.style.boxSizing = "border-box";
4781
- pageCard.style.fontFamily = "serif";
5632
+ pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4782
5633
  pageCard.style.fontSize = "12pt";
4783
5634
  pageCard.style.lineHeight = "1.6";
4784
5635
  pageCard.style.color = "#1e293b";
@@ -4791,29 +5642,6 @@ var RtfPlugin = class {
4791
5642
  }
4792
5643
  const totalPages = Math.max(1, pageElements.length);
4793
5644
  let currentPage = 1;
4794
- let indicator = null;
4795
- if (totalPages > 1) {
4796
- indicator = document.createElement("div");
4797
- indicator.className = "fp-rtf-page-indicator";
4798
- indicator.style.position = "sticky";
4799
- indicator.style.bottom = "16px";
4800
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4801
- indicator.style.backdropFilter = "blur(8px)";
4802
- indicator.style.color = "#f8fafc";
4803
- indicator.style.fontSize = "12px";
4804
- indicator.style.fontWeight = "600";
4805
- indicator.style.padding = "5px 14px";
4806
- indicator.style.borderRadius = "20px";
4807
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4808
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4809
- indicator.style.zIndex = "10";
4810
- indicator.style.userSelect = "none";
4811
- indicator.style.pointerEvents = "none";
4812
- indicator.style.textAlign = "center";
4813
- indicator.style.width = "fit-content";
4814
- indicator.style.margin = "16px auto 0";
4815
- ctx.container.appendChild(indicator);
4816
- }
4817
5645
  const showPage = (pageNum) => {
4818
5646
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4819
5647
  if (totalPages > 1) {
@@ -4821,9 +5649,6 @@ var RtfPlugin = class {
4821
5649
  el.style.display = idx + 1 === currentPage ? "block" : "none";
4822
5650
  });
4823
5651
  }
4824
- if (indicator) {
4825
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4826
- }
4827
5652
  ctx.container.scrollTop = 0;
4828
5653
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4829
5654
  };
@@ -4831,7 +5656,7 @@ var RtfPlugin = class {
4831
5656
  showPage(1);
4832
5657
  }
4833
5658
  const cleanup = () => {
4834
- indicator?.remove();
5659
+ resizeObserver?.disconnect();
4835
5660
  wrapper.remove();
4836
5661
  ctx.container.innerHTML = "";
4837
5662
  };
@@ -4842,21 +5667,25 @@ var RtfPlugin = class {
4842
5667
  getCurrentPage: () => currentPage,
4843
5668
  goToPage: (page) => showPage(page),
4844
5669
  zoomIn: () => {
5670
+ isUserZoomed = true;
4845
5671
  scale += 0.15;
4846
5672
  applyTransform();
4847
5673
  },
4848
5674
  zoomOut: () => {
5675
+ isUserZoomed = true;
4849
5676
  scale = Math.max(0.2, scale - 0.15);
4850
5677
  applyTransform();
4851
5678
  },
4852
5679
  getZoom: () => scale,
4853
5680
  setZoom: (level) => {
5681
+ isUserZoomed = true;
4854
5682
  scale = level;
4855
5683
  applyTransform();
4856
5684
  },
4857
5685
  fitToPage: () => {
4858
- fitMode = fitMode === "width" ? "page" : "width";
4859
- scale = calculateFitScale(fitMode);
5686
+ isUserZoomed = false;
5687
+ fitMode = "width";
5688
+ scale = calculateFitScale("width");
4860
5689
  rotation = 0;
4861
5690
  applyTransform();
4862
5691
  },
@@ -5044,40 +5873,36 @@ var OpenDocumentPlugin = class {
5044
5873
  const isPresentation = instance.isPresentation;
5045
5874
  const totalPages = instance.getPageCount?.() ?? 1;
5046
5875
  const actions = [];
5047
- if (isPresentation || totalPages > 1) {
5048
- if (isPresentation) {
5049
- actions.push({
5050
- id: "thumbnails",
5051
- icon: "thumbnails",
5052
- label: "Slide Thumbnails",
5053
- type: "button",
5054
- group: "navigation",
5055
- execute: () => instance.toggleThumbnails?.()
5056
- });
5057
- }
5058
- actions.push({
5059
- id: "page-nav",
5060
- icon: "",
5061
- label: isPresentation ? "Slide Navigation" : "Page Navigation",
5062
- type: "page-nav",
5063
- group: "navigation",
5064
- value: instance.getCurrentPage?.() ?? 1,
5065
- max: totalPages,
5066
- execute: (action, page) => {
5067
- const cur = instance.getCurrentPage?.() ?? 1;
5068
- const max = instance.getPageCount?.() ?? 1;
5069
- if (action === "prev") {
5070
- if (cur > 1) instance.goToPage?.(cur - 1);
5071
- } else if (action === "next") {
5072
- if (cur < max) instance.goToPage?.(cur + 1);
5073
- } else if (typeof page === "number") {
5074
- instance.goToPage?.(page);
5075
- } else if (typeof action === "number") {
5076
- instance.goToPage?.(action);
5077
- }
5876
+ actions.push({
5877
+ id: "thumbnails",
5878
+ icon: "thumbnails",
5879
+ label: isPresentation ? "Slide Thumbnails" : "Page Thumbnails",
5880
+ type: "button",
5881
+ group: "navigation",
5882
+ execute: () => instance.toggleThumbnails?.()
5883
+ });
5884
+ actions.push({
5885
+ id: "page-nav",
5886
+ icon: "",
5887
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
5888
+ type: "page-nav",
5889
+ group: "navigation",
5890
+ value: instance.getCurrentPage?.() ?? 1,
5891
+ max: totalPages,
5892
+ execute: (action, page) => {
5893
+ const cur = instance.getCurrentPage?.() ?? 1;
5894
+ const max = instance.getPageCount?.() ?? 1;
5895
+ if (action === "prev") {
5896
+ if (cur > 1) instance.goToPage?.(cur - 1);
5897
+ } else if (action === "next") {
5898
+ if (cur < max) instance.goToPage?.(cur + 1);
5899
+ } else if (typeof page === "number") {
5900
+ instance.goToPage?.(page);
5901
+ } else if (typeof action === "number") {
5902
+ instance.goToPage?.(action);
5078
5903
  }
5079
- });
5080
- }
5904
+ }
5905
+ });
5081
5906
  actions.push(
5082
5907
  {
5083
5908
  id: "zoom-out",
@@ -5168,49 +5993,65 @@ var OpenDocumentPlugin = class {
5168
5993
  container.className = "fp-odf-container";
5169
5994
  container.style.width = "100%";
5170
5995
  container.style.height = "100%";
5171
- container.style.overflow = "auto";
5172
- container.style.padding = "24px";
5996
+ container.style.overflowX = "hidden";
5997
+ container.style.overflowY = "auto";
5998
+ container.style.padding = "16px 8px";
5173
5999
  container.style.backgroundColor = "#f1f5f9";
5174
6000
  const wrapper = document.createElement("div");
5175
6001
  wrapper.className = "fp-odf-wrapper";
5176
6002
  wrapper.style.margin = "0 auto";
6003
+ wrapper.style.width = isPresentation ? "960px" : "816px";
6004
+ wrapper.style.boxSizing = "border-box";
5177
6005
  wrapper.style.backgroundColor = "#ffffff";
5178
6006
  wrapper.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
5179
6007
  wrapper.style.borderRadius = "4px";
5180
6008
  wrapper.style.transformOrigin = "top center";
5181
- wrapper.style.transition = "transform 0.2s ease";
6009
+ wrapper.style.transition = "transform 0.15s ease";
5182
6010
  container.appendChild(wrapper);
5183
6011
  ctx.container.appendChild(container);
5184
6012
  let scale = 1;
5185
6013
  let rotation = 0;
5186
- let fitMode = "width";
6014
+ let isUserZoomed = false;
6015
+ let fitMode = ctx?.options?.fitMode || "width";
6016
+ let currentPage = 1;
6017
+ let totalPages = 1;
6018
+ let slides = [];
5187
6019
  const calculateFitScale = (mode = fitMode) => {
5188
- const elW = wrapper.offsetWidth || 850;
5189
- const singlePageH = Math.min(wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
5190
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5191
- const availH = Math.max(280, ctx.container.clientHeight - 80);
6020
+ const activeSlide = slides[currentPage - 1];
6021
+ const elW = isPresentation ? 960 : 816;
6022
+ const singlePageH = Math.min(activeSlide?.offsetHeight || wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
6023
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
6024
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5192
6025
  const sW = availW / elW;
5193
6026
  const sH = availH / (isPresentation ? 540 : singlePageH);
5194
6027
  if (isPresentation) {
5195
- return Math.min(1, Math.min(sW, sH));
6028
+ return Math.min(2.5, Math.min(sW, sH));
5196
6029
  }
5197
6030
  if (mode === "page") {
5198
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
6031
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5199
6032
  }
5200
- return Math.max(0.65, Math.min(1.05, sW));
6033
+ return Math.max(0.4, Math.min(3, sW));
5201
6034
  };
5202
6035
  const applyTransform = () => {
5203
6036
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5204
6037
  wrapper.style.transformOrigin = "top center";
6038
+ const activeSlide = slides[currentPage - 1];
6039
+ const baseH = activeSlide?.offsetHeight || wrapper.offsetHeight || 1056;
6040
+ const scaledH = baseH * scale;
6041
+ const extraH = Math.max(0, scaledH - baseH);
6042
+ wrapper.style.marginBottom = `${extraH + 32}px`;
5205
6043
  };
6044
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6045
+ if (!isUserZoomed) {
6046
+ scale = calculateFitScale(fitMode);
6047
+ applyTransform();
6048
+ }
6049
+ }) : null;
6050
+ resizeObserver?.observe(ctx.container);
5206
6051
  setTimeout(() => {
5207
- fitMode = "width";
5208
- scale = calculateFitScale("width");
6052
+ scale = calculateFitScale(fitMode);
5209
6053
  applyTransform();
5210
6054
  }, 60);
5211
- let currentPage = 1;
5212
- let totalPages = 1;
5213
- let slides = [];
5214
6055
  if (isPresentation) {
5215
6056
  wrapper.style.maxWidth = "960px";
5216
6057
  wrapper.style.aspectRatio = "16 / 9";
@@ -5311,24 +6152,9 @@ var OpenDocumentPlugin = class {
5311
6152
  wrapper.appendChild(page);
5312
6153
  slides.push(page);
5313
6154
  });
5314
- const pageIndicator = document.createElement("div");
5315
- pageIndicator.className = "fp-odt-page-indicator";
5316
- pageIndicator.style.position = "sticky";
5317
- pageIndicator.style.bottom = "16px";
5318
- pageIndicator.style.left = "50%";
5319
- pageIndicator.style.transform = "translateX(-50%)";
5320
- pageIndicator.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
5321
- pageIndicator.style.color = "#fff";
5322
- pageIndicator.style.padding = "6px 12px";
5323
- pageIndicator.style.borderRadius = "16px";
5324
- pageIndicator.style.fontSize = "12px";
5325
- pageIndicator.style.zIndex = "100";
5326
- pageIndicator.style.display = "inline-block";
5327
- pageIndicator.style.width = "fit-content";
5328
- pageIndicator.textContent = `Page 1 of ${totalPages}`;
5329
- container.appendChild(pageIndicator);
5330
6155
  }
5331
6156
  const cleanup = () => {
6157
+ resizeObserver?.disconnect();
5332
6158
  imageUrls.forEach((url) => URL.revokeObjectURL(url));
5333
6159
  container.remove();
5334
6160
  ctx.container.innerHTML = "";
@@ -5340,10 +6166,6 @@ var OpenDocumentPlugin = class {
5340
6166
  slides.forEach((s, idx) => {
5341
6167
  s.style.display = idx === page - 1 ? "block" : "none";
5342
6168
  });
5343
- const indicator = container.querySelector(".fp-odt-page-indicator");
5344
- if (indicator) {
5345
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5346
- }
5347
6169
  container.scrollTop = 0;
5348
6170
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5349
6171
  };
@@ -5351,21 +6173,25 @@ var OpenDocumentPlugin = class {
5351
6173
  destroy: cleanup,
5352
6174
  isPresentation,
5353
6175
  zoomIn: () => {
6176
+ isUserZoomed = true;
5354
6177
  scale += 0.15;
5355
6178
  applyTransform();
5356
6179
  },
5357
6180
  zoomOut: () => {
6181
+ isUserZoomed = true;
5358
6182
  scale = Math.max(0.2, scale - 0.15);
5359
6183
  applyTransform();
5360
6184
  },
5361
6185
  getZoom: () => scale,
5362
6186
  setZoom: (level) => {
6187
+ isUserZoomed = true;
5363
6188
  scale = level;
5364
6189
  applyTransform();
5365
6190
  },
5366
6191
  fitToPage: () => {
5367
- fitMode = fitMode === "width" ? "page" : "width";
5368
- scale = calculateFitScale(fitMode);
6192
+ isUserZoomed = false;
6193
+ fitMode = "width";
6194
+ scale = calculateFitScale("width");
5369
6195
  rotation = 0;
5370
6196
  applyTransform();
5371
6197
  },
@@ -5381,23 +6207,45 @@ var OpenDocumentPlugin = class {
5381
6207
  getPageCount: () => totalPages,
5382
6208
  getCurrentPage: () => currentPage,
5383
6209
  getThumbnails: async () => {
5384
- if (!isPresentation) return [];
5385
- return slides.map((_, idx) => ({
5386
- index: idx,
5387
- label: `Slide ${idx + 1}`,
5388
- render: async (canvas) => {
5389
- const ctx2d = canvas.getContext("2d");
5390
- if (!ctx2d) return;
5391
- canvas.width = 160;
5392
- canvas.height = 90;
5393
- ctx2d.fillStyle = "#f8fafc";
5394
- ctx2d.fillRect(0, 0, 160, 90);
5395
- ctx2d.fillStyle = "#334155";
5396
- ctx2d.font = "bold 12px sans-serif";
5397
- ctx2d.textAlign = "center";
5398
- ctx2d.fillText(`Slide ${idx + 1}`, 80, 50);
5399
- }
5400
- }));
6210
+ const count = totalPages || slides.length || 1;
6211
+ const items = [];
6212
+ for (let idx = 0; idx < count; idx++) {
6213
+ const itemIdx = idx;
6214
+ items.push({
6215
+ index: itemIdx,
6216
+ label: isPresentation ? `Slide ${itemIdx + 1}` : `Page ${itemIdx + 1}`,
6217
+ render: async (canvas) => {
6218
+ const ctx2d = canvas.getContext("2d");
6219
+ if (!ctx2d) return;
6220
+ if (isPresentation) {
6221
+ canvas.width = 160;
6222
+ canvas.height = 90;
6223
+ ctx2d.fillStyle = "#f8fafc";
6224
+ ctx2d.fillRect(0, 0, 160, 90);
6225
+ ctx2d.strokeStyle = "#cbd5e1";
6226
+ ctx2d.lineWidth = 1;
6227
+ ctx2d.strokeRect(0.5, 0.5, 159, 89);
6228
+ ctx2d.fillStyle = "#334155";
6229
+ ctx2d.font = "bold 11px sans-serif";
6230
+ ctx2d.textAlign = "center";
6231
+ ctx2d.fillText(`Slide ${itemIdx + 1}`, 80, 50);
6232
+ } else {
6233
+ canvas.width = 140;
6234
+ canvas.height = 180;
6235
+ ctx2d.fillStyle = "#ffffff";
6236
+ ctx2d.fillRect(0, 0, 140, 180);
6237
+ ctx2d.strokeStyle = "#cbd5e1";
6238
+ ctx2d.lineWidth = 1;
6239
+ ctx2d.strokeRect(0.5, 0.5, 139, 179);
6240
+ ctx2d.fillStyle = "#64748b";
6241
+ ctx2d.font = "bold 10px sans-serif";
6242
+ ctx2d.textAlign = "center";
6243
+ ctx2d.fillText(`Page ${itemIdx + 1}`, 70, 90);
6244
+ }
6245
+ }
6246
+ });
6247
+ }
6248
+ return items;
5401
6249
  },
5402
6250
  download: () => {
5403
6251
  const mimeType = this.mimeTypes.find((m) => m.includes(ext.replace(".", ""))) || "application/octet-stream";
@@ -5502,23 +6350,24 @@ var OpenDocumentPlugin = class {
5502
6350
  case "h": {
5503
6351
  const level = Math.min(6, Math.max(1, Number(node.getAttribute("text:outline-level") || 1)));
5504
6352
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5505
- html += `<h${level} style="${style}; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${node.textContent || ""}</h${level}>`;
6353
+ html += `<h${level} style="${style}; margin: 18px 0 8px; color: #1e3a8a; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${node.textContent || ""}</h${level}>`;
5506
6354
  break;
5507
6355
  }
5508
6356
  case "p": {
5509
6357
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5510
6358
  const inner = this.renderSpans(node, styles, images);
5511
- html += `<p style="${style}; line-height: 1.6; margin: 8px 0; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${inner}</p>`;
6359
+ const isInsideLi = node.parentElement?.localName === "list-item" || node.parentElement?.tagName?.toLowerCase() === "list-item";
6360
+ html += `<p style="${style}; line-height: 1.5; margin: ${isInsideLi ? "0" : "8px 0 12px"}; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${inner}</p>`;
5512
6361
  break;
5513
6362
  }
5514
6363
  case "list": {
5515
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
6364
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
5516
6365
  Array.from(node.children).forEach((c) => walk(c));
5517
6366
  html += "</ul>";
5518
6367
  break;
5519
6368
  }
5520
6369
  case "list-item": {
5521
- html += "<li>";
6370
+ html += '<li style="margin: 4px 0; font-size: 11pt; line-height: 1.5; color: #1e293b;">';
5522
6371
  Array.from(node.children).forEach((c) => walk(c));
5523
6372
  html += "</li>";
5524
6373
  break;
@@ -5644,28 +6493,26 @@ var DocPlugin = class {
5644
6493
  getToolbarActions(instance) {
5645
6494
  const totalPages = instance.getPageCount?.() ?? 1;
5646
6495
  const actions = [];
5647
- if (totalPages > 1) {
5648
- actions.push({
5649
- id: "page-nav",
5650
- icon: "",
5651
- label: "Page Navigation",
5652
- type: "page-nav",
5653
- group: "navigation",
5654
- value: instance.getCurrentPage?.() ?? 1,
5655
- max: totalPages,
5656
- execute: (action, page) => {
5657
- const cur = instance.getCurrentPage?.() ?? 1;
5658
- const max = instance.getPageCount?.() ?? 1;
5659
- if (action === "prev") {
5660
- if (cur > 1) instance.goToPage?.(cur - 1);
5661
- } else if (action === "next") {
5662
- if (cur < max) instance.goToPage?.(cur + 1);
5663
- } else if (typeof page === "number") {
5664
- instance.goToPage?.(page);
5665
- }
6496
+ actions.push({
6497
+ id: "page-nav",
6498
+ icon: "",
6499
+ label: "Page Navigation",
6500
+ type: "page-nav",
6501
+ group: "navigation",
6502
+ value: instance.getCurrentPage?.() ?? 1,
6503
+ max: totalPages,
6504
+ execute: (action, page) => {
6505
+ const cur = instance.getCurrentPage?.() ?? 1;
6506
+ const max = instance.getPageCount?.() ?? 1;
6507
+ if (action === "prev") {
6508
+ if (cur > 1) instance.goToPage?.(cur - 1);
6509
+ } else if (action === "next") {
6510
+ if (cur < max) instance.goToPage?.(cur + 1);
6511
+ } else if (typeof page === "number") {
6512
+ instance.goToPage?.(page);
5666
6513
  }
5667
- });
5668
- }
6514
+ }
6515
+ });
5669
6516
  actions.push(
5670
6517
  {
5671
6518
  id: "zoom-out",
@@ -5739,8 +6586,9 @@ var DocPlugin = class {
5739
6586
  container.className = "fp-doc-container";
5740
6587
  container.style.width = "100%";
5741
6588
  container.style.height = "100%";
5742
- container.style.overflow = "auto";
5743
- container.style.padding = "32px 16px";
6589
+ container.style.overflowX = "hidden";
6590
+ container.style.overflowY = "auto";
6591
+ container.style.padding = "16px 8px";
5744
6592
  container.style.backgroundColor = "#f1f5f9";
5745
6593
  container.style.display = "flex";
5746
6594
  container.style.justifyContent = "center";
@@ -5749,8 +6597,17 @@ var DocPlugin = class {
5749
6597
  let scale = 1;
5750
6598
  let extractedRawText = "";
5751
6599
  let isFallback = false;
6600
+ let chartSvg = "";
5752
6601
  try {
5753
6602
  const cfbf = new CfbfReader(ctx.buffer);
6603
+ try {
6604
+ const pkg = cfbf.readStream("package_stream");
6605
+ if (pkg && pkg.length > 100) {
6606
+ chartSvg = this.parseOdfChartToSvg(pkg);
6607
+ }
6608
+ } catch (chartErr) {
6609
+ console.warn("[DocPlugin] Chart stream parsing info:", chartErr);
6610
+ }
5754
6611
  const wordDocStream = cfbf.readStream("WordDocument");
5755
6612
  if (!wordDocStream || wordDocStream.length < 512) {
5756
6613
  throw new Error("WordDocument stream not found or invalid in CFBF archive");
@@ -5768,7 +6625,7 @@ var DocPlugin = class {
5768
6625
  extractedRawText = fallback;
5769
6626
  isFallback = true;
5770
6627
  }
5771
- const rawPages = this.splitIntoPages(extractedRawText);
6628
+ const rawPages = this.splitIntoPages(extractedRawText, chartSvg);
5772
6629
  const totalPages = Math.max(1, rawPages.length);
5773
6630
  let currentPage = 1;
5774
6631
  const pageCards = [];
@@ -5776,16 +6633,15 @@ var DocPlugin = class {
5776
6633
  const pageCard = document.createElement("div");
5777
6634
  pageCard.className = "fp-doc-page-card";
5778
6635
  pageCard.style.width = "816px";
5779
- pageCard.style.height = "1056px";
6636
+ pageCard.style.minHeight = "1056px";
5780
6637
  pageCard.style.backgroundColor = "#ffffff";
5781
- pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
6638
+ pageCard.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
5782
6639
  pageCard.style.borderRadius = "4px";
5783
- pageCard.style.padding = "96px 72px";
6640
+ pageCard.style.padding = "72px 56px";
5784
6641
  pageCard.style.boxSizing = "border-box";
5785
- pageCard.style.overflow = "hidden";
5786
6642
  pageCard.style.display = i === 0 ? "block" : "none";
5787
6643
  pageCard.style.transformOrigin = "top center";
5788
- pageCard.style.transition = "transform 0.2s ease";
6644
+ pageCard.style.transition = "transform 0.15s ease";
5789
6645
  pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5790
6646
  pageCard.style.color = "#1e293b";
5791
6647
  if (isFallback && i === 0) {
@@ -5802,34 +6658,15 @@ var DocPlugin = class {
5802
6658
  container.appendChild(pageCard);
5803
6659
  pageCards.push(pageCard);
5804
6660
  }
5805
- let indicator = null;
5806
- if (totalPages > 1) {
5807
- indicator = document.createElement("div");
5808
- indicator.className = "fp-doc-page-indicator";
5809
- indicator.style.position = "fixed";
5810
- indicator.style.bottom = "16px";
5811
- indicator.style.left = "50%";
5812
- indicator.style.transform = "translateX(-50%)";
5813
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
5814
- indicator.style.backdropFilter = "blur(8px)";
5815
- indicator.style.color = "#f8fafc";
5816
- indicator.style.fontSize = "12px";
5817
- indicator.style.fontWeight = "600";
5818
- indicator.style.padding = "5px 14px";
5819
- indicator.style.borderRadius = "20px";
5820
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
5821
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
5822
- indicator.style.zIndex = "10";
5823
- indicator.style.userSelect = "none";
5824
- indicator.style.pointerEvents = "none";
5825
- indicator.style.textAlign = "center";
5826
- container.appendChild(indicator);
5827
- }
5828
6661
  let rotation = 0;
5829
6662
  const applyTransform = () => {
5830
6663
  const activeCard = pageCards[currentPage - 1];
5831
6664
  if (activeCard) {
5832
6665
  activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6666
+ const baseH = activeCard.offsetHeight || 1056;
6667
+ const scaledH = baseH * scale;
6668
+ const extraH = Math.max(0, scaledH - baseH);
6669
+ activeCard.style.marginBottom = `${extraH + 32}px`;
5833
6670
  }
5834
6671
  };
5835
6672
  const showPage = (pageNum) => {
@@ -5842,35 +6679,39 @@ var DocPlugin = class {
5842
6679
  card.style.display = "none";
5843
6680
  }
5844
6681
  });
5845
- if (indicator) {
5846
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5847
- }
5848
6682
  container.scrollTop = 0;
5849
6683
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5850
6684
  };
5851
6685
  if (totalPages > 1) {
5852
6686
  showPage(1);
5853
6687
  }
5854
- let fitMode = "width";
6688
+ let fitMode = ctx?.options?.fitMode || "width";
6689
+ let isUserZoomed = false;
5855
6690
  const calculateFitScale = (mode = fitMode) => {
5856
6691
  const elW = 816;
5857
6692
  const elH = 1056;
5858
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5859
- const availH = Math.max(280, ctx.container.clientHeight - 80);
6693
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
6694
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5860
6695
  const sW = availW / elW;
5861
6696
  const sH = availH / elH;
5862
6697
  if (mode === "page") {
5863
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
6698
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5864
6699
  }
5865
- return Math.max(0.65, Math.min(1.05, sW));
6700
+ return Math.max(0.4, Math.min(3, sW));
5866
6701
  };
6702
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6703
+ if (!isUserZoomed) {
6704
+ scale = calculateFitScale(fitMode);
6705
+ applyTransform();
6706
+ }
6707
+ }) : null;
6708
+ resizeObserver?.observe(ctx.container);
5867
6709
  setTimeout(() => {
5868
- fitMode = "width";
5869
- scale = calculateFitScale("width");
6710
+ scale = calculateFitScale(fitMode);
5870
6711
  applyTransform();
5871
6712
  }, 60);
5872
6713
  const cleanup = () => {
5873
- indicator?.remove();
6714
+ resizeObserver?.disconnect();
5874
6715
  container.remove();
5875
6716
  ctx.container.innerHTML = "";
5876
6717
  };
@@ -5881,21 +6722,25 @@ var DocPlugin = class {
5881
6722
  getCurrentPage: () => currentPage,
5882
6723
  goToPage: (page) => showPage(page),
5883
6724
  zoomIn: () => {
6725
+ isUserZoomed = true;
5884
6726
  scale += 0.15;
5885
6727
  applyTransform();
5886
6728
  },
5887
6729
  zoomOut: () => {
6730
+ isUserZoomed = true;
5888
6731
  scale = Math.max(0.2, scale - 0.15);
5889
6732
  applyTransform();
5890
6733
  },
5891
6734
  getZoom: () => scale,
5892
6735
  setZoom: (level) => {
6736
+ isUserZoomed = true;
5893
6737
  scale = level;
5894
6738
  applyTransform();
5895
6739
  },
5896
6740
  fitToPage: () => {
5897
- fitMode = fitMode === "width" ? "page" : "width";
5898
- scale = calculateFitScale(fitMode);
6741
+ isUserZoomed = false;
6742
+ fitMode = "width";
6743
+ scale = calculateFitScale("width");
5899
6744
  rotation = 0;
5900
6745
  applyTransform();
5901
6746
  },
@@ -6010,7 +6855,7 @@ var DocPlugin = class {
6010
6855
  for (const run of [...ansiRuns, ...utf16Runs]) {
6011
6856
  const trimmed = run.trim();
6012
6857
  if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
6013
- if (!trimmed.includes("Normal.dot") && !trimmed.includes("Microsoft Word") && !trimmed.includes("Times New Roman") && !trimmed.startsWith("\xD0\xCF\xE0\xA1\xB1\xE1") && !/^[\W_0-9]+$/.test(trimmed)) {
6858
+ if (!trimmed.includes("Normal.dot") && !trimmed.includes("Microsoft Word") && !trimmed.includes("Times New Roman") && !trimmed.startsWith("\xD0\xCF\xE0\xA1\xB1\xE1") && !/^EMBED\b/i.test(trimmed) && !trimmed.includes("ChartDocument") && !/^[\W_0-9]+$/.test(trimmed)) {
6014
6859
  seen.add(trimmed);
6015
6860
  candidateLines.push(trimmed);
6016
6861
  }
@@ -6021,9 +6866,114 @@ var DocPlugin = class {
6021
6866
  heuristicTextExtraction(buffer) {
6022
6867
  return this.extractStringsFromBytes(new Uint8Array(buffer));
6023
6868
  }
6024
- cleanWordDocFields(text) {
6869
+ /**
6870
+ * Parses an embedded OpenDocument Chart package into a vector SVG bar/column chart
6871
+ */
6872
+ parseOdfChartToSvg(zipBytes) {
6873
+ try {
6874
+ const unzipped = fflate__namespace.unzipSync(zipBytes);
6875
+ const contentXml = unzipped["content.xml"] ? new TextDecoder("utf-8").decode(unzipped["content.xml"]) : "";
6876
+ if (!contentXml) return "";
6877
+ const rowsMatch = contentXml.match(/<table:table-row[\s\S]*?<\/table:table-row>/g) || [];
6878
+ if (rowsMatch.length < 2) return "";
6879
+ const headers = [];
6880
+ const firstRow = rowsMatch[0];
6881
+ const headerCells = firstRow ? firstRow.match(/<text:p>([^<]+)<\/text:p>/g) || [] : [];
6882
+ for (const h of headerCells) {
6883
+ headers.push(h.replace(/<\/?text:p>/g, "").trim());
6884
+ }
6885
+ const categories = [];
6886
+ const seriesValues = headers.map(() => []);
6887
+ for (let r = 1; r < rowsMatch.length; r++) {
6888
+ const rowStr = rowsMatch[r];
6889
+ if (!rowStr) continue;
6890
+ const cells = rowStr.match(/<table:table-cell[\s\S]*?<\/table:table-cell>/g) || [];
6891
+ if (cells.length > 0 && cells[0]) {
6892
+ const catMatch = cells[0].match(/<text:p>([^<]+)<\/text:p>/);
6893
+ categories.push(catMatch ? catMatch[1] : "Row " + r);
6894
+ for (let c = 1; c < cells.length && c - 1 < headers.length; c++) {
6895
+ const cellStr = cells[c];
6896
+ if (!cellStr) continue;
6897
+ const valMatch = cellStr.match(/office:value="([0-9.]+)"/) || cellStr.match(/<text:p>([0-9.]+)<\/text:p>/);
6898
+ const series = seriesValues[c - 1];
6899
+ if (series) {
6900
+ series.push(valMatch ? parseFloat(valMatch[1]) : 0);
6901
+ }
6902
+ }
6903
+ }
6904
+ }
6905
+ const colors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021"];
6906
+ const colorMatches = contentXml.matchAll(/draw:fill-color="(#[0-9a-fA-F]{6})"/g);
6907
+ let cIdx = 0;
6908
+ for (const cm of colorMatches) {
6909
+ if (cIdx < colors.length) colors[cIdx] = cm[1];
6910
+ cIdx++;
6911
+ }
6912
+ let maxVal = 10;
6913
+ for (const s of seriesValues) {
6914
+ for (const v of s) {
6915
+ if (v > maxVal) maxVal = v;
6916
+ }
6917
+ }
6918
+ maxVal = Math.ceil(maxVal * 1.15);
6919
+ const width = 560;
6920
+ const height = 280;
6921
+ const padLeft = 45;
6922
+ const padRight = 100;
6923
+ const padTop = 20;
6924
+ const padBottom = 40;
6925
+ const chartW = width - padLeft - padRight;
6926
+ const chartH = height - padTop - padBottom;
6927
+ let svg = `<svg viewBox="0 0 ${width} ${height}" width="100%" height="auto" style="max-width: 560px; height: 280px; margin: 16px auto; display: block; font-family: Calibri, sans-serif; background: #ffffff; border: 1px solid #e2e8f0; border-radius: 6px; box-shadow: 0 1px 4px rgba(0,0,0,0.05);">`;
6928
+ for (let step = 0; step <= 4; step++) {
6929
+ const yVal = (maxVal / 4 * step).toFixed(1);
6930
+ const yPos = padTop + chartH - step / 4 * chartH;
6931
+ svg += `<line x1="${padLeft}" y1="${yPos}" x2="${padLeft + chartW}" y2="${yPos}" stroke="#e2e8f0" stroke-dasharray="2,2" />`;
6932
+ svg += `<text x="${padLeft - 8}" y="${yPos + 4}" font-size="11" fill="#64748b" text-anchor="end">${yVal}</text>`;
6933
+ }
6934
+ const numCats = categories.length;
6935
+ const numSeries = headers.length;
6936
+ const groupW = chartW / numCats;
6937
+ const barW = Math.max(8, groupW * 0.7 / numSeries);
6938
+ const groupPad = (groupW - barW * numSeries) / 2;
6939
+ for (let catIdx = 0; catIdx < numCats; catIdx++) {
6940
+ const groupX = padLeft + catIdx * groupW + groupPad;
6941
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6942
+ const val = seriesValues[sIdx][catIdx] || 0;
6943
+ const barH = val / maxVal * chartH;
6944
+ const barX = groupX + sIdx * barW;
6945
+ const barY = padTop + chartH - barH;
6946
+ const col = colors[sIdx % colors.length];
6947
+ svg += `<rect x="${barX}" y="${barY}" width="${barW - 2}" height="${barH}" fill="${col}" rx="2"><title>${headers[sIdx]}: ${val}</title></rect>`;
6948
+ }
6949
+ const catX = padLeft + catIdx * groupW + groupW / 2;
6950
+ svg += `<text x="${catX}" y="${padTop + chartH + 18}" font-size="11" fill="#475569" text-anchor="middle">${categories[catIdx]}</text>`;
6951
+ }
6952
+ let legendY = padTop + 20;
6953
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6954
+ const col = colors[sIdx % colors.length];
6955
+ svg += `<rect x="${padLeft + chartW + 15}" y="${legendY}" width="12" height="12" fill="${col}" rx="2" />`;
6956
+ svg += `<text x="${padLeft + chartW + 32}" y="${legendY + 10}" font-size="11" fill="#334155">${headers[sIdx]}</text>`;
6957
+ legendY += 20;
6958
+ }
6959
+ svg += "</svg>";
6960
+ return svg;
6961
+ } catch (e) {
6962
+ console.warn("[DocPlugin] Error generating chart SVG:", e);
6963
+ return "";
6964
+ }
6965
+ }
6966
+ cleanWordDocFields(text, chartSvg = "") {
6025
6967
  if (!text) return "";
6026
6968
  let cleaned = text.replace(
6969
+ /\x13\s*EMBED\b[\s\S]*?\x15/gi,
6970
+ () => chartSvg ? `
6971
+
6972
+ ${chartSvg}
6973
+
6974
+ ` : ""
6975
+ );
6976
+ cleaned = cleaned.replace(
6027
6977
  /\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
6028
6978
  (_match, url, label) => {
6029
6979
  const cleanUrl = url.trim();
@@ -6031,18 +6981,23 @@ var DocPlugin = class {
6031
6981
  return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
6032
6982
  }
6033
6983
  );
6034
- cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, "$1");
6984
+ cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, (_m, res) => {
6985
+ if (/[\x00-\x1F]/.test(res)) return "";
6986
+ return res.trim();
6987
+ });
6035
6988
  cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
6036
6989
  cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
6990
+ cleaned = cleaned.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, "");
6991
+ cleaned = cleaned.replace(/EMBED\s+LibreOffice\.ChartDocument\.[0-9]+/gi, chartSvg || "");
6037
6992
  return cleaned;
6038
6993
  }
6039
- splitIntoPages(text) {
6994
+ splitIntoPages(text, chartSvg = "") {
6040
6995
  if (!text) return [""];
6041
- const cleanedText = this.cleanWordDocFields(text);
6996
+ const cleanedText = this.cleanWordDocFields(text, chartSvg);
6042
6997
  const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
6043
6998
  const explicitParts = normalized.split(/[\x0C\f]|\n\s*[-=_]{3,}\s*(?:PAGE|Page|page break)[\s\d\w-]*[-=_]{3,}\s*\n/i).map((p) => p.trim()).filter((p) => p.length > 0);
6044
6999
  if (explicitParts.length === 0) explicitParts.push(normalized);
6045
- const maxLinesPerPage = 32;
7000
+ const maxLinesPerPage = 34;
6046
7001
  const charsPerLine = 80;
6047
7002
  const finalPages = [];
6048
7003
  for (const part of explicitParts) {
@@ -6050,8 +7005,8 @@ var DocPlugin = class {
6050
7005
  let currentLines = [];
6051
7006
  let count = 0;
6052
7007
  for (const line of lines) {
6053
- const plainLine = line.replace(/<[^>]+>/g, "");
6054
- const vLines = Math.max(1, Math.ceil((plainLine.length || 1) / charsPerLine));
7008
+ const isSvg = line.includes("<svg");
7009
+ const vLines = isSvg ? 12 : Math.max(1, Math.ceil((line.replace(/<[^>]+>/g, "").length || 1) / charsPerLine));
6055
7010
  if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
6056
7011
  finalPages.push(currentLines.join("\n"));
6057
7012
  currentLines = [];
@@ -6091,9 +7046,44 @@ var DocPlugin = class {
6091
7046
  tableLines = [];
6092
7047
  }
6093
7048
  };
7049
+ const sanitizeOptions = {
7050
+ ADD_TAGS: ["a", "svg", "g", "path", "line", "rect", "circle", "text", "title"],
7051
+ ADD_ATTR: [
7052
+ "href",
7053
+ "target",
7054
+ "rel",
7055
+ "style",
7056
+ "viewBox",
7057
+ "width",
7058
+ "height",
7059
+ "x",
7060
+ "y",
7061
+ "x1",
7062
+ "y1",
7063
+ "x2",
7064
+ "y2",
7065
+ "fill",
7066
+ "stroke",
7067
+ "stroke-width",
7068
+ "stroke-dasharray",
7069
+ "rx",
7070
+ "font-size",
7071
+ "text-anchor"
7072
+ ]
7073
+ };
6094
7074
  let i = 0;
6095
7075
  while (i < lines.length) {
6096
7076
  let line = lines[i];
7077
+ if (line.includes("<svg")) {
7078
+ if (inList) {
7079
+ html += "</ul>";
7080
+ inList = false;
7081
+ }
7082
+ flushTable();
7083
+ html += line;
7084
+ i++;
7085
+ continue;
7086
+ }
6097
7087
  let tabCount = (line.match(/\t/g) || []).length;
6098
7088
  if (tabCount > 0) {
6099
7089
  let j = i;
@@ -6127,26 +7117,31 @@ var DocPlugin = class {
6127
7117
  i++;
6128
7118
  continue;
6129
7119
  }
6130
- const sanitizeOptions = { ADD_TAGS: ["a"], ADD_ATTR: ["href", "target", "rel", "style"] };
6131
- if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
7120
+ if (/^\d{1,2}$/.test(line.trim())) {
7121
+ i++;
7122
+ continue;
7123
+ }
7124
+ const isShortLine = line.length < 60 && !line.endsWith(".") && !line.endsWith(",");
7125
+ const isTitleLike = isShortLine && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#") || /^(?:Chapter|Section|Part|\d+\.)\b/i.test(line) || /^[A-Z][a-zA-Z0-9\s#:-]{2,45}$/.test(line));
7126
+ if (isTitleLike) {
6132
7127
  if (inList) {
6133
7128
  html += "</ul>";
6134
7129
  inList = false;
6135
7130
  }
6136
- html += `<h2 style="font-size: 16px; font-weight: 700; color: #1e3a8a; margin: 16px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</h2>`;
7131
+ html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 18px 0 10px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</h2>`;
6137
7132
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
6138
7133
  if (!inList) {
6139
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
7134
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
6140
7135
  inList = true;
6141
7136
  }
6142
7137
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
6143
- html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6__default.default.sanitize(bulletText, sanitizeOptions)}</li>`;
7138
+ html += `<li style="margin: 4px 0; line-height: 1.5; font-size: 11pt; color: #1e293b;">${DOMPurify6__default.default.sanitize(bulletText, sanitizeOptions)}</li>`;
6144
7139
  } else {
6145
7140
  if (inList) {
6146
7141
  html += "</ul>";
6147
7142
  inList = false;
6148
7143
  }
6149
- html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</p>`;
7144
+ html += `<p style="line-height: 1.5; margin: 0 0 12px 0; font-size: 11pt; color: #1e293b; text-align: justify;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</p>`;
6150
7145
  }
6151
7146
  i++;
6152
7147
  }