pptx-angular-viewer 3.2.1 → 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import { NgStyle, NgTemplateOutlet, NgClass } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
3
  import { InjectionToken, input, output, inject, computed, ChangeDetectionStrategy, Component, signal, Injectable, DestroyRef, viewChild, effect, Injector, ElementRef, HostListener, afterNextRender, Renderer2, ViewEncapsulation, forwardRef, untracked, Directive } from '@angular/core';
4
4
  import { TranslateService, TranslatePipe, translate } from '@ngx-translate/core';
5
- import { THEME_PRESETS, guideEmuToPx, getShapeClipPath, getAdjustmentAwareClipPath, getShapeClipPathFromPreset, getCloudPathForRendering, MIN_ELEMENT_SIZE as MIN_ELEMENT_SIZE$1, customGeometryPathsToSvgSubpaths, evaluatePresetShape, hasShapeProperties, ooxmlGradientAngleToCssDegrees, getShapeType, isImageLikeElement, getElementTextContent, getRoundRectRadiusPx as getRoundRectRadiusPx$1, applyDrawingColorTransforms as applyDrawingColorTransforms$1, hslToRgb as hslToRgb$1, PRESET_COLOR_MAP, getSubstituteFontFamily, parsePanoseString, hasTextProperties, stripXmlOrderSuffix, orderedXmlKey, chartDataChangeType, chartDataAddSeries, chartDataRemoveSeries, chartDataAddCategory, chartDataRemoveCategory, chartDataUpdatePoint, rebuildTableStructureInRawXml, updateCellTextInRawXml, deepCloneData, cloneTemplateElementsBySlideId, cloneSlide, EMU_PER_PX as EMU_PER_PX$2, DEFAULT_SCHEME_COLOR_MAP as DEFAULT_SCHEME_COLOR_MAP$1, formatAutoNumberMarker, getLinkedTextBoxSegments, deriveSlideTitle, svgPathToPolygons, THEME_COLOR_SCHEME_KEYS, createBuiltinVariables, resolveCoordinate, lookupPresetShape, normalizeStShapeType, evaluateGuides, PptxHandler, styleStroke, strokeFor, fitFontSize, nodeTextStyle, truncate, nodeOpacity, nodeStroke, nodeFill, styleShadow, buildTree, treeWidth, treeDepth, gearPoints, flattenNodes, interpretSmartArtLayout, addSmartArtNode, removeSmartArtNode, demoteSmartArtNode, promoteSmartArtNode, reorderSmartArtNode, chevronPoints, deobfuscateFont, detectFontFormat, createEditorId, makeStoreAwareId, reassignDescendantIds, checkMissingAltText, checkMissingSlideTitle, checkLowContrast, checkComplexTables, checkBlankSlide, checkDuplicateTitles, createChartElement, detectFileFormat, serializePptxToJson, PPTX_JSON_MIME_TYPE, PptxMarkdownConverter, ThemePresets, formatCommentTimestamp as formatCommentTimestamp$1, EncryptedFileError, decodeXmlEntities, parseSignatureXml, groupElements, ungroupElements, cloneElement, updateSmartArtNodeText, setSmartArtNodeStyle, isInkElement, parseDataUrlToBytes, isZoomElement, SvgExporter, applyThemeToData, pptxActionToElementAction, elementActionToPptxAction, setChartTitle, setChartLegend, setChartDataLabels, setChartAxis, setChartSeriesTrendline, setChartSeriesErrorBars, setChartDataPointLabel, setChartAxisLogScale, setChartAxisTitleStyle, setChartAxisGridlineStyle, setChartSeriesMarker, setChartSeriesChartType, setChartDataPointFill, setChartDataPointMarker, setChartDataPointExplosion, getOleObjectTypeLabel, addSmartArtNodeAsChild, switchSmartArtLayout, SWITCHABLE_LAYOUT_TYPES, COLOR_MAP_ALIAS_KEYS, DEFAULT_COLOR_MAP, applyThemeOverrideToSlide, TRANSITION_VALID_DIRECTIONS } from 'pptx-viewer-core';
5
+ import { THEME_PRESETS, guideEmuToPx, getShapeClipPath, getAdjustmentAwareClipPath, getShapeClipPathFromPreset, getCloudPathForRendering, MIN_ELEMENT_SIZE as MIN_ELEMENT_SIZE$1, customGeometryPathsToSvgSubpaths, evaluatePresetShape, hasShapeProperties, ooxmlGradientAngleToCssDegrees, getShapeType, isImageLikeElement, getElementTextContent, getRoundRectRadiusPx as getRoundRectRadiusPx$1, applyDrawingColorTransforms as applyDrawingColorTransforms$1, hslToRgb as hslToRgb$1, PRESET_COLOR_MAP, getSubstituteFontFamily, parsePanoseString, hasTextProperties, stripXmlOrderSuffix, orderedXmlKey, chartDataChangeType, chartDataAddSeries, chartDataRemoveSeries, chartDataAddCategory, chartDataRemoveCategory, chartDataUpdatePoint, rebuildTableStructureInRawXml, updateCellTextInRawXml, deepCloneData, cloneTemplateElementsBySlideId, cloneSlide, EMU_PER_PX as EMU_PER_PX$2, DEFAULT_SCHEME_COLOR_MAP as DEFAULT_SCHEME_COLOR_MAP$1, formatAutoNumberMarker, getLinkedTextBoxSegments, deriveSlideTitle, svgPathToPolygons, THEME_COLOR_SCHEME_KEYS, createBuiltinVariables, resolveCoordinate, lookupPresetShape, normalizeStShapeType, evaluateGuides, PptxHandler, styleStroke, strokeFor, fitFontSize, nodeTextStyle, truncate, nodeOpacity, nodeStroke, nodeFill, styleShadow, buildTree, treeWidth, treeDepth, gearPoints, flattenNodes, interpretSmartArtLayout, addSmartArtNode, removeSmartArtNode, demoteSmartArtNode, promoteSmartArtNode, reorderSmartArtNode, deobfuscateFont, detectFontFormat, createEditorId, makeStoreAwareId, reassignDescendantIds, checkMissingAltText, checkMissingSlideTitle, checkLowContrast, checkComplexTables, checkBlankSlide, checkDuplicateTitles, createChartElement, detectFileFormat, serializePptxToJson, PPTX_JSON_MIME_TYPE, PptxMarkdownConverter, ThemePresets, formatCommentTimestamp as formatCommentTimestamp$1, EncryptedFileError, decodeXmlEntities, parseSignatureXml, groupElements, ungroupElements, cloneElement, updateSmartArtNodeText, setSmartArtNodeStyle, isInkElement, parseDataUrlToBytes, isZoomElement, SvgExporter, applyThemeToData, pptxActionToElementAction, elementActionToPptxAction, setChartTitle, setChartLegend, setChartDataLabels, setChartAxis, setChartSeriesTrendline, setChartSeriesErrorBars, setChartDataPointLabel, setChartAxisLogScale, setChartAxisTitleStyle, setChartAxisGridlineStyle, setChartSeriesMarker, setChartSeriesChartType, setChartDataPointFill, setChartDataPointMarker, setChartDataPointExplosion, getOleObjectTypeLabel, addSmartArtNodeAsChild, switchSmartArtLayout, SWITCHABLE_LAYOUT_TYPES, COLOR_MAP_ALIAS_KEYS, DEFAULT_COLOR_MAP, applyThemeOverrideToSlide, TRANSITION_VALID_DIRECTIONS } from 'pptx-viewer-core';
6
6
  import DOMPurify from 'dompurify';
7
7
  import { z } from 'zod';
8
8
  import * as mcp from 'pptx-viewer-mcp';
@@ -2331,6 +2331,36 @@ function resolveComputedFill(ss, element) {
2331
2331
  return {};
2332
2332
  }
2333
2333
 
2334
+ const TEXT_ORIENTATION_IDENTITY = [1, 0, 0, 1];
2335
+ function isTextOrientationMatrix(value) {
2336
+ return (Array.isArray(value) &&
2337
+ value.length === 4 &&
2338
+ value.every((entry) => typeof entry === 'number' && Number.isFinite(entry)));
2339
+ }
2340
+ function multiplyTextOrientationMatrices(left, right) {
2341
+ return [
2342
+ left[0] * right[0] + left[2] * right[1],
2343
+ left[1] * right[0] + left[3] * right[1],
2344
+ left[0] * right[2] + left[2] * right[3],
2345
+ left[1] * right[2] + left[3] * right[3],
2346
+ ];
2347
+ }
2348
+ function getElementOrientationMatrix(element) {
2349
+ const radians = ((Number(element.rotation) || 0) * Math.PI) / 180;
2350
+ const cosine = Math.cos(radians);
2351
+ const sine = Math.sin(radians);
2352
+ const rotation = [cosine, sine, -sine, cosine];
2353
+ const flip = [
2354
+ element.flipHorizontal ? -1 : 1,
2355
+ 0,
2356
+ 0,
2357
+ element.flipVertical ? -1 : 1,
2358
+ ];
2359
+ return multiplyTextOrientationMatrices(rotation, flip);
2360
+ }
2361
+ function textOrientationAngleScore(matrix) {
2362
+ return Math.abs(Math.atan2(matrix[1], matrix[0]));
2363
+ }
2334
2364
  /**
2335
2365
  * Normalizes a raw stroke dash type string to a valid `StrokeDashType` enum value.
2336
2366
  * Performs case-insensitive matching against all OOXML dash types.
@@ -2573,12 +2603,37 @@ function getElementTransformWithoutRotation(element) {
2573
2603
  */
2574
2604
  function getTextCompensationTransform(element) {
2575
2605
  const transforms = [];
2576
- if (element.flipHorizontal) {
2606
+ const normalizedRotation = (((Number(element.rotation) || 0) % 360) + 360) % 360;
2607
+ const swapFlipAxis = normalizedRotation > 90 &&
2608
+ normalizedRotation < 270 &&
2609
+ element.flipHorizontal !== element.flipVertical;
2610
+ const compensateHorizontal = swapFlipAxis ? element.flipVertical : element.flipHorizontal;
2611
+ const compensateVertical = swapFlipAxis ? element.flipHorizontal : element.flipVertical;
2612
+ if (compensateHorizontal) {
2577
2613
  transforms.push('scaleX(-1)');
2578
2614
  }
2579
- if (element.flipVertical) {
2615
+ if (compensateVertical) {
2580
2616
  transforms.push('scaleY(-1)');
2581
2617
  }
2618
+ const ancestorGroupTransform = 'textStyle' in element ? element.textStyle?.ancestorGroupTransform : undefined;
2619
+ if (isTextOrientationMatrix(ancestorGroupTransform)) {
2620
+ const localCompensation = [
2621
+ compensateHorizontal ? -1 : 1,
2622
+ 0,
2623
+ 0,
2624
+ compensateVertical ? -1 : 1,
2625
+ ];
2626
+ const visibleTransform = multiplyTextOrientationMatrices(multiplyTextOrientationMatrices(ancestorGroupTransform, getElementOrientationMatrix(element)), localCompensation);
2627
+ const determinant = visibleTransform[0] * visibleTransform[3] - visibleTransform[1] * visibleTransform[2];
2628
+ if (determinant < -1e-6) {
2629
+ const horizontalCandidate = multiplyTextOrientationMatrices(visibleTransform, [-1, 0, 0, 1]);
2630
+ const verticalCandidate = multiplyTextOrientationMatrices(visibleTransform, [1, 0, 0, -1]);
2631
+ transforms.push(textOrientationAngleScore(horizontalCandidate) <=
2632
+ textOrientationAngleScore(verticalCandidate)
2633
+ ? 'scaleX(-1)'
2634
+ : 'scaleY(-1)');
2635
+ }
2636
+ }
2582
2637
  return transforms.length > 0 ? transforms.join(' ') : undefined;
2583
2638
  }
2584
2639
  /**
@@ -2958,6 +3013,7 @@ function getOuterShadowCss(style, context) {
2958
3013
  if (!style?.shadowColor || style.shadowColor === 'transparent') {
2959
3014
  return undefined;
2960
3015
  }
3016
+ const usesOoxmlDefaults = style.outerShadowXml !== undefined;
2961
3017
  let offsetX;
2962
3018
  let offsetY;
2963
3019
  if (typeof style.shadowAngle === 'number' && typeof style.shadowDistance === 'number') {
@@ -2972,18 +3028,30 @@ function getOuterShadowCss(style, context) {
2972
3028
  offsetX =
2973
3029
  typeof style.shadowOffsetX === 'number' && Number.isFinite(style.shadowOffsetX)
2974
3030
  ? style.shadowOffsetX
2975
- : 4;
3031
+ : usesOoxmlDefaults
3032
+ ? 0
3033
+ : 4;
2976
3034
  offsetY =
2977
3035
  typeof style.shadowOffsetY === 'number' && Number.isFinite(style.shadowOffsetY)
2978
3036
  ? style.shadowOffsetY
2979
- : 4;
3037
+ : usesOoxmlDefaults
3038
+ ? 0
3039
+ : 4;
2980
3040
  }
2981
- const blur = typeof style.shadowBlur === 'number' && Number.isFinite(style.shadowBlur)
3041
+ const rawBlur = typeof style.shadowBlur === 'number' && Number.isFinite(style.shadowBlur)
2982
3042
  ? Math.max(0, style.shadowBlur)
2983
- : 6;
3043
+ : usesOoxmlDefaults
3044
+ ? 0
3045
+ : 6;
3046
+ // DrawingML `blurRad` is a radius while CSS shadows take a Gaussian
3047
+ // standard deviation. Halving authored OOXML radii matches PowerPoint's
3048
+ // falloff; host-authored CSS-style values retain their historical meaning.
3049
+ const blur = usesOoxmlDefaults ? rawBlur / 2 : rawBlur;
2984
3050
  const opacity = typeof style.shadowOpacity === 'number' && Number.isFinite(style.shadowOpacity)
2985
3051
  ? clampUnitInterval(style.shadowOpacity)
2986
- : 0.35;
3052
+ : usesOoxmlDefaults
3053
+ ? 1
3054
+ : 0.35;
2987
3055
  // Honour @sx/@sy (1000ths of a percent, 100000 = 100%) as a box-shadow
2988
3056
  // spread: a scaled-up shadow grows outward, a scaled-down one shrinks. This
2989
3057
  // is a best-effort mapping (box-shadow has no true scale). @kx/@ky skew and
@@ -2994,6 +3062,77 @@ function getOuterShadowCss(style, context) {
2994
3062
  const geometry = `${Math.round(offsetX)}px ${Math.round(offsetY)}px ${Math.round(blur)}px`;
2995
3063
  return spread === 0 ? `${geometry} ${color}` : `${geometry} ${spread}px ${color}`;
2996
3064
  }
3065
+ /**
3066
+ * Build pixel-composited outer shadows for images, text and groups.
3067
+ *
3068
+ * `box-shadow` follows the rectangular element box; PowerPoint shadows the
3069
+ * already-composited pixels. CSS `drop-shadow()` has those semantics.
3070
+ */
3071
+ function getCompositeOuterShadowFilterCss(style, context) {
3072
+ if (!style) {
3073
+ return undefined;
3074
+ }
3075
+ const usesOoxmlDefaults = style.outerShadowXml !== undefined;
3076
+ const shadows = style.shadows && style.shadows.length > 0
3077
+ ? style.shadows
3078
+ : style.shadowColor && style.shadowColor !== 'transparent'
3079
+ ? [
3080
+ {
3081
+ angle: style.shadowAngle,
3082
+ distance: style.shadowDistance,
3083
+ offsetX: style.shadowOffsetX,
3084
+ offsetY: style.shadowOffsetY,
3085
+ blur: style.shadowBlur,
3086
+ color: style.shadowColor,
3087
+ opacity: style.shadowOpacity,
3088
+ },
3089
+ ]
3090
+ : [];
3091
+ const parts = [];
3092
+ for (const shadow of shadows) {
3093
+ if (!shadow.color || shadow.color === 'transparent') {
3094
+ continue;
3095
+ }
3096
+ let offsetX;
3097
+ let offsetY;
3098
+ if (typeof shadow.angle === 'number' && typeof shadow.distance === 'number') {
3099
+ const angle = style.shadowRotateWithShape === false && typeof context?.rotation === 'number'
3100
+ ? shadow.angle - context.rotation
3101
+ : shadow.angle;
3102
+ const angleRad = (angle * Math.PI) / 180;
3103
+ offsetX = Math.cos(angleRad) * shadow.distance;
3104
+ offsetY = Math.sin(angleRad) * shadow.distance;
3105
+ }
3106
+ else {
3107
+ offsetX =
3108
+ typeof shadow.offsetX === 'number' && Number.isFinite(shadow.offsetX)
3109
+ ? shadow.offsetX
3110
+ : usesOoxmlDefaults
3111
+ ? 0
3112
+ : 4;
3113
+ offsetY =
3114
+ typeof shadow.offsetY === 'number' && Number.isFinite(shadow.offsetY)
3115
+ ? shadow.offsetY
3116
+ : usesOoxmlDefaults
3117
+ ? 0
3118
+ : 4;
3119
+ }
3120
+ const rawBlur = typeof shadow.blur === 'number' && Number.isFinite(shadow.blur)
3121
+ ? Math.max(0, shadow.blur)
3122
+ : usesOoxmlDefaults
3123
+ ? 0
3124
+ : 6;
3125
+ const blur = usesOoxmlDefaults ? rawBlur / 2 : rawBlur;
3126
+ const opacity = typeof shadow.opacity === 'number' && Number.isFinite(shadow.opacity)
3127
+ ? clampUnitInterval(shadow.opacity)
3128
+ : usesOoxmlDefaults
3129
+ ? 1
3130
+ : 0.35;
3131
+ const color = colorWithOpacity(normalizeHexColor(shadow.color, DEFAULT_SHADOW_COLOR), opacity);
3132
+ parts.push(`drop-shadow(${Math.round(offsetX)}px ${Math.round(offsetY)}px ${Math.round(blur)}px ${color})`);
3133
+ }
3134
+ return parts.length > 0 ? parts.join(' ') : undefined;
3135
+ }
2997
3136
  /**
2998
3137
  * Derive a box-shadow spread (px) from outer-shadow `@sx`/`@sy` scale factors
2999
3138
  * (1000ths of a percent; 100000 = 100%). The spread is proportional to the
@@ -3274,7 +3413,7 @@ function getEffectFilterCss(style, elementId) {
3274
3413
  // Outer glow → drop-shadow
3275
3414
  if (style.glowColor && style.glowColor !== 'transparent' && style.glowRadius) {
3276
3415
  const glowOpacity = typeof style.glowOpacity === 'number' ? style.glowOpacity : 0.75;
3277
- const glowRad = Math.round(Math.max(0, style.glowRadius));
3416
+ const glowRad = Math.max(0, style.glowRadius / 2);
3278
3417
  const glowCol = colorWithOpacity(normalizeHexColor(style.glowColor, DEFAULT_GLOW_COLOR), glowOpacity);
3279
3418
  parts.push(`drop-shadow(0 0 ${glowRad}px ${glowCol})`);
3280
3419
  }
@@ -3479,7 +3618,16 @@ function getComputedEffectStyle(element, options = {}) {
3479
3618
  // container as well would shadow the bounding RECTANGLE on top of the line.
3480
3619
  const paintsLineEffectsItself = element.type === 'connector' ||
3481
3620
  getShapeType('shapeType' in element ? element.shapeType : undefined) === 'connector';
3482
- const boxShadow = getBoxShadowCss(style, { includeGlow: options.includeGlowBoxShadow }, { rotation: element.rotation });
3621
+ const shadowsCompositePixels = element.type === 'group' ||
3622
+ element.type === 'text' ||
3623
+ element.type === 'image' ||
3624
+ element.type === 'picture';
3625
+ const compositeShadow = shadowsCompositePixels
3626
+ ? getCompositeOuterShadowFilterCss(style, { rotation: element.rotation })
3627
+ : undefined;
3628
+ const boxShadow = shadowsCompositePixels
3629
+ ? undefined
3630
+ : getBoxShadowCss(style, { includeGlow: options.includeGlowBoxShadow }, { rotation: element.rotation });
3483
3631
  // The line-level shadow (`a:ln/a:effectLst/a:outerShdw`) is part of the same
3484
3632
  // box-shadow channel. Only React applied it to a shape container; folding it
3485
3633
  // in here is what carries it to the other four bindings.
@@ -3491,7 +3639,7 @@ function getComputedEffectStyle(element, options = {}) {
3491
3639
  const filter = getEffectFilterCss(style, element.id);
3492
3640
  // As above for `a:ln/a:effectLst/a:glow`, which is a `drop-shadow` filter.
3493
3641
  const lineGlow = paintsLineEffectsItself ? undefined : getLineGlowFilterCss(style);
3494
- const filterParts = [filter, lineGlow].filter((part) => part !== undefined && part !== '');
3642
+ const filterParts = [compositeShadow, filter, lineGlow].filter((part) => part !== undefined && part !== '');
3495
3643
  if (filterParts.length > 0) {
3496
3644
  result.filter = filterParts.join(' ');
3497
3645
  }
@@ -3754,6 +3902,59 @@ function outlinePathData(clipPath, width, height) {
3754
3902
  }
3755
3903
  return rect;
3756
3904
  }
3905
+ const WEDGE_CALLOUT_PRESET_NAMES = new Set([
3906
+ 'wedgerectcallout',
3907
+ 'wedgeroundrectcallout',
3908
+ 'wedgeellipsecallout',
3909
+ ]);
3910
+ /** Whether a preset's pointer is part of the authored outline and may extend outside its body box. */
3911
+ function isWedgeCalloutPresetShape(shapeType) {
3912
+ return typeof shapeType === 'string' && WEDGE_CALLOUT_PRESET_NAMES.has(shapeType.toLowerCase());
3913
+ }
3914
+ function clipPathPolygonPoints(clipPath, width, height) {
3915
+ const asPolygon = /^polygon\((.*)\)$/su.exec(clipPath.trim());
3916
+ if (!asPolygon) {
3917
+ return [];
3918
+ }
3919
+ const toPx = (token, extent) => token.endsWith('%') ? (Number.parseFloat(token) / 100) * extent : Number.parseFloat(token);
3920
+ return asPolygon[1]
3921
+ .split(',')
3922
+ .map((pair) => pair.trim().split(/\s+/u))
3923
+ .filter((parts) => parts.length === 2)
3924
+ .map(([rawX, rawY]) => [toPx(rawX, width), toPx(rawY, height)])
3925
+ .filter(([x, y]) => Number.isFinite(x) && Number.isFinite(y));
3926
+ }
3927
+ /** Resolve a preset outline into SVG path data plus any authored out-of-box bounds. */
3928
+ function getPresetShapeVectorGeometry(shapeType, width, height, adjustments) {
3929
+ const normalizedWidth = Math.max(Number.isFinite(width) ? width : 0, 1);
3930
+ const normalizedHeight = Math.max(Number.isFinite(height) ? height : 0, 1);
3931
+ const clipPath = getResolvedShapeClipPathFor(shapeType, normalizedWidth, normalizedHeight, adjustments);
3932
+ const d = outlinePathData(clipPath, normalizedWidth, normalizedHeight);
3933
+ if (!d) {
3934
+ return undefined;
3935
+ }
3936
+ let minX = 0;
3937
+ let minY = 0;
3938
+ let maxX = normalizedWidth;
3939
+ let maxY = normalizedHeight;
3940
+ for (const [x, y] of clipPath
3941
+ ? clipPathPolygonPoints(clipPath, normalizedWidth, normalizedHeight)
3942
+ : []) {
3943
+ minX = Math.min(minX, x);
3944
+ minY = Math.min(minY, y);
3945
+ maxX = Math.max(maxX, x);
3946
+ maxY = Math.max(maxY, y);
3947
+ }
3948
+ return {
3949
+ d,
3950
+ minX,
3951
+ minY,
3952
+ maxX,
3953
+ maxY,
3954
+ viewWidth: Math.max(maxX - minX, 1),
3955
+ viewHeight: Math.max(maxY - minY, 1),
3956
+ };
3957
+ }
3757
3958
  /**
3758
3959
  * The parallel strands a line is painted with. A single (`sng`) line keeps the
3759
3960
  * outline's own width verbatim so the ordinary case is byte-identical to a
@@ -20778,6 +20979,7 @@ function getEffectKeyframes(effect) {
20778
20979
  *
20779
20980
  * @module render/animation-color
20780
20981
  */
20982
+ const HEX_COLOR = /^#?[0-9a-f]{6}$/iu;
20781
20983
  // ==========================================================================
20782
20984
  // Colour conversion utilities
20783
20985
  // ==========================================================================
@@ -20948,64 +21150,178 @@ function resolveCssProperties(attrName) {
20948
21150
  * Returns an empty array for text/`color`-only animations, which do not need a
20949
21151
  * vector renderer to relinquish its painted fill / stroke.
20950
21152
  */
20951
- function resolveColorAnimationTargets(targetAttribute) {
20952
- const cssProperties = resolveCssProperties(targetAttribute);
21153
+ function resolveColorAnimationTargets(animationOrAttribute) {
21154
+ const attributes = typeof animationOrAttribute === 'string' || animationOrAttribute === undefined
21155
+ ? [animationOrAttribute]
21156
+ : colorAnimationComponents(animationOrAttribute).map((component) => component.targetAttribute);
20953
21157
  const targets = [];
20954
- if (cssProperties.includes('fill')) {
20955
- targets.push('fill');
20956
- }
20957
- if (cssProperties.includes('stroke')) {
20958
- targets.push('stroke');
21158
+ for (const attribute of attributes) {
21159
+ const cssProperties = resolveCssProperties(attribute);
21160
+ if (cssProperties.includes('fill') && !targets.includes('fill')) {
21161
+ targets.push('fill');
21162
+ }
21163
+ if (cssProperties.includes('stroke') && !targets.includes('stroke')) {
21164
+ targets.push('stroke');
21165
+ }
20959
21166
  }
20960
21167
  return targets;
20961
21168
  }
20962
- /**
20963
- * Build CSS `@keyframes` for a color animation (`p:animClr`).
20964
- *
20965
- * Generates keyframe stops at regular intervals with interpolated colors.
20966
- * The CSS property is determined from the `targetAttribute` field which is
20967
- * parsed from `p:attrNameLst` (e.g. "fillcolor" → `backgroundColor`).
20968
- *
20969
- * @param colorAnim - Parsed color animation data
20970
- * @param keyframeName - Name for the generated `@keyframes` rule
20971
- * @param steps - Number of keyframe stops (default 10 for smooth interpolation)
20972
- * @returns CSS `@keyframes` string, or undefined if colors are missing
20973
- */
20974
- function buildColorAnimationKeyframes(colorAnim, keyframeName, steps = 10) {
20975
- const { colorSpace, direction, fromColor, toColor, byColor, targetAttribute } = colorAnim;
20976
- // Determine effective start and end colors
21169
+ /** CSS custom properties that snapshot the shape paint before animation owns it. */
21170
+ const ANIMATION_COLOR_BASE_PROPERTIES = {
21171
+ color: '--pptx-animation-color-base',
21172
+ fill: '--pptx-animation-fill-base',
21173
+ stroke: '--pptx-animation-stroke-base',
21174
+ };
21175
+ /** Return every authored sibling behaviour, or the legacy single behaviour. */
21176
+ function colorAnimationComponents(colorAnim) {
21177
+ return colorAnim.components && colorAnim.components.length > 0
21178
+ ? colorAnim.components
21179
+ : [colorAnim];
21180
+ }
21181
+ /** Clamp saturation / lightness to PowerPoint's legal range. */
21182
+ function clampPercent$1(value) {
21183
+ return Math.max(0, Math.min(100, value));
21184
+ }
21185
+ /** Apply a signed OOXML HSL delta to a concrete starting colour. */
21186
+ function applyHslDelta(fromColor, delta, progress) {
21187
+ const fromRgb = hexToRgb$2(fromColor);
21188
+ const fromHsl = rgbToHsl(fromRgb.r, fromRgb.g, fromRgb.b);
21189
+ const hue = (((fromHsl.h + delta.hue * progress) % 360) + 360) % 360;
21190
+ const saturation = clampPercent$1(fromHsl.s + delta.saturation * progress);
21191
+ const lightness = clampPercent$1(fromHsl.l + delta.lightness * progress);
21192
+ const rgb = hslToRgb(hue, saturation, lightness);
21193
+ return rgbToHex$2(rgb.r, rgb.g, rgb.b);
21194
+ }
21195
+ /** Stable compact number for generated CSS (also avoids `-0`). */
21196
+ function cssNumber(value) {
21197
+ const rounded = Math.round(value * 1_000_000) / 1_000_000;
21198
+ return String(Object.is(rounded, -0) ? 0 : rounded);
21199
+ }
21200
+ /** The pre-animation paint snapshot appropriate for one emitted CSS property. */
21201
+ function baseColorForProperty(property) {
21202
+ if (property === 'fill' || property === 'background-color' || property === 'backgroundColor') {
21203
+ return `var(${ANIMATION_COLOR_BASE_PROPERTIES.fill}, currentColor)`;
21204
+ }
21205
+ if (property === 'stroke' || property === 'border-color' || property === 'borderColor') {
21206
+ return `var(${ANIMATION_COLOR_BASE_PROPERTIES.stroke}, currentColor)`;
21207
+ }
21208
+ return `var(${ANIMATION_COLOR_BASE_PROPERTIES.color}, currentColor)`;
21209
+ }
21210
+ /** CSS relative-colour form for a `by` HSL animation with no explicit `from`. */
21211
+ function relativeHslColor(property, delta, progress) {
21212
+ const hue = cssNumber(delta.hue * progress);
21213
+ const saturation = cssNumber(delta.saturation * progress);
21214
+ const lightness = cssNumber(delta.lightness * progress);
21215
+ return `hsl(from ${baseColorForProperty(property)} calc(h + ${hue}) calc(s + ${saturation}) calc(l + ${lightness}) / alpha)`;
21216
+ }
21217
+ /** Resolve an ordinary absolute/RGB-delta component at one progress stop. */
21218
+ function absoluteComponentColor(component, progress) {
21219
+ const { colorSpace, direction, fromColor, toColor, byColor } = component;
20977
21220
  let effectiveFrom;
20978
21221
  let effectiveTo;
20979
21222
  if (fromColor && toColor) {
21223
+ if (!HEX_COLOR.test(fromColor) || !HEX_COLOR.test(toColor)) {
21224
+ return undefined;
21225
+ }
20980
21226
  effectiveFrom = fromColor;
20981
21227
  effectiveTo = toColor;
20982
21228
  }
20983
21229
  else if (fromColor && byColor) {
20984
- // "by" animation: add the delta to the from color
21230
+ if (!HEX_COLOR.test(fromColor) || !HEX_COLOR.test(byColor)) {
21231
+ return undefined;
21232
+ }
20985
21233
  const fromRgb = hexToRgb$2(fromColor);
20986
21234
  const byRgb = hexToRgb$2(byColor);
20987
21235
  effectiveFrom = fromColor;
20988
21236
  effectiveTo = rgbToHex$2(fromRgb.r + byRgb.r, fromRgb.g + byRgb.g, fromRgb.b + byRgb.b);
20989
21237
  }
20990
21238
  else if (toColor) {
20991
- // No from specified — use a neutral starting point
21239
+ if (!HEX_COLOR.test(toColor)) {
21240
+ return undefined;
21241
+ }
20992
21242
  effectiveFrom = '#000000';
20993
21243
  effectiveTo = toColor;
20994
21244
  }
20995
21245
  else {
20996
21246
  return undefined;
20997
21247
  }
20998
- const cssProperties = resolveCssProperties(targetAttribute);
21248
+ return interpolateColor(effectiveFrom, effectiveTo, progress, colorSpace, direction);
21249
+ }
21250
+ /**
21251
+ * Build CSS `@keyframes` for a color animation (`p:animClr`).
21252
+ *
21253
+ * Generates keyframe stops at regular intervals with interpolated colors.
21254
+ * The CSS property is determined from the `targetAttribute` field which is
21255
+ * parsed from `p:attrNameLst` (e.g. "fillcolor" → `backgroundColor`).
21256
+ *
21257
+ * @param colorAnim - Parsed color animation data
21258
+ * @param keyframeName - Name for the generated `@keyframes` rule
21259
+ * @param steps - Number of keyframe stops (default 10 for smooth interpolation)
21260
+ * @returns CSS `@keyframes` string, or undefined if colors are missing
21261
+ */
21262
+ function buildColorAnimationKeyframes(colorAnim, keyframeName, steps = 10) {
21263
+ const components = colorAnimationComponents(colorAnim);
21264
+ if (!components.some((component) => component.hslDelta !== undefined ||
21265
+ component.toColor !== undefined ||
21266
+ (component.fromColor !== undefined && component.byColor !== undefined))) {
21267
+ return undefined;
21268
+ }
20999
21269
  const lines = [];
21270
+ let hasDeclarations = false;
21000
21271
  const actualSteps = Math.max(2, steps);
21001
21272
  for (let i = 0; i <= actualSteps; i++) {
21002
21273
  const t = i / actualSteps;
21003
21274
  const pct = Math.round(t * 100);
21004
- const color = interpolateColor(effectiveFrom, effectiveTo, t, colorSpace, direction);
21005
- const decls = cssProperties.map((prop) => `${prop}: ${color};`).join(' ');
21275
+ const declarations = [];
21276
+ for (const component of components) {
21277
+ const cssProperties = resolveCssProperties(component.targetAttribute);
21278
+ if (component.hslDelta) {
21279
+ if (component.fromColor && !HEX_COLOR.test(component.fromColor)) {
21280
+ continue;
21281
+ }
21282
+ for (const property of cssProperties) {
21283
+ const color = component.fromColor
21284
+ ? applyHslDelta(component.fromColor, component.hslDelta, t)
21285
+ : relativeHslColor(property, component.hslDelta, t);
21286
+ declarations.push(`${property}: ${color};`);
21287
+ hasDeclarations = true;
21288
+ }
21289
+ continue;
21290
+ }
21291
+ const color = absoluteComponentColor(component, t);
21292
+ if (color) {
21293
+ declarations.push(...cssProperties.map((property) => `${property}: ${color};`));
21294
+ hasDeclarations = true;
21295
+ }
21296
+ }
21297
+ const decls = declarations.join(' ');
21006
21298
  lines.push(`\t${pct}% { ${decls} }`);
21007
21299
  }
21008
- return `@keyframes ${keyframeName} {\n${lines.join('\n')}\n}`;
21300
+ return hasDeclarations ? `@keyframes ${keyframeName} {\n${lines.join('\n')}\n}` : undefined;
21301
+ }
21302
+
21303
+ /**
21304
+ * Build CSS custom properties only while a paint animation is active, keeping
21305
+ * ordinary rendering and existing style snapshots unchanged.
21306
+ */
21307
+ function getAnimationColorBaseStyle(element, options = {}) {
21308
+ if (!hasShapeProperties(element) || (!options.animatesFill && !options.animatesStroke)) {
21309
+ return {};
21310
+ }
21311
+ const style = {};
21312
+ if (options.animatesFill) {
21313
+ const fill = getComputedFillStyle(element, options.parentGroupFill);
21314
+ style[ANIMATION_COLOR_BASE_PROPERTIES.fill] =
21315
+ fill?.backgroundColor ?? element.shapeStyle?.fillColor ?? 'currentColor';
21316
+ }
21317
+ if (options.animatesStroke) {
21318
+ const stroke = getComputedStrokeStyle(element);
21319
+ style[ANIMATION_COLOR_BASE_PROPERTIES.stroke] =
21320
+ stroke.borderColor ?? element.shapeStyle?.strokeColor ?? 'currentColor';
21321
+ }
21322
+ style[ANIMATION_COLOR_BASE_PROPERTIES.color] =
21323
+ (hasTextProperties(element) ? element.textStyle?.color : undefined) ?? 'currentColor';
21324
+ return style;
21009
21325
  }
21010
21326
 
21011
21327
  /**
@@ -21098,7 +21414,7 @@ function resolveStepBuildDescriptor(anim) {
21098
21414
  // Playback-time progress helpers
21099
21415
  // ==========================================================================
21100
21416
  /** Clamp a value into the closed unit interval. */
21101
- function clamp01$2(value) {
21417
+ function clamp01$3(value) {
21102
21418
  return Math.max(0, Math.min(1, value));
21103
21419
  }
21104
21420
  /**
@@ -21114,7 +21430,7 @@ function computeBuildProgress(timing, elapsedMs) {
21114
21430
  if (timing.durationMs <= 0) {
21115
21431
  return elapsedMs >= timing.delayMs ? 1 : 0;
21116
21432
  }
21117
- return clamp01$2((elapsedMs - timing.delayMs) / timing.durationMs);
21433
+ return clamp01$3((elapsedMs - timing.delayMs) / timing.durationMs);
21118
21434
  }
21119
21435
  /**
21120
21436
  * Map a 0..1 build progress to a whole number of revealed stages given the
@@ -21129,7 +21445,7 @@ function revealedStageCount(progress, totalStages) {
21129
21445
  if (totalStages <= 0) {
21130
21446
  return 0;
21131
21447
  }
21132
- const p = clamp01$2(progress);
21448
+ const p = clamp01$3(progress);
21133
21449
  if (p <= 0) {
21134
21450
  return 0;
21135
21451
  }
@@ -21305,6 +21621,138 @@ function revealedSmartArtNodeCount(nodes, build) {
21305
21621
  return revealedStageCount(build.progress, nodes.length);
21306
21622
  }
21307
21623
 
21624
+ const ATTRIBUTE_KINDS = {
21625
+ opacity: 'opacity',
21626
+ ppt_h: 'scaleY',
21627
+ ppt_w: 'scaleX',
21628
+ ppt_x: 'translateX',
21629
+ ppt_y: 'translateY',
21630
+ 'style.opacity': 'opacity',
21631
+ 'style.rotation': 'rotation',
21632
+ };
21633
+ const SELF_REFERENCE_RE = /^#?ppt_([whxy])(?:([+-])(\d*\.?\d+))?$/iu;
21634
+ function clamp01$2(value) {
21635
+ return Math.max(0, Math.min(1, value));
21636
+ }
21637
+ function numericValue(keyframe) {
21638
+ const parsed = Number(keyframe.value);
21639
+ return Number.isFinite(parsed) ? parsed : undefined;
21640
+ }
21641
+ function parseSelfReference(keyframe, expectedAxis) {
21642
+ const token = String(keyframe.value).replaceAll(' ', '').toLowerCase();
21643
+ const match = SELF_REFERENCE_RE.exec(token);
21644
+ if (!match || match[1] !== expectedAxis) {
21645
+ return undefined;
21646
+ }
21647
+ const magnitude = match[3] === undefined ? 0 : Number(match[3]);
21648
+ if (!Number.isFinite(magnitude)) {
21649
+ return undefined;
21650
+ }
21651
+ return { offset: match[2] === '-' ? -magnitude : magnitude };
21652
+ }
21653
+ function parseValue(kind, keyframe) {
21654
+ if (kind === 'rotation') {
21655
+ return numericValue(keyframe);
21656
+ }
21657
+ if (kind === 'opacity') {
21658
+ const value = numericValue(keyframe);
21659
+ return value === undefined ? undefined : clamp01$2(value);
21660
+ }
21661
+ if (kind === 'scaleX' || kind === 'scaleY') {
21662
+ const numeric = numericValue(keyframe);
21663
+ if (numeric === 0) {
21664
+ return 0;
21665
+ }
21666
+ const axis = kind === 'scaleX' ? 'w' : 'h';
21667
+ const reference = parseSelfReference(keyframe, axis);
21668
+ return reference?.offset === 0 ? 1 : undefined;
21669
+ }
21670
+ const axis = kind === 'translateX' ? 'x' : 'y';
21671
+ const reference = parseSelfReference(keyframe, axis);
21672
+ return reference === undefined ? undefined : reference.offset * 100;
21673
+ }
21674
+ function parseComponent(component, effectDurationMs) {
21675
+ const kind = ATTRIBUTE_KINDS[component.attrName];
21676
+ if (!kind) {
21677
+ return undefined;
21678
+ }
21679
+ const stops = [];
21680
+ for (const keyframe of component.keyframes) {
21681
+ if (typeof keyframe.tm !== 'number' || !Number.isFinite(keyframe.tm)) {
21682
+ return undefined;
21683
+ }
21684
+ const value = parseValue(kind, keyframe);
21685
+ if (value === undefined) {
21686
+ return undefined;
21687
+ }
21688
+ stops.push({ progress: clamp01$2(keyframe.tm / 100000), value });
21689
+ }
21690
+ if (stops.length < 2) {
21691
+ return undefined;
21692
+ }
21693
+ stops.sort((left, right) => left.progress - right.progress);
21694
+ return {
21695
+ delayMs: component.delayMs ?? 0,
21696
+ durationMs: component.durationMs ?? effectDurationMs,
21697
+ kind,
21698
+ stops,
21699
+ };
21700
+ }
21701
+ function valueAt(component, progress, effectDurationMs) {
21702
+ const elapsedMs = progress * effectDurationMs - component.delayMs;
21703
+ const localProgress = clamp01$2(elapsedMs / Math.max(1, component.durationMs));
21704
+ const first = component.stops[0];
21705
+ const last = component.stops[component.stops.length - 1];
21706
+ if (localProgress <= first.progress) {
21707
+ return first.value;
21708
+ }
21709
+ if (localProgress >= last.progress) {
21710
+ return last.value;
21711
+ }
21712
+ for (let index = 1; index < component.stops.length; index += 1) {
21713
+ const right = component.stops[index];
21714
+ const left = component.stops[index - 1];
21715
+ if (localProgress <= right.progress) {
21716
+ const span = Math.max(Number.EPSILON, right.progress - left.progress);
21717
+ const ratio = (localProgress - left.progress) / span;
21718
+ return left.value + (right.value - left.value) * ratio;
21719
+ }
21720
+ }
21721
+ return last.value;
21722
+ }
21723
+ /** Build a normalized playback model for supported generic `p:anim` transforms. */
21724
+ function createAttributeTransformModel(anim) {
21725
+ const effectDurationMs = Math.max(1, anim.durationMs ?? 1000);
21726
+ const supportedComponents = (anim.attributeAnimations ?? []).filter((component) => ATTRIBUTE_KINDS[component.attrName] !== undefined);
21727
+ const components = supportedComponents.map((component) => parseComponent(component, effectDurationMs));
21728
+ const parsedComponents = components.filter((component) => component !== undefined);
21729
+ // A partial transform is worse than the preset fallback: one unrepresentable
21730
+ // sibling can carry the primary movement while an identity sibling still
21731
+ // parses, causing the incomplete dynamic keyframe to suppress that preset.
21732
+ if (parsedComponents.length !== supportedComponents.length) {
21733
+ return undefined;
21734
+ }
21735
+ if (parsedComponents.length === 0) {
21736
+ return undefined;
21737
+ }
21738
+ const progressSet = new Set([0, 1]);
21739
+ for (const component of parsedComponents) {
21740
+ for (const stop of component.stops) {
21741
+ progressSet.add(clamp01$2((component.delayMs + stop.progress * component.durationMs) / effectDurationMs));
21742
+ }
21743
+ }
21744
+ return {
21745
+ progresses: [...progressSet].sort((left, right) => left - right),
21746
+ stateAt(progress) {
21747
+ const state = {};
21748
+ for (const component of parsedComponents) {
21749
+ state[component.kind] = valueAt(component, progress, effectDurationMs);
21750
+ }
21751
+ return state;
21752
+ },
21753
+ };
21754
+ }
21755
+
21308
21756
  /**
21309
21757
  * `animation-motion-path`: parse an OOXML `p:animMotion/@path` string into a
21310
21758
  * dense list of waypoints (percentages of the element box) suitable for driving
@@ -21422,214 +21870,169 @@ function parseMotionPathPoints(motionPath) {
21422
21870
  }
21423
21871
 
21424
21872
  /**
21425
- * `animation-timeline-absolute` dynamic CSS `@keyframes` for the absolute
21426
- * `from`/`to` form of `p:animRot` (ST_Angle) and `p:animScale`
21427
- * (percentage), and for a `p:tavLst` ramp attached to a generic `p:anim`
21428
- * behaviour (opacity or colour).
21873
+ * Compose authored motion, rotation, and scale behaviours into one CSS
21874
+ * keyframe block. OOXML permits those behaviours to share one effect wrapper,
21875
+ * while CSS animations that each write `transform` would overwrite each other.
21429
21876
  *
21430
- * {@link buildDynamicKeyframe} / {@link buildDynamicKeyframes} in
21431
- * `animation-timeline-helpers` already handle the RELATIVE `@by` form
21432
- * (`rotationBy` / `scaleByX` / `scaleByY`); this module covers the sibling
21433
- * absolute form, which OOXML allows instead of (or alongside) `@by`:
21434
- * ECMA-376 S19.5.7 CT_TLAnimateRotationBehavior and S19.5.8
21435
- * CT_TLAnimateScaleBehavior both make `from`/`to`/`by` independently
21436
- * optional. Only the relative form was ever read, so a deck that authored
21437
- * "rotate from 0 to 180" (no `@by`) or "scale from 50% to 150%" produced no
21438
- * rotation/scale keyframes at all and the effect silently did nothing.
21439
- *
21440
- * `p:tavLst` (ECMA-376 S19.5.30 CT_TLAnimVariantList) is schema-generic: a
21441
- * `p:anim` node can drive ANY attribute its `p:attrNameLst/p:attrName` names,
21442
- * and until the core parser surfaced that name (`PptxNativeAnimation.attrName`)
21443
- * this module could only guess from the keyframe VALUE shape, which is why it
21444
- * only ever recognised a numeric `[0, 1]` ramp as opacity. With the attribute
21445
- * name available, {@link buildOpacityTavKeyframe} now confirms it against a
21446
- * known opacity name instead of trusting the value shape alone, and
21447
- * {@link buildColorTavKeyframe} covers the sibling case: a multi-stop colour
21448
- * ramp on `fillcolor` / `fill.color` / `style.color` / `stroke.color`, reusing
21449
- * the same attribute -> CSS-property mapping as the dedicated `p:animClr`
21450
- * behaviour (`animation-color.ts`). Every other attribute name (position,
21451
- * size, and anything this module cannot confirm a CSS mapping for) is left on
21452
- * the caller's canned-timing fallback; see `docs/guide/limitations.md`.
21453
- *
21454
- * @module render/animation-timeline-absolute
21455
- */
21456
- /**
21457
- * Build an absolute-rotation `@keyframes` block from `rotationFrom`/
21458
- * `rotationTo`, or `undefined` when the animation has no absolute rotation
21459
- * (including when a relative `rotationBy` is present, which the caller
21460
- * already handles and takes priority over this).
21461
- *
21462
- * A `from` with no `to` (or vice versa) still produces a sensible sweep: the
21463
- * missing bound defaults to the other one's value so the animation is a
21464
- * no-op turn rather than being dropped, matching how PowerPoint treats a
21465
- * partially-specified rotation as ending at its one given angle.
21877
+ * @module render/animation-transform-keyframes
21466
21878
  */
21467
- function buildAbsoluteRotationKeyframe(anim, namePrefix, uid) {
21468
- if (anim.rotationBy !== undefined) {
21469
- return undefined;
21879
+ function slideOffset(percent, axis) {
21880
+ const fallback = axis === 'w' ? '1280px' : '720px';
21881
+ return `calc(var(--pptx-slide-${axis}, ${fallback}) * ${(percent / 100).toFixed(4)})`;
21882
+ }
21883
+ function hasRotation(anim) {
21884
+ return (anim.rotationBy !== undefined ||
21885
+ anim.rotationFrom !== undefined ||
21886
+ anim.rotationTo !== undefined);
21887
+ }
21888
+ function hasScale(anim) {
21889
+ return (anim.scaleByX !== undefined ||
21890
+ anim.scaleByY !== undefined ||
21891
+ anim.scaleFromX !== undefined ||
21892
+ anim.scaleFromY !== undefined ||
21893
+ anim.scaleToX !== undefined ||
21894
+ anim.scaleToY !== undefined);
21895
+ }
21896
+ /** True when the parsed animation contains an authored transform behaviour. */
21897
+ function hasAuthoredTransform(anim) {
21898
+ return (Boolean(anim.motionPath) ||
21899
+ hasRotation(anim) ||
21900
+ hasScale(anim) ||
21901
+ createAttributeTransformModel(anim) !== undefined);
21902
+ }
21903
+ function rotationAt(anim, progress) {
21904
+ const from = anim.rotationFrom ?? 0;
21905
+ const to = anim.rotationTo ?? (anim.rotationBy !== undefined ? from + anim.rotationBy : from);
21906
+ return from + (to - from) * progress;
21907
+ }
21908
+ function scaleAt(anim, axis, progress) {
21909
+ const from = axis === 'x' ? (anim.scaleFromX ?? 1) : (anim.scaleFromY ?? 1);
21910
+ const authoredTo = axis === 'x' ? anim.scaleToX : anim.scaleToY;
21911
+ const by = axis === 'x' ? anim.scaleByX : anim.scaleByY;
21912
+ const to = authoredTo ?? (by !== undefined ? from * by : from);
21913
+ return from + (to - from) * progress;
21914
+ }
21915
+ function opacityAt(anim, progress) {
21916
+ if (anim.presetClass === 'entr') {
21917
+ return progress;
21918
+ }
21919
+ if (anim.presetClass === 'exit') {
21920
+ return 1 - progress;
21470
21921
  }
21471
- if (anim.rotationFrom === undefined && anim.rotationTo === undefined) {
21472
- return undefined;
21922
+ return undefined;
21923
+ }
21924
+ function formatNumber(value, fractionDigits = 4) {
21925
+ return String(Number(value.toFixed(fractionDigits)));
21926
+ }
21927
+ function tangentAngle(points, index) {
21928
+ const point = points[index];
21929
+ const next = index < points.length - 1 ? points[index + 1] : point;
21930
+ const previous = index > 0 ? points[index - 1] : point;
21931
+ const dx = index < points.length - 1 ? next.x - point.x : point.x - previous.x;
21932
+ const dy = index < points.length - 1 ? next.y - point.y : point.y - previous.y;
21933
+ return Math.atan2(dy, dx) * (180 / Math.PI);
21934
+ }
21935
+ function rotateMotionPathPoints(points, anim) {
21936
+ const angle = anim.motionPathRotationAngle ?? 0;
21937
+ if (!Number.isFinite(angle) || angle === 0) {
21938
+ return [...points];
21473
21939
  }
21474
- const from = anim.rotationFrom ?? anim.rotationTo ?? 0;
21475
- const to = anim.rotationTo ?? anim.rotationFrom ?? 0;
21476
- const name = `${namePrefix}-${uid}`;
21940
+ const radians = (angle * Math.PI) / 180;
21941
+ const cosine = Math.cos(radians);
21942
+ const sine = Math.sin(radians);
21943
+ const centerX = anim.motionPathRotationCenterX ?? 0;
21944
+ const centerY = anim.motionPathRotationCenterY ?? 0;
21945
+ return points.map((point) => {
21946
+ const x = point.x - centerX;
21947
+ const y = point.y - centerY;
21948
+ return {
21949
+ x: centerX + x * cosine - y * sine,
21950
+ y: centerY + x * sine + y * cosine,
21951
+ };
21952
+ });
21953
+ }
21954
+ function pointAt(points, progress) {
21955
+ if (points.length < 2) {
21956
+ return { x: 0, y: 0 };
21957
+ }
21958
+ const position = progress * (points.length - 1);
21959
+ const leftIndex = Math.floor(position);
21960
+ const rightIndex = Math.min(points.length - 1, leftIndex + 1);
21961
+ const ratio = position - leftIndex;
21962
+ const left = points[leftIndex];
21963
+ const right = points[rightIndex];
21477
21964
  return {
21478
- keyframeName: name,
21479
- css: `@keyframes ${name} {\n\tfrom { transform: rotate(${from}deg); }\n\tto { transform: rotate(${to}deg); }\n}`,
21965
+ x: left.x + (right.x - left.x) * ratio,
21966
+ y: left.y + (right.y - left.y) * ratio,
21480
21967
  };
21481
21968
  }
21482
- /**
21483
- * Build an absolute-scale `@keyframes` block from `scaleFromX`/`scaleFromY`/
21484
- * `scaleToX`/`scaleToY`, or `undefined` when the animation has no absolute
21485
- * scale (including when a relative `scaleByX`/`scaleByY` is present, which
21486
- * the caller already handles and takes priority over this).
21487
- *
21488
- * A missing `from` defaults to 1 (unscaled) and a missing `to` defaults to
21489
- * the `from` value, mirroring {@link buildAbsoluteRotationKeyframe}'s
21490
- * one-bound-given handling.
21491
- */
21492
- function buildAbsoluteScaleKeyframe(anim, namePrefix, uid) {
21493
- if (anim.scaleByX !== undefined || anim.scaleByY !== undefined) {
21494
- return undefined;
21969
+ function keyframeName(anim, prefixes, uid, hasAttributeAnimation) {
21970
+ const motion = Boolean(anim.motionPath);
21971
+ const rotation = hasRotation(anim);
21972
+ const scale = hasScale(anim);
21973
+ if (hasAttributeAnimation || Number(motion) + Number(rotation) + Number(scale) > 1) {
21974
+ return `${prefixes.transform}-${uid}`;
21495
21975
  }
21496
- if (anim.scaleFromX === undefined &&
21497
- anim.scaleFromY === undefined &&
21498
- anim.scaleToX === undefined &&
21499
- anim.scaleToY === undefined) {
21500
- return undefined;
21976
+ if (motion) {
21977
+ return `${prefixes.motion}-${uid}`;
21501
21978
  }
21502
- const fromX = anim.scaleFromX ?? 1;
21503
- const fromY = anim.scaleFromY ?? 1;
21504
- const toX = anim.scaleToX ?? fromX;
21505
- const toY = anim.scaleToY ?? fromY;
21506
- const name = `${namePrefix}-${uid}`;
21507
- return {
21508
- keyframeName: name,
21509
- css: `@keyframes ${name} {\n\tfrom { transform: scale(${fromX}, ${fromY}); }\n\tto { transform: scale(${toX}, ${toY}); }\n}`,
21510
- };
21511
- }
21512
- /** Clamp a value into the closed unit interval. */
21513
- function clamp01$1(value) {
21514
- return Math.max(0, Math.min(1, value));
21979
+ if (rotation) {
21980
+ return `${anim.rotationBy === undefined ? prefixes.rotationAbsolute : prefixes.rotationRelative}-${uid}`;
21981
+ }
21982
+ return `${anim.scaleByX === undefined && anim.scaleByY === undefined ? prefixes.scaleAbsolute : prefixes.scaleRelative}-${uid}`;
21515
21983
  }
21516
- /** `p:attrName` values known to name an opacity attribute. */
21517
- const KNOWN_OPACITY_ATTR_NAMES = new Set(['style.opacity', 'opacity']);
21518
21984
  /**
21519
- * Build a multi-stop opacity `@keyframes` block from a `p:tavLst` keyframe
21520
- * list (parsed onto {@link PptxNativeAnimation.keyframes}), or `undefined`
21521
- * when the list is missing, has fewer than two usable stops, names an
21522
- * attribute other than opacity, or carries a value shape this can't
21523
- * confidently interpret as opacity.
21524
- *
21525
- * `p:tavLst` is schema-generic (ECMA-376 S19.5.30 CT_TLAnimVariantList): a
21526
- * `p:anim` node can drive any numeric/string/color/bool attribute its
21527
- * `p:attrNameLst` names. When the parser DID surface that name
21528
- * ({@link PptxNativeAnimation.attrName}), it is trusted outright: anything
21529
- * other than a known opacity name bails immediately, so e.g. a `ppt_w`
21530
- * size ramp that happens to fall in `[0, 1]` is never misread as opacity.
21531
- * When the name is absent (older parses, or a deck whose `p:cBhvr` omitted
21532
- * `p:attrNameLst` entirely), this falls back to the original heuristic:
21533
- * numeric (`flt`/`int`) stops in the `[0, 1]` fractional range on an
21534
- * EMPHASIS effect, which is exactly what PowerPoint's own "Transparency"
21535
- * effect (and any custom multi-stage fade) writes. Outside that shape the
21536
- * caller keeps its existing (2-stop, canned) behaviour, so nothing regresses.
21985
+ * Build one keyframe block so simultaneous transform behaviours remain
21986
+ * simultaneous instead of competing for the CSS `transform` property.
21537
21987
  */
21538
- function buildOpacityTavKeyframe(anim, namePrefix, uid) {
21539
- if (anim.presetClass !== 'emph') {
21988
+ function buildTransformKeyframes(anim, uid, prefixes) {
21989
+ const rotation = hasRotation(anim);
21990
+ const scale = hasScale(anim);
21991
+ const attributeModel = createAttributeTransformModel(anim);
21992
+ const parsedPoints = anim.motionPath
21993
+ ? rotateMotionPathPoints(parseMotionPathPoints(anim.motionPath), anim)
21994
+ : [];
21995
+ const points = parsedPoints.length >= 2 ? parsedPoints : [];
21996
+ if (points.length === 0 && !rotation && !scale && !attributeModel) {
21540
21997
  return undefined;
21541
21998
  }
21542
- if (anim.attrName !== undefined && !KNOWN_OPACITY_ATTR_NAMES.has(anim.attrName)) {
21543
- return undefined;
21999
+ const progressSet = new Set(attributeModel?.progresses ?? [0, 1]);
22000
+ for (let index = 0; index < points.length; index += 1) {
22001
+ progressSet.add(index / (points.length - 1));
21544
22002
  }
21545
- const frames = anim.keyframes;
21546
- if (!frames || frames.length < 2) {
21547
- return undefined;
21548
- }
21549
- const stops = [];
21550
- for (const kf of frames) {
21551
- if (typeof kf.tm !== 'number' || !Number.isFinite(kf.tm)) {
21552
- return undefined;
22003
+ const progresses = [...progressSet].sort((left, right) => left - right);
22004
+ const lines = progresses.map((progress) => {
22005
+ const point = pointAt(points, progress);
22006
+ const attributeState = attributeModel?.stateAt(progress);
22007
+ const declarations = [];
22008
+ const opacity = attributeState?.opacity ?? opacityAt(anim, progress);
22009
+ if (opacity !== undefined) {
22010
+ declarations.push(`opacity: ${formatNumber(opacity)};`);
21553
22011
  }
21554
- if (kf.valueType !== 'flt' && kf.valueType !== 'int') {
21555
- return undefined;
22012
+ const transforms = [];
22013
+ if (points.length > 0) {
22014
+ transforms.push(`translate(${slideOffset(point.x, 'w')}, ${slideOffset(point.y, 'h')})`);
21556
22015
  }
21557
- const opacity = Number(kf.value);
21558
- if (!Number.isFinite(opacity) || opacity < 0 || opacity > 1) {
21559
- return undefined;
22016
+ if (attributeState?.translateX !== undefined || attributeState?.translateY !== undefined) {
22017
+ transforms.push(`translate(${slideOffset(attributeState.translateX ?? 0, 'w')}, ${slideOffset(attributeState.translateY ?? 0, 'h')})`);
21560
22018
  }
21561
- // `tm` is in 1000ths of a percent of the effect's own duration (100000 = 100%).
21562
- stops.push({ pct: clamp01$1(kf.tm / 100000) * 100, opacity });
21563
- }
21564
- stops.sort((a, b) => a.pct - b.pct);
21565
- const name = `${namePrefix}-${uid}`;
21566
- const lines = stops.map((s) => `\t${Number(s.pct.toFixed(2))}% { opacity: ${s.opacity}; }`);
21567
- return { keyframeName: name, css: `@keyframes ${name} {\n${lines.join('\n')}\n}` };
21568
- }
21569
- /** `p:attrName` values known to name a colour attribute with a CSS mapping. */
21570
- const KNOWN_COLOR_ATTR_NAMES = new Set([
21571
- 'fillcolor',
21572
- 'fill.color',
21573
- 'style.color',
21574
- 'stroke.color',
21575
- ]);
21576
- /** Matches a resolved `#rrggbb` hex colour (what the core parser's `decodeKeyframeValue` emits for `a:srgbClr`). */
21577
- const HEX_COLOR_RE = /^#[0-9a-f]{6}$/iu;
21578
- /**
21579
- * Build a multi-stop colour `@keyframes` block from a `p:tavLst` keyframe
21580
- * list whose `p:attrName` names a known colour attribute (`fillcolor`,
21581
- * `fill.color`, `style.color`, `stroke.color`), or `undefined` when the
21582
- * attribute is unrecognised, there are fewer than two usable stops, or any
21583
- * stop isn't a resolved `#rrggbb` hex colour.
21584
- *
21585
- * This is the sibling of {@link buildOpacityTavKeyframe} for the OTHER shape
21586
- * `p:tavLst` playback can now place with confidence: a full multi-stop colour
21587
- * ramp authored on a generic `p:anim` node, as opposed to the two/three-stop
21588
- * `from`/`to`/`by` model the dedicated `p:animClr` behaviour uses (already
21589
- * handled via {@link PptxNativeAnimation.colorAnimation}, which this
21590
- * function defers to when present so an incidental `p:tavLst` on that same
21591
- * node never overrides it). A scheme-colour token (`a:schemeClr`, e.g.
21592
- * `"accent1"`) cannot be resolved to a CSS colour without theme context this
21593
- * pure function doesn't have, so any non-hex stop also bails to the caller's
21594
- * canned fallback rather than emitting invalid CSS.
21595
- *
21596
- * Reuses {@link resolveCssProperties} (`animation-color.ts`) for the
21597
- * attribute -> CSS-property mapping, the same one `p:animClr` keyframes use,
21598
- * so a `fillcolor` ramp painting an SVG shape's fill behaves identically
21599
- * whichever OOXML behaviour authored it.
21600
- */
21601
- function buildColorTavKeyframe(anim, namePrefix, uid) {
21602
- if (anim.colorAnimation) {
21603
- return undefined;
21604
- }
21605
- if (!anim.attrName || !KNOWN_COLOR_ATTR_NAMES.has(anim.attrName)) {
21606
- return undefined;
21607
- }
21608
- const frames = anim.keyframes;
21609
- if (!frames || frames.length < 2) {
21610
- return undefined;
21611
- }
21612
- const stops = [];
21613
- for (const kf of frames) {
21614
- if (typeof kf.tm !== 'number' || !Number.isFinite(kf.tm)) {
21615
- return undefined;
22019
+ if (rotation ||
22020
+ (points.length > 0 && anim.motionPathRotateAuto) ||
22021
+ attributeState?.rotation !== undefined) {
22022
+ const pointIndex = Math.round(progress * Math.max(0, points.length - 1));
22023
+ const pathRotation = anim.motionPathRotateAuto ? tangentAngle(points, pointIndex) : 0;
22024
+ const angle = pathRotation + (attributeState?.rotation ?? rotationAt(anim, progress));
22025
+ transforms.push(`rotate(${anim.motionPathRotateAuto ? angle.toFixed(2) : formatNumber(angle, 2)}deg)`);
21616
22026
  }
21617
- if (kf.valueType !== 'clr') {
21618
- return undefined;
22027
+ if (scale || attributeState?.scaleX !== undefined || attributeState?.scaleY !== undefined) {
22028
+ transforms.push(`scale(${formatNumber(attributeState?.scaleX ?? scaleAt(anim, 'x', progress))}, ${formatNumber(attributeState?.scaleY ?? scaleAt(anim, 'y', progress))})`);
21619
22029
  }
21620
- const color = String(kf.value);
21621
- if (!HEX_COLOR_RE.test(color)) {
21622
- return undefined;
22030
+ if (transforms.length > 0) {
22031
+ declarations.push(`transform: ${transforms.join(' ')};`);
21623
22032
  }
21624
- stops.push({ pct: clamp01$1(kf.tm / 100000) * 100, color: color.toLowerCase() });
21625
- }
21626
- stops.sort((a, b) => a.pct - b.pct);
21627
- const cssProperties = resolveCssProperties(anim.attrName);
21628
- const name = `${namePrefix}-${uid}`;
21629
- const lines = stops.map((s) => {
21630
- const decls = cssProperties.map((prop) => `${prop}: ${s.color};`).join(' ');
21631
- return `\t${Number(s.pct.toFixed(2))}% { ${decls} }`;
22033
+ return `\t${formatNumber(progress * 100, 2)}% { ${declarations.join(' ')} }`;
21632
22034
  });
22035
+ const name = keyframeName(anim, prefixes, uid, attributeModel !== undefined);
21633
22036
  return { keyframeName: name, css: `@keyframes ${name} {\n${lines.join('\n')}\n}` };
21634
22037
  }
21635
22038
 
@@ -21733,10 +22136,22 @@ function applyFlyDirection(effect, subtype) {
21733
22136
  * @param percent - Sampled coordinate in percent-of-slide units (path * 100).
21734
22137
  * @param axis - `w` for the horizontal offset, `h` for the vertical one.
21735
22138
  */
21736
- function slideOffset(percent, axis) {
21737
- const fallback = axis === 'w' ? '1280px' : '720px';
21738
- return `calc(var(--pptx-slide-${axis}, ${fallback}) * ${(percent / 100).toFixed(4)})`;
21739
- }
22139
+ const FLAT_TRANSFORM_PREFIXES = {
22140
+ motion: 'pptx-motionPath',
22141
+ rotationAbsolute: 'pptx-rotateAbs',
22142
+ rotationRelative: 'pptx-rotateBy',
22143
+ scaleAbsolute: 'pptx-scaleAbs',
22144
+ scaleRelative: 'pptx-scaleBy',
22145
+ transform: 'pptx-transform',
22146
+ };
22147
+ const TIMELINE_TRANSFORM_PREFIXES = {
22148
+ motion: 'pptx-tl-motion',
22149
+ rotationAbsolute: 'pptx-tl-rotateAbs',
22150
+ rotationRelative: 'pptx-tl-rotate',
22151
+ scaleAbsolute: 'pptx-tl-scaleAbs',
22152
+ scaleRelative: 'pptx-tl-scale',
22153
+ transform: 'pptx-tl-transform',
22154
+ };
21740
22155
  /**
21741
22156
  * Build a dynamic CSS `@keyframes` block for motion path, rotation, scale, or
21742
22157
  * colour animations that don't map to a static effect preset. Uses the
@@ -21744,49 +22159,9 @@ function slideOffset(percent, axis) {
21744
22159
  * name prefixes (flat-sequence model).
21745
22160
  */
21746
22161
  function buildDynamicKeyframes(anim, uid) {
21747
- // Motion path animation
21748
- if (anim.motionPath) {
21749
- const name = `pptx-motionPath-${uid}`;
21750
- const points = parseMotionPathPoints(anim.motionPath);
21751
- if (points.length < 2) {
21752
- return undefined;
21753
- }
21754
- const kfLines = [];
21755
- for (let i = 0; i < points.length; i++) {
21756
- const pct = Math.round((i / (points.length - 1)) * 100);
21757
- kfLines.push(`\t${pct}% { transform: translate(${slideOffset(points[i].x, 'w')}, ${slideOffset(points[i].y, 'h')}); }`);
21758
- }
21759
- return {
21760
- keyframeName: name,
21761
- css: `@keyframes ${name} {\n${kfLines.join('\n')}\n}`,
21762
- };
21763
- }
21764
- // Rotation animation (relative `@by`, then absolute `from`/`to`)
21765
- if (anim.rotationBy !== undefined) {
21766
- const name = `pptx-rotateBy-${uid}`;
21767
- const deg = anim.rotationBy;
21768
- return {
21769
- keyframeName: name,
21770
- css: `@keyframes ${name} {\n\tfrom { transform: rotate(0deg); }\n\tto { transform: rotate(${deg}deg); }\n}`,
21771
- };
21772
- }
21773
- const absoluteRotate = buildAbsoluteRotationKeyframe(anim, 'pptx-rotateAbs', uid);
21774
- if (absoluteRotate) {
21775
- return absoluteRotate;
21776
- }
21777
- // Scale animation (relative `@by`, then absolute `from`/`to`)
21778
- if (anim.scaleByX !== undefined || anim.scaleByY !== undefined) {
21779
- const name = `pptx-scaleBy-${uid}`;
21780
- const sx = anim.scaleByX ?? 1;
21781
- const sy = anim.scaleByY ?? 1;
21782
- return {
21783
- keyframeName: name,
21784
- css: `@keyframes ${name} {\n\tfrom { transform: scale(1); }\n\tto { transform: scale(${sx}, ${sy}); }\n}`,
21785
- };
21786
- }
21787
- const absoluteScale = buildAbsoluteScaleKeyframe(anim, 'pptx-scaleAbs', uid);
21788
- if (absoluteScale) {
21789
- return absoluteScale;
22162
+ const transform = buildTransformKeyframes(anim, uid, FLAT_TRANSFORM_PREFIXES);
22163
+ if (transform) {
22164
+ return transform;
21790
22165
  }
21791
22166
  // Color animation (p:animClr)
21792
22167
  if (anim.colorAnimation) {
@@ -21812,59 +22187,9 @@ function buildDynamicKeyframes(anim, uid) {
21812
22187
  * `pptx-tl-color-*` prefixes and supports motion-path auto-rotate.
21813
22188
  */
21814
22189
  function buildDynamicKeyframe(anim, uid) {
21815
- if (anim.motionPath) {
21816
- const name = `pptx-tl-motion-${uid}`;
21817
- const points = parseMotionPathPoints(anim.motionPath);
21818
- if (points.length < 2) {
21819
- return undefined;
21820
- }
21821
- const lines = [];
21822
- for (let i = 0; i < points.length; i++) {
21823
- const pct = Math.round((i / (points.length - 1)) * 100);
21824
- const tx = slideOffset(points[i].x, 'w');
21825
- const ty = slideOffset(points[i].y, 'h');
21826
- if (anim.motionPathRotateAuto) {
21827
- // Compute the tangent angle at this point using the direction
21828
- // to the next point (or from the previous point for the last one).
21829
- const next = i < points.length - 1 ? points[i + 1] : points[i];
21830
- const prev = i > 0 ? points[i - 1] : points[i];
21831
- const dx = i < points.length - 1 ? next.x - points[i].x : points[i].x - prev.x;
21832
- const dy = i < points.length - 1 ? next.y - points[i].y : points[i].y - prev.y;
21833
- const angleDeg = Math.atan2(dy, dx) * (180 / Math.PI);
21834
- lines.push(`\t${pct}% { transform: translate(${tx}, ${ty}) rotate(${angleDeg.toFixed(2)}deg); }`);
21835
- }
21836
- else {
21837
- lines.push(`\t${pct}% { transform: translate(${tx}, ${ty}); }`);
21838
- }
21839
- }
21840
- return {
21841
- keyframeName: name,
21842
- css: `@keyframes ${name} {\n${lines.join('\n')}\n}`,
21843
- };
21844
- }
21845
- if (anim.rotationBy !== undefined) {
21846
- const name = `pptx-tl-rotate-${uid}`;
21847
- return {
21848
- keyframeName: name,
21849
- css: `@keyframes ${name} {\n\tfrom { transform: rotate(0deg); }\n\tto { transform: rotate(${anim.rotationBy}deg); }\n}`,
21850
- };
21851
- }
21852
- const absoluteRotate = buildAbsoluteRotationKeyframe(anim, 'pptx-tl-rotateAbs', uid);
21853
- if (absoluteRotate) {
21854
- return absoluteRotate;
21855
- }
21856
- if (anim.scaleByX !== undefined || anim.scaleByY !== undefined) {
21857
- const name = `pptx-tl-scale-${uid}`;
21858
- const sx = anim.scaleByX ?? 1;
21859
- const sy = anim.scaleByY ?? 1;
21860
- return {
21861
- keyframeName: name,
21862
- css: `@keyframes ${name} {\n\tfrom { transform: scale(1); }\n\tto { transform: scale(${sx}, ${sy}); }\n}`,
21863
- };
21864
- }
21865
- const absoluteScale = buildAbsoluteScaleKeyframe(anim, 'pptx-tl-scaleAbs', uid);
21866
- if (absoluteScale) {
21867
- return absoluteScale;
22190
+ const transform = buildTransformKeyframes(anim, uid, TIMELINE_TRANSFORM_PREFIXES);
22191
+ if (transform) {
22192
+ return transform;
21868
22193
  }
21869
22194
  // Color animation (p:animClr)
21870
22195
  if (anim.colorAnimation) {
@@ -21946,6 +22271,218 @@ function finalizeClickGroup(steps, options) {
21946
22271
  return group;
21947
22272
  }
21948
22273
 
22274
+ /**
22275
+ * `animation-timeline-absolute` — dynamic CSS `@keyframes` for the absolute
22276
+ * `from`/`to` form of `p:animRot` (ST_Angle) and `p:animScale`
22277
+ * (percentage), and for a `p:tavLst` ramp attached to a generic `p:anim`
22278
+ * behaviour (opacity or colour).
22279
+ *
22280
+ * {@link buildDynamicKeyframe} / {@link buildDynamicKeyframes} in
22281
+ * `animation-timeline-helpers` already handle the RELATIVE `@by` form
22282
+ * (`rotationBy` / `scaleByX` / `scaleByY`); this module covers the sibling
22283
+ * absolute form, which OOXML allows instead of (or alongside) `@by`:
22284
+ * ECMA-376 S19.5.7 CT_TLAnimateRotationBehavior and S19.5.8
22285
+ * CT_TLAnimateScaleBehavior both make `from`/`to`/`by` independently
22286
+ * optional. Only the relative form was ever read, so a deck that authored
22287
+ * "rotate from 0 to 180" (no `@by`) or "scale from 50% to 150%" produced no
22288
+ * rotation/scale keyframes at all and the effect silently did nothing.
22289
+ *
22290
+ * `p:tavLst` (ECMA-376 S19.5.30 CT_TLAnimVariantList) is schema-generic: a
22291
+ * `p:anim` node can drive ANY attribute its `p:attrNameLst/p:attrName` names,
22292
+ * and until the core parser surfaced that name (`PptxNativeAnimation.attrName`)
22293
+ * this module could only guess from the keyframe VALUE shape, which is why it
22294
+ * only ever recognised a numeric `[0, 1]` ramp as opacity. With the attribute
22295
+ * name available, {@link buildOpacityTavKeyframe} now confirms it against a
22296
+ * known opacity name instead of trusting the value shape alone, and
22297
+ * {@link buildColorTavKeyframe} covers the sibling case: a multi-stop colour
22298
+ * ramp on `fillcolor` / `fill.color` / `style.color` / `stroke.color`, reusing
22299
+ * the same attribute -> CSS-property mapping as the dedicated `p:animClr`
22300
+ * behaviour (`animation-color.ts`). Every other attribute name (position,
22301
+ * size, and anything this module cannot confirm a CSS mapping for) is left on
22302
+ * the caller's canned-timing fallback; see `docs/guide/limitations.md`.
22303
+ *
22304
+ * @module render/animation-timeline-absolute
22305
+ */
22306
+ /**
22307
+ * Build an absolute-rotation `@keyframes` block from `rotationFrom`/
22308
+ * `rotationTo`, or `undefined` when the animation has no absolute rotation
22309
+ * (including when a relative `rotationBy` is present, which the caller
22310
+ * already handles and takes priority over this).
22311
+ *
22312
+ * A `from` with no `to` (or vice versa) still produces a sensible sweep: the
22313
+ * missing bound defaults to the other one's value so the animation is a
22314
+ * no-op turn rather than being dropped, matching how PowerPoint treats a
22315
+ * partially-specified rotation as ending at its one given angle.
22316
+ */
22317
+ function buildAbsoluteRotationKeyframe(anim, namePrefix, uid) {
22318
+ if (anim.rotationBy !== undefined) {
22319
+ return undefined;
22320
+ }
22321
+ if (anim.rotationFrom === undefined && anim.rotationTo === undefined) {
22322
+ return undefined;
22323
+ }
22324
+ const from = anim.rotationFrom ?? anim.rotationTo ?? 0;
22325
+ const to = anim.rotationTo ?? anim.rotationFrom ?? 0;
22326
+ const name = `${namePrefix}-${uid}`;
22327
+ return {
22328
+ keyframeName: name,
22329
+ css: `@keyframes ${name} {\n\tfrom { transform: rotate(${from}deg); }\n\tto { transform: rotate(${to}deg); }\n}`,
22330
+ };
22331
+ }
22332
+ /**
22333
+ * Build an absolute-scale `@keyframes` block from `scaleFromX`/`scaleFromY`/
22334
+ * `scaleToX`/`scaleToY`, or `undefined` when the animation has no absolute
22335
+ * scale (including when a relative `scaleByX`/`scaleByY` is present, which
22336
+ * the caller already handles and takes priority over this).
22337
+ *
22338
+ * A missing `from` defaults to 1 (unscaled) and a missing `to` defaults to
22339
+ * the `from` value, mirroring {@link buildAbsoluteRotationKeyframe}'s
22340
+ * one-bound-given handling.
22341
+ */
22342
+ function buildAbsoluteScaleKeyframe(anim, namePrefix, uid) {
22343
+ if (anim.scaleByX !== undefined || anim.scaleByY !== undefined) {
22344
+ return undefined;
22345
+ }
22346
+ if (anim.scaleFromX === undefined &&
22347
+ anim.scaleFromY === undefined &&
22348
+ anim.scaleToX === undefined &&
22349
+ anim.scaleToY === undefined) {
22350
+ return undefined;
22351
+ }
22352
+ const fromX = anim.scaleFromX ?? 1;
22353
+ const fromY = anim.scaleFromY ?? 1;
22354
+ const toX = anim.scaleToX ?? fromX;
22355
+ const toY = anim.scaleToY ?? fromY;
22356
+ const name = `${namePrefix}-${uid}`;
22357
+ return {
22358
+ keyframeName: name,
22359
+ css: `@keyframes ${name} {\n\tfrom { transform: scale(${fromX}, ${fromY}); }\n\tto { transform: scale(${toX}, ${toY}); }\n}`,
22360
+ };
22361
+ }
22362
+ /** Clamp a value into the closed unit interval. */
22363
+ function clamp01$1(value) {
22364
+ return Math.max(0, Math.min(1, value));
22365
+ }
22366
+ /** `p:attrName` values known to name an opacity attribute. */
22367
+ const KNOWN_OPACITY_ATTR_NAMES = new Set(['style.opacity', 'opacity']);
22368
+ /**
22369
+ * Build a multi-stop opacity `@keyframes` block from a `p:tavLst` keyframe
22370
+ * list (parsed onto {@link PptxNativeAnimation.keyframes}), or `undefined`
22371
+ * when the list is missing, has fewer than two usable stops, names an
22372
+ * attribute other than opacity, or carries a value shape this can't
22373
+ * confidently interpret as opacity.
22374
+ *
22375
+ * `p:tavLst` is schema-generic (ECMA-376 S19.5.30 CT_TLAnimVariantList): a
22376
+ * `p:anim` node can drive any numeric/string/color/bool attribute its
22377
+ * `p:attrNameLst` names. When the parser DID surface that name
22378
+ * ({@link PptxNativeAnimation.attrName}), it is trusted outright: anything
22379
+ * other than a known opacity name bails immediately, so e.g. a `ppt_w`
22380
+ * size ramp that happens to fall in `[0, 1]` is never misread as opacity.
22381
+ * When the name is absent (older parses, or a deck whose `p:cBhvr` omitted
22382
+ * `p:attrNameLst` entirely), this falls back to the original heuristic:
22383
+ * numeric (`flt`/`int`) stops in the `[0, 1]` fractional range on an
22384
+ * EMPHASIS effect, which is exactly what PowerPoint's own "Transparency"
22385
+ * effect (and any custom multi-stage fade) writes. Outside that shape the
22386
+ * caller keeps its existing (2-stop, canned) behaviour, so nothing regresses.
22387
+ */
22388
+ function buildOpacityTavKeyframe(anim, namePrefix, uid) {
22389
+ if (anim.presetClass !== 'emph') {
22390
+ return undefined;
22391
+ }
22392
+ if (anim.attrName !== undefined && !KNOWN_OPACITY_ATTR_NAMES.has(anim.attrName)) {
22393
+ return undefined;
22394
+ }
22395
+ const frames = anim.keyframes;
22396
+ if (!frames || frames.length < 2) {
22397
+ return undefined;
22398
+ }
22399
+ const stops = [];
22400
+ for (const kf of frames) {
22401
+ if (typeof kf.tm !== 'number' || !Number.isFinite(kf.tm)) {
22402
+ return undefined;
22403
+ }
22404
+ if (kf.valueType !== 'flt' && kf.valueType !== 'int') {
22405
+ return undefined;
22406
+ }
22407
+ const opacity = Number(kf.value);
22408
+ if (!Number.isFinite(opacity) || opacity < 0 || opacity > 1) {
22409
+ return undefined;
22410
+ }
22411
+ // `tm` is in 1000ths of a percent of the effect's own duration (100000 = 100%).
22412
+ stops.push({ pct: clamp01$1(kf.tm / 100000) * 100, opacity });
22413
+ }
22414
+ stops.sort((a, b) => a.pct - b.pct);
22415
+ const name = `${namePrefix}-${uid}`;
22416
+ const lines = stops.map((s) => `\t${Number(s.pct.toFixed(2))}% { opacity: ${s.opacity}; }`);
22417
+ return { keyframeName: name, css: `@keyframes ${name} {\n${lines.join('\n')}\n}` };
22418
+ }
22419
+ /** `p:attrName` values known to name a colour attribute with a CSS mapping. */
22420
+ const KNOWN_COLOR_ATTR_NAMES = new Set([
22421
+ 'fillcolor',
22422
+ 'fill.color',
22423
+ 'style.color',
22424
+ 'stroke.color',
22425
+ ]);
22426
+ /** Matches a resolved `#rrggbb` hex colour (what the core parser's `decodeKeyframeValue` emits for `a:srgbClr`). */
22427
+ const HEX_COLOR_RE = /^#[0-9a-f]{6}$/iu;
22428
+ /**
22429
+ * Build a multi-stop colour `@keyframes` block from a `p:tavLst` keyframe
22430
+ * list whose `p:attrName` names a known colour attribute (`fillcolor`,
22431
+ * `fill.color`, `style.color`, `stroke.color`), or `undefined` when the
22432
+ * attribute is unrecognised, there are fewer than two usable stops, or any
22433
+ * stop isn't a resolved `#rrggbb` hex colour.
22434
+ *
22435
+ * This is the sibling of {@link buildOpacityTavKeyframe} for the OTHER shape
22436
+ * `p:tavLst` playback can now place with confidence: a full multi-stop colour
22437
+ * ramp authored on a generic `p:anim` node, as opposed to the two/three-stop
22438
+ * `from`/`to`/`by` model the dedicated `p:animClr` behaviour uses (already
22439
+ * handled via {@link PptxNativeAnimation.colorAnimation}, which this
22440
+ * function defers to when present so an incidental `p:tavLst` on that same
22441
+ * node never overrides it). A scheme-colour token (`a:schemeClr`, e.g.
22442
+ * `"accent1"`) cannot be resolved to a CSS colour without theme context this
22443
+ * pure function doesn't have, so any non-hex stop also bails to the caller's
22444
+ * canned fallback rather than emitting invalid CSS.
22445
+ *
22446
+ * Reuses {@link resolveCssProperties} (`animation-color.ts`) for the
22447
+ * attribute -> CSS-property mapping, the same one `p:animClr` keyframes use,
22448
+ * so a `fillcolor` ramp painting an SVG shape's fill behaves identically
22449
+ * whichever OOXML behaviour authored it.
22450
+ */
22451
+ function buildColorTavKeyframe(anim, namePrefix, uid) {
22452
+ if (anim.colorAnimation) {
22453
+ return undefined;
22454
+ }
22455
+ if (!anim.attrName || !KNOWN_COLOR_ATTR_NAMES.has(anim.attrName)) {
22456
+ return undefined;
22457
+ }
22458
+ const frames = anim.keyframes;
22459
+ if (!frames || frames.length < 2) {
22460
+ return undefined;
22461
+ }
22462
+ const stops = [];
22463
+ for (const kf of frames) {
22464
+ if (typeof kf.tm !== 'number' || !Number.isFinite(kf.tm)) {
22465
+ return undefined;
22466
+ }
22467
+ if (kf.valueType !== 'clr') {
22468
+ return undefined;
22469
+ }
22470
+ const color = String(kf.value);
22471
+ if (!HEX_COLOR_RE.test(color)) {
22472
+ return undefined;
22473
+ }
22474
+ stops.push({ pct: clamp01$1(kf.tm / 100000) * 100, color: color.toLowerCase() });
22475
+ }
22476
+ stops.sort((a, b) => a.pct - b.pct);
22477
+ const cssProperties = resolveCssProperties(anim.attrName);
22478
+ const name = `${namePrefix}-${uid}`;
22479
+ const lines = stops.map((s) => {
22480
+ const decls = cssProperties.map((prop) => `${prop}: ${s.color};`).join(' ');
22481
+ return `\t${Number(s.pct.toFixed(2))}% { ${decls} }`;
22482
+ });
22483
+ return { keyframeName: name, css: `@keyframes ${name} {\n${lines.join('\n')}\n}` };
22484
+ }
22485
+
21949
22486
  /**
21950
22487
  * `animation-fill-repeat`: pure decisions for `p:cTn/@fill`, `@repeatDur`
21951
22488
  * and `@spd`, the three timing attributes the parse layer now surfaces on
@@ -22010,10 +22547,17 @@ function shouldHoldEndState(anim) {
22010
22547
  /** Resolve all three timing decisions for one animation in a single call. */
22011
22548
  function resolveEffectTiming(anim, baseDurationMs) {
22012
22549
  const durationMs = applySpeedToDuration(anim.speedPct, baseDurationMs);
22013
- const iterationCount = resolveIterationCount(anim.repeatCount, anim.repeatDurMs, durationMs);
22550
+ const authoredIterations = resolveIterationCount(anim.repeatCount, anim.repeatDurMs, durationMs);
22551
+ const iterationCount = anim.autoReverse && anim.repeatDurMs === undefined
22552
+ ? authoredIterations * 2
22553
+ : authoredIterations;
22554
+ const activeDurationMs = Number.isFinite(iterationCount)
22555
+ ? durationMs * iterationCount
22556
+ : durationMs;
22014
22557
  return {
22015
22558
  durationMs,
22016
22559
  iterationCount,
22560
+ activeDurationMs,
22017
22561
  holdEndState: shouldHoldEndState(anim),
22018
22562
  };
22019
22563
  }
@@ -22509,6 +23053,7 @@ function emitStaggeredPieces(anim, kind, counts, output, newClickStepPerParagrap
22509
23053
  triggerDelayMs: undefined,
22510
23054
  startConditions: undefined,
22511
23055
  parGroupIndex: undefined,
23056
+ parGroupDelayMs: undefined,
22512
23057
  }),
22513
23058
  buildType: undefined,
22514
23059
  iterate: undefined,
@@ -22578,6 +23123,7 @@ function expandSingleBuildAnimation(anim, buildType, counts, output) {
22578
23123
  triggerDelayMs: undefined,
22579
23124
  startConditions: undefined,
22580
23125
  parGroupIndex: undefined,
23126
+ parGroupDelayMs: undefined,
22581
23127
  buildType: undefined,
22582
23128
  buildReverse: undefined,
22583
23129
  buildAdvAutoMs: undefined,
@@ -22623,6 +23169,7 @@ function joinPreviousFields() {
22623
23169
  triggerDelayMs: undefined,
22624
23170
  startConditions: undefined,
22625
23171
  parGroupIndex: undefined,
23172
+ parGroupDelayMs: undefined,
22626
23173
  };
22627
23174
  }
22628
23175
  /** Expand a `pRg`-scoped animation into one sub-animation per named paragraph. */
@@ -23304,6 +23851,72 @@ function parseMediaCommand(commandString) {
23304
23851
  return undefined;
23305
23852
  }
23306
23853
 
23854
+ const COLOR_KEYFRAME = /^pptx-tl-(?:color|tavclr)-/u;
23855
+ const TRANSFORM_KEYFRAME = /^pptx-tl-(?:motion|rotate(?:Abs)?|scale(?:Abs)?|transform)-/u;
23856
+ function sameOptionalValue(left, right) {
23857
+ return left === right;
23858
+ }
23859
+ function hasIndependentColourAndTransform(left, right) {
23860
+ return ((COLOR_KEYFRAME.test(left.keyframeName) && TRANSFORM_KEYFRAME.test(right.keyframeName)) ||
23861
+ (TRANSFORM_KEYFRAME.test(left.keyframeName) && COLOR_KEYFRAME.test(right.keyframeName)));
23862
+ }
23863
+ /** Whether two sibling behaviours can safely share one CSS animation list. */
23864
+ function canComposeParallelSteps(left, right) {
23865
+ return (hasIndependentColourAndTransform(left, right) &&
23866
+ left.elementId !== '' &&
23867
+ left.elementId === right.elementId &&
23868
+ left.cssAnimation !== '' &&
23869
+ right.cssAnimation !== '' &&
23870
+ left.delayMs === right.delayMs &&
23871
+ left.durationMs === right.durationMs &&
23872
+ left.presetClass === right.presetClass &&
23873
+ left.fillMode === right.fillMode &&
23874
+ sameOptionalValue(left.holdEndState, right.holdEndState) &&
23875
+ sameOptionalValue(left.hideAfterEffect, right.hideAfterEffect) &&
23876
+ sameOptionalValue(left.pendingHideOnNextClick, right.pendingHideOnNextClick) &&
23877
+ sameOptionalValue(left.seqConcurrent, right.seqConcurrent) &&
23878
+ sameOptionalValue(left.seqNextAction, right.seqNextAction) &&
23879
+ sameOptionalValue(left.seqPrevAction, right.seqPrevAction) &&
23880
+ !left.command &&
23881
+ !right.command &&
23882
+ !left.build &&
23883
+ !right.build &&
23884
+ !left.soundPath &&
23885
+ !right.soundPath &&
23886
+ !left.stopSound &&
23887
+ !right.stopSound &&
23888
+ !left.restart &&
23889
+ !right.restart &&
23890
+ left.exclGroupId === undefined &&
23891
+ right.exclGroupId === undefined);
23892
+ }
23893
+ /** Compose independent CSS properties without dropping either behaviour. */
23894
+ function composeParallelSteps(left, right) {
23895
+ const colorTargets = [...new Set([...(left.colorTargets ?? []), ...(right.colorTargets ?? [])])];
23896
+ return {
23897
+ ...left,
23898
+ cssAnimation: `${left.cssAnimation}, ${right.cssAnimation}`,
23899
+ colorTargets: colorTargets.length > 0 ? colorTargets : undefined,
23900
+ };
23901
+ }
23902
+
23903
+ /** Synthetic state-map suffix for a `p:spTgt/p:bg` background-only target. */
23904
+ const BACKGROUND_ANIMATION_ID_SUFFIX = '::pptx-bg';
23905
+ /**
23906
+ * Resolve the view-layer target for an animation.
23907
+ *
23908
+ * PowerPoint's `p:bg` target animates only the shape paint while its text stays
23909
+ * visible. Keeping that state under a synthetic id lets bindings paint a
23910
+ * separate background layer without hiding or transforming the whole element.
23911
+ */
23912
+ function resolveAnimationTargetId(animation) {
23913
+ const targetId = animation.targetId ?? '';
23914
+ if (targetId && animation.target?.type === 'shape' && animation.target.backgroundOnly === true) {
23915
+ return `${targetId}${BACKGROUND_ANIMATION_ID_SUFFIX}`;
23916
+ }
23917
+ return targetId;
23918
+ }
23919
+
23307
23920
  /**
23308
23921
  * `animation-timeline-builder` — `buildTimeline`, which turns a flat list of
23309
23922
  * native animations into an {@link AnimationTimeline} of click-groups (plus
@@ -23368,11 +23981,11 @@ function stepColorTargets(anim, tavColorApplied) {
23368
23981
  // colour ramp on a generic `p:anim` node (see `buildColorTavKeyframe`)
23369
23982
  // names the same kind of attribute, so it resolves paint targets the
23370
23983
  // same way once there's no dedicated colour animation to defer to.
23371
- const targetAttribute = anim.colorAnimation?.targetAttribute ?? (tavColorApplied ? anim.attrName : undefined);
23372
- if (!targetAttribute) {
23984
+ const colorSource = anim.colorAnimation ?? (tavColorApplied ? anim.attrName : undefined);
23985
+ if (!colorSource) {
23373
23986
  return undefined;
23374
23987
  }
23375
- const targets = resolveColorAnimationTargets(targetAttribute);
23988
+ const targets = resolveColorAnimationTargets(colorSource);
23376
23989
  return targets.length > 0 ? targets : undefined;
23377
23990
  }
23378
23991
  function fallbackEffectForClass(presetClass) {
@@ -23423,18 +24036,24 @@ function buildTimeline(nativeAnimations) {
23423
24036
  entranceElementIds: new Set(),
23424
24037
  keyframesCss: '',
23425
24038
  interactiveSequences: new Map(),
24039
+ restartableInteractiveSequences: new Set(),
23426
24040
  hoverSequences: new Map(),
23427
24041
  };
23428
24042
  }
23429
24043
  // Separate interactive (onShapeClick), hover (onHover), and regular animations
23430
24044
  const regularAnims = [];
23431
24045
  const interactiveAnims = new Map();
24046
+ const restartableInteractiveSequences = new Set();
23432
24047
  const hoverAnims = [];
23433
24048
  for (const anim of nativeAnimations) {
23434
- if (anim.trigger === 'onShapeClick' && anim.triggerShapeId) {
24049
+ if ((anim.interactiveSequence === true || anim.trigger === 'onShapeClick') &&
24050
+ anim.triggerShapeId) {
23435
24051
  const existing = interactiveAnims.get(anim.triggerShapeId) ?? [];
23436
24052
  existing.push(anim);
23437
24053
  interactiveAnims.set(anim.triggerShapeId, existing);
24054
+ if (anim.interactiveRestart === true) {
24055
+ restartableInteractiveSequences.add(anim.triggerShapeId);
24056
+ }
23438
24057
  }
23439
24058
  else if (anim.trigger === 'onHover' && anim.targetId) {
23440
24059
  hoverAnims.push(anim);
@@ -23449,6 +24068,7 @@ function buildTimeline(nativeAnimations) {
23449
24068
  const dynamicBlocks = [];
23450
24069
  let dynamicUid = 0;
23451
24070
  let currentGroup = [];
24071
+ let currentGroupLastParallelIndex;
23452
24072
  /** Whether the current group was started by an onClick trigger. */
23453
24073
  let currentGroupIsClick = false;
23454
24074
  /**
@@ -23467,8 +24087,13 @@ function buildTimeline(nativeAnimations) {
23467
24087
  for (const anim of regularAnims) {
23468
24088
  const expandedSteps = expandIterateAnimation(anim);
23469
24089
  for (const singleAnim of expandedSteps) {
23470
- let effect = resolveEffect(singleAnim);
23471
- let dynamic = effect ? undefined : buildDynamicKeyframe(singleAnim, dynamicUid++);
24090
+ let dynamic = hasAuthoredTransform(singleAnim)
24091
+ ? buildDynamicKeyframe(singleAnim, dynamicUid++)
24092
+ : undefined;
24093
+ let effect = dynamic ? undefined : resolveEffect(singleAnim);
24094
+ if (!effect && !dynamic) {
24095
+ dynamic = buildDynamicKeyframe(singleAnim, dynamicUid++);
24096
+ }
23472
24097
  // A real `p:tavLst` keyframe list on an emphasis effect (e.g.
23473
24098
  // PowerPoint's "Transparency") carries the AUTHORED opacity ramp;
23474
24099
  // prefer it over the canned 2/3-stop static effect so a custom fade
@@ -23538,7 +24163,7 @@ function buildTimeline(nativeAnimations) {
23538
24163
  // Command steps carry no element visibility semantics: an empty
23539
24164
  // elementId keeps them from hiding/revealing a real element; the media
23540
24165
  // target is routed via the command payload instead.
23541
- const elementId = isCommand ? '' : (singleAnim.targetId ?? '');
24166
+ const elementId = isCommand ? '' : resolveAnimationTargetId(singleAnim);
23542
24167
  // Honour the FULL start-condition OR-set (compound / simultaneous
23543
24168
  // triggers) rather than the collapsed single trigger. The effective
23544
24169
  // condition drives grouping and supplies the governing start delay.
@@ -23563,7 +24188,7 @@ function buildTimeline(nativeAnimations) {
23563
24188
  // Apply `@spd` / `@repeatDur` / `@fill` (animation-fill-repeat), then
23564
24189
  // compute direction. A command step carries no timing of its own.
23565
24190
  const timing = isCommand
23566
- ? { durationMs: 0, iterationCount: 1, holdEndState: false }
24191
+ ? { durationMs: 0, iterationCount: 1, activeDurationMs: 0, holdEndState: false }
23567
24192
  : resolveEffectTiming(singleAnim, baseDuration);
23568
24193
  const duration = timing.durationMs;
23569
24194
  const iterCount = timing.iterationCount;
@@ -23587,6 +24212,7 @@ function buildTimeline(nativeAnimations) {
23587
24212
  clickGroups.push(group);
23588
24213
  }
23589
24214
  currentGroup = [];
24215
+ currentGroupLastParallelIndex = undefined;
23590
24216
  currentGroupIsClick = isOnClick || isFirstAnimation;
23591
24217
  // A group the deck marks as auto-starting plays on slide entry. This
23592
24218
  // is how PowerPoint renders a deck whose opening effects are "With
@@ -23603,18 +24229,24 @@ function buildTimeline(nativeAnimations) {
23603
24229
  currentGroupAutoStart =
23604
24230
  singleAnim.groupAutoStart === true && !effective.requiresInteraction;
23605
24231
  subGroupIndex = singleAnim.parGroupIndex;
23606
- subGroupStartMs = 0;
24232
+ subGroupStartMs = singleAnim.parGroupDelayMs ?? 0;
23607
24233
  }
23608
24234
  // Compute delay relative to start of this click-group
23609
24235
  const prevStep = currentGroup.length > 0 ? currentGroup[currentGroup.length - 1] : undefined;
23610
24236
  let delayMs;
23611
- if (singleAnim.parGroupIndex !== undefined && prevStep) {
24237
+ if (singleAnim.parGroupIndex !== undefined) {
23612
24238
  if (singleAnim.parGroupIndex !== subGroupIndex) {
23613
- // New effect wrapper: it starts with (withPrevious) or after
23614
- // (afterPrevious / afterDelay) the step that came before it.
24239
+ // Prefer the wrapper's authored absolute offset. Older or
24240
+ // programmatically-created entries may not carry one, so retain
24241
+ // the duration-based chaining fallback for those entries.
23615
24242
  subGroupIndex = singleAnim.parGroupIndex;
23616
24243
  subGroupStartMs =
23617
- trigger === 'withPrevious' ? prevStep.delayMs : prevStep.delayMs + prevStep.durationMs;
24244
+ singleAnim.parGroupDelayMs ??
24245
+ (prevStep
24246
+ ? trigger === 'withPrevious'
24247
+ ? prevStep.delayMs
24248
+ : prevStep.delayMs + prevStep.durationMs
24249
+ : 0);
23618
24250
  }
23619
24251
  // Siblings of one wrapper are simultaneous in OOXML: each `@delay` is
23620
24252
  // an offset from the wrapper, never a chain off the previous effect.
@@ -23639,17 +24271,17 @@ function buildTimeline(nativeAnimations) {
23639
24271
  // `applyAfterAnimationFromEditorList` before the timeline is built.
23640
24272
  const afterFields = isCommand
23641
24273
  ? { cssAnimation: baseCssAnimation, holdEndState: timing.holdEndState }
23642
- : resolveAfterAnimationStepFields(singleAnim, baseCssAnimation, timing.holdEndState, delayMs + duration, `pptx-tl-dim-${dynamicUid++}`);
24274
+ : resolveAfterAnimationStepFields(singleAnim, baseCssAnimation, timing.holdEndState, delayMs + timing.activeDurationMs, `pptx-tl-dim-${dynamicUid++}`);
23643
24275
  if (afterFields.dimKeyframeBlock) {
23644
24276
  dynamicBlocks.push(afterFields.dimKeyframeBlock);
23645
24277
  }
23646
- currentGroup.push({
24278
+ const step = {
23647
24279
  elementId,
23648
24280
  cssAnimation: afterFields.cssAnimation,
23649
24281
  keyframeName: keyframe,
23650
24282
  trigger,
23651
24283
  delayMs,
23652
- durationMs: duration,
24284
+ durationMs: timing.activeDurationMs,
23653
24285
  fillMode: fill,
23654
24286
  presetClass: presetClass,
23655
24287
  soundPath: singleAnim.soundPath,
@@ -23665,7 +24297,18 @@ function buildTimeline(nativeAnimations) {
23665
24297
  seqNextAction: singleAnim.seqNextAction,
23666
24298
  seqPrevAction: singleAnim.seqPrevAction,
23667
24299
  exclGroupId: singleAnim.exclGroupId,
23668
- });
24300
+ };
24301
+ const previousParallelStep = currentGroup[currentGroup.length - 1];
24302
+ if (singleAnim.parGroupIndex !== undefined &&
24303
+ singleAnim.parGroupIndex === currentGroupLastParallelIndex &&
24304
+ previousParallelStep &&
24305
+ canComposeParallelSteps(previousParallelStep, step)) {
24306
+ currentGroup[currentGroup.length - 1] = composeParallelSteps(previousParallelStep, step);
24307
+ }
24308
+ else {
24309
+ currentGroup.push(step);
24310
+ }
24311
+ currentGroupLastParallelIndex = singleAnim.parGroupIndex;
23669
24312
  }
23670
24313
  }
23671
24314
  // Flush last group
@@ -23706,6 +24349,7 @@ function buildTimeline(nativeAnimations) {
23706
24349
  entranceElementIds: entranceIds,
23707
24350
  keyframesCss: keyframeBlocks.join('\n\n'),
23708
24351
  interactiveSequences,
24352
+ restartableInteractiveSequences,
23709
24353
  hoverSequences,
23710
24354
  };
23711
24355
  }
@@ -23753,9 +24397,17 @@ function buildSequenceGroups(animsByKey, entranceIds, neededKeyframes, dynamicBl
23753
24397
  for (const [shapeId, anims] of animsByKey) {
23754
24398
  const seqGroups = [];
23755
24399
  let seqGroup = [];
24400
+ let seqGroupLastParallelIndex;
24401
+ let subGroupIndex;
24402
+ let subGroupStartMs = 0;
23756
24403
  for (const anim of anims) {
23757
- let effect = resolveEffect(anim);
23758
- let dynamic = effect ? undefined : buildDynamicKeyframe(anim, dynamicUid++);
24404
+ let dynamic = hasAuthoredTransform(anim)
24405
+ ? buildDynamicKeyframe(anim, dynamicUid++)
24406
+ : undefined;
24407
+ let effect = dynamic ? undefined : resolveEffect(anim);
24408
+ if (!effect && !dynamic) {
24409
+ dynamic = buildDynamicKeyframe(anim, dynamicUid++);
24410
+ }
23759
24411
  // Same authored-tavLst-over-canned-default preference as the main
23760
24412
  // click-group loop (see the comment there for why it's gated to
23761
24413
  // 'transparency' / unmapped emphasis effects only).
@@ -23799,7 +24451,7 @@ function buildSequenceGroups(animsByKey, entranceIds, neededKeyframes, dynamicBl
23799
24451
  dynamicBlocks.push(dynamic.css);
23800
24452
  }
23801
24453
  // Command steps carry no element visibility semantics; see the main loop.
23802
- const elementId = isCommand ? '' : (anim.targetId ?? '');
24454
+ const elementId = isCommand ? '' : resolveAnimationTargetId(anim);
23803
24455
  const seqTrigger = anim.trigger ?? 'onShapeClick';
23804
24456
  const baseDuration = isCommand ? 0 : (anim.durationMs ?? defaultDuration(anim.presetClass));
23805
24457
  // Same single-quantity rule as the main sequence: never sum the two.
@@ -23808,7 +24460,7 @@ function buildSequenceGroups(animsByKey, entranceIds, neededKeyframes, dynamicBl
23808
24460
  const presetClass = isCommand ? 'emph' : (anim.presetClass ?? 'entr');
23809
24461
  const fill = fillModeForClass(anim.presetClass);
23810
24462
  const timing = isCommand
23811
- ? { durationMs: 0, iterationCount: 1, holdEndState: false }
24463
+ ? { durationMs: 0, iterationCount: 1, activeDurationMs: 0, holdEndState: false }
23812
24464
  : resolveEffectTiming(anim, baseDuration);
23813
24465
  const duration = timing.durationMs;
23814
24466
  const iterCount = timing.iterationCount;
@@ -23821,15 +24473,26 @@ function buildSequenceGroups(animsByKey, entranceIds, neededKeyframes, dynamicBl
23821
24473
  seqGroups.push(finalizeClickGroup(seqGroup));
23822
24474
  seqGroup = [];
23823
24475
  }
24476
+ const prevStep = seqGroup.length > 0 ? seqGroup[seqGroup.length - 1] : undefined;
23824
24477
  let delayMs;
23825
- if (seqTrigger === 'withPrevious' && seqGroup.length > 0) {
23826
- const prev = seqGroup[seqGroup.length - 1];
23827
- delayMs = prev.delayMs + animDelay + triggerDelay;
24478
+ if (anim.parGroupIndex !== undefined) {
24479
+ if (anim.parGroupIndex !== subGroupIndex) {
24480
+ subGroupIndex = anim.parGroupIndex;
24481
+ subGroupStartMs =
24482
+ anim.parGroupDelayMs ??
24483
+ (prevStep
24484
+ ? seqTrigger === 'withPrevious'
24485
+ ? prevStep.delayMs
24486
+ : prevStep.delayMs + prevStep.durationMs
24487
+ : 0);
24488
+ }
24489
+ delayMs = subGroupStartMs + animDelay + triggerDelay;
23828
24490
  }
23829
- else if ((seqTrigger === 'afterPrevious' || seqTrigger === 'afterDelay') &&
23830
- seqGroup.length > 0) {
23831
- const prev = seqGroup[seqGroup.length - 1];
23832
- delayMs = prev.delayMs + prev.durationMs + animDelay + triggerDelay;
24491
+ else if (seqTrigger === 'withPrevious' && prevStep) {
24492
+ delayMs = prevStep.delayMs + animDelay + triggerDelay;
24493
+ }
24494
+ else if ((seqTrigger === 'afterPrevious' || seqTrigger === 'afterDelay') && prevStep) {
24495
+ delayMs = prevStep.delayMs + prevStep.durationMs + animDelay + triggerDelay;
23833
24496
  }
23834
24497
  else {
23835
24498
  delayMs = animDelay + triggerDelay;
@@ -23845,17 +24508,17 @@ function buildSequenceGroups(animsByKey, entranceIds, neededKeyframes, dynamicBl
23845
24508
  // main-sequence one.
23846
24509
  const afterFields = isCommand
23847
24510
  ? { cssAnimation: baseCssAnimation, holdEndState: timing.holdEndState }
23848
- : resolveAfterAnimationStepFields(anim, baseCssAnimation, timing.holdEndState, delayMs + duration, `pptx-tl-dim-${dynamicUid++}`);
24511
+ : resolveAfterAnimationStepFields(anim, baseCssAnimation, timing.holdEndState, delayMs + timing.activeDurationMs, `pptx-tl-dim-${dynamicUid++}`);
23849
24512
  if (afterFields.dimKeyframeBlock) {
23850
24513
  dynamicBlocks.push(afterFields.dimKeyframeBlock);
23851
24514
  }
23852
- seqGroup.push({
24515
+ const step = {
23853
24516
  elementId,
23854
24517
  cssAnimation: afterFields.cssAnimation,
23855
24518
  keyframeName: keyframe,
23856
24519
  trigger: seqTrigger,
23857
24520
  delayMs,
23858
- durationMs: duration,
24521
+ durationMs: timing.activeDurationMs,
23859
24522
  fillMode: fill,
23860
24523
  presetClass: presetClass,
23861
24524
  soundPath: anim.soundPath,
@@ -23871,7 +24534,18 @@ function buildSequenceGroups(animsByKey, entranceIds, neededKeyframes, dynamicBl
23871
24534
  seqNextAction: anim.seqNextAction,
23872
24535
  seqPrevAction: anim.seqPrevAction,
23873
24536
  exclGroupId: anim.exclGroupId,
23874
- });
24537
+ };
24538
+ const previousParallelStep = seqGroup[seqGroup.length - 1];
24539
+ if (anim.parGroupIndex !== undefined &&
24540
+ anim.parGroupIndex === seqGroupLastParallelIndex &&
24541
+ previousParallelStep &&
24542
+ canComposeParallelSteps(previousParallelStep, step)) {
24543
+ seqGroup[seqGroup.length - 1] = composeParallelSteps(previousParallelStep, step);
24544
+ }
24545
+ else {
24546
+ seqGroup.push(step);
24547
+ }
24548
+ seqGroupLastParallelIndex = anim.parGroupIndex;
23875
24549
  }
23876
24550
  if (seqGroup.length > 0) {
23877
24551
  seqGroups.push(finalizeClickGroup(seqGroup));
@@ -24275,9 +24949,15 @@ class TimelineEngine {
24275
24949
  advanceInteractive(triggerShapeId, nowMs = Date.now()) {
24276
24950
  const groups = this.timeline.interactiveSequences.get(triggerShapeId);
24277
24951
  const currentIdx = this.interactiveGroupIndexes.get(triggerShapeId) ?? -1;
24278
- const result = this.advanceSequenceGroup(groups, currentIdx, this.interactiveGroupStartedAtMs.get(triggerShapeId), nowMs);
24952
+ let result = this.advanceSequenceGroup(groups, currentIdx, this.interactiveGroupStartedAtMs.get(triggerShapeId), nowMs);
24279
24953
  if (result === 'exhausted') {
24280
- return null;
24954
+ if (!this.timeline.restartableInteractiveSequences?.has(triggerShapeId)) {
24955
+ return null;
24956
+ }
24957
+ result = this.advanceSequenceGroup(groups, -1, undefined, nowMs);
24958
+ if (result === 'exhausted') {
24959
+ return null;
24960
+ }
24281
24961
  }
24282
24962
  if (result === 'blocked') {
24283
24963
  return EMPTY_CLICK_GROUP;
@@ -25045,6 +25725,9 @@ function motionPathNativeAnimations(slide) {
25045
25725
  delayMs: animation.delayMs ?? 0,
25046
25726
  motionPath: animation.motionPath,
25047
25727
  motionPathEditMode: animation.motionPathEditMode ?? 'relative',
25728
+ motionPathRotationAngle: animation.motionPathRotationAngle,
25729
+ motionPathRotationCenterX: animation.motionPathRotationCenterX,
25730
+ motionPathRotationCenterY: animation.motionPathRotationCenterY,
25048
25731
  }));
25049
25732
  }
25050
25733
  /**
@@ -25173,8 +25856,9 @@ class PresentationAnimationController {
25173
25856
  const engine = TimelineEngine.fromAnimations(expandedAnims);
25174
25857
  const elementIds = slide.elements.map((element) => element.id);
25175
25858
  for (const anim of expandedAnims) {
25176
- if (anim.targetId && anim.targetId.includes(TEXT_BUILD_ID_SEP)) {
25177
- elementIds.push(anim.targetId);
25859
+ const targetId = resolveAnimationTargetId(anim);
25860
+ if (targetId.includes(TEXT_BUILD_ID_SEP)) {
25861
+ elementIds.push(targetId);
25178
25862
  }
25179
25863
  }
25180
25864
  return new PresentationAnimationController(engine, elementIds);
@@ -30212,7 +30896,7 @@ function getImageFitStyle(el) {
30212
30896
  height: '100%',
30213
30897
  objectFit: 'fill',
30214
30898
  };
30215
- if (!isImageLikeElement(el)) {
30899
+ if (!isImageLikeElement(el) && el.type !== 'media') {
30216
30900
  return uncropped;
30217
30901
  }
30218
30902
  const { placement, crop } = imageFitTransformParts(el);
@@ -30243,7 +30927,7 @@ const IDENTITY_TRANSFORM_PAIR = 'translate(0%, 0%) scale(1, 1)';
30243
30927
  * PLACEMENT and the `a:srcRect` source CROP. Either is `''` when absent.
30244
30928
  */
30245
30929
  function imageFitTransformParts(el) {
30246
- if (!isImageLikeElement(el)) {
30930
+ if (!isImageLikeElement(el) && el.type !== 'media') {
30247
30931
  return { placement: '', crop: '' };
30248
30932
  }
30249
30933
  // `a:stretch/a:fillRect` stretches the (cropped) image into a sub-rect of
@@ -41221,7 +41905,13 @@ function buildParagraphs(element, fieldContext, segmentOverrides) {
41221
41905
  const hasVisibleTextContent = paraSegments.some((seg) => seg !== markerSegment && Boolean(seg.text) && seg.text.trim().length > 0);
41222
41906
  const bullet = hasVisibleTextContent ? bulletResult : undefined;
41223
41907
  const indent = resolveParagraphIndent(paragraphIndents?.[paraIndex], firstSeg?.paragraphLevel);
41224
- const bulletStyle = buildBulletMarkerStyle(bullet, firstSeg, fontScale, indent.textIndentPx);
41908
+ // A bare negative indent without marL or a bullet is not a hanging
41909
+ // indent. Applying it directly sends ordinary text outside its box.
41910
+ const textIndentPx = indent.textIndentPx !== undefined &&
41911
+ (indent.textIndentPx >= 0 || indent.marginLeftPx !== undefined || bullet !== undefined)
41912
+ ? indent.textIndentPx
41913
+ : undefined;
41914
+ const bulletStyle = buildBulletMarkerStyle(bullet, firstSeg, fontScale, textIndentPx);
41225
41915
  const spacing = resolveParagraphSpacing({
41226
41916
  paraProps: propsCarrier?.paragraphProperties,
41227
41917
  bodyStyle,
@@ -41259,7 +41949,7 @@ function buildParagraphs(element, fieldContext, segmentOverrides) {
41259
41949
  bulletPicture: bullet?.picture,
41260
41950
  bulletStyle,
41261
41951
  marginLeftPx: indent.marginLeftPx,
41262
- textIndentPx: indent.textIndentPx,
41952
+ textIndentPx,
41263
41953
  lineHeight: spacing.lineHeight,
41264
41954
  spaceBeforePx: spacing.spaceBeforePx,
41265
41955
  spaceAfterPx: spacing.spaceAfterPx,
@@ -48859,6 +49549,7 @@ const COMPLEX_SLIDE_FIELDS = {
48859
49549
  activeXControls: '_ax',
48860
49550
  legacyVmlElements: '_lvml',
48861
49551
  backgroundPattern: '_bp',
49552
+ backgroundImageProperties: '_bip',
48862
49553
  modernCommentPart: '_mc',
48863
49554
  headerFooterFlags: '_hff',
48864
49555
  slideSynchronization: '_sync',
@@ -56984,8 +57675,53 @@ function buildPieChart3DDataForElement(element, options) {
56984
57675
  *
56985
57676
  * @module text-wrap-estimate
56986
57677
  */
56987
- /** Average glyph advance as a fraction of the font size, across mixed-case Latin text. */
56988
- const AVERAGE_ADVANCE_RATIO = 0.5;
57678
+ function estimatedGlyphAdvanceRatio(glyph) {
57679
+ if (/\p{Mark}/u.test(glyph)) {
57680
+ return 0;
57681
+ }
57682
+ if (/\s/u.test(glyph)) {
57683
+ return 0.33;
57684
+ }
57685
+ if (/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Extended_Pictographic}]/u.test(glyph)) {
57686
+ return 1;
57687
+ }
57688
+ if (/[A-Z]/u.test(glyph)) {
57689
+ return 0.65;
57690
+ }
57691
+ if (/[a-z0-9]/u.test(glyph)) {
57692
+ return 0.55;
57693
+ }
57694
+ if (/[,.;:!?()\u005b\u005d{}'"`\-_/\\]/u.test(glyph)) {
57695
+ return 0.35;
57696
+ }
57697
+ return 0.6;
57698
+ }
57699
+ function estimatedTextWidth(glyphs, fontSize) {
57700
+ return glyphs.reduce((sum, glyph) => sum + estimatedGlyphAdvanceRatio(glyph) * fontSize, 0);
57701
+ }
57702
+ const HANGING_PUNCTUATION_RE = /^[\u3001\u3002\uff0c\uff0e\uff01\uff1f\uff1b\uff1a\u3009\u300b\u300d\u300f\u3011\u3015\u3017\u3019\u301b\uff09\uff3d\uff5d\u2019\u201d\u00bb\u203a]$/u;
57703
+ const BREAKABLE_GLYPH_RE = /^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Extended_Pictographic}]$/u;
57704
+ function estimatedWrapUnits(text) {
57705
+ const units = [];
57706
+ let unbreakableRun = '';
57707
+ const flushRun = () => {
57708
+ if (unbreakableRun.length > 0) {
57709
+ units.push(unbreakableRun);
57710
+ unbreakableRun = '';
57711
+ }
57712
+ };
57713
+ for (const glyph of Array.from(text)) {
57714
+ if (/\s/u.test(glyph) || BREAKABLE_GLYPH_RE.test(glyph)) {
57715
+ flushRun();
57716
+ units.push(glyph);
57717
+ }
57718
+ else {
57719
+ unbreakableRun += glyph;
57720
+ }
57721
+ }
57722
+ flushRun();
57723
+ return units;
57724
+ }
56989
57725
  /**
56990
57726
  * Break `text` into lines that approximately fit `maxWidth`.
56991
57727
  *
@@ -57003,8 +57739,7 @@ function wrapTextByEstimatedWidth(text, maxWidth, fontSize, options = {}) {
57003
57739
  if (!text || text.trim().length === 0) {
57004
57740
  return [];
57005
57741
  }
57006
- const charactersPerLine = Math.floor(maxWidth / Math.max(fontSize * AVERAGE_ADVANCE_RATIO, 1));
57007
- if (charactersPerLine <= 0) {
57742
+ if (!(maxWidth > 0) || !(fontSize > 0)) {
57008
57743
  return [];
57009
57744
  }
57010
57745
  const lines = [];
@@ -57016,20 +57751,31 @@ function wrapTextByEstimatedWidth(text, maxWidth, fontSize, options = {}) {
57016
57751
  continue;
57017
57752
  }
57018
57753
  let current = '';
57019
- for (const word of paragraph.split(/\s+/u)) {
57020
- if (current.length === 0) {
57021
- current = word;
57754
+ let pendingWhitespace = false;
57755
+ for (const unit of estimatedWrapUnits(paragraph)) {
57756
+ if (/^\s+$/u.test(unit)) {
57757
+ pendingWhitespace = current.length > 0;
57758
+ continue;
57022
57759
  }
57023
- else if (current.length + 1 + word.length <= charactersPerLine) {
57024
- current += ` ${word}`;
57760
+ const separator = pendingWhitespace && current.length > 0 ? ' ' : '';
57761
+ const candidate = `${current}${separator}${unit}`;
57762
+ if (current.length === 0 || estimatedTextWidth(Array.from(candidate), fontSize) <= maxWidth) {
57763
+ current = candidate;
57764
+ pendingWhitespace = false;
57765
+ continue;
57025
57766
  }
57026
- else {
57027
- lines.push(current);
57028
- current = word;
57767
+ if (HANGING_PUNCTUATION_RE.test(unit)) {
57768
+ current += unit;
57769
+ pendingWhitespace = false;
57770
+ continue;
57029
57771
  }
57030
- }
57031
- if (current.length > 0) {
57032
57772
  lines.push(current);
57773
+ current = unit;
57774
+ pendingWhitespace = false;
57775
+ }
57776
+ const completed = current.trimEnd();
57777
+ if (completed.length > 0) {
57778
+ lines.push(completed);
57033
57779
  }
57034
57780
  }
57035
57781
  return lines;
@@ -57064,7 +57810,7 @@ function centeredSvgTextLines(text, fontSize, options = {}) {
57064
57810
  if (lines.length === 0) {
57065
57811
  return [{ text: '', y: centerY }];
57066
57812
  }
57067
- const lineHeight = fontSize * LINE_HEIGHT_RATIO$1;
57813
+ const lineHeight = options.lineHeight ?? fontSize * (options.lineHeightRatio ?? LINE_HEIGHT_RATIO$1);
57068
57814
  const blockTop = centerY - (lines.length * lineHeight) / 2;
57069
57815
  return lines.map((line, index) => ({
57070
57816
  text: line,
@@ -57141,6 +57887,14 @@ function buildChromeStyle(chrome) {
57141
57887
  * outline.
57142
57888
  */
57143
57889
  const TEXT_WIDTH_FRACTION = 0.82;
57890
+ const CJK_SMARTART_TEXT_RE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
57891
+ const CJK_SMARTART_LINE_HEIGHT_RATIO = 1.33;
57892
+ const DEFAULT_SMARTART_LINE_HEIGHT_RATIO = 1.2;
57893
+ function drawingTextBaseLineHeightRatio(text) {
57894
+ return CJK_SMARTART_TEXT_RE.test(text ?? '')
57895
+ ? CJK_SMARTART_LINE_HEIGHT_RATIO
57896
+ : DEFAULT_SMARTART_LINE_HEIGHT_RATIO;
57897
+ }
57144
57898
  /**
57145
57899
  * The fill of the nearest shape painted beneath `shape`'s centre.
57146
57900
  *
@@ -57186,17 +57940,21 @@ function computeDrawingViewBox(shapes) {
57186
57940
  let maxX = -Infinity;
57187
57941
  let maxY = -Infinity;
57188
57942
  for (const s of shapes) {
57189
- if (s.x < minX) {
57190
- minX = s.x;
57943
+ const shapeMinX = Math.min(s.x, s.textFrameX ?? s.x);
57944
+ const shapeMinY = Math.min(s.y, s.textFrameY ?? s.y);
57945
+ const shapeMaxX = Math.max(s.x + s.width, (s.textFrameX ?? s.x) + (s.textFrameWidth ?? s.width));
57946
+ const shapeMaxY = Math.max(s.y + s.height, (s.textFrameY ?? s.y) + (s.textFrameHeight ?? s.height));
57947
+ if (shapeMinX < minX) {
57948
+ minX = shapeMinX;
57191
57949
  }
57192
- if (s.y < minY) {
57193
- minY = s.y;
57950
+ if (shapeMinY < minY) {
57951
+ minY = shapeMinY;
57194
57952
  }
57195
- if (s.x + s.width > maxX) {
57196
- maxX = s.x + s.width;
57953
+ if (shapeMaxX > maxX) {
57954
+ maxX = shapeMaxX;
57197
57955
  }
57198
- if (s.y + s.height > maxY) {
57199
- maxY = s.y + s.height;
57956
+ if (shapeMaxY > maxY) {
57957
+ maxY = shapeMaxY;
57200
57958
  }
57201
57959
  }
57202
57960
  if (!Number.isFinite(minX)) {
@@ -57247,14 +58005,22 @@ function resolveShapeKind(shape, hasImage) {
57247
58005
  if (hasImage) {
57248
58006
  return 'image';
57249
58007
  }
57250
- // `getShapeType` folds the aliases it knows (oval -> ellipse, can -> cylinder)
57251
- // but has no vocabulary for the arrow presets SmartArt process layouts use, so
57252
- // those are matched on the normalised raw type.
57253
58008
  const normalized = (shape.shapeType ?? '').trim().toLowerCase();
57254
- if (normalized === 'chevron' || normalized === 'homeplate') {
57255
- return 'polygon';
58009
+ if (normalized === 'ellipse' || normalized === 'oval') {
58010
+ return 'ellipse';
57256
58011
  }
57257
- return getShapeType(shape.shapeType) === 'ellipse' ? 'ellipse' : 'rect';
58012
+ const identityPresets = new Set(['rect', 'flowchartprocess', 'roundrect']);
58013
+ return identityPresets.has(normalized) || normalized.length === 0 ? 'rect' : 'path';
58014
+ }
58015
+ function drawingShapeTransform(shape, cx, cy) {
58016
+ const transforms = [];
58017
+ if (shape.rotation) {
58018
+ transforms.push(`rotate(${shape.rotation} ${cx} ${cy})`);
58019
+ }
58020
+ if (shape.flipHorizontal || shape.flipVertical) {
58021
+ transforms.push(`translate(${cx} ${cy}) scale(${shape.flipHorizontal ? -1 : 1} ${shape.flipVertical ? -1 : 1}) translate(${-cx} ${-cy})`);
58022
+ }
58023
+ return transforms.length > 0 ? transforms.join(' ') : undefined;
57258
58024
  }
57259
58025
  /**
57260
58026
  * Project raw `PptxSmartArtDrawingShape`s into `RenderedShape` view-models,
@@ -57281,13 +58047,46 @@ function projectDrawingShapes(elementId, shapes, viewBox, palette, style) {
57281
58047
  const cx = relX + shape.width / 2;
57282
58048
  const cy = relY + shape.height / 2;
57283
58049
  const stroke = shape.strokeColor ?? (sw > 0 ? 'rgba(255,255,255,0.3)' : 'none');
57284
- const transform = shape.rotation !== undefined ? `rotate(${shape.rotation} ${cx} ${cy})` : undefined;
58050
+ const transform = drawingShapeTransform(shape, cx, cy);
58051
+ const presetGeometry = kind === 'path'
58052
+ ? getPresetShapeVectorGeometry(shape.shapeType, shape.width, shape.height, shape.shapeAdjustments)
58053
+ : undefined;
57285
58054
  const fontSize = shape.fontSize ?? Math.max(8, Math.min(14, shape.height * 0.2));
58055
+ const hasTextFrame = Number.isFinite(shape.textFrameX) &&
58056
+ Number.isFinite(shape.textFrameY) &&
58057
+ Number.isFinite(shape.textFrameWidth) &&
58058
+ Number.isFinite(shape.textFrameHeight) &&
58059
+ (shape.textFrameWidth ?? 0) > 0 &&
58060
+ (shape.textFrameHeight ?? 0) > 0;
58061
+ const textFrameX = (hasTextFrame ? shape.textFrameX : shape.x) - minX;
58062
+ const textFrameY = (hasTextFrame ? shape.textFrameY : shape.y) - minY;
58063
+ const textFrameWidth = hasTextFrame ? shape.textFrameWidth : shape.width;
58064
+ const textFrameHeight = hasTextFrame ? shape.textFrameHeight : shape.height;
58065
+ const textInsetLeft = shape.textInsetLeft ?? 0;
58066
+ const textInsetRight = shape.textInsetRight ?? 0;
58067
+ const textInsetTop = shape.textInsetTop ?? 0;
58068
+ const textInsetBottom = shape.textInsetBottom ?? 0;
58069
+ const textContentWidth = Math.max(1, textFrameWidth - textInsetLeft - textInsetRight);
58070
+ const textContentHeight = Math.max(1, textFrameHeight - textInsetTop - textInsetBottom);
58071
+ const textCenterX = textFrameX + textInsetLeft + textContentWidth / 2;
58072
+ const textCenterY = textFrameY + textInsetTop + textContentHeight / 2;
58073
+ const textWrapWidth = hasTextFrame ? textContentWidth : shape.width * TEXT_WIDTH_FRACTION;
58074
+ const authoredLineHeightRatio = shape.lineHeightRatio ?? 1;
58075
+ const lineHeightRatio = drawingTextBaseLineHeightRatio(shape.text) * authoredLineHeightRatio +
58076
+ (shape.lineSpacingAfterRatio ?? 0);
58077
+ const lineHeight = shape.lineHeight !== undefined || shape.lineSpacingAfter !== undefined
58078
+ ? (shape.lineHeight ??
58079
+ fontSize * drawingTextBaseLineHeightRatio(shape.text) * authoredLineHeightRatio) +
58080
+ (shape.lineSpacingAfter ?? 0)
58081
+ : undefined;
57286
58082
  return {
57287
58083
  key: `${elementId}-dsp-${shape.id}-${i}`,
57288
58084
  kind,
57289
- ...(kind === 'polygon'
57290
- ? { points: chevronPoints(relX, relY, shape.width, shape.height) }
58085
+ ...(presetGeometry
58086
+ ? {
58087
+ pathData: presetGeometry.d,
58088
+ pathTransform: [transform, `translate(${relX} ${relY})`].filter(Boolean).join(' '),
58089
+ }
57291
58090
  : {}),
57292
58091
  ...(gradient ? { gradient } : {}),
57293
58092
  x: relX,
@@ -57304,14 +58103,21 @@ function projectDrawingShapes(elementId, shapes, viewBox, palette, style) {
57304
58103
  imageUrl: shape.fillImageUrl,
57305
58104
  textLines: shape.text
57306
58105
  ? centeredSvgTextLines(shape.text, fontSize, {
57307
- maxWidth: shape.width * TEXT_WIDTH_FRACTION,
57308
- centerY: cy,
58106
+ maxWidth: textWrapWidth,
58107
+ centerY: textCenterY,
58108
+ lineHeight,
58109
+ lineHeightRatio,
57309
58110
  })
57310
58111
  : [],
57311
- textX: cx,
57312
- textY: cy,
58112
+ textX: textCenterX,
58113
+ textY: textCenterY,
58114
+ textWidth: textContentWidth,
58115
+ textHeight: textContentHeight,
57313
58116
  fontColor: shape.fontColor ?? drawingShapeLabelColor(shape, shapes, i, fill),
57314
58117
  fontSize,
58118
+ fontFamily: shape.fontFamily ? getSubstituteFontFamily(shape.fontFamily) : undefined,
58119
+ fontWeight: shape.fontWeight,
58120
+ fontStyle: shape.fontStyle,
57315
58121
  };
57316
58122
  });
57317
58123
  }
@@ -70603,6 +71409,7 @@ function masterViewParts(document, target) {
70603
71409
  elements: master.elements ?? [],
70604
71410
  backgroundColor: master.backgroundColor,
70605
71411
  backgroundImage: master.backgroundImage,
71412
+ backgroundImageProperties: master.backgroundImageProperties,
70606
71413
  };
70607
71414
  const layout = target.layoutIndex === null ? undefined : master.layouts?.[target.layoutIndex];
70608
71415
  if (!layout || target.layoutIndex === null) {
@@ -70618,6 +71425,9 @@ function masterViewParts(document, target) {
70618
71425
  elements: layout.elements ?? [],
70619
71426
  backgroundColor: layout.backgroundColor ?? master.backgroundColor,
70620
71427
  backgroundImage: layout.backgroundImage ?? master.backgroundImage,
71428
+ backgroundImageProperties: layout.backgroundImage
71429
+ ? layout.backgroundImageProperties
71430
+ : master.backgroundImageProperties,
70621
71431
  },
70622
71432
  ];
70623
71433
  }
@@ -70630,6 +71440,7 @@ function auxiliaryParts(kind, part) {
70630
71440
  elements: part.elements ?? [],
70631
71441
  backgroundColor: part.backgroundColor,
70632
71442
  backgroundImage: part.backgroundImage,
71443
+ backgroundImageProperties: part.backgroundImageProperties,
70633
71444
  },
70634
71445
  ]
70635
71446
  : [];
@@ -70658,6 +71469,7 @@ function masterViewPseudoSlide(document, target) {
70658
71469
  elements: parts.flatMap((part) => [...part.elements]),
70659
71470
  backgroundColor: primary.backgroundColor,
70660
71471
  backgroundImage: primary.backgroundImage,
71472
+ backgroundImageProperties: primary.backgroundImageProperties,
70661
71473
  };
70662
71474
  }
70663
71475
  /** Every element the master-view canvas is currently painting. */
@@ -77665,7 +78477,7 @@ function createLocalStorageBackend(namespace) {
77665
78477
  /** Try IndexedDB first; fall back to localStorage on any failure. */
77666
78478
  async function resolveBackend(dbName, namespace) {
77667
78479
  try {
77668
- const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-DTLrxc9s.mjs');
78480
+ const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-Cxqxc-c6.mjs');
77669
78481
  const db = await openChatDb(dbName);
77670
78482
  return createIdbBackend(db);
77671
78483
  }
@@ -86408,11 +87220,11 @@ class SmartArtRendererComponent {
86408
87220
  return rebuildDrawingShapesIfCleared(data, data.layout, resolvePalette(data), data.style ?? 'flat', el.id, { width: el.width, height: el.height });
86409
87221
  }
86410
87222
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SmartArtRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
86411
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SmartArtRendererComponent, isStandalone: true, selector: "pptx-smart-art-renderer", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, animationState: { classPropertyName: "animationState", publicName: "animationState", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "nodeEditor", first: true, predicate: ["nodeEditor"], descendants: true, isSignal: true }, { propertyName: "smartartContainer", first: true, predicate: ["smartartContainer"], descendants: true, isSignal: true }, { propertyName: "styleBar", first: true, predicate: ["styleBar"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n\t#smartartContainer\n\tclass=\"pptx-ng-smartart-chrome\"\n\t[ngStyle]=\"chromeStyle()\"\n\t[attr.role]=\"a11y() ? a11y()!.role : null\"\n\t[attr.aria-label]=\"a11y()?.label ?? null\"\n\t(mousemove)=\"onMouseMove($event)\"\n\t(mouseleave)=\"onMouseLeave()\"\n>\n\t@if (isEmpty()) {\n\t\t<div class=\"pptx-ng-smartart-placeholder\">\n\t\t\t{{ 'pptx.smartArt.placeholder' | translate }}\n\t\t</div>\n\t} @else if (hasDrawingShapes()) {\n\t\t<svg\n\t\t\tclass=\"pptx-ng-smartart-svg\"\n\t\t\tdata-testid=\"smartart-drawing-shapes\"\n\t\t\t[attr.viewBox]=\"svgViewBox()\"\n\t\t\tpreserveAspectRatio=\"xMidYMid meet\"\n\t\t>\n\t\t\t@for (shape of renderedShapes(); track shape.key; let i = $index) {\n\t\t\t\t<g\n\t\t\t\t\t[ngStyle]=\"shadowFilter() ? { filter: shadowFilter() } : {}\"\n\t\t\t\t\t[attr.data-smartart-node-id]=\"drawingShapeNodeIds()[i] ?? null\"\n\t\t\t\t\t[class.pptx-ng-smartart-node--editable]=\"canEditNodes() && !!drawingShapeNodeIds()[i]\"\n\t\t\t\t>\n\t\t\t\t\t@if (shape.gradient) {\n\t\t\t\t\t\t<defs>\n\t\t\t\t\t\t\t@if (shape.gradient!.kind === 'radial') {\n\t\t\t\t\t\t\t\t<radialGradient\n\t\t\t\t\t\t\t\t\t[attr.id]=\"shape.gradient!.id\"\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"shape.gradient!.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"shape.gradient!.cy\"\n\t\t\t\t\t\t\t\t\t[attr.r]=\"shape.gradient!.r\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t@for (stop of shape.gradient!.stops; track $index) {\n\t\t\t\t\t\t\t\t\t\t<stop\n\t\t\t\t\t\t\t\t\t\t\t[attr.offset]=\"stop.offset\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.stop-color]=\"stop.color\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.stop-opacity]=\"stop.opacity ?? null\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t</radialGradient>\n\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t<linearGradient\n\t\t\t\t\t\t\t\t\t[attr.id]=\"shape.gradient!.id\"\n\t\t\t\t\t\t\t\t\t[attr.x1]=\"shape.gradient!.x1\"\n\t\t\t\t\t\t\t\t\t[attr.y1]=\"shape.gradient!.y1\"\n\t\t\t\t\t\t\t\t\t[attr.x2]=\"shape.gradient!.x2\"\n\t\t\t\t\t\t\t\t\t[attr.y2]=\"shape.gradient!.y2\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t@for (stop of shape.gradient!.stops; track $index) {\n\t\t\t\t\t\t\t\t\t\t<stop\n\t\t\t\t\t\t\t\t\t\t\t[attr.offset]=\"stop.offset\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.stop-color]=\"stop.color\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.stop-opacity]=\"stop.opacity ?? null\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t</linearGradient>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</defs>\n\t\t\t\t\t}\n\t\t\t\t\t@if (shape.kind === 'image') {\n\t\t\t\t\t\t<image\n\t\t\t\t\t\t\t[attr.x]=\"shape.x\"\n\t\t\t\t\t\t\t[attr.y]=\"shape.y\"\n\t\t\t\t\t\t\t[attr.width]=\"shape.width\"\n\t\t\t\t\t\t\t[attr.height]=\"shape.height\"\n\t\t\t\t\t\t\t[attr.href]=\"shape.imageUrl\"\n\t\t\t\t\t\t\tpreserveAspectRatio=\"xMidYMid meet\"\n\t\t\t\t\t\t\t[attr.transform]=\"shape.transform ?? null\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else if (shape.kind === 'ellipse') {\n\t\t\t\t\t\t<ellipse\n\t\t\t\t\t\t\t[attr.cx]=\"shape.cx\"\n\t\t\t\t\t\t\t[attr.cy]=\"shape.cy\"\n\t\t\t\t\t\t\t[attr.rx]=\"shape.width / 2\"\n\t\t\t\t\t\t\t[attr.ry]=\"shape.height / 2\"\n\t\t\t\t\t\t\t[attr.fill]=\"shape.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"shape.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"shape.strokeWidth\"\n\t\t\t\t\t\t\t[attr.transform]=\"shape.transform ?? null\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else if (shape.kind === 'polygon') {\n\t\t\t\t\t\t<polygon\n\t\t\t\t\t\t\t[attr.points]=\"shape.points\"\n\t\t\t\t\t\t\t[attr.fill]=\"shape.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"shape.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"shape.strokeWidth\"\n\t\t\t\t\t\t\t[attr.transform]=\"shape.transform ?? null\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<rect\n\t\t\t\t\t\t\t[attr.x]=\"shape.x\"\n\t\t\t\t\t\t\t[attr.y]=\"shape.y\"\n\t\t\t\t\t\t\t[attr.width]=\"shape.width\"\n\t\t\t\t\t\t\t[attr.height]=\"shape.height\"\n\t\t\t\t\t\t\t[attr.rx]=\"shape.rx\"\n\t\t\t\t\t\t\t[attr.fill]=\"shape.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"shape.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"shape.strokeWidth\"\n\t\t\t\t\t\t\t[attr.transform]=\"shape.transform ?? null\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t\t@if (shape.textLines.length > 0) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t[attr.x]=\"shape.textX\"\n\t\t\t\t\t\t\ttext-anchor=\"middle\"\n\t\t\t\t\t\t\tdominant-baseline=\"central\"\n\t\t\t\t\t\t\t[attr.fill]=\"shape.fontColor\"\n\t\t\t\t\t\t\t[attr.font-size]=\"shape.fontSize\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t@for (line of shape.textLines; track $index) {\n\t\t\t\t\t\t\t\t<tspan [attr.x]=\"shape.textX\" [attr.y]=\"line.y\">\n\t\t\t\t\t\t\t\t\t{{ line.text }}\n\t\t\t\t\t\t\t\t</tspan>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t</g>\n\t\t\t}\n\t\t</svg>\n\t} @else if (hasLayout()) {\n\t\t<svg\n\t\t\tclass=\"pptx-ng-smartart-svg\"\n\t\t\t[attr.data-testid]=\"'smartart-' + layout().family\"\n\t\t\t[attr.viewBox]=\"layout().viewBox\"\n\t\t\tpreserveAspectRatio=\"xMidYMid meet\"\n\t\t\t[attr.data-layout-family]=\"layout().family\"\n\t\t>\n\t\t\t@for (conn of layout().connectors; track conn.key; let ci = $index) {\n\t\t\t\t<path\n\t\t\t\t\t[attr.d]=\"layoutConnectors()[ci]!.d\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t[attr.stroke]=\"layoutConnectors()[ci]!.stroke\"\n\t\t\t\t\t[attr.stroke-width]=\"layoutConnectors()[ci]!.strokeWidth\"\n\t\t\t\t\t[attr.opacity]=\"layoutConnectors()[ci]!.opacity\"\n\t\t\t\t\t[attr.stroke-dasharray]=\"layoutConnectors()[ci]!.dash ?? null\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@for (node of layout().nodes; track node.key; let ni = $index) {\n\t\t\t\t<g\n\t\t\t\t\t[ngStyle]=\"shadowFilter() ? { filter: shadowFilter() } : {}\"\n\t\t\t\t\t[class.pptx-ng-smartart-node--editable]=\"canEditNodes()\"\n\t\t\t\t\t[attr.tabindex]=\"canEditNodes() ? 0 : null\"\n\t\t\t\t\t[attr.role]=\"canEditNodes() ? 'button' : 'img'\"\n\t\t\t\t\t[attr.aria-label]=\"nodeAriaLabel(ni) ?? node.text\"\n\t\t\t\t\t[attr.data-smartart-node-id]=\"nodeIdAt(ni)\"\n\t\t\t\t\t(dblclick)=\"onNodeDblClick($event, node, ni)\"\n\t\t\t\t\t(keydown)=\"onNodeKeydown($event, node, ni)\"\n\t\t\t\t>\n\t\t\t\t\t@if (nodeAriaLabel(ni); as title) {\n\t\t\t\t\t\t<title>{{ title }}</title>\n\t\t\t\t\t}\n\t\t\t\t\t@if (asCircle(node); as c) {\n\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t[attr.cx]=\"c.cx\"\n\t\t\t\t\t\t\t[attr.cy]=\"c.cy\"\n\t\t\t\t\t\t\t[attr.r]=\"c.r\"\n\t\t\t\t\t\t\t[attr.fill]=\"c.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"c.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"c.strokeWidth\"\n\t\t\t\t\t\t\t[attr.opacity]=\"c.opacity\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else if (asPolygon(node); as p) {\n\t\t\t\t\t\t<polygon\n\t\t\t\t\t\t\t[attr.points]=\"p.points\"\n\t\t\t\t\t\t\t[attr.fill]=\"p.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"p.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"p.strokeWidth\"\n\t\t\t\t\t\t\t[attr.opacity]=\"p.opacity\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else if (asRect(node); as r) {\n\t\t\t\t\t\t<rect\n\t\t\t\t\t\t\t[attr.x]=\"r.x\"\n\t\t\t\t\t\t\t[attr.y]=\"r.y\"\n\t\t\t\t\t\t\t[attr.width]=\"r.width\"\n\t\t\t\t\t\t\t[attr.height]=\"r.height\"\n\t\t\t\t\t\t\t[attr.rx]=\"r.rx\"\n\t\t\t\t\t\t\t[attr.fill]=\"r.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"r.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"r.strokeWidth\"\n\t\t\t\t\t\t\t[attr.opacity]=\"r.opacity\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t\t<!--\n\t\t\t\t\t\tNode label. Placement, colour, weight and baseline arrive\n\t\t\t\t\t\talready decided from the shared `smartArtNodeLabel`, so an\n\t\t\t\t\t\toff-centre caption (target leader, gear legend row, timeline\n\t\t\t\t\t\tcaption above / below the axis) lands beside its node.\n\t\t\t\t\t-->\n\t\t\t\t\t@if (layoutLabels()[ni]!.visible) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t[attr.x]=\"layoutLabels()[ni]!.x\"\n\t\t\t\t\t\t\t[attr.text-anchor]=\"layoutLabels()[ni]!.textAnchor\"\n\t\t\t\t\t\t\t[attr.dominant-baseline]=\"layoutLabels()[ni]!.dominantBaseline\"\n\t\t\t\t\t\t\t[attr.fill]=\"layoutLabels()[ni]!.fill\"\n\t\t\t\t\t\t\t[attr.font-size]=\"layoutLabels()[ni]!.fontSize\"\n\t\t\t\t\t\t\t[attr.font-weight]=\"layoutLabels()[ni]!.fontWeight ?? null\"\n\t\t\t\t\t\t\t[attr.font-style]=\"layoutLabels()[ni]!.fontStyle ?? null\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t@for (line of layoutLabels()[ni]!.lines; track $index) {\n\t\t\t\t\t\t\t\t<tspan [attr.x]=\"layoutLabels()[ni]!.x\" [attr.y]=\"line.y\">\n\t\t\t\t\t\t\t\t\t{{ line.text }}\n\t\t\t\t\t\t\t\t</tspan>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t</g>\n\t\t\t}\n\t\t</svg>\n\t} @else {\n\t\t<div class=\"pptx-ng-smartart-placeholder\">\n\t\t\t{{ 'pptx.smartArt.placeholder' | translate }}\n\t\t</div>\n\t}\n\n\t@if (canEditNodes() && hoveredNodeId() && !editState() && styleBarStyle()) {\n\t\t<div\n\t\t\t#styleBar\n\t\t\tclass=\"pptx-ng-smartart-style-bar\"\n\t\t\t[ngStyle]=\"styleBarStyle()!\"\n\t\t\t(mousedown)=\"$event.stopPropagation()\"\n\t\t\t(click)=\"$event.stopPropagation()\"\n\t\t>\n\t\t\t@for (color of palette().slice(0, 6); track color) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-smartart-swatch\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\t[attr.aria-label]=\"'pptx.smartArt.setFill' | translate: { color: color }\"\n\t\t\t\t\t[style.background]=\"color\"\n\t\t\t\t\t(click)=\"handleChangeNodeStyle(hoveredNodeId()!, color)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t</div>\n\t}\n\n\t<!--\n\t\tInline node-text editor. Positioned in element-local px (== viewBox\n\t\tunits, since the SVG viewBox matches the element pixel size and the\n\t\tsvg fills the chrome) over the double-clicked node. Commits via the\n\t\tshared EditorStateService.updateElement path on Enter / blur.\n\t-->\n\t@if (editState(); as edit) {\n\t\t<textarea\n\t\t\t#nodeEditor\n\t\t\tclass=\"pptx-ng-smartart-node-editor\"\n\t\t\t[style.left.px]=\"edit.box.x\"\n\t\t\t[style.top.px]=\"edit.box.y\"\n\t\t\t[style.width.px]=\"edit.box.width\"\n\t\t\t[style.height.px]=\"edit.box.height\"\n\t\t\t[value]=\"edit.text\"\n\t\t\t(pointerdown)=\"$event.stopPropagation()\"\n\t\t\t(mousedown)=\"$event.stopPropagation()\"\n\t\t\t(click)=\"$event.stopPropagation()\"\n\t\t\t(dblclick)=\"$event.stopPropagation()\"\n\t\t\t(blur)=\"commitEdit($event)\"\n\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t></textarea>\n\t}\n\n\t<!-- Polite live region: announces node-text edit commits to AT. -->\n\t<span class=\"pptx-ng-sr-only\" aria-live=\"polite\" role=\"status\">{{ liveMessage() }}</span>\n</div>\n", styles: [".pptx-ng-smartart-chrome{box-sizing:border-box;overflow:hidden;position:relative}.pptx-ng-smartart-svg{width:100%;height:100%;pointer-events:none}.pptx-ng-smartart-node--editable{pointer-events:auto;cursor:text}.pptx-ng-smartart-node--editable:hover{filter:drop-shadow(0 0 2px rgba(96,165,250,.8))}.pptx-ng-smartart-node-editor{position:absolute;box-sizing:border-box;margin:0;padding:1px 2px;border:1px solid var(--pptx-inspector-active, #0078d4);border-radius:2px;background:#fff;color:#111;font-size:11px;line-height:1.1;text-align:center;resize:none;overflow:hidden;z-index:2}.pptx-ng-smartart-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:11px;color:#fffc;pointer-events:none}.pptx-ng-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.pptx-ng-smartart-style-bar{position:absolute;pointer-events:auto;display:flex;gap:6px;padding:6px 8px;background:#ffffffe6;border:1px solid var(--border, #e2e8f0);border-radius:9999px;box-shadow:0 1px 2px #0000001a}.pptx-ng-smartart-swatch{width:20px;height:20px;border-radius:50%;border:1px solid rgba(0,0,0,.1);cursor:pointer;transition:transform .1s}.pptx-ng-smartart-swatch:hover{transform:scale(1.25)}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
87223
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SmartArtRendererComponent, isStandalone: true, selector: "pptx-smart-art-renderer", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, animationState: { classPropertyName: "animationState", publicName: "animationState", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "nodeEditor", first: true, predicate: ["nodeEditor"], descendants: true, isSignal: true }, { propertyName: "smartartContainer", first: true, predicate: ["smartartContainer"], descendants: true, isSignal: true }, { propertyName: "styleBar", first: true, predicate: ["styleBar"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n\t#smartartContainer\n\tclass=\"pptx-ng-smartart-chrome\"\n\t[ngStyle]=\"chromeStyle()\"\n\t[attr.role]=\"a11y() ? a11y()!.role : null\"\n\t[attr.aria-label]=\"a11y()?.label ?? null\"\n\t(mousemove)=\"onMouseMove($event)\"\n\t(mouseleave)=\"onMouseLeave()\"\n>\n\t@if (isEmpty()) {\n\t\t<div class=\"pptx-ng-smartart-placeholder\">\n\t\t\t{{ 'pptx.smartArt.placeholder' | translate }}\n\t\t</div>\n\t} @else if (hasDrawingShapes()) {\n\t\t<svg\n\t\t\tclass=\"pptx-ng-smartart-svg\"\n\t\t\tdata-testid=\"smartart-drawing-shapes\"\n\t\t\t[attr.viewBox]=\"svgViewBox()\"\n\t\t\tpreserveAspectRatio=\"xMidYMid meet\"\n\t\t>\n\t\t\t@for (shape of renderedShapes(); track shape.key; let i = $index) {\n\t\t\t\t<g\n\t\t\t\t\t[ngStyle]=\"shadowFilter() ? { filter: shadowFilter() } : {}\"\n\t\t\t\t\t[attr.data-smartart-node-id]=\"drawingShapeNodeIds()[i] ?? null\"\n\t\t\t\t\t[class.pptx-ng-smartart-node--editable]=\"canEditNodes() && !!drawingShapeNodeIds()[i]\"\n\t\t\t\t>\n\t\t\t\t\t@if (shape.gradient) {\n\t\t\t\t\t\t<defs>\n\t\t\t\t\t\t\t@if (shape.gradient!.kind === 'radial') {\n\t\t\t\t\t\t\t\t<radialGradient\n\t\t\t\t\t\t\t\t\t[attr.id]=\"shape.gradient!.id\"\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"shape.gradient!.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"shape.gradient!.cy\"\n\t\t\t\t\t\t\t\t\t[attr.r]=\"shape.gradient!.r\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t@for (stop of shape.gradient!.stops; track $index) {\n\t\t\t\t\t\t\t\t\t\t<stop\n\t\t\t\t\t\t\t\t\t\t\t[attr.offset]=\"stop.offset\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.stop-color]=\"stop.color\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.stop-opacity]=\"stop.opacity ?? null\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t</radialGradient>\n\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t<linearGradient\n\t\t\t\t\t\t\t\t\t[attr.id]=\"shape.gradient!.id\"\n\t\t\t\t\t\t\t\t\t[attr.x1]=\"shape.gradient!.x1\"\n\t\t\t\t\t\t\t\t\t[attr.y1]=\"shape.gradient!.y1\"\n\t\t\t\t\t\t\t\t\t[attr.x2]=\"shape.gradient!.x2\"\n\t\t\t\t\t\t\t\t\t[attr.y2]=\"shape.gradient!.y2\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t@for (stop of shape.gradient!.stops; track $index) {\n\t\t\t\t\t\t\t\t\t\t<stop\n\t\t\t\t\t\t\t\t\t\t\t[attr.offset]=\"stop.offset\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.stop-color]=\"stop.color\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.stop-opacity]=\"stop.opacity ?? null\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t</linearGradient>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</defs>\n\t\t\t\t\t}\n\t\t\t\t\t@if (shape.kind === 'image') {\n\t\t\t\t\t\t<image\n\t\t\t\t\t\t\t[attr.x]=\"shape.x\"\n\t\t\t\t\t\t\t[attr.y]=\"shape.y\"\n\t\t\t\t\t\t\t[attr.width]=\"shape.width\"\n\t\t\t\t\t\t\t[attr.height]=\"shape.height\"\n\t\t\t\t\t\t\t[attr.href]=\"shape.imageUrl\"\n\t\t\t\t\t\t\tpreserveAspectRatio=\"xMidYMid meet\"\n\t\t\t\t\t\t\t[attr.transform]=\"shape.transform ?? null\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else if (shape.kind === 'ellipse') {\n\t\t\t\t\t\t<ellipse\n\t\t\t\t\t\t\t[attr.cx]=\"shape.cx\"\n\t\t\t\t\t\t\t[attr.cy]=\"shape.cy\"\n\t\t\t\t\t\t\t[attr.rx]=\"shape.width / 2\"\n\t\t\t\t\t\t\t[attr.ry]=\"shape.height / 2\"\n\t\t\t\t\t\t\t[attr.fill]=\"shape.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"shape.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"shape.strokeWidth\"\n\t\t\t\t\t\t\t[attr.transform]=\"shape.transform ?? null\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else if (shape.kind === 'path') {\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t[attr.d]=\"shape.pathData\"\n\t\t\t\t\t\t\t[attr.fill]=\"shape.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"shape.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"shape.strokeWidth\"\n\t\t\t\t\t\t\t[attr.transform]=\"shape.pathTransform ?? null\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<rect\n\t\t\t\t\t\t\t[attr.x]=\"shape.x\"\n\t\t\t\t\t\t\t[attr.y]=\"shape.y\"\n\t\t\t\t\t\t\t[attr.width]=\"shape.width\"\n\t\t\t\t\t\t\t[attr.height]=\"shape.height\"\n\t\t\t\t\t\t\t[attr.rx]=\"shape.rx\"\n\t\t\t\t\t\t\t[attr.fill]=\"shape.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"shape.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"shape.strokeWidth\"\n\t\t\t\t\t\t\t[attr.transform]=\"shape.transform ?? null\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t\t@if (shape.textLines.length > 0) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t[attr.x]=\"shape.textX\"\n\t\t\t\t\t\t\ttext-anchor=\"middle\"\n\t\t\t\t\t\t\tdominant-baseline=\"central\"\n\t\t\t\t\t\t\t[attr.fill]=\"shape.fontColor\"\n\t\t\t\t\t\t\t[attr.font-family]=\"shape.fontFamily ?? null\"\n\t\t\t\t\t\t\t[attr.font-weight]=\"shape.fontWeight ?? null\"\n\t\t\t\t\t\t\t[attr.font-style]=\"shape.fontStyle ?? null\"\n\t\t\t\t\t\t\t[attr.font-size]=\"shape.fontSize\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t@for (line of shape.textLines; track $index) {\n\t\t\t\t\t\t\t\t<tspan [attr.x]=\"shape.textX\" [attr.y]=\"line.y\">\n\t\t\t\t\t\t\t\t\t{{ line.text }}\n\t\t\t\t\t\t\t\t</tspan>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t</g>\n\t\t\t}\n\t\t</svg>\n\t} @else if (hasLayout()) {\n\t\t<svg\n\t\t\tclass=\"pptx-ng-smartart-svg\"\n\t\t\t[attr.data-testid]=\"'smartart-' + layout().family\"\n\t\t\t[attr.viewBox]=\"layout().viewBox\"\n\t\t\tpreserveAspectRatio=\"xMidYMid meet\"\n\t\t\t[attr.data-layout-family]=\"layout().family\"\n\t\t>\n\t\t\t@for (conn of layout().connectors; track conn.key; let ci = $index) {\n\t\t\t\t<path\n\t\t\t\t\t[attr.d]=\"layoutConnectors()[ci]!.d\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t[attr.stroke]=\"layoutConnectors()[ci]!.stroke\"\n\t\t\t\t\t[attr.stroke-width]=\"layoutConnectors()[ci]!.strokeWidth\"\n\t\t\t\t\t[attr.opacity]=\"layoutConnectors()[ci]!.opacity\"\n\t\t\t\t\t[attr.stroke-dasharray]=\"layoutConnectors()[ci]!.dash ?? null\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@for (node of layout().nodes; track node.key; let ni = $index) {\n\t\t\t\t<g\n\t\t\t\t\t[ngStyle]=\"shadowFilter() ? { filter: shadowFilter() } : {}\"\n\t\t\t\t\t[class.pptx-ng-smartart-node--editable]=\"canEditNodes()\"\n\t\t\t\t\t[attr.tabindex]=\"canEditNodes() ? 0 : null\"\n\t\t\t\t\t[attr.role]=\"canEditNodes() ? 'button' : 'img'\"\n\t\t\t\t\t[attr.aria-label]=\"nodeAriaLabel(ni) ?? node.text\"\n\t\t\t\t\t[attr.data-smartart-node-id]=\"nodeIdAt(ni)\"\n\t\t\t\t\t(dblclick)=\"onNodeDblClick($event, node, ni)\"\n\t\t\t\t\t(keydown)=\"onNodeKeydown($event, node, ni)\"\n\t\t\t\t>\n\t\t\t\t\t@if (nodeAriaLabel(ni); as title) {\n\t\t\t\t\t\t<title>{{ title }}</title>\n\t\t\t\t\t}\n\t\t\t\t\t@if (asCircle(node); as c) {\n\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t[attr.cx]=\"c.cx\"\n\t\t\t\t\t\t\t[attr.cy]=\"c.cy\"\n\t\t\t\t\t\t\t[attr.r]=\"c.r\"\n\t\t\t\t\t\t\t[attr.fill]=\"c.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"c.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"c.strokeWidth\"\n\t\t\t\t\t\t\t[attr.opacity]=\"c.opacity\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else if (asPolygon(node); as p) {\n\t\t\t\t\t\t<polygon\n\t\t\t\t\t\t\t[attr.points]=\"p.points\"\n\t\t\t\t\t\t\t[attr.fill]=\"p.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"p.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"p.strokeWidth\"\n\t\t\t\t\t\t\t[attr.opacity]=\"p.opacity\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else if (asRect(node); as r) {\n\t\t\t\t\t\t<rect\n\t\t\t\t\t\t\t[attr.x]=\"r.x\"\n\t\t\t\t\t\t\t[attr.y]=\"r.y\"\n\t\t\t\t\t\t\t[attr.width]=\"r.width\"\n\t\t\t\t\t\t\t[attr.height]=\"r.height\"\n\t\t\t\t\t\t\t[attr.rx]=\"r.rx\"\n\t\t\t\t\t\t\t[attr.fill]=\"r.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"r.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"r.strokeWidth\"\n\t\t\t\t\t\t\t[attr.opacity]=\"r.opacity\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t\t<!--\n\t\t\t\t\t\tNode label. Placement, colour, weight and baseline arrive\n\t\t\t\t\t\talready decided from the shared `smartArtNodeLabel`, so an\n\t\t\t\t\t\toff-centre caption (target leader, gear legend row, timeline\n\t\t\t\t\t\tcaption above / below the axis) lands beside its node.\n\t\t\t\t\t-->\n\t\t\t\t\t@if (layoutLabels()[ni]!.visible) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t[attr.x]=\"layoutLabels()[ni]!.x\"\n\t\t\t\t\t\t\t[attr.text-anchor]=\"layoutLabels()[ni]!.textAnchor\"\n\t\t\t\t\t\t\t[attr.dominant-baseline]=\"layoutLabels()[ni]!.dominantBaseline\"\n\t\t\t\t\t\t\t[attr.fill]=\"layoutLabels()[ni]!.fill\"\n\t\t\t\t\t\t\t[attr.font-size]=\"layoutLabels()[ni]!.fontSize\"\n\t\t\t\t\t\t\t[attr.font-weight]=\"layoutLabels()[ni]!.fontWeight ?? null\"\n\t\t\t\t\t\t\t[attr.font-style]=\"layoutLabels()[ni]!.fontStyle ?? null\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t@for (line of layoutLabels()[ni]!.lines; track $index) {\n\t\t\t\t\t\t\t\t<tspan [attr.x]=\"layoutLabels()[ni]!.x\" [attr.y]=\"line.y\">\n\t\t\t\t\t\t\t\t\t{{ line.text }}\n\t\t\t\t\t\t\t\t</tspan>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t</g>\n\t\t\t}\n\t\t</svg>\n\t} @else {\n\t\t<div class=\"pptx-ng-smartart-placeholder\">\n\t\t\t{{ 'pptx.smartArt.placeholder' | translate }}\n\t\t</div>\n\t}\n\n\t@if (canEditNodes() && hoveredNodeId() && !editState() && styleBarStyle()) {\n\t\t<div\n\t\t\t#styleBar\n\t\t\tclass=\"pptx-ng-smartart-style-bar\"\n\t\t\t[ngStyle]=\"styleBarStyle()!\"\n\t\t\t(mousedown)=\"$event.stopPropagation()\"\n\t\t\t(click)=\"$event.stopPropagation()\"\n\t\t>\n\t\t\t@for (color of palette().slice(0, 6); track color) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-smartart-swatch\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\t[attr.aria-label]=\"'pptx.smartArt.setFill' | translate: { color: color }\"\n\t\t\t\t\t[style.background]=\"color\"\n\t\t\t\t\t(click)=\"handleChangeNodeStyle(hoveredNodeId()!, color)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t</div>\n\t}\n\n\t<!--\n\t\tInline node-text editor. Positioned in element-local px (== viewBox\n\t\tunits, since the SVG viewBox matches the element pixel size and the\n\t\tsvg fills the chrome) over the double-clicked node. Commits via the\n\t\tshared EditorStateService.updateElement path on Enter / blur.\n\t-->\n\t@if (editState(); as edit) {\n\t\t<textarea\n\t\t\t#nodeEditor\n\t\t\tclass=\"pptx-ng-smartart-node-editor\"\n\t\t\t[style.left.px]=\"edit.box.x\"\n\t\t\t[style.top.px]=\"edit.box.y\"\n\t\t\t[style.width.px]=\"edit.box.width\"\n\t\t\t[style.height.px]=\"edit.box.height\"\n\t\t\t[value]=\"edit.text\"\n\t\t\t(pointerdown)=\"$event.stopPropagation()\"\n\t\t\t(mousedown)=\"$event.stopPropagation()\"\n\t\t\t(click)=\"$event.stopPropagation()\"\n\t\t\t(dblclick)=\"$event.stopPropagation()\"\n\t\t\t(blur)=\"commitEdit($event)\"\n\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t></textarea>\n\t}\n\n\t<!-- Polite live region: announces node-text edit commits to AT. -->\n\t<span class=\"pptx-ng-sr-only\" aria-live=\"polite\" role=\"status\">{{ liveMessage() }}</span>\n</div>\n", styles: [".pptx-ng-smartart-chrome{box-sizing:border-box;overflow:hidden;position:relative}.pptx-ng-smartart-svg{width:100%;height:100%;pointer-events:none}.pptx-ng-smartart-node--editable{pointer-events:auto;cursor:text}.pptx-ng-smartart-node--editable:hover{filter:drop-shadow(0 0 2px rgba(96,165,250,.8))}.pptx-ng-smartart-node-editor{position:absolute;box-sizing:border-box;margin:0;padding:1px 2px;border:1px solid var(--pptx-inspector-active, #0078d4);border-radius:2px;background:#fff;color:#111;font-size:11px;line-height:1.1;text-align:center;resize:none;overflow:hidden;z-index:2}.pptx-ng-smartart-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:11px;color:#fffc;pointer-events:none}.pptx-ng-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.pptx-ng-smartart-style-bar{position:absolute;pointer-events:auto;display:flex;gap:6px;padding:6px 8px;background:#ffffffe6;border:1px solid var(--border, #e2e8f0);border-radius:9999px;box-shadow:0 1px 2px #0000001a}.pptx-ng-smartart-swatch{width:20px;height:20px;border-radius:50%;border:1px solid rgba(0,0,0,.1);cursor:pointer;transition:transform .1s}.pptx-ng-smartart-swatch:hover{transform:scale(1.25)}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
86412
87224
  }
86413
87225
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SmartArtRendererComponent, decorators: [{
86414
87226
  type: Component,
86415
- args: [{ selector: 'pptx-smart-art-renderer', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgStyle, TranslatePipe], template: "<div\n\t#smartartContainer\n\tclass=\"pptx-ng-smartart-chrome\"\n\t[ngStyle]=\"chromeStyle()\"\n\t[attr.role]=\"a11y() ? a11y()!.role : null\"\n\t[attr.aria-label]=\"a11y()?.label ?? null\"\n\t(mousemove)=\"onMouseMove($event)\"\n\t(mouseleave)=\"onMouseLeave()\"\n>\n\t@if (isEmpty()) {\n\t\t<div class=\"pptx-ng-smartart-placeholder\">\n\t\t\t{{ 'pptx.smartArt.placeholder' | translate }}\n\t\t</div>\n\t} @else if (hasDrawingShapes()) {\n\t\t<svg\n\t\t\tclass=\"pptx-ng-smartart-svg\"\n\t\t\tdata-testid=\"smartart-drawing-shapes\"\n\t\t\t[attr.viewBox]=\"svgViewBox()\"\n\t\t\tpreserveAspectRatio=\"xMidYMid meet\"\n\t\t>\n\t\t\t@for (shape of renderedShapes(); track shape.key; let i = $index) {\n\t\t\t\t<g\n\t\t\t\t\t[ngStyle]=\"shadowFilter() ? { filter: shadowFilter() } : {}\"\n\t\t\t\t\t[attr.data-smartart-node-id]=\"drawingShapeNodeIds()[i] ?? null\"\n\t\t\t\t\t[class.pptx-ng-smartart-node--editable]=\"canEditNodes() && !!drawingShapeNodeIds()[i]\"\n\t\t\t\t>\n\t\t\t\t\t@if (shape.gradient) {\n\t\t\t\t\t\t<defs>\n\t\t\t\t\t\t\t@if (shape.gradient!.kind === 'radial') {\n\t\t\t\t\t\t\t\t<radialGradient\n\t\t\t\t\t\t\t\t\t[attr.id]=\"shape.gradient!.id\"\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"shape.gradient!.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"shape.gradient!.cy\"\n\t\t\t\t\t\t\t\t\t[attr.r]=\"shape.gradient!.r\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t@for (stop of shape.gradient!.stops; track $index) {\n\t\t\t\t\t\t\t\t\t\t<stop\n\t\t\t\t\t\t\t\t\t\t\t[attr.offset]=\"stop.offset\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.stop-color]=\"stop.color\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.stop-opacity]=\"stop.opacity ?? null\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t</radialGradient>\n\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t<linearGradient\n\t\t\t\t\t\t\t\t\t[attr.id]=\"shape.gradient!.id\"\n\t\t\t\t\t\t\t\t\t[attr.x1]=\"shape.gradient!.x1\"\n\t\t\t\t\t\t\t\t\t[attr.y1]=\"shape.gradient!.y1\"\n\t\t\t\t\t\t\t\t\t[attr.x2]=\"shape.gradient!.x2\"\n\t\t\t\t\t\t\t\t\t[attr.y2]=\"shape.gradient!.y2\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t@for (stop of shape.gradient!.stops; track $index) {\n\t\t\t\t\t\t\t\t\t\t<stop\n\t\t\t\t\t\t\t\t\t\t\t[attr.offset]=\"stop.offset\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.stop-color]=\"stop.color\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.stop-opacity]=\"stop.opacity ?? null\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t</linearGradient>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</defs>\n\t\t\t\t\t}\n\t\t\t\t\t@if (shape.kind === 'image') {\n\t\t\t\t\t\t<image\n\t\t\t\t\t\t\t[attr.x]=\"shape.x\"\n\t\t\t\t\t\t\t[attr.y]=\"shape.y\"\n\t\t\t\t\t\t\t[attr.width]=\"shape.width\"\n\t\t\t\t\t\t\t[attr.height]=\"shape.height\"\n\t\t\t\t\t\t\t[attr.href]=\"shape.imageUrl\"\n\t\t\t\t\t\t\tpreserveAspectRatio=\"xMidYMid meet\"\n\t\t\t\t\t\t\t[attr.transform]=\"shape.transform ?? null\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else if (shape.kind === 'ellipse') {\n\t\t\t\t\t\t<ellipse\n\t\t\t\t\t\t\t[attr.cx]=\"shape.cx\"\n\t\t\t\t\t\t\t[attr.cy]=\"shape.cy\"\n\t\t\t\t\t\t\t[attr.rx]=\"shape.width / 2\"\n\t\t\t\t\t\t\t[attr.ry]=\"shape.height / 2\"\n\t\t\t\t\t\t\t[attr.fill]=\"shape.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"shape.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"shape.strokeWidth\"\n\t\t\t\t\t\t\t[attr.transform]=\"shape.transform ?? null\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else if (shape.kind === 'polygon') {\n\t\t\t\t\t\t<polygon\n\t\t\t\t\t\t\t[attr.points]=\"shape.points\"\n\t\t\t\t\t\t\t[attr.fill]=\"shape.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"shape.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"shape.strokeWidth\"\n\t\t\t\t\t\t\t[attr.transform]=\"shape.transform ?? null\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<rect\n\t\t\t\t\t\t\t[attr.x]=\"shape.x\"\n\t\t\t\t\t\t\t[attr.y]=\"shape.y\"\n\t\t\t\t\t\t\t[attr.width]=\"shape.width\"\n\t\t\t\t\t\t\t[attr.height]=\"shape.height\"\n\t\t\t\t\t\t\t[attr.rx]=\"shape.rx\"\n\t\t\t\t\t\t\t[attr.fill]=\"shape.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"shape.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"shape.strokeWidth\"\n\t\t\t\t\t\t\t[attr.transform]=\"shape.transform ?? null\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t\t@if (shape.textLines.length > 0) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t[attr.x]=\"shape.textX\"\n\t\t\t\t\t\t\ttext-anchor=\"middle\"\n\t\t\t\t\t\t\tdominant-baseline=\"central\"\n\t\t\t\t\t\t\t[attr.fill]=\"shape.fontColor\"\n\t\t\t\t\t\t\t[attr.font-size]=\"shape.fontSize\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t@for (line of shape.textLines; track $index) {\n\t\t\t\t\t\t\t\t<tspan [attr.x]=\"shape.textX\" [attr.y]=\"line.y\">\n\t\t\t\t\t\t\t\t\t{{ line.text }}\n\t\t\t\t\t\t\t\t</tspan>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t</g>\n\t\t\t}\n\t\t</svg>\n\t} @else if (hasLayout()) {\n\t\t<svg\n\t\t\tclass=\"pptx-ng-smartart-svg\"\n\t\t\t[attr.data-testid]=\"'smartart-' + layout().family\"\n\t\t\t[attr.viewBox]=\"layout().viewBox\"\n\t\t\tpreserveAspectRatio=\"xMidYMid meet\"\n\t\t\t[attr.data-layout-family]=\"layout().family\"\n\t\t>\n\t\t\t@for (conn of layout().connectors; track conn.key; let ci = $index) {\n\t\t\t\t<path\n\t\t\t\t\t[attr.d]=\"layoutConnectors()[ci]!.d\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t[attr.stroke]=\"layoutConnectors()[ci]!.stroke\"\n\t\t\t\t\t[attr.stroke-width]=\"layoutConnectors()[ci]!.strokeWidth\"\n\t\t\t\t\t[attr.opacity]=\"layoutConnectors()[ci]!.opacity\"\n\t\t\t\t\t[attr.stroke-dasharray]=\"layoutConnectors()[ci]!.dash ?? null\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@for (node of layout().nodes; track node.key; let ni = $index) {\n\t\t\t\t<g\n\t\t\t\t\t[ngStyle]=\"shadowFilter() ? { filter: shadowFilter() } : {}\"\n\t\t\t\t\t[class.pptx-ng-smartart-node--editable]=\"canEditNodes()\"\n\t\t\t\t\t[attr.tabindex]=\"canEditNodes() ? 0 : null\"\n\t\t\t\t\t[attr.role]=\"canEditNodes() ? 'button' : 'img'\"\n\t\t\t\t\t[attr.aria-label]=\"nodeAriaLabel(ni) ?? node.text\"\n\t\t\t\t\t[attr.data-smartart-node-id]=\"nodeIdAt(ni)\"\n\t\t\t\t\t(dblclick)=\"onNodeDblClick($event, node, ni)\"\n\t\t\t\t\t(keydown)=\"onNodeKeydown($event, node, ni)\"\n\t\t\t\t>\n\t\t\t\t\t@if (nodeAriaLabel(ni); as title) {\n\t\t\t\t\t\t<title>{{ title }}</title>\n\t\t\t\t\t}\n\t\t\t\t\t@if (asCircle(node); as c) {\n\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t[attr.cx]=\"c.cx\"\n\t\t\t\t\t\t\t[attr.cy]=\"c.cy\"\n\t\t\t\t\t\t\t[attr.r]=\"c.r\"\n\t\t\t\t\t\t\t[attr.fill]=\"c.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"c.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"c.strokeWidth\"\n\t\t\t\t\t\t\t[attr.opacity]=\"c.opacity\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else if (asPolygon(node); as p) {\n\t\t\t\t\t\t<polygon\n\t\t\t\t\t\t\t[attr.points]=\"p.points\"\n\t\t\t\t\t\t\t[attr.fill]=\"p.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"p.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"p.strokeWidth\"\n\t\t\t\t\t\t\t[attr.opacity]=\"p.opacity\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else if (asRect(node); as r) {\n\t\t\t\t\t\t<rect\n\t\t\t\t\t\t\t[attr.x]=\"r.x\"\n\t\t\t\t\t\t\t[attr.y]=\"r.y\"\n\t\t\t\t\t\t\t[attr.width]=\"r.width\"\n\t\t\t\t\t\t\t[attr.height]=\"r.height\"\n\t\t\t\t\t\t\t[attr.rx]=\"r.rx\"\n\t\t\t\t\t\t\t[attr.fill]=\"r.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"r.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"r.strokeWidth\"\n\t\t\t\t\t\t\t[attr.opacity]=\"r.opacity\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t\t<!--\n\t\t\t\t\t\tNode label. Placement, colour, weight and baseline arrive\n\t\t\t\t\t\talready decided from the shared `smartArtNodeLabel`, so an\n\t\t\t\t\t\toff-centre caption (target leader, gear legend row, timeline\n\t\t\t\t\t\tcaption above / below the axis) lands beside its node.\n\t\t\t\t\t-->\n\t\t\t\t\t@if (layoutLabels()[ni]!.visible) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t[attr.x]=\"layoutLabels()[ni]!.x\"\n\t\t\t\t\t\t\t[attr.text-anchor]=\"layoutLabels()[ni]!.textAnchor\"\n\t\t\t\t\t\t\t[attr.dominant-baseline]=\"layoutLabels()[ni]!.dominantBaseline\"\n\t\t\t\t\t\t\t[attr.fill]=\"layoutLabels()[ni]!.fill\"\n\t\t\t\t\t\t\t[attr.font-size]=\"layoutLabels()[ni]!.fontSize\"\n\t\t\t\t\t\t\t[attr.font-weight]=\"layoutLabels()[ni]!.fontWeight ?? null\"\n\t\t\t\t\t\t\t[attr.font-style]=\"layoutLabels()[ni]!.fontStyle ?? null\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t@for (line of layoutLabels()[ni]!.lines; track $index) {\n\t\t\t\t\t\t\t\t<tspan [attr.x]=\"layoutLabels()[ni]!.x\" [attr.y]=\"line.y\">\n\t\t\t\t\t\t\t\t\t{{ line.text }}\n\t\t\t\t\t\t\t\t</tspan>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t</g>\n\t\t\t}\n\t\t</svg>\n\t} @else {\n\t\t<div class=\"pptx-ng-smartart-placeholder\">\n\t\t\t{{ 'pptx.smartArt.placeholder' | translate }}\n\t\t</div>\n\t}\n\n\t@if (canEditNodes() && hoveredNodeId() && !editState() && styleBarStyle()) {\n\t\t<div\n\t\t\t#styleBar\n\t\t\tclass=\"pptx-ng-smartart-style-bar\"\n\t\t\t[ngStyle]=\"styleBarStyle()!\"\n\t\t\t(mousedown)=\"$event.stopPropagation()\"\n\t\t\t(click)=\"$event.stopPropagation()\"\n\t\t>\n\t\t\t@for (color of palette().slice(0, 6); track color) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-smartart-swatch\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\t[attr.aria-label]=\"'pptx.smartArt.setFill' | translate: { color: color }\"\n\t\t\t\t\t[style.background]=\"color\"\n\t\t\t\t\t(click)=\"handleChangeNodeStyle(hoveredNodeId()!, color)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t</div>\n\t}\n\n\t<!--\n\t\tInline node-text editor. Positioned in element-local px (== viewBox\n\t\tunits, since the SVG viewBox matches the element pixel size and the\n\t\tsvg fills the chrome) over the double-clicked node. Commits via the\n\t\tshared EditorStateService.updateElement path on Enter / blur.\n\t-->\n\t@if (editState(); as edit) {\n\t\t<textarea\n\t\t\t#nodeEditor\n\t\t\tclass=\"pptx-ng-smartart-node-editor\"\n\t\t\t[style.left.px]=\"edit.box.x\"\n\t\t\t[style.top.px]=\"edit.box.y\"\n\t\t\t[style.width.px]=\"edit.box.width\"\n\t\t\t[style.height.px]=\"edit.box.height\"\n\t\t\t[value]=\"edit.text\"\n\t\t\t(pointerdown)=\"$event.stopPropagation()\"\n\t\t\t(mousedown)=\"$event.stopPropagation()\"\n\t\t\t(click)=\"$event.stopPropagation()\"\n\t\t\t(dblclick)=\"$event.stopPropagation()\"\n\t\t\t(blur)=\"commitEdit($event)\"\n\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t></textarea>\n\t}\n\n\t<!-- Polite live region: announces node-text edit commits to AT. -->\n\t<span class=\"pptx-ng-sr-only\" aria-live=\"polite\" role=\"status\">{{ liveMessage() }}</span>\n</div>\n", styles: [".pptx-ng-smartart-chrome{box-sizing:border-box;overflow:hidden;position:relative}.pptx-ng-smartart-svg{width:100%;height:100%;pointer-events:none}.pptx-ng-smartart-node--editable{pointer-events:auto;cursor:text}.pptx-ng-smartart-node--editable:hover{filter:drop-shadow(0 0 2px rgba(96,165,250,.8))}.pptx-ng-smartart-node-editor{position:absolute;box-sizing:border-box;margin:0;padding:1px 2px;border:1px solid var(--pptx-inspector-active, #0078d4);border-radius:2px;background:#fff;color:#111;font-size:11px;line-height:1.1;text-align:center;resize:none;overflow:hidden;z-index:2}.pptx-ng-smartart-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:11px;color:#fffc;pointer-events:none}.pptx-ng-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.pptx-ng-smartart-style-bar{position:absolute;pointer-events:auto;display:flex;gap:6px;padding:6px 8px;background:#ffffffe6;border:1px solid var(--border, #e2e8f0);border-radius:9999px;box-shadow:0 1px 2px #0000001a}.pptx-ng-smartart-swatch{width:20px;height:20px;border-radius:50%;border:1px solid rgba(0,0,0,.1);cursor:pointer;transition:transform .1s}.pptx-ng-smartart-swatch:hover{transform:scale(1.25)}\n"] }]
87227
+ args: [{ selector: 'pptx-smart-art-renderer', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgStyle, TranslatePipe], template: "<div\n\t#smartartContainer\n\tclass=\"pptx-ng-smartart-chrome\"\n\t[ngStyle]=\"chromeStyle()\"\n\t[attr.role]=\"a11y() ? a11y()!.role : null\"\n\t[attr.aria-label]=\"a11y()?.label ?? null\"\n\t(mousemove)=\"onMouseMove($event)\"\n\t(mouseleave)=\"onMouseLeave()\"\n>\n\t@if (isEmpty()) {\n\t\t<div class=\"pptx-ng-smartart-placeholder\">\n\t\t\t{{ 'pptx.smartArt.placeholder' | translate }}\n\t\t</div>\n\t} @else if (hasDrawingShapes()) {\n\t\t<svg\n\t\t\tclass=\"pptx-ng-smartart-svg\"\n\t\t\tdata-testid=\"smartart-drawing-shapes\"\n\t\t\t[attr.viewBox]=\"svgViewBox()\"\n\t\t\tpreserveAspectRatio=\"xMidYMid meet\"\n\t\t>\n\t\t\t@for (shape of renderedShapes(); track shape.key; let i = $index) {\n\t\t\t\t<g\n\t\t\t\t\t[ngStyle]=\"shadowFilter() ? { filter: shadowFilter() } : {}\"\n\t\t\t\t\t[attr.data-smartart-node-id]=\"drawingShapeNodeIds()[i] ?? null\"\n\t\t\t\t\t[class.pptx-ng-smartart-node--editable]=\"canEditNodes() && !!drawingShapeNodeIds()[i]\"\n\t\t\t\t>\n\t\t\t\t\t@if (shape.gradient) {\n\t\t\t\t\t\t<defs>\n\t\t\t\t\t\t\t@if (shape.gradient!.kind === 'radial') {\n\t\t\t\t\t\t\t\t<radialGradient\n\t\t\t\t\t\t\t\t\t[attr.id]=\"shape.gradient!.id\"\n\t\t\t\t\t\t\t\t\t[attr.cx]=\"shape.gradient!.cx\"\n\t\t\t\t\t\t\t\t\t[attr.cy]=\"shape.gradient!.cy\"\n\t\t\t\t\t\t\t\t\t[attr.r]=\"shape.gradient!.r\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t@for (stop of shape.gradient!.stops; track $index) {\n\t\t\t\t\t\t\t\t\t\t<stop\n\t\t\t\t\t\t\t\t\t\t\t[attr.offset]=\"stop.offset\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.stop-color]=\"stop.color\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.stop-opacity]=\"stop.opacity ?? null\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t</radialGradient>\n\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t<linearGradient\n\t\t\t\t\t\t\t\t\t[attr.id]=\"shape.gradient!.id\"\n\t\t\t\t\t\t\t\t\t[attr.x1]=\"shape.gradient!.x1\"\n\t\t\t\t\t\t\t\t\t[attr.y1]=\"shape.gradient!.y1\"\n\t\t\t\t\t\t\t\t\t[attr.x2]=\"shape.gradient!.x2\"\n\t\t\t\t\t\t\t\t\t[attr.y2]=\"shape.gradient!.y2\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t@for (stop of shape.gradient!.stops; track $index) {\n\t\t\t\t\t\t\t\t\t\t<stop\n\t\t\t\t\t\t\t\t\t\t\t[attr.offset]=\"stop.offset\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.stop-color]=\"stop.color\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.stop-opacity]=\"stop.opacity ?? null\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t</linearGradient>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</defs>\n\t\t\t\t\t}\n\t\t\t\t\t@if (shape.kind === 'image') {\n\t\t\t\t\t\t<image\n\t\t\t\t\t\t\t[attr.x]=\"shape.x\"\n\t\t\t\t\t\t\t[attr.y]=\"shape.y\"\n\t\t\t\t\t\t\t[attr.width]=\"shape.width\"\n\t\t\t\t\t\t\t[attr.height]=\"shape.height\"\n\t\t\t\t\t\t\t[attr.href]=\"shape.imageUrl\"\n\t\t\t\t\t\t\tpreserveAspectRatio=\"xMidYMid meet\"\n\t\t\t\t\t\t\t[attr.transform]=\"shape.transform ?? null\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else if (shape.kind === 'ellipse') {\n\t\t\t\t\t\t<ellipse\n\t\t\t\t\t\t\t[attr.cx]=\"shape.cx\"\n\t\t\t\t\t\t\t[attr.cy]=\"shape.cy\"\n\t\t\t\t\t\t\t[attr.rx]=\"shape.width / 2\"\n\t\t\t\t\t\t\t[attr.ry]=\"shape.height / 2\"\n\t\t\t\t\t\t\t[attr.fill]=\"shape.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"shape.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"shape.strokeWidth\"\n\t\t\t\t\t\t\t[attr.transform]=\"shape.transform ?? null\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else if (shape.kind === 'path') {\n\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t[attr.d]=\"shape.pathData\"\n\t\t\t\t\t\t\t[attr.fill]=\"shape.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"shape.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"shape.strokeWidth\"\n\t\t\t\t\t\t\t[attr.transform]=\"shape.pathTransform ?? null\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<rect\n\t\t\t\t\t\t\t[attr.x]=\"shape.x\"\n\t\t\t\t\t\t\t[attr.y]=\"shape.y\"\n\t\t\t\t\t\t\t[attr.width]=\"shape.width\"\n\t\t\t\t\t\t\t[attr.height]=\"shape.height\"\n\t\t\t\t\t\t\t[attr.rx]=\"shape.rx\"\n\t\t\t\t\t\t\t[attr.fill]=\"shape.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"shape.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"shape.strokeWidth\"\n\t\t\t\t\t\t\t[attr.transform]=\"shape.transform ?? null\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t\t@if (shape.textLines.length > 0) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t[attr.x]=\"shape.textX\"\n\t\t\t\t\t\t\ttext-anchor=\"middle\"\n\t\t\t\t\t\t\tdominant-baseline=\"central\"\n\t\t\t\t\t\t\t[attr.fill]=\"shape.fontColor\"\n\t\t\t\t\t\t\t[attr.font-family]=\"shape.fontFamily ?? null\"\n\t\t\t\t\t\t\t[attr.font-weight]=\"shape.fontWeight ?? null\"\n\t\t\t\t\t\t\t[attr.font-style]=\"shape.fontStyle ?? null\"\n\t\t\t\t\t\t\t[attr.font-size]=\"shape.fontSize\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t@for (line of shape.textLines; track $index) {\n\t\t\t\t\t\t\t\t<tspan [attr.x]=\"shape.textX\" [attr.y]=\"line.y\">\n\t\t\t\t\t\t\t\t\t{{ line.text }}\n\t\t\t\t\t\t\t\t</tspan>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t</g>\n\t\t\t}\n\t\t</svg>\n\t} @else if (hasLayout()) {\n\t\t<svg\n\t\t\tclass=\"pptx-ng-smartart-svg\"\n\t\t\t[attr.data-testid]=\"'smartart-' + layout().family\"\n\t\t\t[attr.viewBox]=\"layout().viewBox\"\n\t\t\tpreserveAspectRatio=\"xMidYMid meet\"\n\t\t\t[attr.data-layout-family]=\"layout().family\"\n\t\t>\n\t\t\t@for (conn of layout().connectors; track conn.key; let ci = $index) {\n\t\t\t\t<path\n\t\t\t\t\t[attr.d]=\"layoutConnectors()[ci]!.d\"\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t[attr.stroke]=\"layoutConnectors()[ci]!.stroke\"\n\t\t\t\t\t[attr.stroke-width]=\"layoutConnectors()[ci]!.strokeWidth\"\n\t\t\t\t\t[attr.opacity]=\"layoutConnectors()[ci]!.opacity\"\n\t\t\t\t\t[attr.stroke-dasharray]=\"layoutConnectors()[ci]!.dash ?? null\"\n\t\t\t\t/>\n\t\t\t}\n\t\t\t@for (node of layout().nodes; track node.key; let ni = $index) {\n\t\t\t\t<g\n\t\t\t\t\t[ngStyle]=\"shadowFilter() ? { filter: shadowFilter() } : {}\"\n\t\t\t\t\t[class.pptx-ng-smartart-node--editable]=\"canEditNodes()\"\n\t\t\t\t\t[attr.tabindex]=\"canEditNodes() ? 0 : null\"\n\t\t\t\t\t[attr.role]=\"canEditNodes() ? 'button' : 'img'\"\n\t\t\t\t\t[attr.aria-label]=\"nodeAriaLabel(ni) ?? node.text\"\n\t\t\t\t\t[attr.data-smartart-node-id]=\"nodeIdAt(ni)\"\n\t\t\t\t\t(dblclick)=\"onNodeDblClick($event, node, ni)\"\n\t\t\t\t\t(keydown)=\"onNodeKeydown($event, node, ni)\"\n\t\t\t\t>\n\t\t\t\t\t@if (nodeAriaLabel(ni); as title) {\n\t\t\t\t\t\t<title>{{ title }}</title>\n\t\t\t\t\t}\n\t\t\t\t\t@if (asCircle(node); as c) {\n\t\t\t\t\t\t<circle\n\t\t\t\t\t\t\t[attr.cx]=\"c.cx\"\n\t\t\t\t\t\t\t[attr.cy]=\"c.cy\"\n\t\t\t\t\t\t\t[attr.r]=\"c.r\"\n\t\t\t\t\t\t\t[attr.fill]=\"c.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"c.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"c.strokeWidth\"\n\t\t\t\t\t\t\t[attr.opacity]=\"c.opacity\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else if (asPolygon(node); as p) {\n\t\t\t\t\t\t<polygon\n\t\t\t\t\t\t\t[attr.points]=\"p.points\"\n\t\t\t\t\t\t\t[attr.fill]=\"p.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"p.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"p.strokeWidth\"\n\t\t\t\t\t\t\t[attr.opacity]=\"p.opacity\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t} @else if (asRect(node); as r) {\n\t\t\t\t\t\t<rect\n\t\t\t\t\t\t\t[attr.x]=\"r.x\"\n\t\t\t\t\t\t\t[attr.y]=\"r.y\"\n\t\t\t\t\t\t\t[attr.width]=\"r.width\"\n\t\t\t\t\t\t\t[attr.height]=\"r.height\"\n\t\t\t\t\t\t\t[attr.rx]=\"r.rx\"\n\t\t\t\t\t\t\t[attr.fill]=\"r.fill\"\n\t\t\t\t\t\t\t[attr.stroke]=\"r.stroke\"\n\t\t\t\t\t\t\t[attr.stroke-width]=\"r.strokeWidth\"\n\t\t\t\t\t\t\t[attr.opacity]=\"r.opacity\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t\t<!--\n\t\t\t\t\t\tNode label. Placement, colour, weight and baseline arrive\n\t\t\t\t\t\talready decided from the shared `smartArtNodeLabel`, so an\n\t\t\t\t\t\toff-centre caption (target leader, gear legend row, timeline\n\t\t\t\t\t\tcaption above / below the axis) lands beside its node.\n\t\t\t\t\t-->\n\t\t\t\t\t@if (layoutLabels()[ni]!.visible) {\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t[attr.x]=\"layoutLabels()[ni]!.x\"\n\t\t\t\t\t\t\t[attr.text-anchor]=\"layoutLabels()[ni]!.textAnchor\"\n\t\t\t\t\t\t\t[attr.dominant-baseline]=\"layoutLabels()[ni]!.dominantBaseline\"\n\t\t\t\t\t\t\t[attr.fill]=\"layoutLabels()[ni]!.fill\"\n\t\t\t\t\t\t\t[attr.font-size]=\"layoutLabels()[ni]!.fontSize\"\n\t\t\t\t\t\t\t[attr.font-weight]=\"layoutLabels()[ni]!.fontWeight ?? null\"\n\t\t\t\t\t\t\t[attr.font-style]=\"layoutLabels()[ni]!.fontStyle ?? null\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t@for (line of layoutLabels()[ni]!.lines; track $index) {\n\t\t\t\t\t\t\t\t<tspan [attr.x]=\"layoutLabels()[ni]!.x\" [attr.y]=\"line.y\">\n\t\t\t\t\t\t\t\t\t{{ line.text }}\n\t\t\t\t\t\t\t\t</tspan>\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</text>\n\t\t\t\t\t}\n\t\t\t\t</g>\n\t\t\t}\n\t\t</svg>\n\t} @else {\n\t\t<div class=\"pptx-ng-smartart-placeholder\">\n\t\t\t{{ 'pptx.smartArt.placeholder' | translate }}\n\t\t</div>\n\t}\n\n\t@if (canEditNodes() && hoveredNodeId() && !editState() && styleBarStyle()) {\n\t\t<div\n\t\t\t#styleBar\n\t\t\tclass=\"pptx-ng-smartart-style-bar\"\n\t\t\t[ngStyle]=\"styleBarStyle()!\"\n\t\t\t(mousedown)=\"$event.stopPropagation()\"\n\t\t\t(click)=\"$event.stopPropagation()\"\n\t\t>\n\t\t\t@for (color of palette().slice(0, 6); track color) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"pptx-ng-smartart-swatch\"\n\t\t\t\t\tdata-pptx-compact\n\t\t\t\t\t[attr.aria-label]=\"'pptx.smartArt.setFill' | translate: { color: color }\"\n\t\t\t\t\t[style.background]=\"color\"\n\t\t\t\t\t(click)=\"handleChangeNodeStyle(hoveredNodeId()!, color)\"\n\t\t\t\t></button>\n\t\t\t}\n\t\t</div>\n\t}\n\n\t<!--\n\t\tInline node-text editor. Positioned in element-local px (== viewBox\n\t\tunits, since the SVG viewBox matches the element pixel size and the\n\t\tsvg fills the chrome) over the double-clicked node. Commits via the\n\t\tshared EditorStateService.updateElement path on Enter / blur.\n\t-->\n\t@if (editState(); as edit) {\n\t\t<textarea\n\t\t\t#nodeEditor\n\t\t\tclass=\"pptx-ng-smartart-node-editor\"\n\t\t\t[style.left.px]=\"edit.box.x\"\n\t\t\t[style.top.px]=\"edit.box.y\"\n\t\t\t[style.width.px]=\"edit.box.width\"\n\t\t\t[style.height.px]=\"edit.box.height\"\n\t\t\t[value]=\"edit.text\"\n\t\t\t(pointerdown)=\"$event.stopPropagation()\"\n\t\t\t(mousedown)=\"$event.stopPropagation()\"\n\t\t\t(click)=\"$event.stopPropagation()\"\n\t\t\t(dblclick)=\"$event.stopPropagation()\"\n\t\t\t(blur)=\"commitEdit($event)\"\n\t\t\t(keydown)=\"onEditorKeydown($event)\"\n\t\t></textarea>\n\t}\n\n\t<!-- Polite live region: announces node-text edit commits to AT. -->\n\t<span class=\"pptx-ng-sr-only\" aria-live=\"polite\" role=\"status\">{{ liveMessage() }}</span>\n</div>\n", styles: [".pptx-ng-smartart-chrome{box-sizing:border-box;overflow:hidden;position:relative}.pptx-ng-smartart-svg{width:100%;height:100%;pointer-events:none}.pptx-ng-smartart-node--editable{pointer-events:auto;cursor:text}.pptx-ng-smartart-node--editable:hover{filter:drop-shadow(0 0 2px rgba(96,165,250,.8))}.pptx-ng-smartart-node-editor{position:absolute;box-sizing:border-box;margin:0;padding:1px 2px;border:1px solid var(--pptx-inspector-active, #0078d4);border-radius:2px;background:#fff;color:#111;font-size:11px;line-height:1.1;text-align:center;resize:none;overflow:hidden;z-index:2}.pptx-ng-smartart-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:11px;color:#fffc;pointer-events:none}.pptx-ng-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.pptx-ng-smartart-style-bar{position:absolute;pointer-events:auto;display:flex;gap:6px;padding:6px 8px;background:#ffffffe6;border:1px solid var(--border, #e2e8f0);border-radius:9999px;box-shadow:0 1px 2px #0000001a}.pptx-ng-smartart-swatch{width:20px;height:20px;border-radius:50%;border:1px solid rgba(0,0,0,.1);cursor:pointer;transition:transform .1s}.pptx-ng-smartart-swatch:hover{transform:scale(1.25)}\n"] }]
86416
87228
  }], ctorParameters: () => [], propDecorators: { element: [{ type: i0.Input, args: [{ isSignal: true, alias: "element", required: true }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], animationState: [{ type: i0.Input, args: [{ isSignal: true, alias: "animationState", required: false }] }], nodeEditor: [{ type: i0.ViewChild, args: ['nodeEditor', { isSignal: true }] }], smartartContainer: [{ type: i0.ViewChild, args: ['smartartContainer', { isSignal: true }] }], styleBar: [{ type: i0.ViewChild, args: ['styleBar', { isSignal: true }] }] } });
86417
87229
 
86418
87230
  /**
@@ -113357,7 +114169,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
113357
114169
  }], propDecorators: { canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], slideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideIndex", required: false }] }], selectedElement: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedElement", required: false }] }] } });
113358
114170
 
113359
114171
  // Generated by scripts/inline-shared.mjs from package.json. Do not edit.
113360
- const PPTX_ANGULAR_VIEWER_VERSION = "3.2.0";
114172
+ const PPTX_ANGULAR_VIEWER_VERSION = "3.2.1";
113361
114173
 
113362
114174
  /**
113363
114175
  * account-page.component.ts: File > Account content.
@@ -147479,4 +148291,4 @@ function cn(...values) {
147479
148291
  */
147480
148292
 
147481
148293
  export { ColorChangedImageComponent as $, AFTER_ANIMATION_VALUES as A, AnimationPanelComponent as B, AnimationPlaybackService as C, AutosaveRecoveryDialogComponent as D, AutosaveService as E, BroadcastDialogComponent as F, CHART_EDITOR_STYLES as G, CURSOR_PALETTE as H, CanvasFitService as I, ChartAxisOptionsComponent as J, ChartAxisStyleOptionsComponent as K, ChartComboTypeOptionsComponent as L, ChartDataEditorComponent as M, ChartDataLabelOptionsComponent as N, ChartDatapointMarkerOptionsComponent as O, ChartDatapointOptionsComponent as P, ChartDisplayOptionsComponent as Q, ChartElementViewComponent as R, ChartErrorBarOptionsComponent as S, ChartMarkerOptionsComponent as T, ChartPartSelectionService as U, ChartPrimitivesComponent as V, ChartRendererComponent as W, ChartTrendlineOptionsComponent as X, ChartTypeSelectorComponent as Y, CollaborationCursorsComponent as Z, CollaborationService as _, ALIGN_OPTIONS as a, KeepAnnotationsDialogComponent as a$, CommentMarkersOverlayComponent as a0, CommentsPanelComponent as a1, CommentsService as a2, ComparePanelComponent as a3, ConnectorRendererComponent as a4, ConnectorTextOverlayComponent as a5, CustomShowsComponent as a6, DEFAULT_BOUNDS as a7, DEFAULT_BROADCAST_SERVER_URL as a8, DEFAULT_CANVAS_HEIGHT as a9, ElementRendererComponent as aA, EmbeddedFontsService as aB, EncryptedFileDialogComponent as aC, EquationEditorDialogComponent as aD, EquationRendererComponent as aE, EquationTemplateGalleryComponent as aF, ExportProgressModalComponent as aG, ExportService as aH, FieldContextService as aI, FindBarComponent as aJ, FindReplaceBarComponent as aK, FollowModeBarComponent as aL, FontEmbeddingListComponent as aM, FontEmbeddingPanelComponent as aN, GALLERY_THEME_PRESETS as aO, GRIDLINE_COLOR$1 as aP, GradientPickerComponent as aQ, HANDOUT_OPTIONS as aR, HeaderFooterDialogComponent as aS, HyperlinkDialogComponent as aT, ImagePropertiesPanelComponent as aU, InkDrawingService as aV, InkRendererComponent as aW, InsertSmartArtDialogComponent as aX, InspectorPaneHeaderComponent as aY, InspectorPanelComponent as aZ, IsMobileService as a_, DEFAULT_CANVAS_WIDTH as aa, DEFAULT_COLOR_SCHEME as ab, DEFAULT_FILL_COLOR$1 as ac, DEFAULT_LAYOUT as ad, DEFAULT_PALETTE$1 as ae, DEFAULT_PATTERN_FILL_PRESET as af, DEFAULT_PRINT_SETTINGS as ag, DEFAULT_SLIDE_BACKGROUND as ah, DEFAULT_STROKE_COLOR as ai, DEFAULT_STYLE as aj, DEFAULT_TABLE_ROW_HEIGHT as ak, DEFAULT_TEXT_COLOR$2 as al, DEFAULT_VIEWER_PROFILE as am, DIRECTIONAL_PRESETS as an, DIRECTION_OPTIONS as ao, DocumentPropertiesCardComponent as ap, EMBEDDED_FONTS_STYLE_ID as aq, EMPHASIS_PRESETS as ar, ENTRANCE_PRESETS as as, TEMPLATES as at, EXIT_PRESETS as au, EditorContextMenuComponent as av, EditorHistory as aw, EditorStateService as ax, EditorToolbarComponent as ay, EffectsPanelComponent as az, ANIMATION_PRESET_CATEGORIES as b, RibbonDrawSectionComponent as b$, LOCALE_CATALOG as b0, LONG_PRESS_DURATION_MS as b1, LONG_PRESS_MOVE_TOLERANCE_PX as b2, LoadContentService as b3, LocalPresencePublisher as b4, MAX_ZOOM_SCALE as b5, MIN_ZOOM_SCALE as b6, MOTION_PATH_COLUMNS as b7, MediaPreviewComponent as b8, MediaPropertiesPanelComponent as b9, PresentToolbarAutoHide as bA, PresentationAnnotationOverlayComponent as bB, PresentationAnnotationsService as bC, PresentationOverlayComponent as bD, PresentationPropertiesPanelComponent as bE, PresentationSettingsCardComponent as bF, PresentationSubtitleBarComponent as bG, PresentationToolbarComponent as bH, PresentationTransitionOverlayComponent as bI, PresenterViewComponent as bJ, PresenterWindowService as bK, PrintDialogComponent as bL, PrintService as bM, PrintSettingsPanelComponent as bN, PropertiesDialogComponent as bO, REPEAT_MODE_OPTIONS as bP, RESIZE_HANDLES as bQ, RULER_FONT_SIZE as bR, RULER_THICKNESS as bS, ReadingViewOverlayComponent as bT, RemoteSelectionOverlayComponent as bU, RibbonAnimationGalleryComponent as bV, RibbonAnimationsSectionComponent as bW, RibbonArrangeSectionComponent as bX, RibbonColorPopoverComponent as bY, RibbonComponent as bZ, RibbonDesignSectionComponent as b_, MediaRendererComponent as ba, MediaTrimTimelineComponent as bb, MobileBottomBarComponent as bc, MobileMenuSheetComponent as bd, MobilePresenterViewComponent as be, MobileSheetComponent as bf, MobileSlidesSheetComponent as bg, MobileToolbarComponent as bh, ModalDialogComponent as bi, Model3DRendererComponent as bj, NotesHandoutCardComponent as bk, NotesPanelComponent as bl, NotesToolbarComponent as bm, OleRendererComponent as bn, OutlineViewOverlayComponent as bo, POWER_POINT_VIEWER_PROVIDERS as bp, PPTX_OPEN_ACCEPT as bq, PRESENTATION_OPEN_EXTENSIONS as br, PRESENTER_CHANNEL_NAME as bs, PRESENTER_MSG_ORIGIN as bt, PRESENTER_TIMER_SEGMENT_MS as bu, PX_PER_CM as bv, PX_PER_INCH as bw, PasswordProtectionDialogComponent as bx, PasswordStrengthMeterComponent as by, PowerPointViewerComponent as bz, AUDIENCE_HASH as c, THEME_CATALOG as c$, RibbonDrawingGroupComponent as c0, RibbonEditingSectionComponent as c1, RibbonFileSectionComponent as c2, RibbonFontControlsComponent as c3, RibbonHomeSectionComponent as c4, RibbonHyperlinkButtonComponent as c5, RibbonInsertFieldsComponent as c6, RibbonInsertSectionComponent as c7, RibbonMotionPathGalleryComponent as c8, RibbonParagraphControlsComponent as c9, ShortcutPanelComponent as cA, ShowOptionsFieldsetComponent as cB, ShowSlidesFieldsetComponent as cC, SignatureStrippedDialogComponent as cD, SignaturesPanelComponent as cE, SignaturesService as cF, SlideBackgroundCardComponent as cG, SlideCanvasComponent as cH, SlideDefaultInspectorComponent as cI, SlideDiffChangesComponent as cJ, SlideDiffRowComponent as cK, SlideDiffThumbnailsComponent as cL, SlideSizeCardComponent as cM, SlideSorterOverlayComponent as cN, SlideThemeOverridePanelComponent as cO, SlideTransitionCardComponent as cP, SlidesPanelComponent as cQ, SmartArt3DRendererComponent as cR, SmartArt3DService as cS, SmartArtPreviewComponent as cT, SmartArtPropertiesComponent as cU, SmartArtRendererComponent as cV, StatusBarComponent as cW, TABLE_STRUCTURE_TOGGLES as cX, TEXT_3D_BOTTOM_BEVEL_KEYS as cY, TEXT_3D_TOP_BEVEL_KEYS as cZ, TEXT_DIRECTION_OPTIONS$1 as c_, RibbonPrimaryRowComponent as ca, RibbonReviewSectionComponent as cb, RibbonShapeExtrasComponent as cc, RibbonSlideshowSectionComponent as cd, RibbonTransitionsSectionComponent as ce, RibbonViewSectionComponent as cf, RulerGuidesService as cg, SEQUENCE_OPTIONS as ch, SEVERITY_GROUPS as ci, SEVERITY_LABELS as cj, SHORTCUT_REFERENCE_ITEMS as ck, SLIDE_TRANSITION_KEYFRAMES as cl, DEFAULT_PALETTE as cm, PALETTES$1 as cn, SMART_ART_COLOR_SCHEMES as co, SMART_ART_STYLE_OPTIONS as cp, SUB_ITEM_LABEL as cq, SVG_WARP_PRESETS as cr, SWIPE_MAX_VERTICAL_PX as cs, SWIPE_THRESHOLD_PX as ct, SelectionPaneComponent as cu, SetUpSlideShowDialogComponent as cv, SettingsAppearanceTabComponent as cw, SettingsDialogComponent as cx, SettingsLanguageTabComponent as cy, ShareDialogComponent as cz, AUDIENCE_NONCE_KEY as d, applyAcceptedDiff as d$, TIMING_CURVE_OPTIONS as d0, TRIGGER_OPTIONS as d1, TYPE_LABELS as d2, TableCellAdvancedFillComponent as d3, TableCellFormattingComponent as d4, TableDataEditorComponent as d5, TablePropertiesComponent as d6, TableRendererComponent as d7, TableResizeOverlayComponent as d8, TableSelectionService as d9, ViewerFormatPainterService as dA, ViewerInspectorPanelService as dB, ViewerKeyboardService as dC, ViewerMobileSheetService as dD, ViewerPresentationModeService as dE, ViewerThemeGalleryService as dF, ViewerTouchGesturesService as dG, ViewerZoomService as dH, WEBM_MIME_CANDIDATES as dI, WriteBackScheduler as dJ, ZERO_LINE_COLOR as dK, ZoomNavigationService as dL, ZoomRendererComponent as dM, ZoomTargetService as dN, addCategory as dO, addCommentToList as dP, addGradientStopPatch as dQ, addItem as dR, addSeries as dS, addSubItem as dT, advanceStep as dU, affordanceElements as dV, aiToggleVisible as dW, alignPatch as dX, animationFor as dY, animationPresetLabelKey as dZ, annotationMapToInkInserts as d_, TagsCardComponent as da, Text3DBevelSectionComponent as db, Text3DPanelComponent as dc, TextAdvancedPanelComponent as dd, ThemeEditorFieldsComponent as de, ThemeGalleryComponent as df, ThemeSelectorCardComponent as dg, TitleBarComponent as dh, TitleBarSearchComponent as di, TransitionDirectionPickerComponent as dj, TransitionPreviewComponent as dk, VALIGN_OPTIONS as dl, VIEWER_THEME as dm, VersionHistoryPanelComponent as dn, ViewerCanvasEditingService as dp, ViewerCollabCursorService as dq, ViewerCollaborationSessionService as dr, ViewerCompareService as ds, ViewerCustomShowsService as dt, ViewerDialogsService as du, ViewerDocumentPropertiesService as dv, ViewerExportService as dw, ViewerExtraDialogsComponent as dx, ViewerFileIOService as dy, ViewerFindReplaceService as dz, AVATAR_COLOR_SWATCHES as e, buildZoomContainerStyle as e$, applyAnimationPreset as e0, applyFindReplacements as e1, applyFormatToElement as e2, applyMove as e3, applyResize as e4, asMediaElement as e5, assignUserColor as e6, attachShowVisibilityPause as e7, attachTouchGestures as e8, axisTickValues as e9, buildFontFaceRule as eA, buildGradientFillCss as eB, buildGridlinesAndLabels as eC, buildHyperlinkPatch as eD, buildInkContainerStyle as eE, buildInkStrokes as eF, buildLegend as eG, buildMarkTooltip as eH, buildModel3DContainerStyle as eI, buildModel3DViewModel as eJ, buildOleActionModel as eK, buildOleInfoRows as eL, buildPatternFillCss as eM, buildPrintHtmlDocument as eN, buildPropertiesPatch as eO, buildRegionMapViewModel as eP, buildSaveSlides as eQ, buildShareUrl as eR, buildSmartArtInsertElement as eS, buildSmartArtNodes as eT, buildStockViewModel as eU, buildSurfaceViewModel as eV, buildTableViewModel as eW, buildTreemapViewModel as eX, buildTrimFragment as eY, buildWaterfallViewModel as eZ, buildZeroLine as e_, beginNodeEdit as ea, bevelSizePatch as eb, boolFromEvent as ec, bringForward as ed, bringToFront as ee, buildBarActions as ef, buildBroadcastConfig as eg, buildBroadcastViewerUrl as eh, buildCategoryLabels as ei, buildCellParagraphs as ej, buildChartViewModel as ek, buildChatLogExport as el, buildChatLogMarkdown as em, buildChromeStyle as en, buildClearHyperlinkPatch as eo, buildClickGroups as ep, buildColStyles as eq, buildCollaborationConfig as er, buildComboViewModel as es, buildCssGradientFromShapeStyle as et, buildDuotoneFilter as eu, buildDuotoneFilterId as ev, buildEmbeddedFontStyles as ew, buildEquationElement as ex, buildEquationSegment as ey, buildFallbackViewModel as ez, AXIS_LABEL_COLOR as f, computeSingleSelected as f$, buildZoomViewModel as f0, bulletIndentPx as f1, canAddTopLevelNode as f2, canGroupSelection as f3, canRemoveTopLevelNode as f4, canSetStrokeWidth as f5, canStartBroadcast as f6, canStartShare as f7, canUngroupSelection as f8, canUseClipboard as f9, computeAxisTitlePrimitives as fA, computeBarRects as fB, computeBubbleRadius as fC, computeCornerHandle as fD, computeDistribute as fE, computeDrawingViewBox as fF, computeErrorBarPrimitives as fG, computeFocusTargets as fH, computeGridSpacingPx as fI, computeHandleBoxes as fJ, computeHandoutLayout as fK, computeIsMobile as fL, computeIsTablet as fM, computeLinePoints as fN, computeLinearRegression as fO, computePageCount as fP, computePieLayout as fQ, computePieSlicePath as fR, computePieSlices as fS, computePlotLayout as fT, computeRSquared as fU, computeRadarPoints as fV, computeResizeHandleBoxes as fW, computeRotateHandleBox as fX, computeScatterDots as fY, computeScatterXDomain as fZ, computeSelectionBoxes as f_, captionDisplayText as fa, cellRunStyle as fb, cellStyleToStyleMap as fc, cellTdStyle as fd, changeCountLabel as fe, changeIcon as ff, characterSpacingPatch as fg, chartPreserveAspectRatio as fh, checkFontAvailable as fi, clampCursorPosition as fj, clampGifDimensions as fk, clampIndex as fl, clampNotesFontSize as fm, clampScale as fn, clampStep as fo, clearAllLocalViewerData as fp, clearAudienceContent as fq, cn as fr, collectAccessibilityIssues as fs, collectElementText as ft, collectSlideText as fu, collectStoredChats as fv, collectUsedFontFamilies as fw, columnWidthStyle as fx, commitNodeText as fy, computeAlign as fz, AccessibilityPanelComponent as g, formatFileSize as g$, computeSlideIndices as g0, computeSnap as g1, computeStackedBarRects as g2, computeStackedValueRange as g3, computeTrendlinePrimitives as g4, computeValueRange as g5, convertOmmlToMathMl as g6, copyFormatFromElement as g7, countAccessibilityIssues as g8, countAnnotationStrokes as g9, enableGlowPatch as gA, enableInnerShadowPatch as gB, enableOuterShadowPatch as gC, enableReflectionPatch as gD, enableSoftEdgePatch as gE, encodeGif as gF, endShowMediaCleanup as gG, estimatePageCount as gH, exitPresentationFullscreen as gI, exportAiChatLogs as gJ, extractPathPoints as gK, eyedropperAvailable as gL, fillColorOf$1 as gM, findInSlides as gN, findOwningSlideIndex as gO, findSlideIndexByElementId as gP, firstVisibleIndex as gQ, fitPolynomial as gR, fitZoom as gS, focusTargetChips as gT, fontMimeForFormat as gU, fontSizeOf as gV, forgetSessionDeck as gW, formatAxisValue as gX, formatBytes as gY, formatCursorLabel as gZ, formatElapsed as g_, createAngularAiBridge as ga, createCustomShow as gb, createSwipeDismissDrag as gc, createWebrtcBundle as gd, createWebsocketBundle as ge, cssObjectToStyleMap as gf, currentColorScheme as gg, currentLayout as gh, currentStyle as gi, defaultCssVars as gj, defaultRadius as gk, defaultThemeColors as gl, deleteElementsByIds as gm, deleteVersion as gn, demoteNode as go, deriveModel3DBlobUrl as gp, derivePresenceList as gq, describeSmartArtBounds as gr, disableGlowPatch as gs, disableInnerShadowPatch as gt, disableOuterShadowPatch as gu, disableReflectionPatch as gv, disableSoftEdgePatch as gw, duplicateElementById as gx, durationOf as gy, effectsStateOf as gz, AccessibilityService as h, isPpactionUrl as h$, formatPropertyDate as h0, formatTime as h1, fpsToFrameIntervalMs as h2, generateBroadcastRoomId as h3, generateCommentId as h4, generateCustomShowId as h5, generatePressureCircles as h6, generateTicks as h7, getClrChangeParams as h8, getContainerStyle as h9, getWarpPath as hA, gradientStateFromStyle as hB, gradientStateOf as hC, gradientStatePatch as hD, gridColumns as hE, groupIssuesBySeverity as hF, hasAnimation as hG, hasCopyableFormat as hH, hasExistingLink as hI, hasExitedFullscreen as hJ, hasGradientFill as hK, hasPressureVariation as hL, hasVisibleSlideAfter as hM, headerLabel as hN, imageDimensions as hO, inkViewBox as hP, insertTableElementColumn as hQ, insertTableElementRow as hR, interpolateWidth as hS, isAudienceTab as hT, isBold as hU, isBrowserOpenableMime as hV, isChildNode as hW, isElementInteractive as hX, isInjectableUrl as hY, isItalic as hZ, isLegacyBinaryPresentation as h_, getDuotoneFilterDef as ha, getEffectSoundState as hb, getImageSrc as hc, getLocalStorageUsageSummary as hd, getOleAriaLabel as he, getOleBadgeLabel as hf, getOleDisplayName as hg, getOleDownloadFileName as hh, getOleTypeColor as hi, getOleTypeLabel as hj, getPasswordStrength as hk, getPatternSvg as hl, getPlaceholderStyle as hm, getVersions as hn, getResolvedShapeClipPath as ho, getResolvedShapeClipPathFor as hp, getSessionTabId as hq, getShapeFillStrokeStyle as hr, getSlideBackgroundStyle as hs, getSlideTransitionAnimations as ht, getSmartArtNodeBounds as hu, getSpeechRecognitionCtor as hv, getTextBlockStyle as hw, getTextWarp as hx, getTouchDistance as hy, getWarpCategory as hz, AccountPageComponent as i, patchChartData as i$, isPresenterMessage as i0, isSigned as i1, isSupportedPresentationFile as i2, isTextElement as i3, isTwoTableFocus as i4, isUnderline as i5, isUrlSafe as i6, isValidRoomId as i7, isViewportBackgroundPressTarget as i8, isZoomActivationKey as i9, newChartElement as iA, newEquationElement as iB, newPresetShapeElement as iC, newShapeElement as iD, newSmartArtElement as iE, newTableElement as iF, newTextElement as iG, nextVisibleIndex as iH, nodeBold as iI, nodeEditBox as iJ, nodeFillColor as iK, nodeFontColor as iL, nodeIdFromKey as iM, nodeItalic as iN, nodeStyle as iO, normalizeFontFormat as iP, normalizeSlidesPerPage as iQ, normalizeValue as iR, numFromEvent as iS, ommlToMathml as iT, ooxmlDashToCssBorderStyle as iU, openNativeEyeDropper as iV, overallStatus as iW, paletteColor as iX, parseAudienceNonce as iY, parseNodeTextarea as iZ, partitionSlides as i_, issueTrackKey as ia, issueTypeLabel as ib, keyToLabel as ic, lastVisibleIndex as id, latexToMathml as ie, layoutConnectorPaints as ig, layoutNodeLabels as ih, linePointsToSvgString as ii, lineSpacingPatch as ij, loadAudienceContent as ik, loadSessionDeck as il, mediaFallbackFor as im, mediaSurfaceFor as io, mergeCaptionResults as ip, mergeDown as iq, mergeRight as ir, mergeSelection as is, moveElementBy as it, moveNodeDown as iu, moveNodeUp as iv, msToFrameDelayCs as iw, narrowToCircle as ix, narrowToPolygon as iy, narrowToRect as iz, ActionSettingsPanelComponent as j, rulerDragToGuidePosition as j$, patchChartStyle as j0, patchTableData as j1, patchTextStyle as j2, patternPresetOptions as j3, pendingElementStyles as j4, pickColorByClickFallback as j5, pickFile as j6, pickSupportedMimeType as j7, planGifFrames as j8, planVideoSegments as j9, removeSeries as jA, renderToCanvas as jB, reorderAnimationDown as jC, reorderAnimationUp as jD, replaceInSlides as jE, replaceMatch as jF, requestPresentationFullscreen as jG, resizeElement as jH, resolveCaptionTracks as jI, resolveChartKind as jJ, resolveFontVariant as jK, resolveHyperlinkHref as jL, resolveInteractiveElementId as jM, resolveMediaSrc as jN, resolveOleType as jO, resolveParagraphBullet as jP, resolvePresenterNotes as jQ, resolveProfileInitial as jR, resolveRegionCode as jS, resolveSlideAutoAdvanceMs as jT, resolvePalette as jU, resolveThemeCatalogEntry as jV, resolveTransitionDuration as jW, restoreSessionDeck as jX, revealedElementStyles as jY, routeOrthogonalConnector as jZ, rowStyle as j_, pointsToSvgPathD as ja, presenceToCursors as jb, presentationBaseName as jc, presentationStageStyle as jd, presenterTimerProgress as je, presetByLayout as jf, presetsForCategory as jg, pressuresToWidths as jh, prevVisibleIndex as ji, projectDrawingShapes as jj, promoteNode as jk, provideViewerTheme as jl, radarAngle as jm, radarRingPoints as jn, readAsDataUrl as jo, recordWebm as jp, registerCrossSlideAudio as jq, rememberSessionDeck as jr, removeAnimation as js, removeCategory as jt, removeTableElementColumn as ju, removeCommentFromList as jv, removeElementAnimation as jw, removeGradientStopPatch as jx, removeNode as jy, removeTableElementRow as jz, AdvancedChartEditorComponent as k, sheetAfterNavigate as k$, rulerHighlight as k0, rulerStripTicks as k1, sampleColorFromSlide as k2, sanitizeColor as k3, sanitizeSlideIndex as k4, sanitizeUserName as k5, saveViewerProfile as k6, savedPresentationFileName as k7, scanAvailableFonts as k8, searchSlides as k9, setDataPointMarker as kA, setDelay as kB, setDirection as kC, setDuration as kD, setEffectSound as kE, setElementPosition as kF, setGridlineStyle as kG, setLayout as kH, setLegend as kI, setNodeStyle as kJ, setNodeText as kK, setRepeatCount as kL, setRepeatMode as kM, setSequence as kN, setSeriesChartType as kO, setSeriesColor as kP, setSeriesErrorBars as kQ, setSeriesMarker as kR, setSeriesName as kS, setSeriesTrendline as kT, setSeriesValue as kU, setStyle as kV, setTimingCurve as kW, setTitle as kX, setTrigger as kY, setTriggerShapeId as kZ, shapeStylePatch$1 as k_, seedBroadcastFields as ka, seedHyperlinkDraft as kb, seedPropertiesDraft as kc, seedShareFields as kd, segmentFrameCount as ke, selectValue$2 as kf, sendBackward as kg, sendToBack as kh, sequentialColorScale as ki, serializeWriteBack as kj, seriesColor as kk, setAfterAnimation as kl, setAfterAnimationColor as km, setAnimationEmphasis as kn, setAnimationEntrance as ko, setAnimationExit as kp, setAxis as kq, setAxisLogScale as kr, setAxisTitleStyle as ks, setCategoryLabel as kt, setCellText as ku, setColorScheme as kv, setDataLabels as kw, setDataPointExplosion as kx, setDataPointFill as ky, setDataPointLabel as kz, AiChangeOverlayComponent as l, shouldBlockClickAdvance as l0, shouldUseSvgWarp as l1, showDirectionPicker as l2, showsTemplateAffordance as l3, signatureCountLabel as l4, signatureKey as l5, signatureTimestamp as l6, signerName as l7, statusLabel as l8, slideNumberOf as l9, toggleCommentResolvedInList as lA, toggleNodeBold as lB, toggleNodeItalic as lC, toggleSheet as lD, topLevelNodeCount as lE, transformSelectedTextCase as lF, translationsEn as lG, updateElementById as lH, updateGlowPatch as lI, updateGradientStopPatch as lJ, updateInnerShadowPatch as lK, updateOuterShadowPatch as lL, updateReflectionPatch as lM, vAlignPatch as lN, validatePassword as lO, validatePrintSettings as lP, validateRoomId as lQ, valueToY as lR, vermilionDarkColors as lS, vermilionDarkTheme as lT, vermilionLightColors as lU, vermilionLightTheme as lV, vermilionRadius as lW, waypointsToPathD as lX, worstStatus as lY, zoomTargetSlideIndex as lZ, slidesWithReappliedLayout as la, smartArtNodes as lb, paletteColour as lc, snapToGridStep as ld, splitCursorCell as le, splitMergedCell as lf, statusKind as lg, statusLabel$1 as lh, storeAudienceContent as li, stringFromEvent$5 as lj, strokeColorOf as lk, strokeToInkElement as ll, strokeWidthOf as lm, styleShadowFilter as ln, surfaceColor as lo, textAdvancedPatch as lp, textAdvancedStateFromStyle as lq, textAdvancedStateOf as lr, textColorOf as ls, textDirectionPatch as lt, textStyleOf as lu, textStylePatch as lv, themeStyle as lw, themeToCssVars as lx, thumbnailHeight as ly, thumbnailZoom as lz, AiChatPanelComponent as m, AiChatService as n, AiComposerComponent as o, AiFocusBarComponent as p, AiFocusHighlightOverlayComponent as q, AiHistoryMenuComponent as r, AiHistoryService as s, toChatSummary as t, AiMessageListComponent as u, AiPanelStore as v, AiProposalCardComponent as w, AiSettingsSectionComponent as x, AiToolCallCardComponent as y, AnimationAuthorPanelComponent as z };
147482
- //# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-CY6xB_ld.mjs.map
148294
+ //# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-CblBWEKg.mjs.map