pptx-angular-viewer 1.31.2 → 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,10 @@ 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
|
+
|
|
9
|
+
## [1.31.2](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.31.2) - 2026-07-19
|
|
10
|
+
|
|
7
11
|
## [1.31.1](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.31.1) - 2026-07-18
|
|
8
12
|
|
|
9
13
|
## [1.31.0](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.31.0) - 2026-07-18
|
|
@@ -450,22 +450,96 @@ function getResolvedShapeClipPathFor(shapeType, width, height, adjustments) {
|
|
|
450
450
|
// 4. Final fallback: core's static preset clip-path table.
|
|
451
451
|
return getShapeClipPath(shapeType);
|
|
452
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* Build a CSS `clip-path: path('…')` value from a custom-geometry (`a:custGeom`)
|
|
455
|
+
* SVG path string, rescaling its path-space coordinates (`pathWidth` x
|
|
456
|
+
* `pathHeight`) into the element's pixel box.
|
|
457
|
+
*
|
|
458
|
+
* CSS `path()` coordinates live in the element's own border-box pixel space
|
|
459
|
+
* (there is no viewBox), so freeform coordinates authored against the OOXML path
|
|
460
|
+
* extent must be scaled by `elemW / pathWidth` and `elemH / pathHeight`. Every
|
|
461
|
+
* binding already clips its shape container's background fill with the resolved
|
|
462
|
+
* clip-path, so returning one here lets a themed freeform (e.g. the "Balloons"
|
|
463
|
+
* background) render as its true outline instead of flooding its bounding box.
|
|
464
|
+
*
|
|
465
|
+
* Supports the absolute command set produced by the core geometry engine
|
|
466
|
+
* (M/L/C/Q/Z) plus elliptical arcs (A); unknown commands are passed through
|
|
467
|
+
* unscaled rather than dropped.
|
|
468
|
+
*/
|
|
469
|
+
function buildCustomGeometryClipPath(pathData, pathWidth, pathHeight, elemWidth, elemHeight) {
|
|
470
|
+
if (!pathData ||
|
|
471
|
+
!Number.isFinite(pathWidth) ||
|
|
472
|
+
!Number.isFinite(pathHeight) ||
|
|
473
|
+
pathWidth <= 0 ||
|
|
474
|
+
pathHeight <= 0 ||
|
|
475
|
+
!Number.isFinite(elemWidth) ||
|
|
476
|
+
!Number.isFinite(elemHeight) ||
|
|
477
|
+
elemWidth <= 0 ||
|
|
478
|
+
elemHeight <= 0) {
|
|
479
|
+
return undefined;
|
|
480
|
+
}
|
|
481
|
+
const sx = elemWidth / pathWidth;
|
|
482
|
+
const sy = elemHeight / pathHeight;
|
|
483
|
+
const round = (n) => {
|
|
484
|
+
const r = Math.round(n * 100) / 100;
|
|
485
|
+
return Object.is(r, -0) ? '0' : String(r);
|
|
486
|
+
};
|
|
487
|
+
const tokens = pathData.match(/[MLCQZAHVmlcqzahv][^MLCQZAHVmlcqzahv]*/g) ?? [];
|
|
488
|
+
const out = [];
|
|
489
|
+
for (const token of tokens) {
|
|
490
|
+
const cmd = token[0];
|
|
491
|
+
const upper = cmd.toUpperCase();
|
|
492
|
+
if (upper === 'Z') {
|
|
493
|
+
out.push('Z');
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
const nums = (token.slice(1).match(/-?[\d.]+(?:e-?\d+)?/gi) ?? []).map(Number);
|
|
497
|
+
if (upper === 'A') {
|
|
498
|
+
// rx ry x-axis-rotation large-arc-flag sweep-flag x y (per 7-number group)
|
|
499
|
+
const parts = [];
|
|
500
|
+
for (let i = 0; i + 6 < nums.length; i += 7) {
|
|
501
|
+
parts.push(round(nums[i] * sx), round(nums[i + 1] * sy), String(nums[i + 2]), String(nums[i + 3]), String(nums[i + 4]), round(nums[i + 5] * sx), round(nums[i + 6] * sy));
|
|
502
|
+
}
|
|
503
|
+
out.push(`A ${parts.join(' ')}`);
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
// M/L/C/Q (and H/V) carry (x, y) pairs; scale x by sx and y by sy.
|
|
507
|
+
const scaled = nums.map((n, i) => round(n * (i % 2 === 0 ? sx : sy)));
|
|
508
|
+
out.push(`${upper} ${scaled.join(' ')}`);
|
|
509
|
+
}
|
|
510
|
+
if (out.length === 0) {
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
return `path('${out.join(' ')}')`;
|
|
514
|
+
}
|
|
453
515
|
/**
|
|
454
516
|
* Element-level convenience wrapper. Pulls `shapeType`, `width`, `height`, and
|
|
455
517
|
* `shapeAdjustments` off a {@link PptxElement} and delegates to
|
|
456
518
|
* {@link getResolvedShapeClipPathFor}.
|
|
457
519
|
*
|
|
520
|
+
* Custom-geometry freeforms (which carry `pathData`/`pathWidth`/`pathHeight`
|
|
521
|
+
* rather than a preset `shapeType`) take priority: their outline is rescaled
|
|
522
|
+
* into the element box via {@link buildCustomGeometryClipPath} so the fill clips
|
|
523
|
+
* to the real shape instead of its bounding rectangle.
|
|
524
|
+
*
|
|
458
525
|
* @param element The PPTX element to resolve a clip-path for.
|
|
459
526
|
* @param width Optional width override (pixels). Defaults to `element.width`.
|
|
460
527
|
* @param height Optional height override (pixels). Defaults to `element.height`.
|
|
461
528
|
*/
|
|
462
529
|
function getResolvedShapeClipPath(element, width, height) {
|
|
530
|
+
const w = typeof width === 'number' ? width : element.width;
|
|
531
|
+
const h = typeof height === 'number' ? height : element.height;
|
|
532
|
+
const custom = element;
|
|
533
|
+
if (custom.pathData && custom.pathWidth && custom.pathHeight) {
|
|
534
|
+
const customClip = buildCustomGeometryClipPath(custom.pathData, custom.pathWidth, custom.pathHeight, w, h);
|
|
535
|
+
if (customClip) {
|
|
536
|
+
return customClip;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
463
539
|
const shapeType = element.shapeType;
|
|
464
540
|
if (!shapeType) {
|
|
465
541
|
return undefined;
|
|
466
542
|
}
|
|
467
|
-
const w = typeof width === 'number' ? width : element.width;
|
|
468
|
-
const h = typeof height === 'number' ? height : element.height;
|
|
469
543
|
const adjustments = element.shapeAdjustments;
|
|
470
544
|
return getResolvedShapeClipPathFor(shapeType, w, h, adjustments);
|
|
471
545
|
}
|
|
@@ -1450,20 +1524,29 @@ function getSvgStrokeDasharray(dashType, strokeWidth, customDashSegments) {
|
|
|
1450
1524
|
/**
|
|
1451
1525
|
* Builds a CSS `transform` string combining flip and rotation transforms for an element.
|
|
1452
1526
|
* Flips are expressed as `scaleX(-1)` / `scaleY(-1)`, rotation as `rotate(Ndeg)`.
|
|
1527
|
+
*
|
|
1528
|
+
* Order matters: OOXML `a:xfrm` mirrors the shape *within* its bounding box
|
|
1529
|
+
* (`flipH`/`flipV`) and *then* rotates the box by `rot`. With CSS
|
|
1530
|
+
* `transform-origin: center`, transforms apply right-to-left, so the flips must
|
|
1531
|
+
* come AFTER the rotation in the string (`rotate(θ) scaleX(-1)`) to be applied
|
|
1532
|
+
* first. Emitting `scaleX(-1) rotate(θ)` instead reflects the rotation direction
|
|
1533
|
+
* for any shape that is both flipped and rotated (e.g. the "Balloons" freeforms
|
|
1534
|
+
* render mirrored/tilted the wrong way). This matches the Angular binding's
|
|
1535
|
+
* `getContainerStyle`, which already emits `rotate() scaleX() scaleY()`.
|
|
1453
1536
|
* @param element - The element whose transforms are read.
|
|
1454
1537
|
* @returns A CSS transform string, or `undefined` if no transforms apply.
|
|
1455
1538
|
*/
|
|
1456
1539
|
function getElementTransform(element) {
|
|
1457
1540
|
const transforms = [];
|
|
1541
|
+
if (element.rotation) {
|
|
1542
|
+
transforms.push(`rotate(${element.rotation}deg)`);
|
|
1543
|
+
}
|
|
1458
1544
|
if (element.flipHorizontal) {
|
|
1459
1545
|
transforms.push('scaleX(-1)');
|
|
1460
1546
|
}
|
|
1461
1547
|
if (element.flipVertical) {
|
|
1462
1548
|
transforms.push('scaleY(-1)');
|
|
1463
1549
|
}
|
|
1464
|
-
if (element.rotation) {
|
|
1465
|
-
transforms.push(`rotate(${element.rotation}deg)`);
|
|
1466
|
-
}
|
|
1467
1550
|
if (element.skewX) {
|
|
1468
1551
|
transforms.push(`skewX(${element.skewX}deg)`);
|
|
1469
1552
|
}
|
|
@@ -6250,6 +6333,16 @@ function resolveDataPointFill(series, pointIndex, fallbackColor) {
|
|
|
6250
6333
|
const point = findDataPoint(series, pointIndex);
|
|
6251
6334
|
return point?.spPr?.fillColor ?? series.color ?? fallbackColor;
|
|
6252
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
|
+
}
|
|
6253
6346
|
/**
|
|
6254
6347
|
* Resolve the slice explosion (pull-out distance, 0-100) for a pie/doughnut
|
|
6255
6348
|
* data point: the per-point `c:dPt` explosion when present, otherwise the
|
|
@@ -6947,7 +7040,8 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
|
|
|
6947
7040
|
y,
|
|
6948
7041
|
w: singleBarWidth,
|
|
6949
7042
|
h,
|
|
6950
|
-
fill:
|
|
7043
|
+
fill: resolveDataPointFill(series[si], sourceIndex, paletteColor(si, palette)) ??
|
|
7044
|
+
seriesColor(series[si], si, palette),
|
|
6951
7045
|
rx: 1,
|
|
6952
7046
|
part: { role: 'dataPoint', seriesIndex: si, pointIndex: sourceIndex },
|
|
6953
7047
|
});
|
|
@@ -6977,22 +7071,14 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
|
|
|
6977
7071
|
}));
|
|
6978
7072
|
const rects = computeStackedBarRects(displaySeries, catCount, layout, primaryRange, palette);
|
|
6979
7073
|
for (const r of rects) {
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
part: r.seriesIndex !== undefined && r.pointIndex !== undefined
|
|
6989
|
-
? {
|
|
6990
|
-
role: 'dataPoint',
|
|
6991
|
-
seriesIndex: r.seriesIndex,
|
|
6992
|
-
pointIndex: sourceIndices[r.pointIndex] ?? r.pointIndex,
|
|
6993
|
-
}
|
|
6994
|
-
: undefined,
|
|
6995
|
-
});
|
|
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 });
|
|
6996
7082
|
}
|
|
6997
7083
|
if (showLabels) {
|
|
6998
7084
|
pushClusteredStackedLabels(series, sourceIndices, catCount, layout, primaryRange, dataLabels);
|
|
@@ -7030,7 +7116,8 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
|
|
|
7030
7116
|
y,
|
|
7031
7117
|
w: barW,
|
|
7032
7118
|
h,
|
|
7033
|
-
fill:
|
|
7119
|
+
fill: resolveDataPointFill(series[si], sourceIndex, paletteColor(si, palette)) ??
|
|
7120
|
+
seriesColor(series[si], si, palette),
|
|
7034
7121
|
part: { role: 'dataPoint', seriesIndex: si, pointIndex: sourceIndex },
|
|
7035
7122
|
});
|
|
7036
7123
|
if (showLabels && Math.abs(val) > 0) {
|
|
@@ -7123,7 +7210,7 @@ function buildLines(chartData, catCount, layout, primaryRange, secondaryRange, s
|
|
|
7123
7210
|
cx: pt.x,
|
|
7124
7211
|
cy: pt.y,
|
|
7125
7212
|
r: 2.5,
|
|
7126
|
-
fill: c,
|
|
7213
|
+
fill: resolveDataPointFill(series, sourceIndex, c) ?? c,
|
|
7127
7214
|
part: { role: 'dataPoint', seriesIndex: si, pointIndex: sourceIndex },
|
|
7128
7215
|
});
|
|
7129
7216
|
});
|
|
@@ -7187,16 +7274,17 @@ function buildAreas(chartData, catCount, layout, range, sourceIndices, xPosition
|
|
|
7187
7274
|
part: { role: 'series', seriesIndex: si },
|
|
7188
7275
|
});
|
|
7189
7276
|
pts.forEach((pt, displayIndex) => {
|
|
7277
|
+
const sourceIndex = sourceIndices[displayIndex] ?? displayIndex;
|
|
7190
7278
|
primitives.push({
|
|
7191
7279
|
kind: 'circle',
|
|
7192
7280
|
cx: pt.x,
|
|
7193
7281
|
cy: pt.y,
|
|
7194
7282
|
r: 2,
|
|
7195
|
-
fill: c,
|
|
7283
|
+
fill: resolveDataPointFill(series, sourceIndex, c) ?? c,
|
|
7196
7284
|
part: {
|
|
7197
7285
|
role: 'dataPoint',
|
|
7198
7286
|
seriesIndex: si,
|
|
7199
|
-
pointIndex:
|
|
7287
|
+
pointIndex: sourceIndex,
|
|
7200
7288
|
},
|
|
7201
7289
|
});
|
|
7202
7290
|
});
|
|
@@ -7238,7 +7326,7 @@ function buildScatter(chartData, layout, range) {
|
|
|
7238
7326
|
cx: dot.cx,
|
|
7239
7327
|
cy: dot.cy,
|
|
7240
7328
|
r: 4,
|
|
7241
|
-
fill: c,
|
|
7329
|
+
fill: resolveDataPointFill(series, vi, c) ?? c,
|
|
7242
7330
|
opacity: 0.85,
|
|
7243
7331
|
part: { role: 'dataPoint', seriesIndex: si, pointIndex: vi },
|
|
7244
7332
|
});
|
|
@@ -7286,7 +7374,7 @@ function buildBubbles(chartData, layout, range) {
|
|
|
7286
7374
|
cx: dot.cx,
|
|
7287
7375
|
cy: dot.cy,
|
|
7288
7376
|
r,
|
|
7289
|
-
fill: c,
|
|
7377
|
+
fill: resolveDataPointFill(series, vi, c) ?? c,
|
|
7290
7378
|
opacity: 0.6,
|
|
7291
7379
|
part: { role: 'dataPoint', seriesIndex: si, pointIndex: vi },
|
|
7292
7380
|
});
|
|
@@ -11451,12 +11539,17 @@ function buildPieViewModel(element, chartData, categoryLabels, isDoughnut) {
|
|
|
11451
11539
|
const { cx, cy, outerR, innerR, size } = computePieLayout(element.width, element.height, chartData, isDoughnut);
|
|
11452
11540
|
const svgWidth = Math.max(size, 100);
|
|
11453
11541
|
const svgHeight = Math.max(size, 60);
|
|
11454
|
-
const
|
|
11542
|
+
const pieSeries = chartData.series[0];
|
|
11543
|
+
const values = pieSeries?.values ?? [];
|
|
11455
11544
|
const slices = computePieSlices(values, cx, cy, outerR, innerR);
|
|
11456
11545
|
const primitives = slices.map(({ d }, i) => ({
|
|
11457
11546
|
kind: 'path',
|
|
11458
11547
|
d,
|
|
11459
|
-
|
|
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),
|
|
11460
11553
|
stroke: '#ffffff',
|
|
11461
11554
|
strokeWidth: 1.5,
|
|
11462
11555
|
part: { role: 'dataPoint', seriesIndex: 0, pointIndex: i },
|
|
@@ -12810,6 +12903,25 @@ const PRESET_ID_TO_EFFECT = {
|
|
|
12810
12903
|
26: 'pulse',
|
|
12811
12904
|
},
|
|
12812
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
|
+
};
|
|
12813
12925
|
|
|
12814
12926
|
/**
|
|
12815
12927
|
* `animation-keyframes` — CSS `@keyframes` definitions for every static
|
|
@@ -13271,10 +13383,10 @@ function resolveEffect(anim) {
|
|
|
13271
13383
|
return undefined;
|
|
13272
13384
|
}
|
|
13273
13385
|
if (cls === 'entr') {
|
|
13274
|
-
return PRESET_ID_TO_EFFECT.entr[id];
|
|
13386
|
+
return applyFlyDirection(PRESET_ID_TO_EFFECT.entr[id], anim.presetSubtype);
|
|
13275
13387
|
}
|
|
13276
13388
|
if (cls === 'exit') {
|
|
13277
|
-
return PRESET_ID_TO_EFFECT.exit[id];
|
|
13389
|
+
return applyFlyDirection(PRESET_ID_TO_EFFECT.exit[id], anim.presetSubtype);
|
|
13278
13390
|
}
|
|
13279
13391
|
if (cls === 'emph') {
|
|
13280
13392
|
return PRESET_ID_TO_EFFECT.emph[id];
|
|
@@ -13282,6 +13394,28 @@ function resolveEffect(anim) {
|
|
|
13282
13394
|
// For path/motion/rotation/scale, return undefined — handled dynamically.
|
|
13283
13395
|
return undefined;
|
|
13284
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
|
+
}
|
|
13285
13419
|
// ==========================================================================
|
|
13286
13420
|
// Dynamic keyframe generation (motion path / rotation / scale / colour)
|
|
13287
13421
|
// ==========================================================================
|
|
@@ -16014,6 +16148,122 @@ function getComputed3dStyle(el) {
|
|
|
16014
16148
|
return result;
|
|
16015
16149
|
}
|
|
16016
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
|
+
|
|
16017
16267
|
/**
|
|
16018
16268
|
* Convert a {@link CellTextRun}'s per-run formatting to an inline CSS style
|
|
16019
16269
|
* object suitable for a `<span>` element.
|
|
@@ -16498,6 +16748,16 @@ function getTableCellBandStyle(tableData, rowIndex, cellIndex, rowCount, columnC
|
|
|
16498
16748
|
applyStyleText(styleEntry?.lastColText, colorScheme, style);
|
|
16499
16749
|
applied = true;
|
|
16500
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
|
+
}
|
|
16501
16761
|
return applied ? style : undefined;
|
|
16502
16762
|
}
|
|
16503
16763
|
|
|
@@ -73870,7 +74130,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
73870
74130
|
}], propDecorators: { canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], slideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideIndex", required: false }] }] } });
|
|
73871
74131
|
|
|
73872
74132
|
// Generated by scripts/inline-shared.mjs from package.json. Do not edit.
|
|
73873
|
-
const PPTX_ANGULAR_VIEWER_VERSION = "1.31.
|
|
74133
|
+
const PPTX_ANGULAR_VIEWER_VERSION = "1.31.3";
|
|
73874
74134
|
|
|
73875
74135
|
/**
|
|
73876
74136
|
* account-page.component.ts: File > Account content.
|