pptx-angular-viewer 1.16.1 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,8 @@ All notable changes to this project are documented here.
4
4
  This file is generated from [Conventional Commits](https://www.conventionalcommits.org)
5
5
  by [git-cliff](https://git-cliff.org); do not edit it by hand.
6
6
 
7
+ ## [1.16.1](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.16.1) - 2026-07-13
8
+
7
9
  ## [1.16.0](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.16.0) - 2026-07-11
8
10
 
9
11
  ## [1.15.0](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.15.0) - 2026-07-11
@@ -3736,6 +3736,80 @@ function getWarpCssTransform(preset, adj, adj2) {
3736
3736
  return undefined;
3737
3737
  }
3738
3738
 
3739
+ /**
3740
+ * Read the first explicit DrawingML run colour nested inside an OMML tree.
3741
+ * Office Math stores formatting in `a:rPr`, not in `m:rPr`, so this colour
3742
+ * must travel with the generated MathML rather than the text shape's default
3743
+ * paragraph style.
3744
+ */
3745
+ function getOmmlMathColor(node) {
3746
+ if (!node || typeof node !== 'object') {
3747
+ return undefined;
3748
+ }
3749
+ const drawingRunProperties = node['a:rPr'];
3750
+ if (drawingRunProperties && typeof drawingRunProperties === 'object') {
3751
+ const solidFill = drawingRunProperties['a:solidFill'];
3752
+ const srgbColor = solidFill && typeof solidFill === 'object'
3753
+ ? solidFill['a:srgbClr']
3754
+ : undefined;
3755
+ const value = srgbColor?.['@_val'];
3756
+ if (typeof value === 'string' && /^[0-9a-f]{6}$/iu.test(value)) {
3757
+ return `#${value.toUpperCase()}`;
3758
+ }
3759
+ }
3760
+ for (const value of Object.values(node)) {
3761
+ if (Array.isArray(value)) {
3762
+ for (const child of value) {
3763
+ if (child && typeof child === 'object') {
3764
+ const color = getOmmlMathColor(child);
3765
+ if (color) {
3766
+ return color;
3767
+ }
3768
+ }
3769
+ }
3770
+ }
3771
+ else if (value && typeof value === 'object') {
3772
+ const color = getOmmlMathColor(value);
3773
+ if (color) {
3774
+ return color;
3775
+ }
3776
+ }
3777
+ }
3778
+ return undefined;
3779
+ }
3780
+ /** Read the first explicit DrawingML run size, stored in hundredths of a point. */
3781
+ function getOmmlMathFontSize(node) {
3782
+ if (!node || typeof node !== 'object') {
3783
+ return undefined;
3784
+ }
3785
+ const drawingRunProperties = node['a:rPr'];
3786
+ if (drawingRunProperties && typeof drawingRunProperties === 'object') {
3787
+ const size = Number(drawingRunProperties['@_sz']);
3788
+ if (Number.isFinite(size) && size > 0) {
3789
+ return size / 100;
3790
+ }
3791
+ }
3792
+ for (const value of Object.values(node)) {
3793
+ if (Array.isArray(value)) {
3794
+ for (const child of value) {
3795
+ if (child && typeof child === 'object') {
3796
+ const size = getOmmlMathFontSize(child);
3797
+ if (size) {
3798
+ return size;
3799
+ }
3800
+ }
3801
+ }
3802
+ }
3803
+ else if (value && typeof value === 'object') {
3804
+ const size = getOmmlMathFontSize(value);
3805
+ if (size) {
3806
+ return size;
3807
+ }
3808
+ }
3809
+ }
3810
+ return undefined;
3811
+ }
3812
+
3739
3813
  /**
3740
3814
  * omml-to-mathml.ts — pure OMML → MathML conversion.
3741
3815
  *
@@ -4250,7 +4324,11 @@ function convertOmmlToMathMl(ommlNode) {
4250
4324
  if (inner.length === 0) {
4251
4325
  return '';
4252
4326
  }
4253
- return `<math xmlns="http://www.w3.org/1998/Math/MathML" display="inline">${inner}</math>`;
4327
+ const color = getOmmlMathColor(ommlNode);
4328
+ const colorAttribute = color ? ` mathcolor="${color}"` : '';
4329
+ const fontSize = getOmmlMathFontSize(ommlNode);
4330
+ const sizeAttribute = fontSize ? ` mathsize="${fontSize}pt"` : '';
4331
+ return `<math xmlns="http://www.w3.org/1998/Math/MathML" display="inline"${colorAttribute}${sizeAttribute}>${inner}</math>`;
4254
4332
  }
4255
4333
  /**
4256
4334
  * Convenience alias used by the Vue viewer: `ommlToMathml(omml)`.
@@ -16722,6 +16800,37 @@ function mergeAdditiveSelection(baseSelectionIds, hitIds) {
16722
16800
  return Array.from(new Set([...(baseSelectionIds ?? []), ...hitIds]));
16723
16801
  }
16724
16802
 
16803
+ /** Union of all selected element boxes, or null for an empty selection. */
16804
+ function selectionBounds(boxes) {
16805
+ if (boxes.length === 0) {
16806
+ return null;
16807
+ }
16808
+ const minX = Math.min(...boxes.map((box) => box.x));
16809
+ const minY = Math.min(...boxes.map((box) => box.y));
16810
+ const maxX = Math.max(...boxes.map((box) => box.x + box.width));
16811
+ const maxY = Math.max(...boxes.map((box) => box.y + box.height));
16812
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY, rotation: 0 };
16813
+ }
16814
+ /** Move all boxes by the same slide-space delta. */
16815
+ function moveSelection(boxes, dx, dy) {
16816
+ return boxes.map((box) => ({ ...box, x: box.x + dx, y: box.y + dy }));
16817
+ }
16818
+ /**
16819
+ * Scale boxes from one selection boundary into another. Each element keeps its
16820
+ * relative position and size within the collective selection.
16821
+ */
16822
+ function resizeSelection(boxes, from, to) {
16823
+ const scaleX = from.width === 0 ? 1 : to.width / from.width;
16824
+ const scaleY = from.height === 0 ? 1 : to.height / from.height;
16825
+ return boxes.map((box) => ({
16826
+ ...box,
16827
+ x: to.x + (box.x - from.x) * scaleX,
16828
+ y: to.y + (box.y - from.y) * scaleY,
16829
+ width: Math.max(1, box.width * scaleX),
16830
+ height: Math.max(1, box.height * scaleY),
16831
+ }));
16832
+ }
16833
+
16725
16834
  /** Map a number to a CSS pixel string. */
16726
16835
  function px(n) {
16727
16836
  return `${n}px`;
@@ -20459,7 +20568,14 @@ function sanitizeMarkupOrEmpty(markup, config) {
20459
20568
  * @returns The sanitised markup, or the raw input when no DOM is available.
20460
20569
  */
20461
20570
  function sanitizeMathMl(markup) {
20462
- return sanitizeMarkupOrRaw(markup, { USE_PROFILES: { mathMl: true, svg: true } });
20571
+ const sanitized = sanitizeMarkupOrRaw(markup, { USE_PROFILES: { mathMl: true, svg: true } });
20572
+ // DOMPurify's HTML parser can retain MathML descendants while dropping the
20573
+ // outer namespace-bearing <math> node. Restore that generated, constant
20574
+ // wrapper so browsers keep the fragment in the MathML rendering context.
20575
+ if (/<math(?:\s|>)/iu.test(markup) && !/<math(?:\s|>)/iu.test(sanitized) && sanitized) {
20576
+ return `<math xmlns="http://www.w3.org/1998/Math/MathML" display="inline">${sanitized}</math>`;
20577
+ }
20578
+ return sanitized;
20463
20579
  }
20464
20580
 
20465
20581
  /**
@@ -29437,6 +29553,23 @@ function buildFontFaceRule(variant) {
29437
29553
  '}',
29438
29554
  ].join('\n');
29439
29555
  }
29556
+ /** Build a safe `@font-face` stylesheet for host-provided font sources. */
29557
+ function buildUserFontFaceStyles(fonts) {
29558
+ const rules = [];
29559
+ for (const font of fonts ?? []) {
29560
+ const family = typeof font.family === 'string' ? font.family.trim() : '';
29561
+ const src = typeof font.src === 'string' ? font.src.trim() : '';
29562
+ const isRemoteUrl = /^https?:\/\/[^"\\\n\r{};]+$/iu.test(src);
29563
+ if (!family || FONT_NAME_UNSAFE_CHARS.test(family) || !(isInjectableUrl(src) || isRemoteUrl)) {
29564
+ continue;
29565
+ }
29566
+ const format = normalizeFontFormat(font.format);
29567
+ const weight = typeof font.weight === 'number' || typeof font.weight === 'string' ? font.weight : '400';
29568
+ const style = font.style === 'italic' ? 'italic' : 'normal';
29569
+ rules.push(buildFontFaceRule({ name: family, url: src, format, weight: String(weight), style }));
29570
+ }
29571
+ return rules.join('\n\n');
29572
+ }
29440
29573
  /**
29441
29574
  * Resolve a list of embedded fonts into the injectable `@font-face` stylesheet,
29442
29575
  * the distinct substitution-resolved family strings, and any object URLs minted
@@ -32593,6 +32726,60 @@ function createTouchGestureRecognizer(config) {
32593
32726
  };
32594
32727
  }
32595
32728
 
32729
+ /**
32730
+ * Build the stable presentation-control state shared by every UI binding.
32731
+ * Empty presentations disable both directions and use the same 0 / 0 label.
32732
+ */
32733
+ function buildPresentationTouchControlState(currentSlideIndex, totalSlides) {
32734
+ if (totalSlides <= 0) {
32735
+ return {
32736
+ previousDisabled: true,
32737
+ nextDisabled: true,
32738
+ counterLabel: '0 / 0',
32739
+ };
32740
+ }
32741
+ return {
32742
+ previousDisabled: currentSlideIndex <= 0,
32743
+ nextDisabled: currentSlideIndex >= totalSlides - 1,
32744
+ counterLabel: `${currentSlideIndex + 1} / ${totalSlides}`,
32745
+ };
32746
+ }
32747
+
32748
+ /** Framework-neutral downward drag recognizer for mobile bottom sheets. */
32749
+ function createSheetDismissGesture(onOffset, onDismiss, threshold = 120) {
32750
+ let startY = null;
32751
+ const finish = (event, allowDismiss) => {
32752
+ if (startY === null) {
32753
+ return;
32754
+ }
32755
+ const delta = Math.max(0, event.clientY - startY);
32756
+ startY = null;
32757
+ event.currentTarget?.releasePointerCapture?.(event.pointerId);
32758
+ onOffset(0, false);
32759
+ if (allowDismiss && delta > threshold) {
32760
+ onDismiss();
32761
+ }
32762
+ };
32763
+ return {
32764
+ pointerDown(event) {
32765
+ startY = event.clientY;
32766
+ event.currentTarget?.setPointerCapture?.(event.pointerId);
32767
+ onOffset(0, true);
32768
+ },
32769
+ pointerMove(event) {
32770
+ if (startY !== null) {
32771
+ onOffset(Math.max(0, event.clientY - startY), true);
32772
+ }
32773
+ },
32774
+ pointerUp(event) {
32775
+ finish(event, true);
32776
+ },
32777
+ cancel(event) {
32778
+ finish(event, false);
32779
+ },
32780
+ };
32781
+ }
32782
+
32596
32783
  /**
32597
32784
  * insert-chart.ts - framework-agnostic factory for a sensible DEFAULT new
32598
32785
  * chart element, the single source of truth every binding (React, Vue,
@@ -33299,6 +33486,39 @@ function deserializeElementClipboard(text) {
33299
33486
  };
33300
33487
  }
33301
33488
 
33489
+ /** Immutably replace one slide's inherited element list. */
33490
+ function setTemplateElements(map, slideId, elements) {
33491
+ return { ...map, [slideId]: elements };
33492
+ }
33493
+ /** Find an inherited element on one slide. */
33494
+ function findTemplateElement(map, slideId, elementId) {
33495
+ return slideId ? map[slideId]?.find((element) => element.id === elementId) : undefined;
33496
+ }
33497
+ /** Gate layout/master hits unless template editing is enabled. */
33498
+ function isElementIdInteractive(elementId, editTemplateMode) {
33499
+ return !isTemplateElementId(elementId) || editTemplateMode;
33500
+ }
33501
+ /** Split layout/master elements out of each loaded slide while preserving order. */
33502
+ function partitionTemplateElements(slides) {
33503
+ const templateElementsBySlideId = {};
33504
+ const nextSlides = slides.map((slide) => {
33505
+ const template = slide.elements.filter(isTemplateElement);
33506
+ if (template.length === 0) {
33507
+ return slide;
33508
+ }
33509
+ templateElementsBySlideId[slide.id] = template;
33510
+ return { ...slide, elements: slide.elements.filter((element) => !isTemplateElement(element)) };
33511
+ });
33512
+ return { slides: nextSlides, templateElementsBySlideId };
33513
+ }
33514
+ /** Merge editable layout/master elements behind slide-owned elements for save/export. */
33515
+ function buildSaveSlides$1(slides, templateElementsBySlideId) {
33516
+ return slides.map((slide) => {
33517
+ const template = templateElementsBySlideId[slide.id];
33518
+ return template?.length ? { ...slide, elements: [...template, ...slide.elements] } : slide;
33519
+ });
33520
+ }
33521
+
33302
33522
  /**
33303
33523
  * shape-preset-catalog.ts: the Insert > Shape picker catalogue shared by every
33304
33524
  * binding's toolbar/inspector.