pptx-angular-viewer 1.31.3 → 1.31.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,8 @@ All notable changes to this project are documented here.
4
4
  This file is generated from [Conventional Commits](https://www.conventionalcommits.org)
5
5
  by [git-cliff](https://git-cliff.org); do not edit it by hand.
6
6
 
7
+ ## [1.31.3](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.31.3) - 2026-07-19
8
+
7
9
  ## [1.31.2](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.31.2) - 2026-07-19
8
10
 
9
11
  ## [1.31.1](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.31.1) - 2026-07-18
@@ -6333,6 +6333,16 @@ function resolveDataPointFill(series, pointIndex, fallbackColor) {
6333
6333
  const point = findDataPoint(series, pointIndex);
6334
6334
  return point?.spPr?.fillColor ?? series.color ?? fallbackColor;
6335
6335
  }
6336
+ /**
6337
+ * Resolve the fill for a data point in a "vary colours" context, where every
6338
+ * point in a single series is drawn with a distinct palette colour (pie /
6339
+ * doughnut slices, or a bar/column series with `c:varyColors=1`). A per-point
6340
+ * `c:dPt` fill still wins; otherwise the supplied per-point `paletteColor` is
6341
+ * used (NOT the single series colour, which would make every point identical).
6342
+ */
6343
+ function resolveVaryColorFill(series, pointIndex, paletteColor) {
6344
+ return findDataPoint(series, pointIndex)?.spPr?.fillColor ?? paletteColor;
6345
+ }
6336
6346
  /**
6337
6347
  * Resolve the slice explosion (pull-out distance, 0-100) for a pie/doughnut
6338
6348
  * data point: the per-point `c:dPt` explosion when present, otherwise the
@@ -7030,7 +7040,8 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
7030
7040
  y,
7031
7041
  w: singleBarWidth,
7032
7042
  h,
7033
- fill: seriesColor(series[si], si, palette),
7043
+ fill: resolveDataPointFill(series[si], sourceIndex, paletteColor(si, palette)) ??
7044
+ seriesColor(series[si], si, palette),
7034
7045
  rx: 1,
7035
7046
  part: { role: 'dataPoint', seriesIndex: si, pointIndex: sourceIndex },
7036
7047
  });
@@ -7060,22 +7071,14 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
7060
7071
  }));
7061
7072
  const rects = computeStackedBarRects(displaySeries, catCount, layout, primaryRange, palette);
7062
7073
  for (const r of rects) {
7063
- primitives.push({
7064
- kind: 'rect',
7065
- x: r.x,
7066
- y: r.y,
7067
- w: r.w,
7068
- h: r.h,
7069
- fill: r.fill,
7070
- rx: 1,
7071
- part: r.seriesIndex !== undefined && r.pointIndex !== undefined
7072
- ? {
7073
- role: 'dataPoint',
7074
- seriesIndex: r.seriesIndex,
7075
- pointIndex: sourceIndices[r.pointIndex] ?? r.pointIndex,
7076
- }
7077
- : undefined,
7078
- });
7074
+ let fill = r.fill;
7075
+ let part;
7076
+ if (r.seriesIndex !== undefined && r.pointIndex !== undefined) {
7077
+ const sourcePointIndex = sourceIndices[r.pointIndex] ?? r.pointIndex;
7078
+ fill = resolveDataPointFill(series[r.seriesIndex], sourcePointIndex, r.fill) ?? r.fill;
7079
+ part = { role: 'dataPoint', seriesIndex: r.seriesIndex, pointIndex: sourcePointIndex };
7080
+ }
7081
+ primitives.push({ kind: 'rect', x: r.x, y: r.y, w: r.w, h: r.h, fill, rx: 1, part });
7079
7082
  }
7080
7083
  if (showLabels) {
7081
7084
  pushClusteredStackedLabels(series, sourceIndices, catCount, layout, primaryRange, dataLabels);
@@ -7113,7 +7116,8 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
7113
7116
  y,
7114
7117
  w: barW,
7115
7118
  h,
7116
- fill: seriesColor(series[si], si, palette),
7119
+ fill: resolveDataPointFill(series[si], sourceIndex, paletteColor(si, palette)) ??
7120
+ seriesColor(series[si], si, palette),
7117
7121
  part: { role: 'dataPoint', seriesIndex: si, pointIndex: sourceIndex },
7118
7122
  });
7119
7123
  if (showLabels && Math.abs(val) > 0) {
@@ -7206,7 +7210,7 @@ function buildLines(chartData, catCount, layout, primaryRange, secondaryRange, s
7206
7210
  cx: pt.x,
7207
7211
  cy: pt.y,
7208
7212
  r: 2.5,
7209
- fill: c,
7213
+ fill: resolveDataPointFill(series, sourceIndex, c) ?? c,
7210
7214
  part: { role: 'dataPoint', seriesIndex: si, pointIndex: sourceIndex },
7211
7215
  });
7212
7216
  });
@@ -7270,16 +7274,17 @@ function buildAreas(chartData, catCount, layout, range, sourceIndices, xPosition
7270
7274
  part: { role: 'series', seriesIndex: si },
7271
7275
  });
7272
7276
  pts.forEach((pt, displayIndex) => {
7277
+ const sourceIndex = sourceIndices[displayIndex] ?? displayIndex;
7273
7278
  primitives.push({
7274
7279
  kind: 'circle',
7275
7280
  cx: pt.x,
7276
7281
  cy: pt.y,
7277
7282
  r: 2,
7278
- fill: c,
7283
+ fill: resolveDataPointFill(series, sourceIndex, c) ?? c,
7279
7284
  part: {
7280
7285
  role: 'dataPoint',
7281
7286
  seriesIndex: si,
7282
- pointIndex: sourceIndices[displayIndex] ?? displayIndex,
7287
+ pointIndex: sourceIndex,
7283
7288
  },
7284
7289
  });
7285
7290
  });
@@ -7321,7 +7326,7 @@ function buildScatter(chartData, layout, range) {
7321
7326
  cx: dot.cx,
7322
7327
  cy: dot.cy,
7323
7328
  r: 4,
7324
- fill: c,
7329
+ fill: resolveDataPointFill(series, vi, c) ?? c,
7325
7330
  opacity: 0.85,
7326
7331
  part: { role: 'dataPoint', seriesIndex: si, pointIndex: vi },
7327
7332
  });
@@ -7369,7 +7374,7 @@ function buildBubbles(chartData, layout, range) {
7369
7374
  cx: dot.cx,
7370
7375
  cy: dot.cy,
7371
7376
  r,
7372
- fill: c,
7377
+ fill: resolveDataPointFill(series, vi, c) ?? c,
7373
7378
  opacity: 0.6,
7374
7379
  part: { role: 'dataPoint', seriesIndex: si, pointIndex: vi },
7375
7380
  });
@@ -11534,12 +11539,17 @@ function buildPieViewModel(element, chartData, categoryLabels, isDoughnut) {
11534
11539
  const { cx, cy, outerR, innerR, size } = computePieLayout(element.width, element.height, chartData, isDoughnut);
11535
11540
  const svgWidth = Math.max(size, 100);
11536
11541
  const svgHeight = Math.max(size, 60);
11537
- const values = chartData.series[0]?.values ?? [];
11542
+ const pieSeries = chartData.series[0];
11543
+ const values = pieSeries?.values ?? [];
11538
11544
  const slices = computePieSlices(values, cx, cy, outerR, innerR);
11539
11545
  const primitives = slices.map(({ d }, i) => ({
11540
11546
  kind: 'path',
11541
11547
  d,
11542
- fill: chartData.series[0]?.color ?? paletteColor(i, chartData.colorPalette),
11548
+ // Pie/doughnut vary colours per slice (c:varyColors defaults on), so each
11549
+ // slice takes its palette colour, with a per-point c:dPt fill overriding.
11550
+ fill: pieSeries
11551
+ ? resolveVaryColorFill(pieSeries, i, paletteColor(i, chartData.colorPalette))
11552
+ : paletteColor(i, chartData.colorPalette),
11543
11553
  stroke: '#ffffff',
11544
11554
  strokeWidth: 1.5,
11545
11555
  part: { role: 'dataPoint', seriesIndex: 0, pointIndex: i },
@@ -12893,6 +12903,25 @@ const PRESET_ID_TO_EFFECT = {
12893
12903
  26: 'pulse',
12894
12904
  },
12895
12905
  };
12906
+ /**
12907
+ * Map an OOXML `p:cTn/@presetSubtype` code to a {@link FlyEdge} for Fly In and
12908
+ * Fly Out effects. PowerPoint encodes the direction as a bitmask on the object
12909
+ * origin edge: 1=top, 2=right, 4=bottom, 8=left. Corners combine two bits
12910
+ * (e.g. 12 = 8|4 = bottom-left) and fall back to their horizontal edge, which
12911
+ * is the more visually distinct component. Unknown/absent codes are left to the
12912
+ * caller (which keeps the preset default of bottom).
12913
+ */
12914
+ const FLY_SUBTYPE_TO_EDGE = {
12915
+ 1: 'top',
12916
+ 2: 'right',
12917
+ 4: 'bottom',
12918
+ 8: 'left',
12919
+ // Corners -> nearest (horizontal) edge.
12920
+ 3: 'right', // top-right (1|2)
12921
+ 6: 'right', // bottom-right (4|2)
12922
+ 9: 'left', // top-left (8|1)
12923
+ 12: 'left', // bottom-left (8|4)
12924
+ };
12896
12925
 
12897
12926
  /**
12898
12927
  * `animation-keyframes` — CSS `@keyframes` definitions for every static
@@ -13354,10 +13383,10 @@ function resolveEffect(anim) {
13354
13383
  return undefined;
13355
13384
  }
13356
13385
  if (cls === 'entr') {
13357
- return PRESET_ID_TO_EFFECT.entr[id];
13386
+ return applyFlyDirection(PRESET_ID_TO_EFFECT.entr[id], anim.presetSubtype);
13358
13387
  }
13359
13388
  if (cls === 'exit') {
13360
- return PRESET_ID_TO_EFFECT.exit[id];
13389
+ return applyFlyDirection(PRESET_ID_TO_EFFECT.exit[id], anim.presetSubtype);
13361
13390
  }
13362
13391
  if (cls === 'emph') {
13363
13392
  return PRESET_ID_TO_EFFECT.emph[id];
@@ -13365,6 +13394,28 @@ function resolveEffect(anim) {
13365
13394
  // For path/motion/rotation/scale, return undefined — handled dynamically.
13366
13395
  return undefined;
13367
13396
  }
13397
+ /**
13398
+ * Redirect a Fly In / Fly Out effect according to its `presetSubtype` code.
13399
+ * The preset tables default the fly family to the bottom edge; when a subtype
13400
+ * is present and maps to a known edge, swap in the matching directional effect.
13401
+ * A missing or unrecognised subtype preserves the bottom default so existing
13402
+ * decks are unaffected.
13403
+ */
13404
+ function applyFlyDirection(effect, subtype) {
13405
+ if (effect !== 'flyInBottom' && effect !== 'flyOutBottom') {
13406
+ return effect;
13407
+ }
13408
+ if (subtype === undefined) {
13409
+ return effect;
13410
+ }
13411
+ const edge = FLY_SUBTYPE_TO_EDGE[subtype];
13412
+ if (!edge) {
13413
+ return effect;
13414
+ }
13415
+ const prefix = effect === 'flyInBottom' ? 'flyIn' : 'flyOut';
13416
+ const suffix = `${edge.charAt(0).toUpperCase()}${edge.slice(1)}`;
13417
+ return `${prefix}${suffix}`;
13418
+ }
13368
13419
  // ==========================================================================
13369
13420
  // Dynamic keyframe generation (motion path / rotation / scale / colour)
13370
13421
  // ==========================================================================
@@ -16097,6 +16148,122 @@ function getComputed3dStyle(el) {
16097
16148
  return result;
16098
16149
  }
16099
16150
 
16151
+ /**
16152
+ * Map a section's border set onto a cell's four edges given the cell's
16153
+ * position relative to that section's region. Outer region edges use the
16154
+ * `top/bottom/left/right` sides; interior edges use `insideH`/`insideV`.
16155
+ */
16156
+ function edgesFromSection(borders, regionTop, regionBottom, regionLeft, regionRight) {
16157
+ if (!borders) {
16158
+ return undefined;
16159
+ }
16160
+ return {
16161
+ top: regionTop ? (borders.top ?? borders.insideH) : borders.insideH,
16162
+ bottom: regionBottom ? (borders.bottom ?? borders.insideH) : borders.insideH,
16163
+ left: regionLeft ? (borders.left ?? borders.insideV) : borders.insideV,
16164
+ right: regionRight ? (borders.right ?? borders.insideV) : borders.insideV,
16165
+ };
16166
+ }
16167
+ /** Convert a resolved border side to a CSS `border-*` shorthand value. */
16168
+ function borderToCss(border, resolve) {
16169
+ if (!border) {
16170
+ return undefined;
16171
+ }
16172
+ if (border.noFill) {
16173
+ return 'none';
16174
+ }
16175
+ const width = border.width ?? 1;
16176
+ const dash = ooxmlDashToCssBorderStyle(border.dash);
16177
+ const color = border.color ?? resolve(border.fill) ?? '#000000';
16178
+ return `${width}px ${dash} ${color}`;
16179
+ }
16180
+ /**
16181
+ * Build the ordered (low -> high precedence) list of edge candidates from the
16182
+ * sections that apply to this cell.
16183
+ */
16184
+ function collectLayers(entry, tableData, pos) {
16185
+ const { rowIndex, cellIndex, rowCount, columnCount } = pos;
16186
+ const isTop = rowIndex === 0;
16187
+ const isBottom = rowIndex === rowCount - 1;
16188
+ const isLeft = cellIndex === 0;
16189
+ const isRight = cellIndex === columnCount - 1;
16190
+ const layers = [];
16191
+ // Whole table: region spans the entire table.
16192
+ layers.push(edgesFromSection(entry.wholeTblBorders, isTop, isBottom, isLeft, isRight));
16193
+ // Banded rows: treat each banded row as its own single-row region.
16194
+ if (tableData.bandedRows) {
16195
+ const bandStartRow = tableData.firstRowHeader ? 1 : 0;
16196
+ const bandEndRow = tableData.lastRow ? rowCount - 1 : rowCount;
16197
+ if (rowIndex >= bandStartRow && rowIndex < bandEndRow) {
16198
+ const rowCycle = Math.max(tableData.bandRowCycle ?? 1, 1);
16199
+ const bandGroup = Math.floor((rowIndex - bandStartRow) / rowCycle) % 2;
16200
+ const bands = bandGroup === 0 ? entry.band1HBorders : entry.band2HBorders;
16201
+ layers.push(edgesFromSection(bands, true, true, isLeft, isRight));
16202
+ }
16203
+ }
16204
+ // Banded columns: treat each banded column as its own single-column region.
16205
+ if (tableData.bandedColumns) {
16206
+ const colStart = tableData.firstCol ? 1 : 0;
16207
+ const colEnd = tableData.lastCol ? columnCount - 1 : columnCount;
16208
+ if (cellIndex >= colStart && cellIndex < colEnd) {
16209
+ const colCycle = Math.max(tableData.bandColCycle ?? 1, 1);
16210
+ const colGroup = Math.floor((cellIndex - colStart) / colCycle) % 2;
16211
+ const bands = colGroup === 0 ? entry.band1VBorders : entry.band2VBorders;
16212
+ layers.push(edgesFromSection(bands, isTop, isBottom, true, true));
16213
+ }
16214
+ }
16215
+ // First/last row: single-row region spanning all columns.
16216
+ if (tableData.firstRowHeader && isTop) {
16217
+ layers.push(edgesFromSection(entry.firstRowBorders, true, true, isLeft, isRight));
16218
+ }
16219
+ if (tableData.lastRow && isBottom) {
16220
+ layers.push(edgesFromSection(entry.lastRowBorders, true, true, isLeft, isRight));
16221
+ }
16222
+ // First/last column: single-column region spanning all rows.
16223
+ if (tableData.firstCol && isLeft) {
16224
+ layers.push(edgesFromSection(entry.firstColBorders, isTop, isBottom, true, true));
16225
+ }
16226
+ if (tableData.lastCol && isRight) {
16227
+ layers.push(edgesFromSection(entry.lastColBorders, isTop, isBottom, true, true));
16228
+ }
16229
+ return layers.filter((layer) => layer !== undefined);
16230
+ }
16231
+ /**
16232
+ * Resolve the CSS borders a cell inherits from its table style. Returns
16233
+ * `undefined` when the style defines no applicable borders (so the caller can
16234
+ * keep its own fallback border, e.g. the hardcoded total-row line).
16235
+ */
16236
+ function resolveCellBorderCss(entry, tableData, pos, resolve) {
16237
+ if (!entry) {
16238
+ return undefined;
16239
+ }
16240
+ const layers = collectLayers(entry, tableData, pos);
16241
+ if (layers.length === 0) {
16242
+ return undefined;
16243
+ }
16244
+ const pick = (edge) => {
16245
+ let winner;
16246
+ for (const layer of layers) {
16247
+ if (layer[edge]) {
16248
+ winner = layer[edge];
16249
+ }
16250
+ }
16251
+ return winner;
16252
+ };
16253
+ const css = {};
16254
+ let applied = false;
16255
+ const edges = ['top', 'bottom', 'left', 'right'];
16256
+ for (const edge of edges) {
16257
+ const value = borderToCss(pick(edge), resolve);
16258
+ if (value) {
16259
+ const key = `border${edge[0].toUpperCase()}${edge.slice(1)}`;
16260
+ css[key] = value;
16261
+ applied = true;
16262
+ }
16263
+ }
16264
+ return applied ? css : undefined;
16265
+ }
16266
+
16100
16267
  /**
16101
16268
  * Convert a {@link CellTextRun}'s per-run formatting to an inline CSS style
16102
16269
  * object suitable for a `<span>` element.
@@ -16581,6 +16748,16 @@ function getTableCellBandStyle(tableData, rowIndex, cellIndex, rowCount, columnC
16581
16748
  applyStyleText(styleEntry?.lastColText, colorScheme, style);
16582
16749
  applied = true;
16583
16750
  }
16751
+ // ── Table-style borders (issue #71). ──
16752
+ // Resolve gridlines/edges the cell inherits from the table style and let
16753
+ // them supersede the hardcoded total-row line above. Per-cell explicit
16754
+ // `a:lnX` borders (parsed into the cell style) are applied on top of this
16755
+ // layer by the renderer, so they still win.
16756
+ const borderCss = resolveCellBorderCss(styleEntry, tableData, { rowIndex, cellIndex, rowCount, columnCount }, (fill) => resolveStyleFillColor(fill, colorScheme));
16757
+ if (borderCss) {
16758
+ Object.assign(style, borderCss);
16759
+ applied = true;
16760
+ }
16584
16761
  return applied ? style : undefined;
16585
16762
  }
16586
16763
 
@@ -73953,7 +74130,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
73953
74130
  }], propDecorators: { canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], slideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideIndex", required: false }] }] } });
73954
74131
 
73955
74132
  // Generated by scripts/inline-shared.mjs from package.json. Do not edit.
73956
- const PPTX_ANGULAR_VIEWER_VERSION = "1.31.2";
74133
+ const PPTX_ANGULAR_VIEWER_VERSION = "1.31.3";
73957
74134
 
73958
74135
  /**
73959
74136
  * account-page.component.ts: File > Account content.