pptx-react-viewer 1.1.4 → 1.1.5

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.
Files changed (28) hide show
  1. package/dist/index.js +512 -96
  2. package/dist/index.mjs +512 -97
  3. package/dist/viewer/index.js +530 -102
  4. package/dist/viewer/index.mjs +530 -103
  5. package/node_modules/emf-converter/dist/index.d.mts +2 -2
  6. package/node_modules/emf-converter/dist/index.d.ts +2 -2
  7. package/node_modules/emf-converter/dist/index.js +91 -33
  8. package/node_modules/emf-converter/dist/index.mjs +91 -33
  9. package/node_modules/emf-converter/package.json +1 -1
  10. package/node_modules/mtx-decompressor/dist/index.js +39 -9
  11. package/node_modules/mtx-decompressor/dist/index.mjs +39 -9
  12. package/node_modules/mtx-decompressor/package.json +1 -1
  13. package/node_modules/pptx-viewer-core/dist/cli/index.js +0 -0
  14. package/node_modules/pptx-viewer-core/dist/cli/index.mjs +0 -0
  15. package/node_modules/pptx-viewer-core/dist/converter/index.js +0 -0
  16. package/node_modules/pptx-viewer-core/dist/converter/index.mjs +0 -0
  17. package/node_modules/pptx-viewer-core/dist/index.d.mts +95 -11
  18. package/node_modules/pptx-viewer-core/dist/index.d.ts +95 -11
  19. package/node_modules/pptx-viewer-core/dist/index.js +795 -257
  20. package/node_modules/pptx-viewer-core/dist/index.mjs +791 -258
  21. package/node_modules/pptx-viewer-core/dist/{signature-inspection-status-BcJSdOvb.d.mts → signature-inspection-status-BCUpfCQh.d.mts} +13 -2
  22. package/node_modules/pptx-viewer-core/dist/{signature-inspection-status-BcJSdOvb.d.ts → signature-inspection-status-BCUpfCQh.d.ts} +13 -2
  23. package/node_modules/pptx-viewer-core/dist/signature-node/index.d.mts +2 -2
  24. package/node_modules/pptx-viewer-core/dist/signature-node/index.d.ts +2 -2
  25. package/node_modules/pptx-viewer-core/dist/signature-node/index.js +17 -3
  26. package/node_modules/pptx-viewer-core/dist/signature-node/index.mjs +16 -4
  27. package/node_modules/pptx-viewer-core/package.json +1 -1
  28. package/package.json +6 -4
@@ -1,11 +1,12 @@
1
1
  import * as React10 from 'react';
2
- import React10__default, { createContext, useState, useEffect, useMemo, Suspense, useCallback, forwardRef, useRef, useSyncExternalStore, useImperativeHandle, useContext, useLayoutEffect } from 'react';
2
+ import React10__default, { createContext, useState, useEffect, useMemo, Suspense, useCallback, forwardRef, useRef, useSyncExternalStore, useImperativeHandle, useContext, useLayoutEffect, useDeferredValue } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import * as ReactDOM from 'react-dom/client';
5
5
  import { clsx } from 'clsx';
6
6
  import { twMerge } from 'tailwind-merge';
7
7
  import { LuMessageSquare, LuEyeOff, LuSettings, LuX, LuCast, LuShieldCheck, LuPlus, LuPanelLeftClose, LuStickyNote, LuMonitor, LuColumns2, LuPresentation, LuMinus, LuClock, LuDownload, LuTrash2, LuCheck, LuLock, LuEye, LuFileText, LuType, LuLoader, LuShieldAlert, LuSignature, LuInfo, LuTriangleAlert, LuPrinter, LuPenTool, LuWifi, LuWifiOff, LuUsers, LuCopy, LuMonitorOff, LuChevronLeft, LuChevronRight, LuPlay, LuPause, LuPanelLeft, LuUndo, LuRedo, LuSearch, LuShare2, LuPanelRight, LuFolderOpen, LuVideo, LuImage, LuClipboardPaste, LuScissors, LuPaintbrush, LuChevronDown, LuSquare, LuDatabase, LuLayers, LuAArrowUp, LuAArrowDown, LuRemoveFormatting, LuHighlighter, LuList, LuListOrdered, LuIndentDecrease, LuIndentIncrease, LuChevronUp, LuPalette, LuPencil, LuPaintBucket, LuSparkles, LuCaptions, LuSpellCheck, LuGitCompare, LuPipette, LuCaseSensitive, LuReplace, LuTimer, LuMousePointer2, LuEraser, LuGripVertical, LuUpload, LuBold, LuItalic, LuUnderline, LuStrikethrough, LuLink, LuGrid2X2, LuCopyPlus, LuEllipsis, LuCircle, LuMoveRight, LuTriangle, LuDiamond, LuAlignLeft, LuAlignCenter, LuAlignRight, LuAlignJustify, LuSpline, LuSettings2, LuMove, LuRadio, LuArrowDown, LuArrowUp, LuArrowRight, LuArrowLeft, LuReply, LuRotateCw, LuBookmark } from 'react-icons/lu';
8
8
  import { hasShapeProperties, hasTextProperties, SWITCHABLE_LAYOUT_TYPES, isCalloutShape, getCalloutLeaderLineGeometry, buildCalloutLeaderLineSvgPath, getCalloutViewBoxBounds, isInkElement, getLinkedTextBoxSegments, isImageLikeElement, getSubstituteFontFamily, PptxHandler, EncryptedFileError, guidePxToEmu, guideEmuToPx, THEME_COLOR_SCHEME_KEYS, hslToRgb, PRESET_COLOR_MAP, elementActionToPptxAction, mergeShapes, SvgExporter, applyDrawingColorTransforms as applyDrawingColorTransforms$1, getPresetShapeClipPath, svgPathToPolygons, polygonsToSvgPath, EMU_PER_PX as EMU_PER_PX$1, chartDataChangeType, chartDataUpdatePoint, chartDataAddCategory, chartDataRemoveCategory, chartDataAddSeries, chartDataRemoveSeries, getOleObjectTypeLabel, pptxActionToElementAction, hasNonTrivialOverride, COLOR_MAP_ALIAS_KEYS, DEFAULT_COLOR_MAP, addSmartArtNodeAsChild, updateSmartArtNodeText, removeSmartArtNode, switchSmartArtLayout, applyThemeToData, THEME_PRESETS } from 'pptx-viewer-core';
9
+ import DOMPurify from 'dompurify';
9
10
  import { useTranslation } from 'react-i18next';
10
11
  import html2canvasPro from 'html2canvas-pro';
11
12
  import JSZip from 'jszip';
@@ -43570,7 +43571,7 @@ var require_use_sync_external_store_shim_development = __commonJS({
43570
43571
  return x2 === y && (0 !== x2 || 1 / x2 === 1 / y) || x2 !== x2 && y !== y;
43571
43572
  }
43572
43573
  function useSyncExternalStore$2(subscribe3, getSnapshot2) {
43573
- didWarnOld18Alpha || void 0 === React97.startTransition || (didWarnOld18Alpha = true, console.error(
43574
+ didWarnOld18Alpha || void 0 === React100.startTransition || (didWarnOld18Alpha = true, console.error(
43574
43575
  "You are using an outdated, pre-release alpha of React 18 that does not support useSyncExternalStore. The use-sync-external-store shim will not work correctly. Upgrade to a newer pre-release."
43575
43576
  ));
43576
43577
  var value = getSnapshot2();
@@ -43580,7 +43581,7 @@ var require_use_sync_external_store_shim_development = __commonJS({
43580
43581
  "The result of getSnapshot should be cached to avoid an infinite loop"
43581
43582
  ), didWarnUncachedGetSnapshot = true);
43582
43583
  }
43583
- cachedValue = useState85({
43584
+ cachedValue = useState86({
43584
43585
  inst: { value, getSnapshot: getSnapshot2 }
43585
43586
  });
43586
43587
  var inst = cachedValue[0].inst, forceUpdate = cachedValue[1];
@@ -43618,8 +43619,8 @@ var require_use_sync_external_store_shim_development = __commonJS({
43618
43619
  return getSnapshot2();
43619
43620
  }
43620
43621
  "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
43621
- var React97 = __require("react"), objectIs = "function" === typeof Object.is ? Object.is : is2, useState85 = React97.useState, useEffect72 = React97.useEffect, useLayoutEffect7 = React97.useLayoutEffect, useDebugValue = React97.useDebugValue, didWarnOld18Alpha = false, didWarnUncachedGetSnapshot = false, shim = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? useSyncExternalStore$1 : useSyncExternalStore$2;
43622
- exports$1.useSyncExternalStore = void 0 !== React97.useSyncExternalStore ? React97.useSyncExternalStore : shim;
43622
+ var React100 = __require("react"), objectIs = "function" === typeof Object.is ? Object.is : is2, useState86 = React100.useState, useEffect72 = React100.useEffect, useLayoutEffect7 = React100.useLayoutEffect, useDebugValue = React100.useDebugValue, didWarnOld18Alpha = false, didWarnUncachedGetSnapshot = false, shim = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? useSyncExternalStore$1 : useSyncExternalStore$2;
43623
+ exports$1.useSyncExternalStore = void 0 !== React100.useSyncExternalStore ? React100.useSyncExternalStore : shim;
43623
43624
  "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
43624
43625
  })();
43625
43626
  }
@@ -43642,7 +43643,7 @@ var require_with_selector_development = __commonJS({
43642
43643
  return x2 === y && (0 !== x2 || 1 / x2 === 1 / y) || x2 !== x2 && y !== y;
43643
43644
  }
43644
43645
  "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
43645
- var React97 = __require("react"), shim = require_shim(), objectIs = "function" === typeof Object.is ? Object.is : is2, useSyncExternalStore3 = shim.useSyncExternalStore, useRef73 = React97.useRef, useEffect72 = React97.useEffect, useMemo42 = React97.useMemo, useDebugValue = React97.useDebugValue;
43646
+ var React100 = __require("react"), shim = require_shim(), objectIs = "function" === typeof Object.is ? Object.is : is2, useSyncExternalStore3 = shim.useSyncExternalStore, useRef73 = React100.useRef, useEffect72 = React100.useEffect, useMemo42 = React100.useMemo, useDebugValue = React100.useDebugValue;
43646
43647
  exports$1.useSyncExternalStoreWithSelector = function(subscribe3, getSnapshot2, getServerSnapshot2, selector, isEqual) {
43647
43648
  var instRef = useRef73(null);
43648
43649
  if (null === instRef.current) {
@@ -74063,6 +74064,13 @@ function hasDistinctScriptFonts(fonts) {
74063
74064
  }
74064
74065
  return Boolean(fonts.eastAsia) && fonts.eastAsia !== base || Boolean(fonts.complexScript) && fonts.complexScript !== base || Boolean(fonts.symbol) && fonts.symbol !== base;
74065
74066
  }
74067
+ function sanitizeMathMl(markup) {
74068
+ const purify = DOMPurify;
74069
+ if (typeof purify.sanitize !== "function") {
74070
+ return markup;
74071
+ }
74072
+ return purify.sanitize(markup, { USE_PROFILES: { mathMl: true, svg: true } });
74073
+ }
74066
74074
  function renderScriptAwareText(text2, needsScriptFonts, scriptFonts, baseFontFamily, keyPrefix) {
74067
74075
  if (!needsScriptFonts || !text2) {
74068
74076
  return text2;
@@ -74153,14 +74161,15 @@ function renderSegmentContent(elementId, segmentIndex, textValue, lines, needsSc
74153
74161
  }
74154
74162
  function renderEquationSegment(elementId, segmentIndex, equationXml, equationNumber) {
74155
74163
  const mathml = convertOmmlToMathMl(equationXml);
74156
- const equationContent = mathml ? /* @__PURE__ */ jsx(
74164
+ const safeMathml = mathml ? sanitizeMathMl(mathml) : "";
74165
+ const equationContent = safeMathml ? /* @__PURE__ */ jsx(
74157
74166
  "span",
74158
74167
  {
74159
74168
  className: "inline-block align-middle",
74160
74169
  style: {
74161
74170
  fontFamily: '"Cambria Math", "STIX Two Math", serif'
74162
74171
  },
74163
- dangerouslySetInnerHTML: { __html: mathml }
74172
+ dangerouslySetInnerHTML: { __html: safeMathml }
74164
74173
  }
74165
74174
  ) : /* @__PURE__ */ jsx("span", { className: "inline-block px-1 py-0.5 rounded text-xs bg-gray-200/20 text-gray-400 italic", children: "Equation" });
74166
74175
  if (equationNumber) {
@@ -86517,7 +86526,7 @@ function ResizeHandle({
86517
86526
  }
86518
86527
  );
86519
86528
  }
86520
- function SlideThumbnail({
86529
+ function SlideThumbnailImpl({
86521
86530
  slide,
86522
86531
  templateElements,
86523
86532
  canvasSize
@@ -86755,6 +86764,37 @@ function ThumbnailTable({
86755
86764
  }
86756
86765
  return /* @__PURE__ */ jsx("div", { className: "w-full h-full flex items-center justify-center text-[10px] text-muted-foreground pointer-events-none", children: "Table" });
86757
86766
  }
86767
+ function arePropsEqual(prev, next) {
86768
+ if (prev.slide.id !== next.slide.id) {
86769
+ return false;
86770
+ }
86771
+ if (prev.slide.isDirty !== next.slide.isDirty) {
86772
+ return false;
86773
+ }
86774
+ if (prev.slide.hidden !== next.slide.hidden) {
86775
+ return false;
86776
+ }
86777
+ if (prev.slide.elements !== next.slide.elements) {
86778
+ return false;
86779
+ }
86780
+ if (prev.slide.backgroundColor !== next.slide.backgroundColor) {
86781
+ return false;
86782
+ }
86783
+ if (prev.slide.backgroundImage !== next.slide.backgroundImage) {
86784
+ return false;
86785
+ }
86786
+ if (prev.slide.backgroundGradient !== next.slide.backgroundGradient) {
86787
+ return false;
86788
+ }
86789
+ if (prev.templateElements !== next.templateElements) {
86790
+ return false;
86791
+ }
86792
+ if (prev.canvasSize.width !== next.canvasSize.width || prev.canvasSize.height !== next.canvasSize.height) {
86793
+ return false;
86794
+ }
86795
+ return true;
86796
+ }
86797
+ var SlideThumbnail = React10__default.memo(SlideThumbnailImpl, arePropsEqual);
86758
86798
  function ContextMenu({
86759
86799
  contextMenuState,
86760
86800
  mode,
@@ -90488,7 +90528,7 @@ function BendingProcessRenderer({
90488
90528
  }
90489
90529
  );
90490
90530
  }
90491
- function SmartArtRenderer({
90531
+ function SmartArtRendererImpl({
90492
90532
  element: element2,
90493
90533
  className = ""
90494
90534
  }) {
@@ -90599,6 +90639,30 @@ function renderLayout(layoutType, element2, nodes, palette, style) {
90599
90639
  }
90600
90640
  return /* @__PURE__ */ jsx(ListRenderer, { element: element2, nodes, palette, style });
90601
90641
  }
90642
+ function arePropsEqual2(prev, next) {
90643
+ if (prev.className !== next.className) {
90644
+ return false;
90645
+ }
90646
+ if (prev.element.id !== next.element.id) {
90647
+ return false;
90648
+ }
90649
+ if (prev.element.type !== next.element.type) {
90650
+ return false;
90651
+ }
90652
+ if (prev.element.width !== next.element.width || prev.element.height !== next.element.height) {
90653
+ return false;
90654
+ }
90655
+ if (prev.element.x !== next.element.x || prev.element.y !== next.element.y) {
90656
+ return false;
90657
+ }
90658
+ const prevData = prev.element.type === "smartArt" ? prev.element.smartArtData : void 0;
90659
+ const nextData = next.element.type === "smartArt" ? next.element.smartArtData : void 0;
90660
+ if (prevData !== nextData) {
90661
+ return false;
90662
+ }
90663
+ return true;
90664
+ }
90665
+ var SmartArtRenderer = React10__default.memo(SmartArtRendererImpl, arePropsEqual2);
90602
90666
  function ZoomElementRenderer({
90603
90667
  element: element2,
90604
90668
  slides,
@@ -93637,7 +93701,7 @@ function SectionBlock({
93637
93701
  )
93638
93702
  ] });
93639
93703
  }
93640
- function SlideCard({
93704
+ function SlideCardImpl({
93641
93705
  slide,
93642
93706
  index,
93643
93707
  isActive,
@@ -93691,6 +93755,49 @@ function SlideCard({
93691
93755
  }
93692
93756
  );
93693
93757
  }
93758
+ function arePropsEqual3(prev, next) {
93759
+ if (prev.slide.id !== next.slide.id) {
93760
+ return false;
93761
+ }
93762
+ if (prev.slide.isDirty !== next.slide.isDirty) {
93763
+ return false;
93764
+ }
93765
+ if (prev.slide.hidden !== next.slide.hidden) {
93766
+ return false;
93767
+ }
93768
+ if (prev.slide.elements !== next.slide.elements) {
93769
+ return false;
93770
+ }
93771
+ if (prev.index !== next.index) {
93772
+ return false;
93773
+ }
93774
+ if (prev.isActive !== next.isActive) {
93775
+ return false;
93776
+ }
93777
+ if (prev.isDragTarget !== next.isDragTarget) {
93778
+ return false;
93779
+ }
93780
+ if (prev.isSelected !== next.isSelected) {
93781
+ return false;
93782
+ }
93783
+ if (prev.selectedCount !== next.selectedCount) {
93784
+ return false;
93785
+ }
93786
+ if (prev.selectionOrder !== next.selectionOrder) {
93787
+ return false;
93788
+ }
93789
+ if (prev.canEdit !== next.canEdit) {
93790
+ return false;
93791
+ }
93792
+ if (prev.canvasSize.width !== next.canvasSize.width || prev.canvasSize.height !== next.canvasSize.height) {
93793
+ return false;
93794
+ }
93795
+ if (prev.onSlideClick !== next.onSlideClick || prev.onDoubleClick !== next.onDoubleClick || prev.onContextMenu !== next.onContextMenu || prev.onDragStart !== next.onDragStart || prev.onDragOver !== next.onDragOver || prev.onDragLeave !== next.onDragLeave || prev.onDrop !== next.onDrop) {
93796
+ return false;
93797
+ }
93798
+ return true;
93799
+ }
93800
+ var SlideCard = React10__default.memo(SlideCardImpl, arePropsEqual3);
93694
93801
  function SorterContextMenu({
93695
93802
  x: x2,
93696
93803
  y,
@@ -94484,6 +94591,14 @@ function getCurrentParagraphIndex(editorEl, segments) {
94484
94591
  }
94485
94592
  return paraIdx;
94486
94593
  }
94594
+ var CSS_VALUE_SAFE = /^[a-zA-Z0-9 _,.\-+#'%/]{1,100}$/;
94595
+ function isCssValueSafe(value) {
94596
+ if (value === void 0 || value === null) {
94597
+ return false;
94598
+ }
94599
+ const str = String(value);
94600
+ return str.length > 0 && CSS_VALUE_SAFE.test(str);
94601
+ }
94487
94602
  function deriveStyleFromElement(element2, inheritedStyle) {
94488
94603
  const style = { ...inheritedStyle };
94489
94604
  const tagName = element2.tagName.toLowerCase();
@@ -94616,17 +94731,17 @@ function segmentsToEditorHtml(segments) {
94616
94731
  if (segment.style.strikethrough) {
94617
94732
  inlineStyles.push("text-decoration:line-through");
94618
94733
  }
94619
- if (segment.style.color) {
94734
+ if (segment.style.color && isCssValueSafe(segment.style.color)) {
94620
94735
  inlineStyles.push(`color:${segment.style.color}`);
94621
94736
  }
94622
- if (segment.style.fontSize) {
94623
- inlineStyles.push(`font-size:${segment.style.fontSize}pt`);
94737
+ if (segment.style.fontSize && Number.isFinite(Number(segment.style.fontSize))) {
94738
+ inlineStyles.push(`font-size:${Number(segment.style.fontSize)}pt`);
94624
94739
  }
94625
- if (segment.style.fontFamily) {
94740
+ if (segment.style.fontFamily && isCssValueSafe(segment.style.fontFamily)) {
94626
94741
  inlineStyles.push(`font-family:${segment.style.fontFamily}`);
94627
94742
  }
94628
94743
  const text2 = escapeHtml(segment.text);
94629
- if (segment.style.hyperlink) {
94744
+ if (segment.style.hyperlink && isUrlSafe(segment.style.hyperlink)) {
94630
94745
  const href = escapeHtml(segment.style.hyperlink);
94631
94746
  return `<a href="${href}" style="color:#4a9eff;text-decoration:underline;cursor:pointer" data-hyperlink="${href}">${text2}</a>`;
94632
94747
  }
@@ -94709,7 +94824,8 @@ function renderRichNotesSegments(segments) {
94709
94824
  if (segment.style.fontFamily) {
94710
94825
  style.fontFamily = segment.style.fontFamily;
94711
94826
  }
94712
- if (segment.style.hyperlink) {
94827
+ if (segment.style.hyperlink && isUrlSafe(segment.style.hyperlink)) {
94828
+ const safeHref = segment.style.hyperlink;
94713
94829
  style.color = "#4a9eff";
94714
94830
  style.textDecoration = "underline";
94715
94831
  style.cursor = "pointer";
@@ -94717,11 +94833,11 @@ function renderRichNotesSegments(segments) {
94717
94833
  /* @__PURE__ */ jsx(
94718
94834
  "a",
94719
94835
  {
94720
- href: segment.style.hyperlink,
94836
+ href: safeHref,
94721
94837
  style,
94722
94838
  onClick: (e2) => {
94723
94839
  e2.preventDefault();
94724
- window.open(segment.style.hyperlink, "_blank");
94840
+ safeOpenUrl(safeHref);
94725
94841
  },
94726
94842
  children: segment.text
94727
94843
  },
@@ -95273,7 +95389,7 @@ function useSlideNotes({
95273
95389
  const href = target.getAttribute("data-hyperlink") || target.getAttribute("href");
95274
95390
  if (href && (e2.ctrlKey || e2.metaKey)) {
95275
95391
  e2.preventDefault();
95276
- window.open(href, "_blank");
95392
+ safeOpenUrl(href);
95277
95393
  }
95278
95394
  }, []);
95279
95395
  return {
@@ -96946,7 +97062,7 @@ function renderNotesSegments(segments) {
96946
97062
  return React10__default.createElement("span", { key: `seg-${index}`, style }, segment.text);
96947
97063
  });
96948
97064
  }
96949
- function ScaledSlidePreview({
97065
+ function ScaledSlidePreviewImpl({
96950
97066
  slide,
96951
97067
  templateElements,
96952
97068
  canvasSize,
@@ -97093,6 +97209,40 @@ function ScaledSlidePreview({
97093
97209
  }
97094
97210
  );
97095
97211
  }
97212
+ function arePropsEqual4(prev, next) {
97213
+ if (prev.slide.id !== next.slide.id) {
97214
+ return false;
97215
+ }
97216
+ if (prev.slide.isDirty !== next.slide.isDirty) {
97217
+ return false;
97218
+ }
97219
+ if (prev.slide.hidden !== next.slide.hidden) {
97220
+ return false;
97221
+ }
97222
+ if (prev.slide.elements !== next.slide.elements) {
97223
+ return false;
97224
+ }
97225
+ if (prev.slide.backgroundColor !== next.slide.backgroundColor) {
97226
+ return false;
97227
+ }
97228
+ if (prev.slide.backgroundImage !== next.slide.backgroundImage) {
97229
+ return false;
97230
+ }
97231
+ if (prev.slide.backgroundGradient !== next.slide.backgroundGradient) {
97232
+ return false;
97233
+ }
97234
+ if (prev.templateElements !== next.templateElements) {
97235
+ return false;
97236
+ }
97237
+ if (prev.canvasSize.width !== next.canvasSize.width || prev.canvasSize.height !== next.canvasSize.height) {
97238
+ return false;
97239
+ }
97240
+ if (prev.className !== next.className) {
97241
+ return false;
97242
+ }
97243
+ return true;
97244
+ }
97245
+ var ScaledSlidePreview = React10__default.memo(ScaledSlidePreviewImpl, arePropsEqual4);
97096
97246
  function PresenterView({
97097
97247
  slides,
97098
97248
  currentSlideIndex,
@@ -111396,6 +111546,13 @@ function convertOmmlToLatex(omml) {
111396
111546
  }
111397
111547
  return ommlChildrenToLatex(oMath);
111398
111548
  }
111549
+ function sanitizeMathMl2(markup) {
111550
+ const purify = DOMPurify;
111551
+ if (typeof purify.sanitize !== "function") {
111552
+ return markup;
111553
+ }
111554
+ return purify.sanitize(markup, { USE_PROFILES: { mathMl: true, svg: true } });
111555
+ }
111399
111556
  var TEMPLATES = [
111400
111557
  {
111401
111558
  label: "Fraction",
@@ -111461,7 +111618,8 @@ var TEMPLATES = [
111461
111618
  var TEMPLATE_MATHML = TEMPLATES.map((tmpl) => {
111462
111619
  try {
111463
111620
  const tmplOmml = convertLatexToOmml(tmpl.latex);
111464
- return convertOmmlToMathMl(tmplOmml);
111621
+ const raw = convertOmmlToMathMl(tmplOmml);
111622
+ return raw ? sanitizeMathMl2(raw) : "";
111465
111623
  } catch {
111466
111624
  return "";
111467
111625
  }
@@ -111473,7 +111631,7 @@ function MathMlPreview({ mathml }) {
111473
111631
  return;
111474
111632
  }
111475
111633
  if (mathml) {
111476
- containerRef.current.innerHTML = mathml;
111634
+ containerRef.current.innerHTML = sanitizeMathMl2(mathml);
111477
111635
  } else {
111478
111636
  containerRef.current.innerHTML = "";
111479
111637
  }
@@ -111501,6 +111659,7 @@ function EquationEditorDialog({
111501
111659
  return convertOmmlToLatex(existingOmml);
111502
111660
  }, [existingOmml]);
111503
111661
  const [latex, setLatex] = useState(initialLatex);
111662
+ const deferredLatex = useDeferredValue(latex);
111504
111663
  const textareaRef = useRef(null);
111505
111664
  useEffect(() => {
111506
111665
  if (isOpen) {
@@ -111509,17 +111668,17 @@ function EquationEditorDialog({
111509
111668
  }
111510
111669
  }, [isOpen, initialLatex]);
111511
111670
  const { mathml, omml } = useMemo(() => {
111512
- if (!latex.trim()) {
111671
+ if (!deferredLatex.trim()) {
111513
111672
  return { mathml: "", omml: {} };
111514
111673
  }
111515
111674
  try {
111516
- const ommlObj = convertLatexToOmml(latex);
111675
+ const ommlObj = convertLatexToOmml(deferredLatex);
111517
111676
  const mathmlStr = convertOmmlToMathMl(ommlObj);
111518
111677
  return { mathml: mathmlStr, omml: ommlObj };
111519
111678
  } catch {
111520
111679
  return { mathml: "", omml: {} };
111521
111680
  }
111522
- }, [latex]);
111681
+ }, [deferredLatex]);
111523
111682
  const handleInsert = useCallback(() => {
111524
111683
  if (!latex.trim()) {
111525
111684
  return;
@@ -114283,6 +114442,7 @@ function useEditorHistory(input) {
114283
114442
  const historyFutureRef = useRef([]);
114284
114443
  const lastHistorySnapshotRef = useRef(null);
114285
114444
  const lastHistorySerializedRef = useRef("");
114445
+ const lastCheapHashRef = useRef("");
114286
114446
  const isApplyingHistoryRef = useRef(false);
114287
114447
  const unlockHistoryTimerRef = useRef(null);
114288
114448
  const [canUndo, setCanUndo] = useState(false);
@@ -114424,15 +114584,21 @@ function useEditorHistory(input) {
114424
114584
  if (hasActivePointerInteraction()) {
114425
114585
  return;
114426
114586
  }
114587
+ const cheapHash = `${slides.length}|${activeSlideIndex}|${canvasSize.width}x${canvasSize.height}|${slides.map((s) => `${s.id}:${s.elements.length}`).join("/")}`;
114588
+ if (cheapHash === lastCheapHashRef.current) {
114589
+ return;
114590
+ }
114427
114591
  const snapshot2 = buildHistorySnapshot();
114428
114592
  const serialized = JSON.stringify(snapshot2);
114429
114593
  if (serialized === lastHistorySerializedRef.current) {
114594
+ lastCheapHashRef.current = cheapHash;
114430
114595
  return;
114431
114596
  }
114432
114597
  const previousSnapshot = lastHistorySnapshotRef.current;
114433
114598
  if (!previousSnapshot) {
114434
114599
  lastHistorySnapshotRef.current = cloneHistorySnapshot(snapshot2);
114435
114600
  lastHistorySerializedRef.current = serialized;
114601
+ lastCheapHashRef.current = cheapHash;
114436
114602
  updateHistoryAvailability();
114437
114603
  return;
114438
114604
  }
@@ -114443,13 +114609,18 @@ function useEditorHistory(input) {
114443
114609
  historyFutureRef.current = [];
114444
114610
  lastHistorySnapshotRef.current = cloneHistorySnapshot(snapshot2);
114445
114611
  lastHistorySerializedRef.current = serialized;
114612
+ lastCheapHashRef.current = cheapHash;
114446
114613
  updateHistoryAvailability();
114447
114614
  }, [
114615
+ activeSlideIndex,
114448
114616
  buildHistorySnapshot,
114617
+ canvasSize.height,
114618
+ canvasSize.width,
114449
114619
  error2,
114450
114620
  hasActivePointerInteraction,
114451
114621
  loading2,
114452
114622
  pointerCommitNonce,
114623
+ slides,
114453
114624
  updateHistoryAvailability
114454
114625
  ]);
114455
114626
  return {
@@ -118142,6 +118313,7 @@ var DB_NAME2 = "pptx-viewer-audience";
118142
118313
  var DB_VERSION2 = 1;
118143
118314
  var STORE_NAME2 = "content";
118144
118315
  var CONTENT_KEY = "pptx-bytes";
118316
+ var MAX_CONTENT_AGE_MS = 5 * 60 * 1e3;
118145
118317
  function openDb() {
118146
118318
  return new Promise((resolve2, reject) => {
118147
118319
  const request = indexedDB.open(DB_NAME2, DB_VERSION2);
@@ -118161,7 +118333,8 @@ async function storeAudienceContent(content) {
118161
118333
  const tx = db.transaction(STORE_NAME2, "readwrite");
118162
118334
  const store = tx.objectStore(STORE_NAME2);
118163
118335
  const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
118164
- store.put(bytes, CONTENT_KEY);
118336
+ const record = { bytes, createdAt: Date.now() };
118337
+ store.put(record, CONTENT_KEY);
118165
118338
  tx.oncomplete = () => {
118166
118339
  db.close();
118167
118340
  resolve2();
@@ -118182,13 +118355,24 @@ async function loadAudienceContent() {
118182
118355
  request.onsuccess = () => {
118183
118356
  db.close();
118184
118357
  const result = request.result;
118185
- if (result instanceof Uint8Array) {
118186
- resolve2(result);
118187
- } else if (result instanceof ArrayBuffer) {
118188
- resolve2(new Uint8Array(result));
118189
- } else {
118190
- resolve2(null);
118358
+ if (result && typeof result === "object" && "bytes" in result && "createdAt" in result) {
118359
+ const record = result;
118360
+ const age = Date.now() - record.createdAt;
118361
+ if (age > MAX_CONTENT_AGE_MS) {
118362
+ resolve2(null);
118363
+ return;
118364
+ }
118365
+ const raw = record.bytes;
118366
+ if (raw instanceof Uint8Array) {
118367
+ resolve2(raw);
118368
+ } else if (raw instanceof ArrayBuffer) {
118369
+ resolve2(new Uint8Array(raw));
118370
+ } else {
118371
+ resolve2(null);
118372
+ }
118373
+ return;
118191
118374
  }
118375
+ resolve2(null);
118192
118376
  };
118193
118377
  request.onerror = () => {
118194
118378
  db.close();
@@ -118221,14 +118405,34 @@ async function clearAudienceContent() {
118221
118405
  var PRESENTER_CHANNEL_NAME = "pptx-viewer-presenter";
118222
118406
  var AUDIENCE_HASH = "#pptx-audience";
118223
118407
  var PRESENTER_MSG_ORIGIN = "pptx-viewer-presenter";
118408
+ var AUDIENCE_NONCE_KEY = "nonce";
118409
+ function generateSessionId() {
118410
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
118411
+ return crypto.randomUUID();
118412
+ }
118413
+ return `s${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
118414
+ }
118415
+ function parseAudienceNonce() {
118416
+ const hash = window.location.hash;
118417
+ if (!hash.startsWith(AUDIENCE_HASH)) {
118418
+ return null;
118419
+ }
118420
+ const trailing = hash.slice(AUDIENCE_HASH.length);
118421
+ if (!trailing) {
118422
+ return null;
118423
+ }
118424
+ const params2 = new URLSearchParams(trailing.replace(/^[&;?]/, ""));
118425
+ return params2.get(AUDIENCE_NONCE_KEY);
118426
+ }
118224
118427
  function isAudienceTab() {
118225
- return window.location.hash === AUDIENCE_HASH;
118428
+ return window.location.hash.startsWith(AUDIENCE_HASH);
118226
118429
  }
118227
118430
  function usePresenterWindow(input) {
118228
118431
  const { currentSlideIndex, isPresenterMode, content } = input;
118229
118432
  const audienceWindowRef = useRef(null);
118230
118433
  const channelRef = useRef(null);
118231
118434
  const pollTimerRef = useRef(null);
118435
+ const sessionIdRef = useRef("");
118232
118436
  const getChannel2 = useCallback(() => {
118233
118437
  if (!channelRef.current) {
118234
118438
  channelRef.current = new BroadcastChannel(PRESENTER_CHANNEL_NAME);
@@ -118240,10 +118444,14 @@ function usePresenterWindow(input) {
118240
118444
  }, []);
118241
118445
  const syncSlideToAudience = useCallback(
118242
118446
  (slideIndex) => {
118447
+ if (!sessionIdRef.current) {
118448
+ return;
118449
+ }
118243
118450
  const msg = {
118244
118451
  origin: PRESENTER_MSG_ORIGIN,
118245
118452
  type: "presenter-slide-change",
118246
- slideIndex
118453
+ slideIndex,
118454
+ sessionId: sessionIdRef.current
118247
118455
  };
118248
118456
  try {
118249
118457
  getChannel2().postMessage(msg);
@@ -118253,13 +118461,16 @@ function usePresenterWindow(input) {
118253
118461
  [getChannel2]
118254
118462
  );
118255
118463
  const closeAudienceWindow = useCallback(() => {
118256
- try {
118257
- const exitMsg = {
118258
- origin: PRESENTER_MSG_ORIGIN,
118259
- type: "presenter-exit"
118260
- };
118261
- getChannel2().postMessage(exitMsg);
118262
- } catch {
118464
+ if (sessionIdRef.current) {
118465
+ try {
118466
+ const exitMsg = {
118467
+ origin: PRESENTER_MSG_ORIGIN,
118468
+ type: "presenter-exit",
118469
+ sessionId: sessionIdRef.current
118470
+ };
118471
+ getChannel2().postMessage(exitMsg);
118472
+ } catch {
118473
+ }
118263
118474
  }
118264
118475
  const win = audienceWindowRef.current;
118265
118476
  if (win && !win.closed) {
@@ -118269,6 +118480,7 @@ function usePresenterWindow(input) {
118269
118480
  }
118270
118481
  }
118271
118482
  audienceWindowRef.current = null;
118483
+ sessionIdRef.current = "";
118272
118484
  if (pollTimerRef.current !== null) {
118273
118485
  clearInterval(pollTimerRef.current);
118274
118486
  pollTimerRef.current = null;
@@ -118279,20 +118491,53 @@ function usePresenterWindow(input) {
118279
118491
  if (isAudienceWindowOpen()) {
118280
118492
  closeAudienceWindow();
118281
118493
  }
118282
- if (content) {
118283
- void storeAudienceContent(content);
118284
- }
118285
- const url = new URL(window.location.href);
118286
- url.hash = AUDIENCE_HASH;
118287
- const win = window.open(url.toString(), "_blank");
118288
- if (!win) {
118494
+ const blankWin = window.open("about:blank", "_blank");
118495
+ if (!blankWin) {
118289
118496
  return false;
118290
118497
  }
118291
- audienceWindowRef.current = win;
118498
+ audienceWindowRef.current = blankWin;
118499
+ const sessionId = generateSessionId();
118500
+ sessionIdRef.current = sessionId;
118501
+ const audienceUrl = new URL(window.location.href);
118502
+ const params2 = new URLSearchParams();
118503
+ params2.set(AUDIENCE_NONCE_KEY, sessionId);
118504
+ audienceUrl.hash = `${AUDIENCE_HASH}&${params2.toString()}`;
118505
+ const navigateOrClose = (ok) => {
118506
+ const win = audienceWindowRef.current;
118507
+ if (!win || win.closed) {
118508
+ return;
118509
+ }
118510
+ if (!ok) {
118511
+ try {
118512
+ win.close();
118513
+ } catch {
118514
+ }
118515
+ audienceWindowRef.current = null;
118516
+ sessionIdRef.current = "";
118517
+ return;
118518
+ }
118519
+ try {
118520
+ win.location.replace(audienceUrl.toString());
118521
+ } catch {
118522
+ try {
118523
+ win.close();
118524
+ } catch {
118525
+ }
118526
+ audienceWindowRef.current = null;
118527
+ sessionIdRef.current = "";
118528
+ }
118529
+ };
118530
+ if (content) {
118531
+ void storeAudienceContent(content).then(() => navigateOrClose(true)).catch(() => navigateOrClose(false));
118532
+ } else {
118533
+ navigateOrClose(true);
118534
+ }
118292
118535
  window.setTimeout(() => syncSlideToAudience(currentSlideIndex), 1500);
118293
118536
  pollTimerRef.current = setInterval(() => {
118294
- if (win.closed) {
118537
+ const win = audienceWindowRef.current;
118538
+ if (!win || win.closed) {
118295
118539
  audienceWindowRef.current = null;
118540
+ sessionIdRef.current = "";
118296
118541
  if (pollTimerRef.current !== null) {
118297
118542
  clearInterval(pollTimerRef.current);
118298
118543
  pollTimerRef.current = null;
@@ -118320,6 +118565,13 @@ function usePresenterWindow(input) {
118320
118565
  closeAudienceWindow();
118321
118566
  }
118322
118567
  }, [isPresenterMode, closeAudienceWindow]);
118568
+ useEffect(() => {
118569
+ const handleBeforeUnload = () => {
118570
+ void clearAudienceContent();
118571
+ };
118572
+ window.addEventListener("beforeunload", handleBeforeUnload);
118573
+ return () => window.removeEventListener("beforeunload", handleBeforeUnload);
118574
+ }, []);
118323
118575
  return {
118324
118576
  openAudienceWindow,
118325
118577
  closeAudienceWindow,
@@ -118357,6 +118609,7 @@ function useAudienceMode(input) {
118357
118609
  if (!isAudienceTab()) {
118358
118610
  return;
118359
118611
  }
118612
+ const expectedSessionId = parseAudienceNonce();
118360
118613
  let channel;
118361
118614
  try {
118362
118615
  channel = new BroadcastChannel(PRESENTER_CHANNEL_NAME);
@@ -118368,6 +118621,9 @@ function useAudienceMode(input) {
118368
118621
  if (!data || data.origin !== PRESENTER_MSG_ORIGIN) {
118369
118622
  return;
118370
118623
  }
118624
+ if (expectedSessionId && data.sessionId !== expectedSessionId) {
118625
+ return;
118626
+ }
118371
118627
  if (data.type === "presenter-slide-change") {
118372
118628
  onSetActiveSlideIndex(data.slideIndex);
118373
118629
  }
@@ -119301,6 +119557,12 @@ function useTouchGestures(input) {
119301
119557
  callbacksRef.current = callbacks;
119302
119558
  const scaleRef = useRef(currentScale);
119303
119559
  scaleRef.current = currentScale;
119560
+ const [targetVersion, setTargetVersion] = useState(0);
119561
+ const lastTargetRef = useRef(null);
119562
+ if (targetRef.current !== lastTargetRef.current) {
119563
+ lastTargetRef.current = targetRef.current;
119564
+ queueMicrotask(() => setTargetVersion((v) => v + 1));
119565
+ }
119304
119566
  useEffect(() => {
119305
119567
  const el = targetRef.current;
119306
119568
  if (!el || !enabled) {
@@ -119389,7 +119651,7 @@ function useTouchGestures(input) {
119389
119651
  el.removeEventListener("touchcancel", handleTouchCancel);
119390
119652
  cancelLongPress();
119391
119653
  };
119392
- }, [targetRef, enabled]);
119654
+ }, [targetRef, enabled, targetVersion]);
119393
119655
  }
119394
119656
 
119395
119657
  // src/viewer/utils/dom-helpers.ts
@@ -119410,11 +119672,31 @@ function safeConfirm(message) {
119410
119672
  return false;
119411
119673
  }
119412
119674
  }
119675
+ function sanitizeDownloadFilename(input) {
119676
+ if (typeof input !== "string" || input.trim().length === 0) {
119677
+ return "presentation.pptx";
119678
+ }
119679
+ let cleaned = input.replace(/[\x00-\x1f\x7f"\\/:*?<>|]/g, "_").replace(/\.\./g, "__").replace(/^\.+/, "").trim();
119680
+ if (cleaned.length === 0) {
119681
+ return "presentation.pptx";
119682
+ }
119683
+ if (cleaned.length > 200) {
119684
+ const dot = cleaned.lastIndexOf(".");
119685
+ if (dot > 0 && cleaned.length - dot <= 16) {
119686
+ const ext = cleaned.slice(dot);
119687
+ cleaned = cleaned.slice(0, 200 - ext.length) + ext;
119688
+ } else {
119689
+ cleaned = cleaned.slice(0, 200);
119690
+ }
119691
+ }
119692
+ return cleaned;
119693
+ }
119413
119694
  function downloadBlob(blob, filename) {
119695
+ const safeName = sanitizeDownloadFilename(filename);
119414
119696
  const url = URL.createObjectURL(blob);
119415
119697
  const a2 = document.createElement("a");
119416
119698
  a2.href = url;
119417
- a2.download = filename;
119699
+ a2.download = safeName;
119418
119700
  document.body.appendChild(a2);
119419
119701
  a2.click();
119420
119702
  setTimeout(() => {
@@ -119845,27 +120127,82 @@ function openAutosaveDb2() {
119845
120127
  req.onerror = () => reject(req.error);
119846
120128
  });
119847
120129
  }
119848
- async function saveToIndexedDb(filePath, data) {
120130
+ async function deleteOldestAutosaveEntry() {
119849
120131
  const db = await openAutosaveDb2();
119850
- return new Promise((resolve2, reject) => {
119851
- const tx = db.transaction(STORE_NAME3, "readwrite");
119852
- const store = tx.objectStore(STORE_NAME3);
119853
- store.put({
119854
- key: filePath,
119855
- data,
119856
- timestamp: Date.now(),
119857
- size: data.byteLength
119858
- });
119859
- tx.oncomplete = () => {
119860
- db.close();
119861
- resolve2(true);
119862
- };
119863
- tx.onerror = () => {
119864
- db.close();
119865
- reject(tx.error);
119866
- };
120132
+ return new Promise((resolve2) => {
120133
+ try {
120134
+ const tx = db.transaction(STORE_NAME3, "readwrite");
120135
+ const store = tx.objectStore(STORE_NAME3);
120136
+ let oldestKey = null;
120137
+ let oldestTimestamp = Infinity;
120138
+ const cursorReq = store.openCursor();
120139
+ cursorReq.onsuccess = () => {
120140
+ const cursor = cursorReq.result;
120141
+ if (cursor) {
120142
+ const value = cursor.value;
120143
+ if (typeof value.timestamp === "number" && value.timestamp < oldestTimestamp) {
120144
+ oldestTimestamp = value.timestamp;
120145
+ oldestKey = cursor.primaryKey;
120146
+ }
120147
+ cursor.continue();
120148
+ } else if (oldestKey !== null) {
120149
+ store.delete(oldestKey);
120150
+ }
120151
+ };
120152
+ tx.oncomplete = () => {
120153
+ db.close();
120154
+ resolve2(oldestKey !== null);
120155
+ };
120156
+ tx.onerror = () => {
120157
+ db.close();
120158
+ resolve2(false);
120159
+ };
120160
+ } catch {
120161
+ try {
120162
+ db.close();
120163
+ } catch {
120164
+ }
120165
+ resolve2(false);
120166
+ }
119867
120167
  });
119868
120168
  }
120169
+ function putAutosaveRecord(filePath, data) {
120170
+ return openAutosaveDb2().then(
120171
+ (db) => new Promise((resolve2, reject) => {
120172
+ const tx = db.transaction(STORE_NAME3, "readwrite");
120173
+ const store = tx.objectStore(STORE_NAME3);
120174
+ store.put({
120175
+ key: filePath,
120176
+ data,
120177
+ timestamp: Date.now(),
120178
+ size: data.byteLength
120179
+ });
120180
+ tx.oncomplete = () => {
120181
+ db.close();
120182
+ resolve2(true);
120183
+ };
120184
+ tx.onerror = () => {
120185
+ db.close();
120186
+ reject(tx.error);
120187
+ };
120188
+ })
120189
+ );
120190
+ }
120191
+ async function saveToIndexedDb(filePath, data) {
120192
+ try {
120193
+ return await putAutosaveRecord(filePath, data);
120194
+ } catch (err) {
120195
+ const errName = err instanceof Error || err instanceof DOMException ? err.name : "";
120196
+ if (errName !== "QuotaExceededError") {
120197
+ throw err;
120198
+ }
120199
+ const deleted = await deleteOldestAutosaveEntry();
120200
+ if (!deleted) {
120201
+ throw err;
120202
+ }
120203
+ return putAutosaveRecord(filePath, data);
120204
+ }
120205
+ }
119869
120206
  function useAutosave(input) {
119870
120207
  const {
119871
120208
  isDirty,
@@ -119982,6 +120319,25 @@ function collectReferencedFontFamilies(slides) {
119982
120319
  }
119983
120320
  return families;
119984
120321
  }
120322
+ var FONT_NAME_UNSAFE_CHARS = /["\\\n\r;}<>]/;
120323
+ var FONT_FORMAT_ALLOWED = /* @__PURE__ */ new Set([
120324
+ "truetype",
120325
+ "opentype",
120326
+ "woff",
120327
+ "woff2",
120328
+ "svg",
120329
+ "embedded-opentype"
120330
+ ]);
120331
+ var FONT_DATA_URL_PATTERN = /^data:font\/[a-z0-9+.-]+(?:;charset=[a-z0-9-]+)?;base64,[A-Za-z0-9+/=]+$/i;
120332
+ function isFontDataUrlSafe(url) {
120333
+ if (typeof url !== "string" || url.length === 0) {
120334
+ return false;
120335
+ }
120336
+ if (url.startsWith("blob:")) {
120337
+ return true;
120338
+ }
120339
+ return FONT_DATA_URL_PATTERN.test(url);
120340
+ }
119985
120341
  function useFontInjection({ embeddedFonts, slides }) {
119986
120342
  useEffect(() => {
119987
120343
  if (!embeddedFonts.length) {
@@ -119989,17 +120345,28 @@ function useFontInjection({ embeddedFonts, slides }) {
119989
120345
  }
119990
120346
  const styleEl = document.createElement("style");
119991
120347
  styleEl.id = EMBEDDED_FONTS_STYLE_ID;
119992
- const cssRules = embeddedFonts.map((font) => {
120348
+ const cssRules = embeddedFonts.flatMap((font) => {
120349
+ if (typeof font.name !== "string" || font.name.length === 0 || FONT_NAME_UNSAFE_CHARS.test(font.name)) {
120350
+ return [];
120351
+ }
120352
+ if (!isFontDataUrlSafe(font.dataUrl)) {
120353
+ return [];
120354
+ }
120355
+ const fontFormat = font.format ?? "truetype";
120356
+ if (!FONT_FORMAT_ALLOWED.has(fontFormat)) {
120357
+ return [];
120358
+ }
119993
120359
  const fontWeight = font.bold ? "700" : "400";
119994
120360
  const fontStyleCss = font.italic ? "italic" : "normal";
119995
- const fontFormat = font.format ?? "truetype";
119996
- return `@font-face {
120361
+ return [
120362
+ `@font-face {
119997
120363
  font-family: "${font.name}";
119998
120364
  src: url("${font.dataUrl}") format("${fontFormat}");
119999
120365
  font-weight: ${fontWeight};
120000
120366
  font-style: ${fontStyleCss};
120001
120367
  font-display: swap;
120002
- }`;
120368
+ }`
120369
+ ];
120003
120370
  }).join("\n");
120004
120371
  styleEl.textContent = cssRules;
120005
120372
  document.head.appendChild(styleEl);
@@ -120028,7 +120395,7 @@ function useFontInjection({ embeddedFonts, slides }) {
120028
120395
  const linkEl = document.createElement("link");
120029
120396
  linkEl.id = GOOGLE_FONTS_LINK_ID;
120030
120397
  linkEl.rel = "stylesheet";
120031
- linkEl.href = `https://fonts.googleapis.com/css2?${googleFamilies.map((f) => `family=${GOOGLE_FONTS_AVAILABLE[f]}`).join("&")}&display=swap`;
120398
+ linkEl.href = `https://fonts.googleapis.com/css2?${googleFamilies.map((f) => `family=${encodeURIComponent(GOOGLE_FONTS_AVAILABLE[f])}`).join("&")}&display=swap`;
120032
120399
  document.head.appendChild(linkEl);
120033
120400
  return () => {
120034
120401
  const existing = document.getElementById(GOOGLE_FONTS_LINK_ID);
@@ -120044,13 +120411,18 @@ function useFontInjection({ embeddedFonts, slides }) {
120044
120411
  }
120045
120412
  const styleEl = document.createElement("style");
120046
120413
  styleEl.id = SYMBOL_FONTS_STYLE_ID;
120047
- const rules = neededSymbolFonts.map(
120048
- (font) => `@font-face {
120414
+ const rules = neededSymbolFonts.flatMap((font) => {
120415
+ if (typeof font !== "string" || FONT_NAME_UNSAFE_CHARS.test(font)) {
120416
+ return [];
120417
+ }
120418
+ return [
120419
+ `@font-face {
120049
120420
  font-family: "${font}";
120050
120421
  src: local("${font}"), local("${font} Regular");
120051
120422
  font-display: swap;
120052
120423
  }`
120053
- ).join("\n");
120424
+ ];
120425
+ }).join("\n");
120054
120426
  styleEl.textContent = rules;
120055
120427
  document.head.appendChild(styleEl);
120056
120428
  return () => {
@@ -120193,16 +120565,17 @@ function useLoadContent({
120193
120565
  `[pptx] Large file detected (${fileSizeMB.toFixed(1)} MB). Loading may use significant memory.`
120194
120566
  );
120195
120567
  }
120196
- if (handlerRef.current) {
120197
- handlerRef.current.dispose();
120198
- handlerRef.current = null;
120199
- }
120568
+ const previousHandler = handlerRef.current;
120200
120569
  const handler = new PptxHandler();
120201
120570
  const parsed = await handler.load(buffer);
120202
120571
  if (cancelled || token !== renderTokenRef.current) {
120203
120572
  handler.dispose();
120204
120573
  return;
120205
120574
  }
120575
+ if (previousHandler) {
120576
+ previousHandler.dispose();
120577
+ }
120578
+ handlerRef.current = null;
120206
120579
  const mediaElements = [];
120207
120580
  for (const slide of parsed.slides) {
120208
120581
  collectMediaElements(slide.elements, mediaElements);
@@ -120247,6 +120620,7 @@ function useLoadContent({
120247
120620
  })
120248
120621
  );
120249
120622
  const { paths: imagePaths, refs: imageRefs } = collectImagePaths(parsed.slides);
120623
+ let nextSlides = parsed.slides;
120250
120624
  if (imagePaths.size > 0) {
120251
120625
  const resolvedMap = /* @__PURE__ */ new Map();
120252
120626
  await Promise.all(
@@ -120260,15 +120634,47 @@ function useLoadContent({
120260
120634
  }
120261
120635
  })
120262
120636
  );
120637
+ const elementPatches = /* @__PURE__ */ new Map();
120263
120638
  for (const ref of imageRefs) {
120264
120639
  const url = resolvedMap.get(ref.path);
120265
- if (url) {
120266
- ref.element[ref.field] = url;
120640
+ if (!url) {
120641
+ continue;
120267
120642
  }
120643
+ const id2 = ref.element.id;
120644
+ const existing = elementPatches.get(id2) ?? {};
120645
+ existing[ref.field] = url;
120646
+ elementPatches.set(id2, existing);
120647
+ }
120648
+ if (elementPatches.size > 0) {
120649
+ const patchElements = (elements) => {
120650
+ let mutated = false;
120651
+ const next = elements.map((el) => {
120652
+ let updated = el;
120653
+ const patch = elementPatches.get(el.id);
120654
+ if (patch) {
120655
+ updated = { ...el, ...patch };
120656
+ }
120657
+ if (updated.type === "group" && updated.children?.length) {
120658
+ const newChildren = patchElements(updated.children);
120659
+ if (newChildren !== updated.children) {
120660
+ updated = { ...updated, children: newChildren };
120661
+ }
120662
+ }
120663
+ if (updated !== el) {
120664
+ mutated = true;
120665
+ }
120666
+ return updated;
120667
+ });
120668
+ return mutated ? next : elements;
120669
+ };
120670
+ nextSlides = parsed.slides.map((s) => {
120671
+ const newElements = patchElements(s.elements);
120672
+ return newElements === s.elements ? s : { ...s, elements: newElements };
120673
+ });
120268
120674
  }
120269
120675
  }
120270
120676
  handlerRef.current = handler;
120271
- setSlides(parsed.slides);
120677
+ setSlides(nextSlides);
120272
120678
  setTemplateElementsBySlideId({});
120273
120679
  setCanvasSize({
120274
120680
  width: parsed.width ?? DEFAULT_CANVAS_WIDTH,
@@ -121442,7 +121848,19 @@ function injectFontFaces(svg, fontFaces) {
121442
121848
  if (!fontFaces.length) {
121443
121849
  return svg;
121444
121850
  }
121445
- const styleBlock = `<style type="text/css">${fontFaces.map((f) => f.css).join("\n")}</style>`;
121851
+ const safeFontFaces = fontFaces.filter((f) => {
121852
+ if (f.css.toLowerCase().includes("</style")) {
121853
+ console.warn(
121854
+ `[export-svg] Dropping @font-face entry for "${f.family}" containing "</style" \u2014 would break out of the <style> block.`
121855
+ );
121856
+ return false;
121857
+ }
121858
+ return true;
121859
+ });
121860
+ if (!safeFontFaces.length) {
121861
+ return svg;
121862
+ }
121863
+ const styleBlock = `<style type="text/css">${safeFontFaces.map((f) => f.css).join("\n")}</style>`;
121446
121864
  if (svg.includes("<defs>")) {
121447
121865
  return svg.replace("<defs>", `<defs>${styleBlock}`);
121448
121866
  }
@@ -121768,7 +122186,7 @@ function useExportHandlers(input) {
121768
122186
  activeSlideIndexForGuides,
121769
122187
  modalControls
121770
122188
  });
121771
- const handleExportPng = async () => {
122189
+ const handleExportPng = useCallback(async () => {
121772
122190
  const stageEl = canvasStageRef.current;
121773
122191
  if (!stageEl) {
121774
122192
  return;
@@ -121780,8 +122198,8 @@ function useExportHandlers(input) {
121780
122198
  } catch (err) {
121781
122199
  console.error("[PowerPointViewer] PNG export failed:", err);
121782
122200
  }
121783
- };
121784
- const handleExportPdf = async () => {
122201
+ }, [canvasStageRef, activeSlideIndex, activeSlide?.backgroundColor]);
122202
+ const handleExportPdf = useCallback(async () => {
121785
122203
  if (!canvasStageRef.current) {
121786
122204
  return;
121787
122205
  }
@@ -121822,8 +122240,8 @@ function useExportHandlers(input) {
121822
122240
  exportAbortRef.current = null;
121823
122241
  setExportModalOpen(false);
121824
122242
  }
121825
- };
121826
- const handleExportNotesPdf = async () => {
122243
+ }, [canvasStageRef, slides.length, setActiveSlideIndex, activeSlideIndex]);
122244
+ const handleExportNotesPdf = useCallback(async () => {
121827
122245
  if (!canvasStageRef.current) {
121828
122246
  return;
121829
122247
  }
@@ -121866,8 +122284,8 @@ function useExportHandlers(input) {
121866
122284
  exportAbortRef.current = null;
121867
122285
  setExportModalOpen(false);
121868
122286
  }
121869
- };
121870
- const handleCopySlideAsImage = async () => {
122287
+ }, [canvasStageRef, slides, setActiveSlideIndex, activeSlideIndex]);
122288
+ const handleCopySlideAsImage = useCallback(async () => {
121871
122289
  const stageEl = canvasStageRef.current;
121872
122290
  if (!stageEl) {
121873
122291
  return;
@@ -121879,8 +122297,8 @@ function useExportHandlers(input) {
121879
122297
  } catch (err) {
121880
122298
  console.error("[PowerPointViewer] Copy slide as image failed:", err);
121881
122299
  }
121882
- };
121883
- const handleExportVideo = async () => {
122300
+ }, [canvasStageRef, activeSlide?.backgroundColor]);
122301
+ const handleExportVideo = useCallback(async () => {
121884
122302
  if (!canvasStageRef.current) {
121885
122303
  return;
121886
122304
  }
@@ -121922,8 +122340,8 @@ function useExportHandlers(input) {
121922
122340
  exportAbortRef.current = null;
121923
122341
  setExportModalOpen(false);
121924
122342
  }
121925
- };
121926
- const handleExportGif = async () => {
122343
+ }, [canvasStageRef, slides.length, setActiveSlideIndex, activeSlideIndex]);
122344
+ const handleExportGif = useCallback(async () => {
121927
122345
  if (!canvasStageRef.current) {
121928
122346
  return;
121929
122347
  }
@@ -121961,7 +122379,7 @@ function useExportHandlers(input) {
121961
122379
  exportAbortRef.current = null;
121962
122380
  setExportModalOpen(false);
121963
122381
  }
121964
- };
122382
+ }, [canvasStageRef, slides.length, setActiveSlideIndex, activeSlideIndex]);
121965
122383
  const handleCancelExport = useCallback(() => {
121966
122384
  exportAbortRef.current?.abort();
121967
122385
  exportAbortRef.current = null;
@@ -121987,6 +122405,15 @@ function useExportHandlers(input) {
121987
122405
  exportStatusMessage
121988
122406
  };
121989
122407
  }
122408
+ function escapeHtmlAttr(value) {
122409
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
122410
+ }
122411
+ function safeDataImageSrc(src) {
122412
+ if (typeof src !== "string" || !src.startsWith("data:image/")) {
122413
+ return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgAAIAAAUAAen63NgAAAAASUVORK5CYII=";
122414
+ }
122415
+ return escapeHtmlAttr(src);
122416
+ }
121990
122417
  function openPrintWindow(title, bodyHtml, orientation, colorFilter, frameSlides) {
121991
122418
  const printWindow = window.open("", "_blank", "noopener,noreferrer");
121992
122419
  if (!printWindow) {
@@ -122169,7 +122596,7 @@ function usePrintHandlers(input) {
122169
122596
  const slideImages = slideIndices.map((idx) => allImages[idx]).filter(Boolean);
122170
122597
  if (settings.printWhat === "slides") {
122171
122598
  const bodyHtml = slideImages.map(
122172
- (img, i3) => `<section class="page slide-page"><img class="slide-img" src="${img}" alt="Slide ${slideIndices[i3] + 1}" /></section>`
122599
+ (img, i3) => `<section class="page slide-page"><img class="slide-img" src="${safeDataImageSrc(img)}" alt="Slide ${slideIndices[i3] + 1}" /></section>`
122173
122600
  ).join("");
122174
122601
  openPrintWindow(
122175
122602
  "Slides",
@@ -122185,7 +122612,7 @@ function usePrintHandlers(input) {
122185
122612
  const idx = slideIndices[i3];
122186
122613
  const notes = slides[idx]?.notes?.trim() || "";
122187
122614
  return `<section class="page notes-page">
122188
- <img class="notes-slide" src="${img}" alt="Slide ${idx + 1}" />
122615
+ <img class="notes-slide" src="${safeDataImageSrc(img)}" alt="Slide ${idx + 1}" />
122189
122616
  <div class="notes-text">${escapeHtml2(notes)}</div>
122190
122617
  </section>`;
122191
122618
  }).join("");
@@ -122217,14 +122644,14 @@ function usePrintHandlers(input) {
122217
122644
  if (isThreePerPage) {
122218
122645
  const rows = Array.from({ length: spp }, (_, cellIndex) => {
122219
122646
  const img = pageImgs[cellIndex];
122220
- const slideCell = img ? `<div class="handout-cell"><img src="${img}" alt="Slide ${slideIndices[i3 + cellIndex] + 1}" /></div>` : `<div class="handout-cell"></div>`;
122647
+ const slideCell = img ? `<div class="handout-cell"><img src="${safeDataImageSrc(img)}" alt="Slide ${slideIndices[i3 + cellIndex] + 1}" /></div>` : `<div class="handout-cell"></div>`;
122221
122648
  return `<div class="handout-row-3">${slideCell}${buildNoteLines()}</div>`;
122222
122649
  }).join("");
122223
122650
  pages.push(`<section class="page"><div class="handout-grid-3">${rows}</div></section>`);
122224
122651
  } else {
122225
122652
  const cells = Array.from({ length: spp }, (_, cellIndex) => {
122226
122653
  const img = pageImgs[cellIndex];
122227
- return img ? `<div class="handout-cell"><img src="${img}" alt="Slide ${slideIndices[i3 + cellIndex] + 1}" /></div>` : `<div class="handout-cell"></div>`;
122654
+ return img ? `<div class="handout-cell"><img src="${safeDataImageSrc(img)}" alt="Slide ${slideIndices[i3 + cellIndex] + 1}" /></div>` : `<div class="handout-cell"></div>`;
122228
122655
  }).join("");
122229
122656
  pages.push(
122230
122657
  `<section class="page"><div class="handout-grid" style="grid-template-columns: repeat(${grid.columns}, minmax(0, 1fr)); grid-template-rows: repeat(${grid.rows}, minmax(0, 1fr));">${cells}</div></section>`