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
package/dist/index.mjs CHANGED
@@ -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 } 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, useMemo41 = 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, useMemo41 = 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) {
@@ -74074,6 +74075,13 @@ function hasDistinctScriptFonts(fonts) {
74074
74075
  }
74075
74076
  return Boolean(fonts.eastAsia) && fonts.eastAsia !== base || Boolean(fonts.complexScript) && fonts.complexScript !== base || Boolean(fonts.symbol) && fonts.symbol !== base;
74076
74077
  }
74078
+ function sanitizeMathMl(markup) {
74079
+ const purify = DOMPurify;
74080
+ if (typeof purify.sanitize !== "function") {
74081
+ return markup;
74082
+ }
74083
+ return purify.sanitize(markup, { USE_PROFILES: { mathMl: true, svg: true } });
74084
+ }
74077
74085
  function renderScriptAwareText(text2, needsScriptFonts, scriptFonts, baseFontFamily, keyPrefix) {
74078
74086
  if (!needsScriptFonts || !text2) {
74079
74087
  return text2;
@@ -74164,14 +74172,15 @@ function renderSegmentContent(elementId, segmentIndex, textValue, lines, needsSc
74164
74172
  }
74165
74173
  function renderEquationSegment(elementId, segmentIndex, equationXml, equationNumber) {
74166
74174
  const mathml = convertOmmlToMathMl(equationXml);
74167
- const equationContent = mathml ? /* @__PURE__ */ jsx(
74175
+ const safeMathml = mathml ? sanitizeMathMl(mathml) : "";
74176
+ const equationContent = safeMathml ? /* @__PURE__ */ jsx(
74168
74177
  "span",
74169
74178
  {
74170
74179
  className: "inline-block align-middle",
74171
74180
  style: {
74172
74181
  fontFamily: '"Cambria Math", "STIX Two Math", serif'
74173
74182
  },
74174
- dangerouslySetInnerHTML: { __html: mathml }
74183
+ dangerouslySetInnerHTML: { __html: safeMathml }
74175
74184
  }
74176
74185
  ) : /* @__PURE__ */ jsx("span", { className: "inline-block px-1 py-0.5 rounded text-xs bg-gray-200/20 text-gray-400 italic", children: "Equation" });
74177
74186
  if (equationNumber) {
@@ -86528,7 +86537,7 @@ function ResizeHandle({
86528
86537
  }
86529
86538
  );
86530
86539
  }
86531
- function SlideThumbnail({
86540
+ function SlideThumbnailImpl({
86532
86541
  slide,
86533
86542
  templateElements,
86534
86543
  canvasSize
@@ -86766,6 +86775,37 @@ function ThumbnailTable({
86766
86775
  }
86767
86776
  return /* @__PURE__ */ jsx("div", { className: "w-full h-full flex items-center justify-center text-[10px] text-muted-foreground pointer-events-none", children: "Table" });
86768
86777
  }
86778
+ function arePropsEqual(prev, next) {
86779
+ if (prev.slide.id !== next.slide.id) {
86780
+ return false;
86781
+ }
86782
+ if (prev.slide.isDirty !== next.slide.isDirty) {
86783
+ return false;
86784
+ }
86785
+ if (prev.slide.hidden !== next.slide.hidden) {
86786
+ return false;
86787
+ }
86788
+ if (prev.slide.elements !== next.slide.elements) {
86789
+ return false;
86790
+ }
86791
+ if (prev.slide.backgroundColor !== next.slide.backgroundColor) {
86792
+ return false;
86793
+ }
86794
+ if (prev.slide.backgroundImage !== next.slide.backgroundImage) {
86795
+ return false;
86796
+ }
86797
+ if (prev.slide.backgroundGradient !== next.slide.backgroundGradient) {
86798
+ return false;
86799
+ }
86800
+ if (prev.templateElements !== next.templateElements) {
86801
+ return false;
86802
+ }
86803
+ if (prev.canvasSize.width !== next.canvasSize.width || prev.canvasSize.height !== next.canvasSize.height) {
86804
+ return false;
86805
+ }
86806
+ return true;
86807
+ }
86808
+ var SlideThumbnail = React10__default.memo(SlideThumbnailImpl, arePropsEqual);
86769
86809
  function ContextMenu({
86770
86810
  contextMenuState,
86771
86811
  mode,
@@ -90499,7 +90539,7 @@ function BendingProcessRenderer({
90499
90539
  }
90500
90540
  );
90501
90541
  }
90502
- function SmartArtRenderer({
90542
+ function SmartArtRendererImpl({
90503
90543
  element: element2,
90504
90544
  className = ""
90505
90545
  }) {
@@ -90610,6 +90650,30 @@ function renderLayout(layoutType, element2, nodes, palette, style) {
90610
90650
  }
90611
90651
  return /* @__PURE__ */ jsx(ListRenderer, { element: element2, nodes, palette, style });
90612
90652
  }
90653
+ function arePropsEqual2(prev, next) {
90654
+ if (prev.className !== next.className) {
90655
+ return false;
90656
+ }
90657
+ if (prev.element.id !== next.element.id) {
90658
+ return false;
90659
+ }
90660
+ if (prev.element.type !== next.element.type) {
90661
+ return false;
90662
+ }
90663
+ if (prev.element.width !== next.element.width || prev.element.height !== next.element.height) {
90664
+ return false;
90665
+ }
90666
+ if (prev.element.x !== next.element.x || prev.element.y !== next.element.y) {
90667
+ return false;
90668
+ }
90669
+ const prevData = prev.element.type === "smartArt" ? prev.element.smartArtData : void 0;
90670
+ const nextData = next.element.type === "smartArt" ? next.element.smartArtData : void 0;
90671
+ if (prevData !== nextData) {
90672
+ return false;
90673
+ }
90674
+ return true;
90675
+ }
90676
+ var SmartArtRenderer = React10__default.memo(SmartArtRendererImpl, arePropsEqual2);
90613
90677
  function ZoomElementRenderer({
90614
90678
  element: element2,
90615
90679
  slides,
@@ -93553,7 +93617,7 @@ function SectionBlock({
93553
93617
  )
93554
93618
  ] });
93555
93619
  }
93556
- function SlideCard({
93620
+ function SlideCardImpl({
93557
93621
  slide,
93558
93622
  index,
93559
93623
  isActive,
@@ -93607,6 +93671,49 @@ function SlideCard({
93607
93671
  }
93608
93672
  );
93609
93673
  }
93674
+ function arePropsEqual3(prev, next) {
93675
+ if (prev.slide.id !== next.slide.id) {
93676
+ return false;
93677
+ }
93678
+ if (prev.slide.isDirty !== next.slide.isDirty) {
93679
+ return false;
93680
+ }
93681
+ if (prev.slide.hidden !== next.slide.hidden) {
93682
+ return false;
93683
+ }
93684
+ if (prev.slide.elements !== next.slide.elements) {
93685
+ return false;
93686
+ }
93687
+ if (prev.index !== next.index) {
93688
+ return false;
93689
+ }
93690
+ if (prev.isActive !== next.isActive) {
93691
+ return false;
93692
+ }
93693
+ if (prev.isDragTarget !== next.isDragTarget) {
93694
+ return false;
93695
+ }
93696
+ if (prev.isSelected !== next.isSelected) {
93697
+ return false;
93698
+ }
93699
+ if (prev.selectedCount !== next.selectedCount) {
93700
+ return false;
93701
+ }
93702
+ if (prev.selectionOrder !== next.selectionOrder) {
93703
+ return false;
93704
+ }
93705
+ if (prev.canEdit !== next.canEdit) {
93706
+ return false;
93707
+ }
93708
+ if (prev.canvasSize.width !== next.canvasSize.width || prev.canvasSize.height !== next.canvasSize.height) {
93709
+ return false;
93710
+ }
93711
+ 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) {
93712
+ return false;
93713
+ }
93714
+ return true;
93715
+ }
93716
+ var SlideCard = React10__default.memo(SlideCardImpl, arePropsEqual3);
93610
93717
  function SorterContextMenu({
93611
93718
  x: x2,
93612
93719
  y,
@@ -94400,6 +94507,14 @@ function getCurrentParagraphIndex(editorEl, segments) {
94400
94507
  }
94401
94508
  return paraIdx;
94402
94509
  }
94510
+ var CSS_VALUE_SAFE = /^[a-zA-Z0-9 _,.\-+#'%/]{1,100}$/;
94511
+ function isCssValueSafe(value) {
94512
+ if (value === void 0 || value === null) {
94513
+ return false;
94514
+ }
94515
+ const str = String(value);
94516
+ return str.length > 0 && CSS_VALUE_SAFE.test(str);
94517
+ }
94403
94518
  function deriveStyleFromElement(element2, inheritedStyle) {
94404
94519
  const style = { ...inheritedStyle };
94405
94520
  const tagName = element2.tagName.toLowerCase();
@@ -94532,17 +94647,17 @@ function segmentsToEditorHtml(segments) {
94532
94647
  if (segment.style.strikethrough) {
94533
94648
  inlineStyles.push("text-decoration:line-through");
94534
94649
  }
94535
- if (segment.style.color) {
94650
+ if (segment.style.color && isCssValueSafe(segment.style.color)) {
94536
94651
  inlineStyles.push(`color:${segment.style.color}`);
94537
94652
  }
94538
- if (segment.style.fontSize) {
94539
- inlineStyles.push(`font-size:${segment.style.fontSize}pt`);
94653
+ if (segment.style.fontSize && Number.isFinite(Number(segment.style.fontSize))) {
94654
+ inlineStyles.push(`font-size:${Number(segment.style.fontSize)}pt`);
94540
94655
  }
94541
- if (segment.style.fontFamily) {
94656
+ if (segment.style.fontFamily && isCssValueSafe(segment.style.fontFamily)) {
94542
94657
  inlineStyles.push(`font-family:${segment.style.fontFamily}`);
94543
94658
  }
94544
94659
  const text2 = escapeHtml(segment.text);
94545
- if (segment.style.hyperlink) {
94660
+ if (segment.style.hyperlink && isUrlSafe(segment.style.hyperlink)) {
94546
94661
  const href = escapeHtml(segment.style.hyperlink);
94547
94662
  return `<a href="${href}" style="color:#4a9eff;text-decoration:underline;cursor:pointer" data-hyperlink="${href}">${text2}</a>`;
94548
94663
  }
@@ -94625,7 +94740,8 @@ function renderRichNotesSegments(segments) {
94625
94740
  if (segment.style.fontFamily) {
94626
94741
  style.fontFamily = segment.style.fontFamily;
94627
94742
  }
94628
- if (segment.style.hyperlink) {
94743
+ if (segment.style.hyperlink && isUrlSafe(segment.style.hyperlink)) {
94744
+ const safeHref = segment.style.hyperlink;
94629
94745
  style.color = "#4a9eff";
94630
94746
  style.textDecoration = "underline";
94631
94747
  style.cursor = "pointer";
@@ -94633,11 +94749,11 @@ function renderRichNotesSegments(segments) {
94633
94749
  /* @__PURE__ */ jsx(
94634
94750
  "a",
94635
94751
  {
94636
- href: segment.style.hyperlink,
94752
+ href: safeHref,
94637
94753
  style,
94638
94754
  onClick: (e2) => {
94639
94755
  e2.preventDefault();
94640
- window.open(segment.style.hyperlink, "_blank");
94756
+ safeOpenUrl(safeHref);
94641
94757
  },
94642
94758
  children: segment.text
94643
94759
  },
@@ -95189,7 +95305,7 @@ function useSlideNotes({
95189
95305
  const href = target.getAttribute("data-hyperlink") || target.getAttribute("href");
95190
95306
  if (href && (e2.ctrlKey || e2.metaKey)) {
95191
95307
  e2.preventDefault();
95192
- window.open(href, "_blank");
95308
+ safeOpenUrl(href);
95193
95309
  }
95194
95310
  }, []);
95195
95311
  return {
@@ -96862,7 +96978,7 @@ function renderNotesSegments(segments) {
96862
96978
  return React10__default.createElement("span", { key: `seg-${index}`, style }, segment.text);
96863
96979
  });
96864
96980
  }
96865
- function ScaledSlidePreview({
96981
+ function ScaledSlidePreviewImpl({
96866
96982
  slide,
96867
96983
  templateElements,
96868
96984
  canvasSize,
@@ -97009,6 +97125,40 @@ function ScaledSlidePreview({
97009
97125
  }
97010
97126
  );
97011
97127
  }
97128
+ function arePropsEqual4(prev, next) {
97129
+ if (prev.slide.id !== next.slide.id) {
97130
+ return false;
97131
+ }
97132
+ if (prev.slide.isDirty !== next.slide.isDirty) {
97133
+ return false;
97134
+ }
97135
+ if (prev.slide.hidden !== next.slide.hidden) {
97136
+ return false;
97137
+ }
97138
+ if (prev.slide.elements !== next.slide.elements) {
97139
+ return false;
97140
+ }
97141
+ if (prev.slide.backgroundColor !== next.slide.backgroundColor) {
97142
+ return false;
97143
+ }
97144
+ if (prev.slide.backgroundImage !== next.slide.backgroundImage) {
97145
+ return false;
97146
+ }
97147
+ if (prev.slide.backgroundGradient !== next.slide.backgroundGradient) {
97148
+ return false;
97149
+ }
97150
+ if (prev.templateElements !== next.templateElements) {
97151
+ return false;
97152
+ }
97153
+ if (prev.canvasSize.width !== next.canvasSize.width || prev.canvasSize.height !== next.canvasSize.height) {
97154
+ return false;
97155
+ }
97156
+ if (prev.className !== next.className) {
97157
+ return false;
97158
+ }
97159
+ return true;
97160
+ }
97161
+ var ScaledSlidePreview = React10__default.memo(ScaledSlidePreviewImpl, arePropsEqual4);
97012
97162
  function PresenterView({
97013
97163
  slides,
97014
97164
  currentSlideIndex,
@@ -111312,6 +111462,13 @@ function convertOmmlToLatex(omml) {
111312
111462
  }
111313
111463
  return ommlChildrenToLatex(oMath);
111314
111464
  }
111465
+ function sanitizeMathMl2(markup) {
111466
+ const purify = DOMPurify;
111467
+ if (typeof purify.sanitize !== "function") {
111468
+ return markup;
111469
+ }
111470
+ return purify.sanitize(markup, { USE_PROFILES: { mathMl: true, svg: true } });
111471
+ }
111315
111472
  var TEMPLATES = [
111316
111473
  {
111317
111474
  label: "Fraction",
@@ -111377,7 +111534,8 @@ var TEMPLATES = [
111377
111534
  var TEMPLATE_MATHML = TEMPLATES.map((tmpl) => {
111378
111535
  try {
111379
111536
  const tmplOmml = convertLatexToOmml(tmpl.latex);
111380
- return convertOmmlToMathMl(tmplOmml);
111537
+ const raw = convertOmmlToMathMl(tmplOmml);
111538
+ return raw ? sanitizeMathMl2(raw) : "";
111381
111539
  } catch {
111382
111540
  return "";
111383
111541
  }
@@ -111389,7 +111547,7 @@ function MathMlPreview({ mathml }) {
111389
111547
  return;
111390
111548
  }
111391
111549
  if (mathml) {
111392
- containerRef.current.innerHTML = mathml;
111550
+ containerRef.current.innerHTML = sanitizeMathMl2(mathml);
111393
111551
  } else {
111394
111552
  containerRef.current.innerHTML = "";
111395
111553
  }
@@ -111417,6 +111575,7 @@ function EquationEditorDialog({
111417
111575
  return convertOmmlToLatex(existingOmml);
111418
111576
  }, [existingOmml]);
111419
111577
  const [latex, setLatex] = useState(initialLatex);
111578
+ const deferredLatex = useDeferredValue(latex);
111420
111579
  const textareaRef = useRef(null);
111421
111580
  useEffect(() => {
111422
111581
  if (isOpen) {
@@ -111425,17 +111584,17 @@ function EquationEditorDialog({
111425
111584
  }
111426
111585
  }, [isOpen, initialLatex]);
111427
111586
  const { mathml, omml } = useMemo(() => {
111428
- if (!latex.trim()) {
111587
+ if (!deferredLatex.trim()) {
111429
111588
  return { mathml: "", omml: {} };
111430
111589
  }
111431
111590
  try {
111432
- const ommlObj = convertLatexToOmml(latex);
111591
+ const ommlObj = convertLatexToOmml(deferredLatex);
111433
111592
  const mathmlStr = convertOmmlToMathMl(ommlObj);
111434
111593
  return { mathml: mathmlStr, omml: ommlObj };
111435
111594
  } catch {
111436
111595
  return { mathml: "", omml: {} };
111437
111596
  }
111438
- }, [latex]);
111597
+ }, [deferredLatex]);
111439
111598
  const handleInsert = useCallback(() => {
111440
111599
  if (!latex.trim()) {
111441
111600
  return;
@@ -114170,6 +114329,7 @@ function useEditorHistory(input) {
114170
114329
  const historyFutureRef = useRef([]);
114171
114330
  const lastHistorySnapshotRef = useRef(null);
114172
114331
  const lastHistorySerializedRef = useRef("");
114332
+ const lastCheapHashRef = useRef("");
114173
114333
  const isApplyingHistoryRef = useRef(false);
114174
114334
  const unlockHistoryTimerRef = useRef(null);
114175
114335
  const [canUndo, setCanUndo] = useState(false);
@@ -114311,15 +114471,21 @@ function useEditorHistory(input) {
114311
114471
  if (hasActivePointerInteraction()) {
114312
114472
  return;
114313
114473
  }
114474
+ const cheapHash = `${slides.length}|${activeSlideIndex}|${canvasSize.width}x${canvasSize.height}|${slides.map((s) => `${s.id}:${s.elements.length}`).join("/")}`;
114475
+ if (cheapHash === lastCheapHashRef.current) {
114476
+ return;
114477
+ }
114314
114478
  const snapshot2 = buildHistorySnapshot();
114315
114479
  const serialized = JSON.stringify(snapshot2);
114316
114480
  if (serialized === lastHistorySerializedRef.current) {
114481
+ lastCheapHashRef.current = cheapHash;
114317
114482
  return;
114318
114483
  }
114319
114484
  const previousSnapshot = lastHistorySnapshotRef.current;
114320
114485
  if (!previousSnapshot) {
114321
114486
  lastHistorySnapshotRef.current = cloneHistorySnapshot(snapshot2);
114322
114487
  lastHistorySerializedRef.current = serialized;
114488
+ lastCheapHashRef.current = cheapHash;
114323
114489
  updateHistoryAvailability();
114324
114490
  return;
114325
114491
  }
@@ -114330,13 +114496,18 @@ function useEditorHistory(input) {
114330
114496
  historyFutureRef.current = [];
114331
114497
  lastHistorySnapshotRef.current = cloneHistorySnapshot(snapshot2);
114332
114498
  lastHistorySerializedRef.current = serialized;
114499
+ lastCheapHashRef.current = cheapHash;
114333
114500
  updateHistoryAvailability();
114334
114501
  }, [
114502
+ activeSlideIndex,
114335
114503
  buildHistorySnapshot,
114504
+ canvasSize.height,
114505
+ canvasSize.width,
114336
114506
  error2,
114337
114507
  hasActivePointerInteraction,
114338
114508
  loading2,
114339
114509
  pointerCommitNonce,
114510
+ slides,
114340
114511
  updateHistoryAvailability
114341
114512
  ]);
114342
114513
  return {
@@ -118048,7 +118219,8 @@ async function storeAudienceContent(content) {
118048
118219
  const tx = db.transaction(STORE_NAME2, "readwrite");
118049
118220
  const store = tx.objectStore(STORE_NAME2);
118050
118221
  const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
118051
- store.put(bytes, CONTENT_KEY);
118222
+ const record = { bytes, createdAt: Date.now() };
118223
+ store.put(record, CONTENT_KEY);
118052
118224
  tx.oncomplete = () => {
118053
118225
  db.close();
118054
118226
  resolve2();
@@ -118081,14 +118253,34 @@ async function clearAudienceContent() {
118081
118253
  var PRESENTER_CHANNEL_NAME = "pptx-viewer-presenter";
118082
118254
  var AUDIENCE_HASH = "#pptx-audience";
118083
118255
  var PRESENTER_MSG_ORIGIN = "pptx-viewer-presenter";
118256
+ var AUDIENCE_NONCE_KEY = "nonce";
118257
+ function generateSessionId() {
118258
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
118259
+ return crypto.randomUUID();
118260
+ }
118261
+ return `s${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
118262
+ }
118263
+ function parseAudienceNonce() {
118264
+ const hash = window.location.hash;
118265
+ if (!hash.startsWith(AUDIENCE_HASH)) {
118266
+ return null;
118267
+ }
118268
+ const trailing = hash.slice(AUDIENCE_HASH.length);
118269
+ if (!trailing) {
118270
+ return null;
118271
+ }
118272
+ const params2 = new URLSearchParams(trailing.replace(/^[&;?]/, ""));
118273
+ return params2.get(AUDIENCE_NONCE_KEY);
118274
+ }
118084
118275
  function isAudienceTab() {
118085
- return window.location.hash === AUDIENCE_HASH;
118276
+ return window.location.hash.startsWith(AUDIENCE_HASH);
118086
118277
  }
118087
118278
  function usePresenterWindow(input) {
118088
118279
  const { currentSlideIndex, isPresenterMode, content } = input;
118089
118280
  const audienceWindowRef = useRef(null);
118090
118281
  const channelRef = useRef(null);
118091
118282
  const pollTimerRef = useRef(null);
118283
+ const sessionIdRef = useRef("");
118092
118284
  const getChannel2 = useCallback(() => {
118093
118285
  if (!channelRef.current) {
118094
118286
  channelRef.current = new BroadcastChannel(PRESENTER_CHANNEL_NAME);
@@ -118100,10 +118292,14 @@ function usePresenterWindow(input) {
118100
118292
  }, []);
118101
118293
  const syncSlideToAudience = useCallback(
118102
118294
  (slideIndex) => {
118295
+ if (!sessionIdRef.current) {
118296
+ return;
118297
+ }
118103
118298
  const msg = {
118104
118299
  origin: PRESENTER_MSG_ORIGIN,
118105
118300
  type: "presenter-slide-change",
118106
- slideIndex
118301
+ slideIndex,
118302
+ sessionId: sessionIdRef.current
118107
118303
  };
118108
118304
  try {
118109
118305
  getChannel2().postMessage(msg);
@@ -118113,13 +118309,16 @@ function usePresenterWindow(input) {
118113
118309
  [getChannel2]
118114
118310
  );
118115
118311
  const closeAudienceWindow = useCallback(() => {
118116
- try {
118117
- const exitMsg = {
118118
- origin: PRESENTER_MSG_ORIGIN,
118119
- type: "presenter-exit"
118120
- };
118121
- getChannel2().postMessage(exitMsg);
118122
- } catch {
118312
+ if (sessionIdRef.current) {
118313
+ try {
118314
+ const exitMsg = {
118315
+ origin: PRESENTER_MSG_ORIGIN,
118316
+ type: "presenter-exit",
118317
+ sessionId: sessionIdRef.current
118318
+ };
118319
+ getChannel2().postMessage(exitMsg);
118320
+ } catch {
118321
+ }
118123
118322
  }
118124
118323
  const win = audienceWindowRef.current;
118125
118324
  if (win && !win.closed) {
@@ -118129,6 +118328,7 @@ function usePresenterWindow(input) {
118129
118328
  }
118130
118329
  }
118131
118330
  audienceWindowRef.current = null;
118331
+ sessionIdRef.current = "";
118132
118332
  if (pollTimerRef.current !== null) {
118133
118333
  clearInterval(pollTimerRef.current);
118134
118334
  pollTimerRef.current = null;
@@ -118139,20 +118339,53 @@ function usePresenterWindow(input) {
118139
118339
  if (isAudienceWindowOpen()) {
118140
118340
  closeAudienceWindow();
118141
118341
  }
118142
- if (content) {
118143
- void storeAudienceContent(content);
118144
- }
118145
- const url = new URL(window.location.href);
118146
- url.hash = AUDIENCE_HASH;
118147
- const win = window.open(url.toString(), "_blank");
118148
- if (!win) {
118342
+ const blankWin = window.open("about:blank", "_blank");
118343
+ if (!blankWin) {
118149
118344
  return false;
118150
118345
  }
118151
- audienceWindowRef.current = win;
118346
+ audienceWindowRef.current = blankWin;
118347
+ const sessionId = generateSessionId();
118348
+ sessionIdRef.current = sessionId;
118349
+ const audienceUrl = new URL(window.location.href);
118350
+ const params2 = new URLSearchParams();
118351
+ params2.set(AUDIENCE_NONCE_KEY, sessionId);
118352
+ audienceUrl.hash = `${AUDIENCE_HASH}&${params2.toString()}`;
118353
+ const navigateOrClose = (ok) => {
118354
+ const win = audienceWindowRef.current;
118355
+ if (!win || win.closed) {
118356
+ return;
118357
+ }
118358
+ if (!ok) {
118359
+ try {
118360
+ win.close();
118361
+ } catch {
118362
+ }
118363
+ audienceWindowRef.current = null;
118364
+ sessionIdRef.current = "";
118365
+ return;
118366
+ }
118367
+ try {
118368
+ win.location.replace(audienceUrl.toString());
118369
+ } catch {
118370
+ try {
118371
+ win.close();
118372
+ } catch {
118373
+ }
118374
+ audienceWindowRef.current = null;
118375
+ sessionIdRef.current = "";
118376
+ }
118377
+ };
118378
+ if (content) {
118379
+ void storeAudienceContent(content).then(() => navigateOrClose(true)).catch(() => navigateOrClose(false));
118380
+ } else {
118381
+ navigateOrClose(true);
118382
+ }
118152
118383
  window.setTimeout(() => syncSlideToAudience(currentSlideIndex), 1500);
118153
118384
  pollTimerRef.current = setInterval(() => {
118154
- if (win.closed) {
118385
+ const win = audienceWindowRef.current;
118386
+ if (!win || win.closed) {
118155
118387
  audienceWindowRef.current = null;
118388
+ sessionIdRef.current = "";
118156
118389
  if (pollTimerRef.current !== null) {
118157
118390
  clearInterval(pollTimerRef.current);
118158
118391
  pollTimerRef.current = null;
@@ -118180,6 +118413,13 @@ function usePresenterWindow(input) {
118180
118413
  closeAudienceWindow();
118181
118414
  }
118182
118415
  }, [isPresenterMode, closeAudienceWindow]);
118416
+ useEffect(() => {
118417
+ const handleBeforeUnload = () => {
118418
+ void clearAudienceContent();
118419
+ };
118420
+ window.addEventListener("beforeunload", handleBeforeUnload);
118421
+ return () => window.removeEventListener("beforeunload", handleBeforeUnload);
118422
+ }, []);
118183
118423
  return {
118184
118424
  openAudienceWindow,
118185
118425
  closeAudienceWindow,
@@ -118217,6 +118457,7 @@ function useAudienceMode(input) {
118217
118457
  if (!isAudienceTab()) {
118218
118458
  return;
118219
118459
  }
118460
+ const expectedSessionId = parseAudienceNonce();
118220
118461
  let channel;
118221
118462
  try {
118222
118463
  channel = new BroadcastChannel(PRESENTER_CHANNEL_NAME);
@@ -118228,6 +118469,9 @@ function useAudienceMode(input) {
118228
118469
  if (!data || data.origin !== PRESENTER_MSG_ORIGIN) {
118229
118470
  return;
118230
118471
  }
118472
+ if (expectedSessionId && data.sessionId !== expectedSessionId) {
118473
+ return;
118474
+ }
118231
118475
  if (data.type === "presenter-slide-change") {
118232
118476
  onSetActiveSlideIndex(data.slideIndex);
118233
118477
  }
@@ -119161,6 +119405,12 @@ function useTouchGestures(input) {
119161
119405
  callbacksRef.current = callbacks;
119162
119406
  const scaleRef = useRef(currentScale);
119163
119407
  scaleRef.current = currentScale;
119408
+ const [targetVersion, setTargetVersion] = useState(0);
119409
+ const lastTargetRef = useRef(null);
119410
+ if (targetRef.current !== lastTargetRef.current) {
119411
+ lastTargetRef.current = targetRef.current;
119412
+ queueMicrotask(() => setTargetVersion((v) => v + 1));
119413
+ }
119164
119414
  useEffect(() => {
119165
119415
  const el = targetRef.current;
119166
119416
  if (!el || !enabled) {
@@ -119249,7 +119499,7 @@ function useTouchGestures(input) {
119249
119499
  el.removeEventListener("touchcancel", handleTouchCancel);
119250
119500
  cancelLongPress();
119251
119501
  };
119252
- }, [targetRef, enabled]);
119502
+ }, [targetRef, enabled, targetVersion]);
119253
119503
  }
119254
119504
 
119255
119505
  // src/viewer/utils/dom-helpers.ts
@@ -119270,11 +119520,31 @@ function safeConfirm(message) {
119270
119520
  return false;
119271
119521
  }
119272
119522
  }
119523
+ function sanitizeDownloadFilename(input) {
119524
+ if (typeof input !== "string" || input.trim().length === 0) {
119525
+ return "presentation.pptx";
119526
+ }
119527
+ let cleaned = input.replace(/[\x00-\x1f\x7f"\\/:*?<>|]/g, "_").replace(/\.\./g, "__").replace(/^\.+/, "").trim();
119528
+ if (cleaned.length === 0) {
119529
+ return "presentation.pptx";
119530
+ }
119531
+ if (cleaned.length > 200) {
119532
+ const dot = cleaned.lastIndexOf(".");
119533
+ if (dot > 0 && cleaned.length - dot <= 16) {
119534
+ const ext = cleaned.slice(dot);
119535
+ cleaned = cleaned.slice(0, 200 - ext.length) + ext;
119536
+ } else {
119537
+ cleaned = cleaned.slice(0, 200);
119538
+ }
119539
+ }
119540
+ return cleaned;
119541
+ }
119273
119542
  function downloadBlob(blob, filename) {
119543
+ const safeName = sanitizeDownloadFilename(filename);
119274
119544
  const url = URL.createObjectURL(blob);
119275
119545
  const a2 = document.createElement("a");
119276
119546
  a2.href = url;
119277
- a2.download = filename;
119547
+ a2.download = safeName;
119278
119548
  document.body.appendChild(a2);
119279
119549
  a2.click();
119280
119550
  setTimeout(() => {
@@ -119705,27 +119975,82 @@ function openAutosaveDb2() {
119705
119975
  req.onerror = () => reject(req.error);
119706
119976
  });
119707
119977
  }
119708
- async function saveToIndexedDb(filePath, data) {
119978
+ async function deleteOldestAutosaveEntry() {
119709
119979
  const db = await openAutosaveDb2();
119710
- return new Promise((resolve2, reject) => {
119711
- const tx = db.transaction(STORE_NAME3, "readwrite");
119712
- const store = tx.objectStore(STORE_NAME3);
119713
- store.put({
119714
- key: filePath,
119715
- data,
119716
- timestamp: Date.now(),
119717
- size: data.byteLength
119718
- });
119719
- tx.oncomplete = () => {
119720
- db.close();
119721
- resolve2(true);
119722
- };
119723
- tx.onerror = () => {
119724
- db.close();
119725
- reject(tx.error);
119726
- };
119980
+ return new Promise((resolve2) => {
119981
+ try {
119982
+ const tx = db.transaction(STORE_NAME3, "readwrite");
119983
+ const store = tx.objectStore(STORE_NAME3);
119984
+ let oldestKey = null;
119985
+ let oldestTimestamp = Infinity;
119986
+ const cursorReq = store.openCursor();
119987
+ cursorReq.onsuccess = () => {
119988
+ const cursor = cursorReq.result;
119989
+ if (cursor) {
119990
+ const value = cursor.value;
119991
+ if (typeof value.timestamp === "number" && value.timestamp < oldestTimestamp) {
119992
+ oldestTimestamp = value.timestamp;
119993
+ oldestKey = cursor.primaryKey;
119994
+ }
119995
+ cursor.continue();
119996
+ } else if (oldestKey !== null) {
119997
+ store.delete(oldestKey);
119998
+ }
119999
+ };
120000
+ tx.oncomplete = () => {
120001
+ db.close();
120002
+ resolve2(oldestKey !== null);
120003
+ };
120004
+ tx.onerror = () => {
120005
+ db.close();
120006
+ resolve2(false);
120007
+ };
120008
+ } catch {
120009
+ try {
120010
+ db.close();
120011
+ } catch {
120012
+ }
120013
+ resolve2(false);
120014
+ }
119727
120015
  });
119728
120016
  }
120017
+ function putAutosaveRecord(filePath, data) {
120018
+ return openAutosaveDb2().then(
120019
+ (db) => new Promise((resolve2, reject) => {
120020
+ const tx = db.transaction(STORE_NAME3, "readwrite");
120021
+ const store = tx.objectStore(STORE_NAME3);
120022
+ store.put({
120023
+ key: filePath,
120024
+ data,
120025
+ timestamp: Date.now(),
120026
+ size: data.byteLength
120027
+ });
120028
+ tx.oncomplete = () => {
120029
+ db.close();
120030
+ resolve2(true);
120031
+ };
120032
+ tx.onerror = () => {
120033
+ db.close();
120034
+ reject(tx.error);
120035
+ };
120036
+ })
120037
+ );
120038
+ }
120039
+ async function saveToIndexedDb(filePath, data) {
120040
+ try {
120041
+ return await putAutosaveRecord(filePath, data);
120042
+ } catch (err) {
120043
+ const errName = err instanceof Error || err instanceof DOMException ? err.name : "";
120044
+ if (errName !== "QuotaExceededError") {
120045
+ throw err;
120046
+ }
120047
+ const deleted = await deleteOldestAutosaveEntry();
120048
+ if (!deleted) {
120049
+ throw err;
120050
+ }
120051
+ return putAutosaveRecord(filePath, data);
120052
+ }
120053
+ }
119729
120054
  function useAutosave(input) {
119730
120055
  const {
119731
120056
  isDirty,
@@ -119842,6 +120167,25 @@ function collectReferencedFontFamilies(slides) {
119842
120167
  }
119843
120168
  return families;
119844
120169
  }
120170
+ var FONT_NAME_UNSAFE_CHARS = /["\\\n\r;}<>]/;
120171
+ var FONT_FORMAT_ALLOWED = /* @__PURE__ */ new Set([
120172
+ "truetype",
120173
+ "opentype",
120174
+ "woff",
120175
+ "woff2",
120176
+ "svg",
120177
+ "embedded-opentype"
120178
+ ]);
120179
+ var FONT_DATA_URL_PATTERN = /^data:font\/[a-z0-9+.-]+(?:;charset=[a-z0-9-]+)?;base64,[A-Za-z0-9+/=]+$/i;
120180
+ function isFontDataUrlSafe(url) {
120181
+ if (typeof url !== "string" || url.length === 0) {
120182
+ return false;
120183
+ }
120184
+ if (url.startsWith("blob:")) {
120185
+ return true;
120186
+ }
120187
+ return FONT_DATA_URL_PATTERN.test(url);
120188
+ }
119845
120189
  function useFontInjection({ embeddedFonts, slides }) {
119846
120190
  useEffect(() => {
119847
120191
  if (!embeddedFonts.length) {
@@ -119849,17 +120193,28 @@ function useFontInjection({ embeddedFonts, slides }) {
119849
120193
  }
119850
120194
  const styleEl = document.createElement("style");
119851
120195
  styleEl.id = EMBEDDED_FONTS_STYLE_ID;
119852
- const cssRules = embeddedFonts.map((font) => {
120196
+ const cssRules = embeddedFonts.flatMap((font) => {
120197
+ if (typeof font.name !== "string" || font.name.length === 0 || FONT_NAME_UNSAFE_CHARS.test(font.name)) {
120198
+ return [];
120199
+ }
120200
+ if (!isFontDataUrlSafe(font.dataUrl)) {
120201
+ return [];
120202
+ }
120203
+ const fontFormat = font.format ?? "truetype";
120204
+ if (!FONT_FORMAT_ALLOWED.has(fontFormat)) {
120205
+ return [];
120206
+ }
119853
120207
  const fontWeight = font.bold ? "700" : "400";
119854
120208
  const fontStyleCss = font.italic ? "italic" : "normal";
119855
- const fontFormat = font.format ?? "truetype";
119856
- return `@font-face {
120209
+ return [
120210
+ `@font-face {
119857
120211
  font-family: "${font.name}";
119858
120212
  src: url("${font.dataUrl}") format("${fontFormat}");
119859
120213
  font-weight: ${fontWeight};
119860
120214
  font-style: ${fontStyleCss};
119861
120215
  font-display: swap;
119862
- }`;
120216
+ }`
120217
+ ];
119863
120218
  }).join("\n");
119864
120219
  styleEl.textContent = cssRules;
119865
120220
  document.head.appendChild(styleEl);
@@ -119888,7 +120243,7 @@ function useFontInjection({ embeddedFonts, slides }) {
119888
120243
  const linkEl = document.createElement("link");
119889
120244
  linkEl.id = GOOGLE_FONTS_LINK_ID;
119890
120245
  linkEl.rel = "stylesheet";
119891
- linkEl.href = `https://fonts.googleapis.com/css2?${googleFamilies.map((f) => `family=${GOOGLE_FONTS_AVAILABLE[f]}`).join("&")}&display=swap`;
120246
+ linkEl.href = `https://fonts.googleapis.com/css2?${googleFamilies.map((f) => `family=${encodeURIComponent(GOOGLE_FONTS_AVAILABLE[f])}`).join("&")}&display=swap`;
119892
120247
  document.head.appendChild(linkEl);
119893
120248
  return () => {
119894
120249
  const existing = document.getElementById(GOOGLE_FONTS_LINK_ID);
@@ -119904,13 +120259,18 @@ function useFontInjection({ embeddedFonts, slides }) {
119904
120259
  }
119905
120260
  const styleEl = document.createElement("style");
119906
120261
  styleEl.id = SYMBOL_FONTS_STYLE_ID;
119907
- const rules = neededSymbolFonts.map(
119908
- (font) => `@font-face {
120262
+ const rules = neededSymbolFonts.flatMap((font) => {
120263
+ if (typeof font !== "string" || FONT_NAME_UNSAFE_CHARS.test(font)) {
120264
+ return [];
120265
+ }
120266
+ return [
120267
+ `@font-face {
119909
120268
  font-family: "${font}";
119910
120269
  src: local("${font}"), local("${font} Regular");
119911
120270
  font-display: swap;
119912
120271
  }`
119913
- ).join("\n");
120272
+ ];
120273
+ }).join("\n");
119914
120274
  styleEl.textContent = rules;
119915
120275
  document.head.appendChild(styleEl);
119916
120276
  return () => {
@@ -120053,16 +120413,17 @@ function useLoadContent({
120053
120413
  `[pptx] Large file detected (${fileSizeMB.toFixed(1)} MB). Loading may use significant memory.`
120054
120414
  );
120055
120415
  }
120056
- if (handlerRef.current) {
120057
- handlerRef.current.dispose();
120058
- handlerRef.current = null;
120059
- }
120416
+ const previousHandler = handlerRef.current;
120060
120417
  const handler = new PptxHandler();
120061
120418
  const parsed = await handler.load(buffer);
120062
120419
  if (cancelled || token !== renderTokenRef.current) {
120063
120420
  handler.dispose();
120064
120421
  return;
120065
120422
  }
120423
+ if (previousHandler) {
120424
+ previousHandler.dispose();
120425
+ }
120426
+ handlerRef.current = null;
120066
120427
  const mediaElements = [];
120067
120428
  for (const slide of parsed.slides) {
120068
120429
  collectMediaElements(slide.elements, mediaElements);
@@ -120107,6 +120468,7 @@ function useLoadContent({
120107
120468
  })
120108
120469
  );
120109
120470
  const { paths: imagePaths, refs: imageRefs } = collectImagePaths(parsed.slides);
120471
+ let nextSlides = parsed.slides;
120110
120472
  if (imagePaths.size > 0) {
120111
120473
  const resolvedMap = /* @__PURE__ */ new Map();
120112
120474
  await Promise.all(
@@ -120120,15 +120482,47 @@ function useLoadContent({
120120
120482
  }
120121
120483
  })
120122
120484
  );
120485
+ const elementPatches = /* @__PURE__ */ new Map();
120123
120486
  for (const ref of imageRefs) {
120124
120487
  const url = resolvedMap.get(ref.path);
120125
- if (url) {
120126
- ref.element[ref.field] = url;
120488
+ if (!url) {
120489
+ continue;
120127
120490
  }
120491
+ const id2 = ref.element.id;
120492
+ const existing = elementPatches.get(id2) ?? {};
120493
+ existing[ref.field] = url;
120494
+ elementPatches.set(id2, existing);
120495
+ }
120496
+ if (elementPatches.size > 0) {
120497
+ const patchElements = (elements) => {
120498
+ let mutated = false;
120499
+ const next = elements.map((el) => {
120500
+ let updated = el;
120501
+ const patch = elementPatches.get(el.id);
120502
+ if (patch) {
120503
+ updated = { ...el, ...patch };
120504
+ }
120505
+ if (updated.type === "group" && updated.children?.length) {
120506
+ const newChildren = patchElements(updated.children);
120507
+ if (newChildren !== updated.children) {
120508
+ updated = { ...updated, children: newChildren };
120509
+ }
120510
+ }
120511
+ if (updated !== el) {
120512
+ mutated = true;
120513
+ }
120514
+ return updated;
120515
+ });
120516
+ return mutated ? next : elements;
120517
+ };
120518
+ nextSlides = parsed.slides.map((s) => {
120519
+ const newElements = patchElements(s.elements);
120520
+ return newElements === s.elements ? s : { ...s, elements: newElements };
120521
+ });
120128
120522
  }
120129
120523
  }
120130
120524
  handlerRef.current = handler;
120131
- setSlides(parsed.slides);
120525
+ setSlides(nextSlides);
120132
120526
  setTemplateElementsBySlideId({});
120133
120527
  setCanvasSize({
120134
120528
  width: parsed.width ?? DEFAULT_CANVAS_WIDTH,
@@ -121302,7 +121696,19 @@ function injectFontFaces(svg, fontFaces) {
121302
121696
  if (!fontFaces.length) {
121303
121697
  return svg;
121304
121698
  }
121305
- const styleBlock = `<style type="text/css">${fontFaces.map((f) => f.css).join("\n")}</style>`;
121699
+ const safeFontFaces = fontFaces.filter((f) => {
121700
+ if (f.css.toLowerCase().includes("</style")) {
121701
+ console.warn(
121702
+ `[export-svg] Dropping @font-face entry for "${f.family}" containing "</style" \u2014 would break out of the <style> block.`
121703
+ );
121704
+ return false;
121705
+ }
121706
+ return true;
121707
+ });
121708
+ if (!safeFontFaces.length) {
121709
+ return svg;
121710
+ }
121711
+ const styleBlock = `<style type="text/css">${safeFontFaces.map((f) => f.css).join("\n")}</style>`;
121306
121712
  if (svg.includes("<defs>")) {
121307
121713
  return svg.replace("<defs>", `<defs>${styleBlock}`);
121308
121714
  }
@@ -121628,7 +122034,7 @@ function useExportHandlers(input) {
121628
122034
  activeSlideIndexForGuides,
121629
122035
  modalControls
121630
122036
  });
121631
- const handleExportPng = async () => {
122037
+ const handleExportPng = useCallback(async () => {
121632
122038
  const stageEl = canvasStageRef.current;
121633
122039
  if (!stageEl) {
121634
122040
  return;
@@ -121640,8 +122046,8 @@ function useExportHandlers(input) {
121640
122046
  } catch (err) {
121641
122047
  console.error("[PowerPointViewer] PNG export failed:", err);
121642
122048
  }
121643
- };
121644
- const handleExportPdf = async () => {
122049
+ }, [canvasStageRef, activeSlideIndex, activeSlide?.backgroundColor]);
122050
+ const handleExportPdf = useCallback(async () => {
121645
122051
  if (!canvasStageRef.current) {
121646
122052
  return;
121647
122053
  }
@@ -121682,8 +122088,8 @@ function useExportHandlers(input) {
121682
122088
  exportAbortRef.current = null;
121683
122089
  setExportModalOpen(false);
121684
122090
  }
121685
- };
121686
- const handleExportNotesPdf = async () => {
122091
+ }, [canvasStageRef, slides.length, setActiveSlideIndex, activeSlideIndex]);
122092
+ const handleExportNotesPdf = useCallback(async () => {
121687
122093
  if (!canvasStageRef.current) {
121688
122094
  return;
121689
122095
  }
@@ -121726,8 +122132,8 @@ function useExportHandlers(input) {
121726
122132
  exportAbortRef.current = null;
121727
122133
  setExportModalOpen(false);
121728
122134
  }
121729
- };
121730
- const handleCopySlideAsImage = async () => {
122135
+ }, [canvasStageRef, slides, setActiveSlideIndex, activeSlideIndex]);
122136
+ const handleCopySlideAsImage = useCallback(async () => {
121731
122137
  const stageEl = canvasStageRef.current;
121732
122138
  if (!stageEl) {
121733
122139
  return;
@@ -121739,8 +122145,8 @@ function useExportHandlers(input) {
121739
122145
  } catch (err) {
121740
122146
  console.error("[PowerPointViewer] Copy slide as image failed:", err);
121741
122147
  }
121742
- };
121743
- const handleExportVideo = async () => {
122148
+ }, [canvasStageRef, activeSlide?.backgroundColor]);
122149
+ const handleExportVideo = useCallback(async () => {
121744
122150
  if (!canvasStageRef.current) {
121745
122151
  return;
121746
122152
  }
@@ -121782,8 +122188,8 @@ function useExportHandlers(input) {
121782
122188
  exportAbortRef.current = null;
121783
122189
  setExportModalOpen(false);
121784
122190
  }
121785
- };
121786
- const handleExportGif = async () => {
122191
+ }, [canvasStageRef, slides.length, setActiveSlideIndex, activeSlideIndex]);
122192
+ const handleExportGif = useCallback(async () => {
121787
122193
  if (!canvasStageRef.current) {
121788
122194
  return;
121789
122195
  }
@@ -121821,7 +122227,7 @@ function useExportHandlers(input) {
121821
122227
  exportAbortRef.current = null;
121822
122228
  setExportModalOpen(false);
121823
122229
  }
121824
- };
122230
+ }, [canvasStageRef, slides.length, setActiveSlideIndex, activeSlideIndex]);
121825
122231
  const handleCancelExport = useCallback(() => {
121826
122232
  exportAbortRef.current?.abort();
121827
122233
  exportAbortRef.current = null;
@@ -121847,6 +122253,15 @@ function useExportHandlers(input) {
121847
122253
  exportStatusMessage
121848
122254
  };
121849
122255
  }
122256
+ function escapeHtmlAttr(value) {
122257
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
122258
+ }
122259
+ function safeDataImageSrc(src) {
122260
+ if (typeof src !== "string" || !src.startsWith("data:image/")) {
122261
+ return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgAAIAAAUAAen63NgAAAAASUVORK5CYII=";
122262
+ }
122263
+ return escapeHtmlAttr(src);
122264
+ }
121850
122265
  function openPrintWindow(title, bodyHtml, orientation, colorFilter, frameSlides) {
121851
122266
  const printWindow = window.open("", "_blank", "noopener,noreferrer");
121852
122267
  if (!printWindow) {
@@ -122029,7 +122444,7 @@ function usePrintHandlers(input) {
122029
122444
  const slideImages = slideIndices.map((idx) => allImages[idx]).filter(Boolean);
122030
122445
  if (settings.printWhat === "slides") {
122031
122446
  const bodyHtml = slideImages.map(
122032
- (img, i3) => `<section class="page slide-page"><img class="slide-img" src="${img}" alt="Slide ${slideIndices[i3] + 1}" /></section>`
122447
+ (img, i3) => `<section class="page slide-page"><img class="slide-img" src="${safeDataImageSrc(img)}" alt="Slide ${slideIndices[i3] + 1}" /></section>`
122033
122448
  ).join("");
122034
122449
  openPrintWindow(
122035
122450
  "Slides",
@@ -122045,7 +122460,7 @@ function usePrintHandlers(input) {
122045
122460
  const idx = slideIndices[i3];
122046
122461
  const notes = slides[idx]?.notes?.trim() || "";
122047
122462
  return `<section class="page notes-page">
122048
- <img class="notes-slide" src="${img}" alt="Slide ${idx + 1}" />
122463
+ <img class="notes-slide" src="${safeDataImageSrc(img)}" alt="Slide ${idx + 1}" />
122049
122464
  <div class="notes-text">${escapeHtml2(notes)}</div>
122050
122465
  </section>`;
122051
122466
  }).join("");
@@ -122077,14 +122492,14 @@ function usePrintHandlers(input) {
122077
122492
  if (isThreePerPage) {
122078
122493
  const rows = Array.from({ length: spp }, (_, cellIndex) => {
122079
122494
  const img = pageImgs[cellIndex];
122080
- const slideCell = img ? `<div class="handout-cell"><img src="${img}" alt="Slide ${slideIndices[i3 + cellIndex] + 1}" /></div>` : `<div class="handout-cell"></div>`;
122495
+ const slideCell = img ? `<div class="handout-cell"><img src="${safeDataImageSrc(img)}" alt="Slide ${slideIndices[i3 + cellIndex] + 1}" /></div>` : `<div class="handout-cell"></div>`;
122081
122496
  return `<div class="handout-row-3">${slideCell}${buildNoteLines()}</div>`;
122082
122497
  }).join("");
122083
122498
  pages.push(`<section class="page"><div class="handout-grid-3">${rows}</div></section>`);
122084
122499
  } else {
122085
122500
  const cells = Array.from({ length: spp }, (_, cellIndex) => {
122086
122501
  const img = pageImgs[cellIndex];
122087
- return img ? `<div class="handout-cell"><img src="${img}" alt="Slide ${slideIndices[i3 + cellIndex] + 1}" /></div>` : `<div class="handout-cell"></div>`;
122502
+ return img ? `<div class="handout-cell"><img src="${safeDataImageSrc(img)}" alt="Slide ${slideIndices[i3 + cellIndex] + 1}" /></div>` : `<div class="handout-cell"></div>`;
122088
122503
  }).join("");
122089
122504
  pages.push(
122090
122505
  `<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>`