pptx-angular-viewer 1.31.2 → 1.31.3

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.31.2](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.31.2) - 2026-07-19
8
+
7
9
  ## [1.31.1](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.31.1) - 2026-07-18
8
10
 
9
11
  ## [1.31.0](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.31.0) - 2026-07-18
@@ -450,22 +450,96 @@ function getResolvedShapeClipPathFor(shapeType, width, height, adjustments) {
450
450
  // 4. Final fallback: core's static preset clip-path table.
451
451
  return getShapeClipPath(shapeType);
452
452
  }
453
+ /**
454
+ * Build a CSS `clip-path: path('…')` value from a custom-geometry (`a:custGeom`)
455
+ * SVG path string, rescaling its path-space coordinates (`pathWidth` x
456
+ * `pathHeight`) into the element's pixel box.
457
+ *
458
+ * CSS `path()` coordinates live in the element's own border-box pixel space
459
+ * (there is no viewBox), so freeform coordinates authored against the OOXML path
460
+ * extent must be scaled by `elemW / pathWidth` and `elemH / pathHeight`. Every
461
+ * binding already clips its shape container's background fill with the resolved
462
+ * clip-path, so returning one here lets a themed freeform (e.g. the "Balloons"
463
+ * background) render as its true outline instead of flooding its bounding box.
464
+ *
465
+ * Supports the absolute command set produced by the core geometry engine
466
+ * (M/L/C/Q/Z) plus elliptical arcs (A); unknown commands are passed through
467
+ * unscaled rather than dropped.
468
+ */
469
+ function buildCustomGeometryClipPath(pathData, pathWidth, pathHeight, elemWidth, elemHeight) {
470
+ if (!pathData ||
471
+ !Number.isFinite(pathWidth) ||
472
+ !Number.isFinite(pathHeight) ||
473
+ pathWidth <= 0 ||
474
+ pathHeight <= 0 ||
475
+ !Number.isFinite(elemWidth) ||
476
+ !Number.isFinite(elemHeight) ||
477
+ elemWidth <= 0 ||
478
+ elemHeight <= 0) {
479
+ return undefined;
480
+ }
481
+ const sx = elemWidth / pathWidth;
482
+ const sy = elemHeight / pathHeight;
483
+ const round = (n) => {
484
+ const r = Math.round(n * 100) / 100;
485
+ return Object.is(r, -0) ? '0' : String(r);
486
+ };
487
+ const tokens = pathData.match(/[MLCQZAHVmlcqzahv][^MLCQZAHVmlcqzahv]*/g) ?? [];
488
+ const out = [];
489
+ for (const token of tokens) {
490
+ const cmd = token[0];
491
+ const upper = cmd.toUpperCase();
492
+ if (upper === 'Z') {
493
+ out.push('Z');
494
+ continue;
495
+ }
496
+ const nums = (token.slice(1).match(/-?[\d.]+(?:e-?\d+)?/gi) ?? []).map(Number);
497
+ if (upper === 'A') {
498
+ // rx ry x-axis-rotation large-arc-flag sweep-flag x y (per 7-number group)
499
+ const parts = [];
500
+ for (let i = 0; i + 6 < nums.length; i += 7) {
501
+ parts.push(round(nums[i] * sx), round(nums[i + 1] * sy), String(nums[i + 2]), String(nums[i + 3]), String(nums[i + 4]), round(nums[i + 5] * sx), round(nums[i + 6] * sy));
502
+ }
503
+ out.push(`A ${parts.join(' ')}`);
504
+ continue;
505
+ }
506
+ // M/L/C/Q (and H/V) carry (x, y) pairs; scale x by sx and y by sy.
507
+ const scaled = nums.map((n, i) => round(n * (i % 2 === 0 ? sx : sy)));
508
+ out.push(`${upper} ${scaled.join(' ')}`);
509
+ }
510
+ if (out.length === 0) {
511
+ return undefined;
512
+ }
513
+ return `path('${out.join(' ')}')`;
514
+ }
453
515
  /**
454
516
  * Element-level convenience wrapper. Pulls `shapeType`, `width`, `height`, and
455
517
  * `shapeAdjustments` off a {@link PptxElement} and delegates to
456
518
  * {@link getResolvedShapeClipPathFor}.
457
519
  *
520
+ * Custom-geometry freeforms (which carry `pathData`/`pathWidth`/`pathHeight`
521
+ * rather than a preset `shapeType`) take priority: their outline is rescaled
522
+ * into the element box via {@link buildCustomGeometryClipPath} so the fill clips
523
+ * to the real shape instead of its bounding rectangle.
524
+ *
458
525
  * @param element The PPTX element to resolve a clip-path for.
459
526
  * @param width Optional width override (pixels). Defaults to `element.width`.
460
527
  * @param height Optional height override (pixels). Defaults to `element.height`.
461
528
  */
462
529
  function getResolvedShapeClipPath(element, width, height) {
530
+ const w = typeof width === 'number' ? width : element.width;
531
+ const h = typeof height === 'number' ? height : element.height;
532
+ const custom = element;
533
+ if (custom.pathData && custom.pathWidth && custom.pathHeight) {
534
+ const customClip = buildCustomGeometryClipPath(custom.pathData, custom.pathWidth, custom.pathHeight, w, h);
535
+ if (customClip) {
536
+ return customClip;
537
+ }
538
+ }
463
539
  const shapeType = element.shapeType;
464
540
  if (!shapeType) {
465
541
  return undefined;
466
542
  }
467
- const w = typeof width === 'number' ? width : element.width;
468
- const h = typeof height === 'number' ? height : element.height;
469
543
  const adjustments = element.shapeAdjustments;
470
544
  return getResolvedShapeClipPathFor(shapeType, w, h, adjustments);
471
545
  }
@@ -1450,20 +1524,29 @@ function getSvgStrokeDasharray(dashType, strokeWidth, customDashSegments) {
1450
1524
  /**
1451
1525
  * Builds a CSS `transform` string combining flip and rotation transforms for an element.
1452
1526
  * Flips are expressed as `scaleX(-1)` / `scaleY(-1)`, rotation as `rotate(Ndeg)`.
1527
+ *
1528
+ * Order matters: OOXML `a:xfrm` mirrors the shape *within* its bounding box
1529
+ * (`flipH`/`flipV`) and *then* rotates the box by `rot`. With CSS
1530
+ * `transform-origin: center`, transforms apply right-to-left, so the flips must
1531
+ * come AFTER the rotation in the string (`rotate(θ) scaleX(-1)`) to be applied
1532
+ * first. Emitting `scaleX(-1) rotate(θ)` instead reflects the rotation direction
1533
+ * for any shape that is both flipped and rotated (e.g. the "Balloons" freeforms
1534
+ * render mirrored/tilted the wrong way). This matches the Angular binding's
1535
+ * `getContainerStyle`, which already emits `rotate() scaleX() scaleY()`.
1453
1536
  * @param element - The element whose transforms are read.
1454
1537
  * @returns A CSS transform string, or `undefined` if no transforms apply.
1455
1538
  */
1456
1539
  function getElementTransform(element) {
1457
1540
  const transforms = [];
1541
+ if (element.rotation) {
1542
+ transforms.push(`rotate(${element.rotation}deg)`);
1543
+ }
1458
1544
  if (element.flipHorizontal) {
1459
1545
  transforms.push('scaleX(-1)');
1460
1546
  }
1461
1547
  if (element.flipVertical) {
1462
1548
  transforms.push('scaleY(-1)');
1463
1549
  }
1464
- if (element.rotation) {
1465
- transforms.push(`rotate(${element.rotation}deg)`);
1466
- }
1467
1550
  if (element.skewX) {
1468
1551
  transforms.push(`skewX(${element.skewX}deg)`);
1469
1552
  }
@@ -73870,7 +73953,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
73870
73953
  }], propDecorators: { canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], slideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideIndex", required: false }] }] } });
73871
73954
 
73872
73955
  // Generated by scripts/inline-shared.mjs from package.json. Do not edit.
73873
- const PPTX_ANGULAR_VIEWER_VERSION = "1.31.1";
73956
+ const PPTX_ANGULAR_VIEWER_VERSION = "1.31.2";
73874
73957
 
73875
73958
  /**
73876
73959
  * account-page.component.ts: File > Account content.