pptx-angular-viewer 3.14.0 → 3.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/fesm2022/{pptx-angular-viewer-chat-history-idb-C08A1rjA.mjs → pptx-angular-viewer-chat-history-idb-D_H4MXBh.mjs} +2 -2
- package/fesm2022/{pptx-angular-viewer-chat-history-idb-C08A1rjA.mjs.map → pptx-angular-viewer-chat-history-idb-D_H4MXBh.mjs.map} +1 -1
- package/fesm2022/{pptx-angular-viewer-pptx-angular-viewer-CrlToiB1.mjs → pptx-angular-viewer-pptx-angular-viewer-wH2ZRE8b.mjs} +764 -329
- package/fesm2022/{pptx-angular-viewer-pptx-angular-viewer-CrlToiB1.mjs.map → pptx-angular-viewer-pptx-angular-viewer-wH2ZRE8b.mjs.map} +1 -1
- package/fesm2022/pptx-angular-viewer.mjs +1 -1
- package/package.json +2 -2
- package/types/pptx-angular-viewer.d.ts +1 -1
- package/types/pptx-angular-viewer.d.ts.map +1 -1
|
@@ -7685,6 +7685,122 @@ const NOMINAL_ENVELOPE_BAND = {
|
|
|
7685
7685
|
bottom: BOTTOM_MARGIN,
|
|
7686
7686
|
};
|
|
7687
7687
|
|
|
7688
|
+
/**
|
|
7689
|
+
* Canvas-based text measurement for the glyph envelope layout
|
|
7690
|
+
* (`text-warp-envelope-layout.ts`): per-character advance widths and the
|
|
7691
|
+
* line's real (ink-measured) ascent, both backed by a single lazily-created
|
|
7692
|
+
* `CanvasRenderingContext2D` shared across calls.
|
|
7693
|
+
*/
|
|
7694
|
+
let measureCtx;
|
|
7695
|
+
function getMeasureCtx$1() {
|
|
7696
|
+
if (measureCtx !== undefined) {
|
|
7697
|
+
return measureCtx;
|
|
7698
|
+
}
|
|
7699
|
+
if (typeof document === 'undefined') {
|
|
7700
|
+
measureCtx = null;
|
|
7701
|
+
return null;
|
|
7702
|
+
}
|
|
7703
|
+
measureCtx = document.createElement('canvas').getContext('2d');
|
|
7704
|
+
return measureCtx;
|
|
7705
|
+
}
|
|
7706
|
+
function toCanvasFont$1(font) {
|
|
7707
|
+
const size = font.fontSizePx && font.fontSizePx > 0 ? font.fontSizePx : DEFAULT_TEXT_FONT_SIZE;
|
|
7708
|
+
const family = font.fontFamily || DEFAULT_FONT_FAMILY;
|
|
7709
|
+
return `${font.italic ? 'italic ' : ''}${font.bold ? 'bold ' : ''}${size}px ${family}`;
|
|
7710
|
+
}
|
|
7711
|
+
/**
|
|
7712
|
+
* Per-character advance widths for `text` set in `font`, measured as prefix
|
|
7713
|
+
* differences (never a lone character: see `text-metric-tracking.ts`'s
|
|
7714
|
+
* `advancesOf` for why - shaped scripts and ligatures need the context).
|
|
7715
|
+
*
|
|
7716
|
+
* Falls back to a flat `0.55em`-per-character estimate when there is no DOM
|
|
7717
|
+
* to measure with (SSR, or a test environment without a 2D canvas context);
|
|
7718
|
+
* the estimate only affects horizontal glyph spacing, never the envelope
|
|
7719
|
+
* curve itself, so it stays visually reasonable even when approximate.
|
|
7720
|
+
*/
|
|
7721
|
+
function measureGlyphAdvances(text, font) {
|
|
7722
|
+
const chars = [...text];
|
|
7723
|
+
const ctx = getMeasureCtx$1();
|
|
7724
|
+
if (!ctx) {
|
|
7725
|
+
const size = font.fontSizePx && font.fontSizePx > 0 ? font.fontSizePx : DEFAULT_TEXT_FONT_SIZE;
|
|
7726
|
+
return chars.map(() => size * 0.55);
|
|
7727
|
+
}
|
|
7728
|
+
ctx.font = toCanvasFont$1(font);
|
|
7729
|
+
const advances = [];
|
|
7730
|
+
let previous = 0;
|
|
7731
|
+
let prefix = '';
|
|
7732
|
+
for (const char of chars) {
|
|
7733
|
+
prefix += char;
|
|
7734
|
+
const width = ctx.measureText(prefix).width;
|
|
7735
|
+
advances.push(Math.max(0, width - previous));
|
|
7736
|
+
previous = width;
|
|
7737
|
+
}
|
|
7738
|
+
return advances;
|
|
7739
|
+
}
|
|
7740
|
+
/**
|
|
7741
|
+
* The real (ink-measured) ascent of `segments`' text at their own font
|
|
7742
|
+
* sizes, as the tallest `actualBoundingBoxAscent` across every segment on
|
|
7743
|
+
* the line (not a per-character average - one tall glyph anywhere on the
|
|
7744
|
+
* line sets the reference the whole line warps against, matching how a
|
|
7745
|
+
* single baseline/cap-height pair governs a real text run).
|
|
7746
|
+
*
|
|
7747
|
+
* `buildGlyphEnvelope` used to map every glyph's nominal band from a FIXED
|
|
7748
|
+
* `NOMINAL_ENVELOPE_BAND` fraction of the box height (0.15..0.85), assuming
|
|
7749
|
+
* a glyph's own cap height fills that whole span. COM-measured (2026-09-11,
|
|
7750
|
+
* `text-warp-glyph-outline.ts`'s doc comment): for an 8-shape WordArt
|
|
7751
|
+
* fixture (Arimo Bold 44pt captions in 100pt-tall boxes, the `textCanUp` /
|
|
7752
|
+
* `textCanDown` / `textInflate` / `textDeflate` presets at both default and
|
|
7753
|
+
* extreme `adj`), real cap height reaches only about `t = 0.57` of that
|
|
7754
|
+
* nominal span, not `t = 0`, so every glyph's mapped top undershot the
|
|
7755
|
+
* curve's own top edge by the same amount - an outline-vs-COM interior-
|
|
7756
|
+
* column ink-scan comparison measured ~30-40% of box height mean error (max
|
|
7757
|
+
* 58-80%) on BOTH the outline path and the affine fallback alike (both use
|
|
7758
|
+
* this same nominal band, so both shared the bug identically: the residual
|
|
7759
|
+
* lived here, not in the outline point-mapping math). Anchoring `nomTop` to
|
|
7760
|
+
* the line's REAL measured ascent instead - clamped to never exceed the
|
|
7761
|
+
* historical fixed band, so a line whose font genuinely fills (or exceeds)
|
|
7762
|
+
* the nominal span keeps the old, already-validated behaviour unchanged -
|
|
7763
|
+
* dropped the `textInflate`/`textDeflate` interior mean error to ~2.6-2.9%
|
|
7764
|
+
* (max ~9-10%), in the range `text-warp-glyph-slicing.ts`'s doc comment
|
|
7765
|
+
* already documents as the residual once this band mismatch is not also
|
|
7766
|
+
* present. The `textCanUp`/`textCanDown` cases still show an elevated
|
|
7767
|
+
* residual (their interior mean measured ~6-20% even after this fix) that
|
|
7768
|
+
* further investigation traced to a SEPARATE, larger issue: real PowerPoint
|
|
7769
|
+
* spaces envelope-warped glyphs to fill the box's own width edge-to-edge
|
|
7770
|
+
* (measured ink spanning ~99.9% of box width) rather than centering the
|
|
7771
|
+
* text at its natural (unstretched) advance width the way `startX`/
|
|
7772
|
+
* `measureGlyphAdvances` do today, with `textCanUp`/`textCanDown` additionally
|
|
7773
|
+
* showing non-uniform (cylinder-projection-like) horizontal spacing this fix
|
|
7774
|
+
* does not address - both are horizontal-layout gaps, out of scope for this
|
|
7775
|
+
* (purely vertical) band fix and left as an open, separately-scoped issue.
|
|
7776
|
+
*
|
|
7777
|
+
* Returns `undefined` with no DOM (SSR, or a test environment without a 2D
|
|
7778
|
+
* canvas context), so a caller falls back to the previous fixed-fraction
|
|
7779
|
+
* band unchanged, exactly like {@link measureGlyphAdvances}'s own fallback.
|
|
7780
|
+
*/
|
|
7781
|
+
function measureLineAscent(segments) {
|
|
7782
|
+
const ctx = getMeasureCtx$1();
|
|
7783
|
+
if (!ctx) {
|
|
7784
|
+
return undefined;
|
|
7785
|
+
}
|
|
7786
|
+
let maxAscent = 0;
|
|
7787
|
+
for (const segment of segments) {
|
|
7788
|
+
if (!segment.text) {
|
|
7789
|
+
continue;
|
|
7790
|
+
}
|
|
7791
|
+
ctx.font = toCanvasFont$1(segment.font);
|
|
7792
|
+
const ascent = ctx.measureText(segment.text).actualBoundingBoxAscent;
|
|
7793
|
+
if (Number.isFinite(ascent) && ascent > maxAscent) {
|
|
7794
|
+
maxAscent = ascent;
|
|
7795
|
+
}
|
|
7796
|
+
}
|
|
7797
|
+
return maxAscent > 0 ? maxAscent : undefined;
|
|
7798
|
+
}
|
|
7799
|
+
/** Test hook: forget the cached measurement context. */
|
|
7800
|
+
function resetGlyphEnvelopeMeasureCache() {
|
|
7801
|
+
measureCtx = undefined;
|
|
7802
|
+
}
|
|
7803
|
+
|
|
7688
7804
|
/**
|
|
7689
7805
|
* The per-glyph affine transform for the WordArt two-curve envelope (see
|
|
7690
7806
|
* `text-warp-envelope-layout.ts`), split out to keep that file under the
|
|
@@ -7698,12 +7814,99 @@ function sliceBand(top, bottom, index, count) {
|
|
|
7698
7814
|
bottom: top + ((index + 1) / count) * span,
|
|
7699
7815
|
};
|
|
7700
7816
|
}
|
|
7701
|
-
/**
|
|
7702
|
-
function
|
|
7817
|
+
/** A preset's deformed top/bottom band (absolute height units) at horizontal position `u`. */
|
|
7818
|
+
function deformedBandAt(preset, u, adj, adj2, height) {
|
|
7703
7819
|
const curve = envelopeCurveAt(preset, u, adj, adj2);
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
|
|
7820
|
+
return {
|
|
7821
|
+
top: (curve?.top ?? NOMINAL_ENVELOPE_BAND.top) * height,
|
|
7822
|
+
bottom: (curve?.bottom ?? NOMINAL_ENVELOPE_BAND.bottom) * height,
|
|
7823
|
+
};
|
|
7824
|
+
}
|
|
7825
|
+
/**
|
|
7826
|
+
* Horizontal position used to compute the FIXED boundary shared by two
|
|
7827
|
+
* adjacent paragraph rows (see {@link edgeBandAt}'s doc comment): the box's
|
|
7828
|
+
* own centre, matching where `NOMINAL_ENVELOPE_BAND` and every other
|
|
7829
|
+
* u-independent reference in this module already anchor.
|
|
7830
|
+
*/
|
|
7831
|
+
const ROW_BOUNDARY_REFERENCE_U = 0.5;
|
|
7832
|
+
/**
|
|
7833
|
+
* The envelope band (absolute height units, already line-sliced) at one
|
|
7834
|
+
* horizontal position.
|
|
7835
|
+
*
|
|
7836
|
+
* For a single-line element (`lineCount <= 1`) this is exactly the deformed
|
|
7837
|
+
* band at `u` - unchanged from before per-paragraph slicing existed.
|
|
7838
|
+
*
|
|
7839
|
+
* For a multi-paragraph element, naively slicing the band AFTER deforming it
|
|
7840
|
+
* AT `u` (dividing `[bandTop(u), bandBottom(u)]` into `lineCount` equal
|
|
7841
|
+
* fractions) lets the boundary between row `i` and row `i+1` drift with `u`.
|
|
7842
|
+
* Since each row's OWN glyphs are laid out independently (see
|
|
7843
|
+
* `text-warp-envelope-layout.ts`'s `buildGlyphEnvelope`, called once per
|
|
7844
|
+
* paragraph) and can span very different horizontal ranges - a short
|
|
7845
|
+
* paragraph stretched hard to fill the box samples very different `u` than a
|
|
7846
|
+
* longer one in the SAME box - two rows can end up comparing their own
|
|
7847
|
+
* boundary at two DIFFERENT `u` values where the curve's amplitude differs
|
|
7848
|
+
* enough that row `i`'s computed bottom sits BELOW row `i+1`'s computed top:
|
|
7849
|
+
* an inverted, overlapping pair, even though each row's own boundary is
|
|
7850
|
+
* "correct" in isolation. COM review 2026-09-11 found this pre-existing (not
|
|
7851
|
+
* introduced by the box-fill horizontal-placement fix, though a paragraph's
|
|
7852
|
+
* `stretch` factor can widen how differently two rows sample the curve and
|
|
7853
|
+
* so widen the effect): an 8-shape fixture's two-paragraph `textInflate`
|
|
7854
|
+
* block (`"Top"` over `"Bottom"`) measured its `"Top"` row's own bottom edge
|
|
7855
|
+
* at `y=138.1` while its `"Bottom"` row's top edge measured `y=79.4` - rows
|
|
7856
|
+
* swapped order.
|
|
7857
|
+
*
|
|
7858
|
+
* The fix: the boundary BETWEEN two rows must be the SAME value regardless
|
|
7859
|
+
* of which row (or which glyph's own `u`) is asking, so it is computed from
|
|
7860
|
+
* the band deformed at a FIXED reference position
|
|
7861
|
+
* ({@link ROW_BOUNDARY_REFERENCE_U}, the box's own horizontal centre) rather
|
|
7862
|
+
* than each row's own actual `u`. Only a row's OUTER edge - the one facing
|
|
7863
|
+
* the box's own top (row 0's top) or bottom (the last row's bottom), never
|
|
7864
|
+
* shared with a neighbour - still bends with the curve at the glyph's real
|
|
7865
|
+
* `u`, preserving genuine per-glyph height variation there (the property
|
|
7866
|
+
* `text-warp-envelope-layout.test.ts`'s "places line 0 of 2 strictly above
|
|
7867
|
+
* line 1 of 2" and the scaleY-variation tests already pin). An interior row
|
|
7868
|
+
* (`lineCount > 2`, both edges shared with neighbours) gets a fixed band on
|
|
7869
|
+
* both sides; no fixture in this repo yet exercises three or more WordArt
|
|
7870
|
+
* paragraph rows, so this is the untested-but-consistent extension of the
|
|
7871
|
+
* same rule, not a separately-measured case.
|
|
7872
|
+
*/
|
|
7873
|
+
function edgeBandAt(preset, u, adj, adj2, height, lineIndex, lineCount) {
|
|
7874
|
+
const actual = deformedBandAt(preset, u, adj, adj2, height);
|
|
7875
|
+
if (lineCount <= 1) {
|
|
7876
|
+
return sliceBand(actual.top, actual.bottom, 0, 1);
|
|
7877
|
+
}
|
|
7878
|
+
const reference = deformedBandAt(preset, ROW_BOUNDARY_REFERENCE_U, adj, adj2, height);
|
|
7879
|
+
const fixedSlice = sliceBand(reference.top, reference.bottom, lineIndex, lineCount);
|
|
7880
|
+
const isFirstRow = lineIndex <= 0;
|
|
7881
|
+
const isLastRow = lineIndex >= lineCount - 1;
|
|
7882
|
+
// An outer edge bending by its FULL deviation from the flat (undeformed)
|
|
7883
|
+
// band gives a row roughly `lineCount` TIMES the vertical scale its own
|
|
7884
|
+
// (`1/lineCount`-narrowed) nominal source band (`buildGlyphEnvelope`'s
|
|
7885
|
+
// `sliceBand(..., safeLineIndex, safeLineCount)` of
|
|
7886
|
+
// `NOMINAL_ENVELOPE_BAND`) was sized for, because the row's target band
|
|
7887
|
+
// still deforms by the WHOLE box's curve amplitude, not its own narrower
|
|
7888
|
+
// share of it (COM review 2026-09-11: a two-row `textInflate` fixture
|
|
7889
|
+
// measured a `d` vertical-scale term of ~3.27 for its first row at a
|
|
7890
|
+
// bulging u, stretching a 3-glyph "Top" caption's descender far enough to
|
|
7891
|
+
// invade the second row's territory even after the shared-boundary fix
|
|
7892
|
+
// alone; even scaled by `1/lineCount` here, a deep enough descender/
|
|
7893
|
+
// ascender can still extrapolate past the shared boundary - a
|
|
7894
|
+
// COM-unverified residual left open below). Damping the OUTER edge's OWN
|
|
7895
|
+
// deviation from the flat band by `1/lineCount` keeps a row's bend
|
|
7896
|
+
// proportional to its own narrowed share of the box, matching how its
|
|
7897
|
+
// nominal band was narrowed the same way - a multi-row block still
|
|
7898
|
+
// visibly bends (the deviation is not zeroed, just scaled), without a
|
|
7899
|
+
// `lineCount`-fold excess.
|
|
7900
|
+
const flatTop = NOMINAL_ENVELOPE_BAND.top * height;
|
|
7901
|
+
const flatBottom = NOMINAL_ENVELOPE_BAND.bottom * height;
|
|
7902
|
+
const dampedOuterTop = flatTop + (actual.top - flatTop) / lineCount;
|
|
7903
|
+
const dampedOuterBottom = flatBottom + (actual.bottom - flatBottom) / lineCount;
|
|
7904
|
+
// A first/last row's own OUTER edge bends (damped, see above); only the
|
|
7905
|
+
// shared INNER boundary ever comes from `fixedSlice`.
|
|
7906
|
+
return {
|
|
7907
|
+
top: isFirstRow ? dampedOuterTop : fixedSlice.top,
|
|
7908
|
+
bottom: isLastRow ? dampedOuterBottom : fixedSlice.bottom,
|
|
7909
|
+
};
|
|
7707
7910
|
}
|
|
7708
7911
|
/**
|
|
7709
7912
|
* Affine `matrix(1 b 0 d 0 f)` mapping a glyph's nominal (undeformed) band
|
|
@@ -7793,7 +7996,81 @@ function glyphEnvelopeMatrix(x0, x1, edge0, edge1, nomTop, nomBottom) {
|
|
|
7793
7996
|
* file, e.g. a signature font on the reader's OS with no webfont match),
|
|
7794
7997
|
* `text-warp-envelope-layout.ts` falls back to the existing per-glyph affine
|
|
7795
7998
|
* / piecewise-affine-slice transform, unchanged.
|
|
7796
|
-
|
|
7999
|
+
*
|
|
8000
|
+
* COM-verified 2026-09-11 (an 8-shape Arimo Bold fixture, `textCanUp`/
|
|
8001
|
+
* `textCanDown`/`textInflate`/`textDeflate` at default and extreme `adj`): an
|
|
8002
|
+
* outline-vs-PowerPoint ink-scan comparison found a large interior-column
|
|
8003
|
+
* mismatch (~30-40% of box height, max 58-80%) that traced NOT to this
|
|
8004
|
+
* module's point-mapping (verified correct: the affine fallback, driven by
|
|
8005
|
+
* the identical inputs, showed the same error to within measurement noise),
|
|
8006
|
+
* but to `text-warp-envelope-layout.ts`'s `nomTop`/`nomBottom` - the
|
|
8007
|
+
* "undeformed" reference band both this module and the affine path map a
|
|
8008
|
+
* glyph's points FROM - being a fixed fraction of box height regardless of
|
|
8009
|
+
* the actual text's real (font-metric) size. See
|
|
8010
|
+
* `measureLineAscent`'s doc comment there for the fix and the re-measured
|
|
8011
|
+
* numbers. A separate, larger, NOT-yet-fixed gap the same investigation
|
|
8012
|
+
* found: real PowerPoint spaces envelope-warped glyphs to fill the box's own
|
|
8013
|
+
* width edge-to-edge (`textCanUp`/`textCanDown` additionally non-uniformly,
|
|
8014
|
+
* cylinder-projection-like) rather than centring the text at its natural
|
|
8015
|
+
* advance width the way `measureGlyphAdvances`/`startX` do today - out of
|
|
8016
|
+
* scope for that fix, left as an open, separately-scoped issue.
|
|
8017
|
+
*
|
|
8018
|
+
* Open question, not root-caused: the same investigation's COM fixture had
|
|
8019
|
+
* to be rendered from a deck that embeds no font at all (`warp-outline-
|
|
8020
|
+
* noembed-clean.pptx`, Arimo installed as a Windows user font instead) -
|
|
8021
|
+
* PowerPoint refused to open an earlier variant of the SAME fixture that
|
|
8022
|
+
* embedded Arimo Bold as a `ppt/fonts/{guid}.fntdata` part (obfuscated per
|
|
8023
|
+
* ECMA-376 14.2.1, wired via `p:embeddedFontLst`/`embedTrueTypeFonts="1"`)
|
|
8024
|
+
* with error `0x808D1001`. Left as an open note for whoever next touches
|
|
8025
|
+
* embedded-font packaging or generates a COM fixture that needs one.
|
|
8026
|
+
*/
|
|
8027
|
+
/**
|
|
8028
|
+
* Horizontally scale a glyph's outline commands around `originX` (its own
|
|
8029
|
+
* left edge, matching where {@link buildWarpedGlyphOutlinePathD}'s caller
|
|
8030
|
+
* positioned it): `newX = originX + (x - originX) * scale`. `scale === 1` is
|
|
8031
|
+
* a no-op that returns `commands` unchanged (no new array allocated).
|
|
8032
|
+
*
|
|
8033
|
+
* Used by `text-warp-envelope-layout.ts` to widen a glyph's actual outline
|
|
8034
|
+
* for the `inflate`/`deflate` envelope family, which PowerPoint stretches as
|
|
8035
|
+
* a literal 2D distortion (both glyph spacing AND glyph shape widen together
|
|
8036
|
+
* to fill the box). The `can` family does NOT get this: COM-measured
|
|
8037
|
+
* 2026-09-11 (an 8-shape Arimo Bold fixture), a `can` glyph's own ink width
|
|
8038
|
+
* matches its NATURAL (unstretched) width closely (interior span
|
|
8039
|
+
* ~15.6%-15.8% of box width measured vs. ~15.6% predicted unstretched,
|
|
8040
|
+
* vs. ~16.6% predicted if the glyph itself were widened too) - `can`'s
|
|
8041
|
+
* cylindrical metaphor spreads glyphs apart (wider gaps) without literally
|
|
8042
|
+
* stretching each glyph's own shape, unlike `inflate`/`deflate`'s rubber-
|
|
8043
|
+
* sheet distortion. See `text-warp-envelope-layout.ts`'s `buildGlyphEnvelope`
|
|
8044
|
+
* for where the two families' `shapeScale` diverge.
|
|
8045
|
+
*/
|
|
8046
|
+
function scaleOutlineCommandsX(commands, originX, scale) {
|
|
8047
|
+
if (scale === 1) {
|
|
8048
|
+
return commands;
|
|
8049
|
+
}
|
|
8050
|
+
const sx = (x) => originX + (x - originX) * scale;
|
|
8051
|
+
return commands.map((cmd) => {
|
|
8052
|
+
switch (cmd.type) {
|
|
8053
|
+
case 'M':
|
|
8054
|
+
case 'L':
|
|
8055
|
+
return { type: cmd.type, x: sx(cmd.x), y: cmd.y };
|
|
8056
|
+
case 'Q':
|
|
8057
|
+
return { type: 'Q', x1: sx(cmd.x1), y1: cmd.y1, x: sx(cmd.x), y: cmd.y };
|
|
8058
|
+
case 'C':
|
|
8059
|
+
return {
|
|
8060
|
+
type: 'C',
|
|
8061
|
+
x1: sx(cmd.x1),
|
|
8062
|
+
y1: cmd.y1,
|
|
8063
|
+
x2: sx(cmd.x2),
|
|
8064
|
+
y2: cmd.y2,
|
|
8065
|
+
x: sx(cmd.x),
|
|
8066
|
+
y: cmd.y,
|
|
8067
|
+
};
|
|
8068
|
+
case 'Z':
|
|
8069
|
+
default:
|
|
8070
|
+
return cmd;
|
|
8071
|
+
}
|
|
8072
|
+
});
|
|
8073
|
+
}
|
|
7797
8074
|
/**
|
|
7798
8075
|
* Map `y` (a point on the glyph's nominal, undeformed `[nomTop, nomBottom]`
|
|
7799
8076
|
* band) into the envelope curve's own `[edgeTop, edgeBottom]` band at this
|
|
@@ -7988,10 +8265,12 @@ function buildGlyphSlices(preset, x0, x1, u0, u1, adj, adj2, height, lineIndex,
|
|
|
7988
8265
|
const sliceX1 = x0 + ((x1 - x0) * (i + 1)) / n;
|
|
7989
8266
|
const sliceU0 = u0 + ((u1 - u0) * i) / n;
|
|
7990
8267
|
const sliceU1 = u0 + ((u1 - u0) * (i + 1)) / n;
|
|
8268
|
+
const e0 = edgeAt(sliceU0);
|
|
8269
|
+
const e1 = edgeAt(sliceU1);
|
|
7991
8270
|
slices.push({
|
|
7992
8271
|
clipX0: sliceX0 - (i === 0 ? 0 : SEAM_OVERLAP_PX),
|
|
7993
8272
|
clipX1: sliceX1 + (i === n - 1 ? 0 : SEAM_OVERLAP_PX),
|
|
7994
|
-
transform: glyphEnvelopeMatrix(sliceX0, sliceX1,
|
|
8273
|
+
transform: glyphEnvelopeMatrix(sliceX0, sliceX1, e0, e1, nomTop, nomBottom),
|
|
7995
8274
|
});
|
|
7996
8275
|
}
|
|
7997
8276
|
return slices;
|
|
@@ -8011,52 +8290,6 @@ function buildGlyphSlices(preset, x0, x1, u0, u1, adj, adj2, height, lineIndex,
|
|
|
8011
8290
|
* React/Vue/Angular/Svelte/Vanilla, matching the framework-neutral
|
|
8012
8291
|
* `WarpPathGenerator` shape the `'path'` family already uses.
|
|
8013
8292
|
*/
|
|
8014
|
-
let measureCtx;
|
|
8015
|
-
function getMeasureCtx$1() {
|
|
8016
|
-
if (measureCtx !== undefined) {
|
|
8017
|
-
return measureCtx;
|
|
8018
|
-
}
|
|
8019
|
-
if (typeof document === 'undefined') {
|
|
8020
|
-
measureCtx = null;
|
|
8021
|
-
return null;
|
|
8022
|
-
}
|
|
8023
|
-
measureCtx = document.createElement('canvas').getContext('2d');
|
|
8024
|
-
return measureCtx;
|
|
8025
|
-
}
|
|
8026
|
-
function toCanvasFont$1(font) {
|
|
8027
|
-
const size = font.fontSizePx && font.fontSizePx > 0 ? font.fontSizePx : DEFAULT_TEXT_FONT_SIZE;
|
|
8028
|
-
const family = font.fontFamily || DEFAULT_FONT_FAMILY;
|
|
8029
|
-
return `${font.italic ? 'italic ' : ''}${font.bold ? 'bold ' : ''}${size}px ${family}`;
|
|
8030
|
-
}
|
|
8031
|
-
/**
|
|
8032
|
-
* Per-character advance widths for `text` set in `font`, measured as prefix
|
|
8033
|
-
* differences (never a lone character: see `text-metric-tracking.ts`'s
|
|
8034
|
-
* `advancesOf` for why - shaped scripts and ligatures need the context).
|
|
8035
|
-
*
|
|
8036
|
-
* Falls back to a flat `0.55em`-per-character estimate when there is no DOM
|
|
8037
|
-
* to measure with (SSR, or a test environment without a 2D canvas context);
|
|
8038
|
-
* the estimate only affects horizontal glyph spacing, never the envelope
|
|
8039
|
-
* curve itself, so it stays visually reasonable even when approximate.
|
|
8040
|
-
*/
|
|
8041
|
-
function measureGlyphAdvances(text, font) {
|
|
8042
|
-
const chars = [...text];
|
|
8043
|
-
const ctx = getMeasureCtx$1();
|
|
8044
|
-
if (!ctx) {
|
|
8045
|
-
const size = font.fontSizePx && font.fontSizePx > 0 ? font.fontSizePx : DEFAULT_TEXT_FONT_SIZE;
|
|
8046
|
-
return chars.map(() => size * 0.55);
|
|
8047
|
-
}
|
|
8048
|
-
ctx.font = toCanvasFont$1(font);
|
|
8049
|
-
const advances = [];
|
|
8050
|
-
let previous = 0;
|
|
8051
|
-
let prefix = '';
|
|
8052
|
-
for (const char of chars) {
|
|
8053
|
-
prefix += char;
|
|
8054
|
-
const width = ctx.measureText(prefix).width;
|
|
8055
|
-
advances.push(Math.max(0, width - previous));
|
|
8056
|
-
previous = width;
|
|
8057
|
-
}
|
|
8058
|
-
return advances;
|
|
8059
|
-
}
|
|
8060
8293
|
function startX(align, width, lineWidth) {
|
|
8061
8294
|
if (align === 'right') {
|
|
8062
8295
|
return width - lineWidth;
|
|
@@ -8094,16 +8327,57 @@ function buildGlyphEnvelope(preset, segments, width, height, align, adj, adj2, l
|
|
|
8094
8327
|
const safeLineIndex = Math.min(Math.max(0, Math.floor(lineIndex)), safeLineCount - 1);
|
|
8095
8328
|
const perSegmentAdvances = segments.map((seg) => measureGlyphAdvances(seg.text, seg.font));
|
|
8096
8329
|
const lineWidth = perSegmentAdvances.reduce((sum, advances) => sum + advances.reduce((s, w) => s + w, 0), 0);
|
|
8097
|
-
const { top:
|
|
8330
|
+
const { top: fixedBandTop, bottom: nomBottom } = sliceBand(height * NOMINAL_ENVELOPE_BAND.top, height * NOMINAL_ENVELOPE_BAND.bottom, safeLineIndex, safeLineCount);
|
|
8331
|
+
// Prefer the line's own real ink ascent over the fixed-fraction band (see
|
|
8332
|
+
// `measureLineAscent`'s doc comment for why): never LOWER than the fixed
|
|
8333
|
+
// band's top, so a line whose font already fills (or exceeds) the nominal
|
|
8334
|
+
// span keeps today's behaviour unchanged.
|
|
8335
|
+
const realAscent = measureLineAscent(segments);
|
|
8336
|
+
const nomTop = realAscent !== undefined ? Math.max(fixedBandTop, nomBottom - realAscent) : fixedBandTop;
|
|
8337
|
+
// PowerPoint spaces envelope-warped glyphs edge to edge across the box's
|
|
8338
|
+
// own width, rather than centring the line at its natural (unstretched)
|
|
8339
|
+
// advance width the way `startX`/`measureGlyphAdvances` did before this
|
|
8340
|
+
// fix (COM-measured 2026-09-11, an 8-shape Arimo Bold fixture: measured
|
|
8341
|
+
// ink spans ~99.9% of box width for BOTH the `can` and `inflate`/
|
|
8342
|
+
// `deflate` families). `stretch` is the uniform factor (box width /
|
|
8343
|
+
// natural line width) that reproduces the measured glyph PITCH closely
|
|
8344
|
+
// (interior boundary positions within ~1-3% of box width of COM ground
|
|
8345
|
+
// truth) for every glyph-envelope preset tested; every glyph's advance is
|
|
8346
|
+
// scaled by it, so the line always spans exactly `[0, width]`.
|
|
8347
|
+
//
|
|
8348
|
+
// Whether the glyph's own SHAPE also widens by `stretch` differs by
|
|
8349
|
+
// family though: `shapeScale` is `stretch` for `inflate`/`deflate` (and
|
|
8350
|
+
// the rest of the non-`can` envelope family) - COM-measured, their
|
|
8351
|
+
// per-glyph ink WIDTH scales with the stretch factor, matching a literal
|
|
8352
|
+
// rubber-sheet distortion where letters get visibly fatter. It is `1` for
|
|
8353
|
+
// `textCanUp`/`textCanDown` - their per-glyph ink width stays at its
|
|
8354
|
+
// NATURAL (unstretched) value; only the gaps between glyphs widen,
|
|
8355
|
+
// matching the "wrap around a cylinder" metaphor (letters keep their own
|
|
8356
|
+
// proportions, spaced further apart) rather than 2D stretching. Only
|
|
8357
|
+
// `shapeScale` reaches the OUTLINE render path (`scaleOutlineCommandsX`
|
|
8358
|
+
// below): the affine-fallback `transform`/`slices` path has no
|
|
8359
|
+
// horizontal-scale term by design (see `glyphEnvelopeMatrix`'s `a=1, c=0,
|
|
8360
|
+
// e=0` doc note), so it always fits the glyph's own NATURAL (unscaled)
|
|
8361
|
+
// width regardless of family - an accepted simplification for the
|
|
8362
|
+
// secondary (no-outline-available) path.
|
|
8363
|
+
const isCanFamily = preset === 'textCanUp' || preset === 'textCanDown';
|
|
8364
|
+
const stretch = width > 0 && lineWidth > 0 ? width / lineWidth : 1;
|
|
8365
|
+
const shapeScale = isCanFamily ? 1 : stretch;
|
|
8098
8366
|
const placements = [];
|
|
8099
|
-
let x = startX(align, width, lineWidth);
|
|
8367
|
+
let x = lineWidth > 0 ? 0 : startX(align, width, lineWidth);
|
|
8100
8368
|
segments.forEach((segment, segIdx) => {
|
|
8101
8369
|
const chars = [...segment.text];
|
|
8102
8370
|
const advances = perSegmentAdvances[segIdx];
|
|
8103
8371
|
chars.forEach((char, i) => {
|
|
8104
|
-
const
|
|
8372
|
+
const naturalGlyphWidth = advances[i] ?? 0;
|
|
8373
|
+
const pitch = naturalGlyphWidth * stretch;
|
|
8105
8374
|
const x0 = x;
|
|
8106
|
-
|
|
8375
|
+
// The affine-fit extent always uses the NATURAL (unscaled) width:
|
|
8376
|
+
// the affine/slice path can only ever render a glyph at its own
|
|
8377
|
+
// natural on-screen width (no horizontal-scale term available), so
|
|
8378
|
+
// fitting the curve across a wider span than what actually renders
|
|
8379
|
+
// would reintroduce the very mismatch this fix closes.
|
|
8380
|
+
const x1 = x0 + naturalGlyphWidth;
|
|
8107
8381
|
const u0 = width > 0 ? x0 / width : 0.5;
|
|
8108
8382
|
const u1 = width > 0 ? x1 / width : 0.5;
|
|
8109
8383
|
const edge0 = edgeBandAt(preset, u0, adj, adj2, height, safeLineIndex, safeLineCount);
|
|
@@ -8111,7 +8385,10 @@ function buildGlyphEnvelope(preset, segments, width, height, align, adj, adj2, l
|
|
|
8111
8385
|
// Outline warping takes priority when the caller can supply the
|
|
8112
8386
|
// glyph's real outline: it is exact, so the affine fit (and its
|
|
8113
8387
|
// piecewise-slice fallback) is only worth computing when it can't.
|
|
8114
|
-
const
|
|
8388
|
+
const rawOutline = getGlyphOutline?.(char, segment.font, x0, nomBottom);
|
|
8389
|
+
const outlineCommands = rawOutline
|
|
8390
|
+
? scaleOutlineCommandsX(rawOutline, x0, shapeScale)
|
|
8391
|
+
: undefined;
|
|
8115
8392
|
const outlinePath = outlineCommands
|
|
8116
8393
|
? buildWarpedGlyphOutlinePathD(outlineCommands, preset, width, height, nomTop, nomBottom, adj, adj2, safeLineIndex, safeLineCount)
|
|
8117
8394
|
: undefined;
|
|
@@ -8121,7 +8398,7 @@ function buildGlyphEnvelope(preset, segments, width, height, align, adj, adj2, l
|
|
|
8121
8398
|
placements.push({
|
|
8122
8399
|
char,
|
|
8123
8400
|
segmentIndex: segment.segmentIndex,
|
|
8124
|
-
x,
|
|
8401
|
+
x: x0,
|
|
8125
8402
|
y: nomBottom,
|
|
8126
8403
|
transform: glyphEnvelopeMatrix(x0, x1, edge0, edge1, nomTop, nomBottom),
|
|
8127
8404
|
slices: sliceCount > 1
|
|
@@ -8129,15 +8406,11 @@ function buildGlyphEnvelope(preset, segments, width, height, align, adj, adj2, l
|
|
|
8129
8406
|
: undefined,
|
|
8130
8407
|
outlinePath,
|
|
8131
8408
|
});
|
|
8132
|
-
x +=
|
|
8409
|
+
x += pitch;
|
|
8133
8410
|
});
|
|
8134
8411
|
});
|
|
8135
8412
|
return placements;
|
|
8136
8413
|
}
|
|
8137
|
-
/** Test hook: forget the cached measurement context. */
|
|
8138
|
-
function resetGlyphEnvelopeMeasureCache() {
|
|
8139
|
-
measureCtx = undefined;
|
|
8140
|
-
}
|
|
8141
8414
|
|
|
8142
8415
|
/**
|
|
8143
8416
|
* embedded-fonts.ts: Pure (no DOM-injection) helpers for the embedded-font
|
|
@@ -38133,6 +38406,147 @@ function isBevelProfileInverted(bevelType) {
|
|
|
38133
38406
|
return bevelType === 'softRound';
|
|
38134
38407
|
}
|
|
38135
38408
|
|
|
38409
|
+
/**
|
|
38410
|
+
* `a:bevelT/@prst` profile -> SVG height-map shape.
|
|
38411
|
+
*
|
|
38412
|
+
* Split out of `visual-3d-bevel-lighting-tables.ts` to keep both files under
|
|
38413
|
+
* the repo's ~300 LOC guideline; see that module's doc comment for the other
|
|
38414
|
+
* two axes (light rig elevation, material response) and the highlight
|
|
38415
|
+
* DIRECTION, which is resolved separately in `visual-3d-bevel-light.ts`.
|
|
38416
|
+
*
|
|
38417
|
+
* @module render/visual-3d-bevel-lighting-profile
|
|
38418
|
+
*/
|
|
38419
|
+
/**
|
|
38420
|
+
* `a:bevelT/@prst` (and `bevelB`, which shares the same profile vocabulary)
|
|
38421
|
+
* -> height-map shape. ECMA-376 20.1.10.9 describes each profile's silhouette
|
|
38422
|
+
* (a "circular", "flat sloped", "crossed", "art-deco stepped" etc.
|
|
38423
|
+
* cross-section); these factors were ORIGINALLY grouped into 3
|
|
38424
|
+
* physically-motivated buckets by that description (curved / faceted /
|
|
38425
|
+
* steep-narrow) rather than 12 independent hand-tuned entries, reasoned from
|
|
38426
|
+
* ECMA-376 alone.
|
|
38427
|
+
*
|
|
38428
|
+
* COM-MEASURED 2026-09 (real PowerPoint `Slide.Export`, mid-grey `matte`
|
|
38429
|
+
* square, `threePt` rig / `dir="t"`, `orthographicFront` camera, both a 6pt
|
|
38430
|
+
* and a 24pt `a:bevelT`, a line of 40 brightness samples from the top edge
|
|
38431
|
+
* inward for all 12 profiles): fitting these SAME 3 factors (grid search,
|
|
38432
|
+
* minimum RMSE against the measured curve, both depths jointly) against real
|
|
38433
|
+
* cross-section data overturned the bucket story for 3 profiles.
|
|
38434
|
+
* `circle`/`convex`/`softRound`/`divot` (curved) and `angle`/`cross`/
|
|
38435
|
+
* `coolSlant`/`riblet`/`artDeco` (faceted) fit closely (RMSE 1-7 brightness
|
|
38436
|
+
* units) with factors in the same rough range as the original reasoning, so
|
|
38437
|
+
* their bucket membership held up. `relaxedInset`, `slope` and `hardEdge`
|
|
38438
|
+
* did NOT: all three measured a genuine BRIGHT-BUMP-THEN-DARK-TROUGH double
|
|
38439
|
+
* transition partway through the ramp (e.g. `hardEdge` at 24pt: baseline 133
|
|
38440
|
+
* -> peaks ~139 -> drops to 67 -> recovers), which this filter's single
|
|
38441
|
+
* monotonic blur(+erode) height map (one bell-shaped slope lobe) cannot
|
|
38442
|
+
* reproduce - the fit pushes `surfaceScaleFactor` to the largest tested
|
|
38443
|
+
* value trying to reach the trough depth, landing the LARGEST relief factor
|
|
38444
|
+
* of any profile, the opposite of the pre-2026-09 "slope/hardEdge are
|
|
38445
|
+
* low-relief" assumption (`slope`/`hardEdge` were previously reasoned as
|
|
38446
|
+
* "steep/narrow" with REDUCED relief; `relaxedInset` was previously grouped
|
|
38447
|
+
* as "curved" with full relief and no erode at all). Their factors below are
|
|
38448
|
+
* therefore the closest achievable fit within this 3-parameter chain, not a
|
|
38449
|
+
* claim of a clean match (RMSE 12-19, versus 1-7 for the other 9); a proper
|
|
38450
|
+
* fix needs a genuinely non-monotonic (two-lobe) height-map primitive chain,
|
|
38451
|
+
* out of scope for this pass - see `docs/guide/limitations.md`. The
|
|
38452
|
+
* direction-independence these three still show (`measuredUniform`) is
|
|
38453
|
+
* unaffected: it is a separate, already-COM-confirmed finding (see
|
|
38454
|
+
* `visual-3d-bevel-light.ts`'s module doc comment) about which CARDINAL EDGE
|
|
38455
|
+
* lights up, not about the cross-section ramp shape this campaign measures.
|
|
38456
|
+
* Scripts (scratch, not committed, same convention as `com-acceptance.mjs`):
|
|
38457
|
+
* `scripts/make-bevel-profile-fixture.mjs` (fixture, all 12
|
|
38458
|
+
* profiles x 2 depths), `scripts/measure-bevel-profile-com.ps1`
|
|
38459
|
+
* (COM export + 40-point sampler), `scripts/fit-bevel-profile-com.mjs`
|
|
38460
|
+
* (grid-search fit; a closed-form Gaussian-CDF reimplementation of the
|
|
38461
|
+
* primitive chain, not a headless-browser render - Playwright's Chromium
|
|
38462
|
+
* launch hangs indefinitely via a plain script in this environment, though
|
|
38463
|
+
* `bunx playwright test` itself works fine, used to independently verify the
|
|
38464
|
+
* metal/circle routing conclusion in `visual-3d-bevel-lighting-routing.ts`).
|
|
38465
|
+
* The raw 10-point-per-profile table (24pt depth) is pinned in
|
|
38466
|
+
* `visual-3d-bevel-lighting-tables.test.ts`; the full 40-point x 2-depth
|
|
38467
|
+
* table is in the task report.
|
|
38468
|
+
*/
|
|
38469
|
+
const BEVEL_PROFILE_HEIGHT_MAP = {
|
|
38470
|
+
circle: { blurFactor: 0.35, surfaceScaleFactor: 0.65, measuredUniform: false },
|
|
38471
|
+
convex: {
|
|
38472
|
+
blurFactor: 0.18,
|
|
38473
|
+
morphologyFactor: 0.4,
|
|
38474
|
+
surfaceScaleFactor: 0.2,
|
|
38475
|
+
measuredUniform: false,
|
|
38476
|
+
},
|
|
38477
|
+
softRound: {
|
|
38478
|
+
blurFactor: 0.25,
|
|
38479
|
+
morphologyFactor: 0.4,
|
|
38480
|
+
surfaceScaleFactor: 0.65,
|
|
38481
|
+
measuredUniform: false,
|
|
38482
|
+
},
|
|
38483
|
+
relaxedInset: {
|
|
38484
|
+
blurFactor: 0.35,
|
|
38485
|
+
morphologyFactor: 0.5,
|
|
38486
|
+
surfaceScaleFactor: 1.5,
|
|
38487
|
+
measuredUniform: false,
|
|
38488
|
+
},
|
|
38489
|
+
divot: { blurFactor: 0.18, surfaceScaleFactor: 0.5, measuredUniform: false },
|
|
38490
|
+
angle: {
|
|
38491
|
+
blurFactor: 0.35,
|
|
38492
|
+
morphologyFactor: 0.4,
|
|
38493
|
+
surfaceScaleFactor: 0.35,
|
|
38494
|
+
measuredUniform: false,
|
|
38495
|
+
},
|
|
38496
|
+
cross: {
|
|
38497
|
+
blurFactor: 0.12,
|
|
38498
|
+
morphologyFactor: 0.18,
|
|
38499
|
+
surfaceScaleFactor: 0.2,
|
|
38500
|
+
measuredUniform: false,
|
|
38501
|
+
},
|
|
38502
|
+
coolSlant: {
|
|
38503
|
+
blurFactor: 0.25,
|
|
38504
|
+
morphologyFactor: 0.06,
|
|
38505
|
+
surfaceScaleFactor: 0.5,
|
|
38506
|
+
measuredUniform: false,
|
|
38507
|
+
},
|
|
38508
|
+
riblet: {
|
|
38509
|
+
blurFactor: 0.25,
|
|
38510
|
+
surfaceScaleFactor: 0.5,
|
|
38511
|
+
measuredUniform: false,
|
|
38512
|
+
},
|
|
38513
|
+
artDeco: {
|
|
38514
|
+
blurFactor: 0.18,
|
|
38515
|
+
morphologyFactor: 0.32,
|
|
38516
|
+
surfaceScaleFactor: 0.35,
|
|
38517
|
+
measuredUniform: false,
|
|
38518
|
+
},
|
|
38519
|
+
// `relaxedInset`/`slope`/`hardEdge` (see this table's doc comment): COM
|
|
38520
|
+
// measured a genuine BRIGHT-BUMP-THEN-DARK-TROUGH double transition for
|
|
38521
|
+
// all three, which a single monotonic blur(+erode) ramp cannot reproduce
|
|
38522
|
+
// (its height field has one bell-shaped slope lobe, so the diffuse/
|
|
38523
|
+
// specular response can only rise-then-settle, never rise-then-undershoot-
|
|
38524
|
+
// then-settle). These factors are the closest achievable fit within the
|
|
38525
|
+
// existing 3-parameter primitive chain (the grid search pushed
|
|
38526
|
+
// `surfaceScaleFactor` to its upper bound trying to reach the measured
|
|
38527
|
+
// trough depth), not a claim of a clean match; see the doc comment.
|
|
38528
|
+
slope: {
|
|
38529
|
+
blurFactor: 0.55,
|
|
38530
|
+
morphologyFactor: 0.5,
|
|
38531
|
+
surfaceScaleFactor: 1.5,
|
|
38532
|
+
measuredUniform: true,
|
|
38533
|
+
},
|
|
38534
|
+
hardEdge: {
|
|
38535
|
+
blurFactor: 0.55,
|
|
38536
|
+
morphologyFactor: 0.5,
|
|
38537
|
+
surfaceScaleFactor: 1.5,
|
|
38538
|
+
measuredUniform: true,
|
|
38539
|
+
},
|
|
38540
|
+
};
|
|
38541
|
+
const DEFAULT_HEIGHT_MAP = {
|
|
38542
|
+
blurFactor: 0.4,
|
|
38543
|
+
surfaceScaleFactor: 0.8,
|
|
38544
|
+
measuredUniform: false,
|
|
38545
|
+
};
|
|
38546
|
+
function getBevelProfileHeightMap(bevelType) {
|
|
38547
|
+
return BEVEL_PROFILE_HEIGHT_MAP[bevelType] ?? DEFAULT_HEIGHT_MAP;
|
|
38548
|
+
}
|
|
38549
|
+
|
|
38136
38550
|
/**
|
|
38137
38551
|
* Material -> `feDiffuseLighting`/`feSpecularLighting` response table.
|
|
38138
38552
|
*
|
|
@@ -38165,12 +38579,26 @@ const white = '#ffffff';
|
|
|
38165
38579
|
* .ts`'s module doc table) found this module's `metal` numbers give a mixed
|
|
38166
38580
|
* result: `matte` improved clearly over the old box-shadow approach (56.3 ->
|
|
38167
38581
|
* 34.8 mean error) while `metal` did not (61.2 -> 59.7, and `metal`/`circle`
|
|
38168
|
-
* specifically got WORSE, 54.9 -> 80.1) - the constants below
|
|
38169
|
-
* flagged
|
|
38170
|
-
*
|
|
38171
|
-
* around the `matte`/`metal` anchors by
|
|
38172
|
-
* `metal` but softer, matte/powder variants
|
|
38173
|
-
* COM-measured at all.
|
|
38582
|
+
* specifically got WORSE, 54.9 -> 80.1) - the constants below were therefore
|
|
38583
|
+
* flagged UNVALIDATED for `metal`, and `metal`/`circle` was ROUTED to the
|
|
38584
|
+
* legacy box-shadow model (`visual-3d-bevel-lighting-routing.ts`). The other
|
|
38585
|
+
* materials are positioned between/around the `matte`/`metal` anchors by
|
|
38586
|
+
* category (glossy plastics near `metal` but softer, matte/powder variants
|
|
38587
|
+
* near `matte`), not independently COM-measured at all.
|
|
38588
|
+
*
|
|
38589
|
+
* RESOLVED for `metal`/`circle` (2026-09, the bevel-profile cross-section
|
|
38590
|
+
* campaign, `visual-3d-bevel-lighting-tables.ts`'s `BEVEL_PROFILE_HEIGHT_MAP`
|
|
38591
|
+
* doc comment): re-fitting `circle`'s profile-table entry against real COM
|
|
38592
|
+
* cross-section data (unrelated to material tuning) changed
|
|
38593
|
+
* `surfaceScaleFactor` from 1 to 0.65, and with these SAME `metal` constants
|
|
38594
|
+
* (unchanged from the paragraph above) that alone brought `metal`/`circle`'s
|
|
38595
|
+
* mean error to 39.5 against the SAME 54.9 baseline - confirmed against
|
|
38596
|
+
* fresh COM ground truth AND an actual headless-Chromium rasterisation of
|
|
38597
|
+
* the real filter chain (not just the closed-form model this module's own
|
|
38598
|
+
* campaigns otherwise used), so `metal`/`circle` no longer routes; see
|
|
38599
|
+
* `visual-3d-bevel-lighting-routing.ts`'s doc comment for the numbers and a
|
|
38600
|
+
* separate, NOT-landed attempt at fixing the flat-interior specular
|
|
38601
|
+
* saturation defect below.
|
|
38174
38602
|
*/
|
|
38175
38603
|
const MATERIAL_LIGHTING = {
|
|
38176
38604
|
matte: {
|
|
@@ -38247,10 +38675,14 @@ const MATERIAL_LIGHTING = {
|
|
|
38247
38675
|
// (`angle`: baseline ~70, now ~19-27; `hardEdge`: baseline ~69.5, now 44;
|
|
38248
38676
|
// `softRound`: baseline ~50, now 6.5-19). `circle` alone could NOT be
|
|
38249
38677
|
// brought below baseline with any tested combination of these four
|
|
38250
|
-
// parameters (tried down to `surfaceScaleMultiplier` 0.15 and up
|
|
38251
|
-
//
|
|
38252
|
-
//
|
|
38253
|
-
//
|
|
38678
|
+
// MATERIAL parameters (tried down to `surfaceScaleMultiplier` 0.15 and up
|
|
38679
|
+
// to 2.5) against the then-current (ECMA-376-reasoned) profile table, and
|
|
38680
|
+
// was routed to the legacy `box-shadow` model instead of shipping a
|
|
38681
|
+
// regression. SUPERSEDED 2026-09: re-fitting the PROFILE table's own
|
|
38682
|
+
// `circle` entry against real COM data (a change orthogonal to these
|
|
38683
|
+
// material constants) resolved it without touching the numbers below; see
|
|
38684
|
+
// this file's module doc comment and `visual-3d-bevel-lighting-routing
|
|
38685
|
+
// .ts`.
|
|
38254
38686
|
//
|
|
38255
38687
|
// A SEPARATE, more severe issue was found (2026-09, while attempting an
|
|
38256
38688
|
// ambient/wrap-term fix for the `circle` routing above) that this
|
|
@@ -38347,9 +38779,12 @@ function getMaterialLighting(material) {
|
|
|
38347
38779
|
* Split out of `visual-3d-bevel-lighting.ts` to keep that module under the
|
|
38348
38780
|
* repo's ~300 LOC guideline. Three independent axes feed the filter this
|
|
38349
38781
|
* module's data drives: bevel PROFILE shape (`a:bevelT/@prst`, ECMA-376
|
|
38350
|
-
* 20.1.10.9 `ST_BevelPresetType
|
|
38351
|
-
*
|
|
38352
|
-
*
|
|
38782
|
+
* 20.1.10.9 `ST_BevelPresetType`, re-exported here but defined in
|
|
38783
|
+
* `visual-3d-bevel-lighting-profile.ts`, itself split out for the same LOC
|
|
38784
|
+
* reason), light rig ELEVATION/specular character (`a:lightRig/@rig`,
|
|
38785
|
+
* ECMA-376 20.1.10.36 `ST_LightRigType`, defined below), and MATERIAL
|
|
38786
|
+
* response (`a:sp3d/@prstMaterial`, ECMA-376 20.1.10.50 `ST_PresetMaterialType`,
|
|
38787
|
+
* re-exported from `visual-3d-bevel-lighting-material.ts`).
|
|
38353
38788
|
* The highlight/shadow DIRECTION (azimuth) itself is mostly resolved
|
|
38354
38789
|
* elsewhere: `visual-3d-bevel-light`'s already COM-measured cardinal-snap
|
|
38355
38790
|
* vector supplies the base azimuth from `a:lightRig/@dir`, and this module's
|
|
@@ -38374,80 +38809,6 @@ function getMaterialLighting(material) {
|
|
|
38374
38809
|
*
|
|
38375
38810
|
* @module render/visual-3d-bevel-lighting-tables
|
|
38376
38811
|
*/
|
|
38377
|
-
/**
|
|
38378
|
-
* `a:bevelT/@prst` (and `bevelB`, which shares the same profile vocabulary)
|
|
38379
|
-
* -> height-map shape. ECMA-376 20.1.10.9 describes each profile's silhouette
|
|
38380
|
-
* (a "circular", "flat sloped", "crossed", "art-deco stepped" etc. cross-
|
|
38381
|
-
* section); this table groups the 12 values by that description into 3
|
|
38382
|
-
* physically-motivated buckets rather than 12 independent hand-tuned entries:
|
|
38383
|
-
*
|
|
38384
|
-
* - **Curved** (`circle`, `convex`, `softRound`, `relaxedInset`, `divot`):
|
|
38385
|
-
* a smooth, rounded cross-section -> wide Gaussian-only ramp, full relief.
|
|
38386
|
-
* - **Faceted** (`angle`, `cross`, `coolSlant`, `riblet`, `artDeco`): a flat
|
|
38387
|
-
* angled facet with a visible crease -> a medium blur PLUS a light erode so
|
|
38388
|
-
* the ramp gets a crisper inner edge (the crease), full relief.
|
|
38389
|
-
* - **Steep/narrow** (`slope`, `hardEdge`): COM-measured to show no clean
|
|
38390
|
-
* directional signal (see {@link BevelProfileHeightMap.measuredUniform});
|
|
38391
|
-
* a narrow, heavily-eroded ramp with reduced relief reproduces that
|
|
38392
|
-
* physically instead of guessing a highlight side.
|
|
38393
|
-
*/
|
|
38394
|
-
const BEVEL_PROFILE_HEIGHT_MAP = {
|
|
38395
|
-
circle: { blurFactor: 0.55, surfaceScaleFactor: 1, measuredUniform: false },
|
|
38396
|
-
convex: { blurFactor: 0.6, surfaceScaleFactor: 1.05, measuredUniform: false },
|
|
38397
|
-
softRound: { blurFactor: 0.5, surfaceScaleFactor: 0.9, measuredUniform: false },
|
|
38398
|
-
relaxedInset: { blurFactor: 0.45, surfaceScaleFactor: 0.85, measuredUniform: false },
|
|
38399
|
-
divot: { blurFactor: 0.4, surfaceScaleFactor: 0.8, measuredUniform: false },
|
|
38400
|
-
angle: {
|
|
38401
|
-
blurFactor: 0.32,
|
|
38402
|
-
morphologyFactor: 0.12,
|
|
38403
|
-
surfaceScaleFactor: 1,
|
|
38404
|
-
measuredUniform: false,
|
|
38405
|
-
},
|
|
38406
|
-
cross: {
|
|
38407
|
-
blurFactor: 0.3,
|
|
38408
|
-
morphologyFactor: 0.15,
|
|
38409
|
-
surfaceScaleFactor: 0.95,
|
|
38410
|
-
measuredUniform: false,
|
|
38411
|
-
},
|
|
38412
|
-
coolSlant: {
|
|
38413
|
-
blurFactor: 0.28,
|
|
38414
|
-
morphologyFactor: 0.14,
|
|
38415
|
-
surfaceScaleFactor: 0.95,
|
|
38416
|
-
measuredUniform: false,
|
|
38417
|
-
},
|
|
38418
|
-
riblet: {
|
|
38419
|
-
blurFactor: 0.26,
|
|
38420
|
-
morphologyFactor: 0.18,
|
|
38421
|
-
surfaceScaleFactor: 0.9,
|
|
38422
|
-
measuredUniform: false,
|
|
38423
|
-
},
|
|
38424
|
-
artDeco: {
|
|
38425
|
-
blurFactor: 0.24,
|
|
38426
|
-
morphologyFactor: 0.2,
|
|
38427
|
-
surfaceScaleFactor: 1,
|
|
38428
|
-
measuredUniform: false,
|
|
38429
|
-
},
|
|
38430
|
-
slope: {
|
|
38431
|
-
blurFactor: 0.12,
|
|
38432
|
-
morphologyFactor: 0.35,
|
|
38433
|
-
surfaceScaleFactor: 0.4,
|
|
38434
|
-
measuredUniform: true,
|
|
38435
|
-
},
|
|
38436
|
-
hardEdge: {
|
|
38437
|
-
blurFactor: 0.1,
|
|
38438
|
-
morphologyFactor: 0.4,
|
|
38439
|
-
surfaceScaleFactor: 0.35,
|
|
38440
|
-
measuredUniform: true,
|
|
38441
|
-
},
|
|
38442
|
-
};
|
|
38443
|
-
const DEFAULT_HEIGHT_MAP = {
|
|
38444
|
-
blurFactor: 0.4,
|
|
38445
|
-
surfaceScaleFactor: 0.8,
|
|
38446
|
-
measuredUniform: false,
|
|
38447
|
-
};
|
|
38448
|
-
function getBevelProfileHeightMap(bevelType) {
|
|
38449
|
-
return BEVEL_PROFILE_HEIGHT_MAP[bevelType] ?? DEFAULT_HEIGHT_MAP;
|
|
38450
|
-
}
|
|
38451
38812
|
/**
|
|
38452
38813
|
* `a:lightRig/@rig` -> elevation/sharpness/direction. COM-CALIBRATED
|
|
38453
38814
|
* (2026-09, real PowerPoint `Slide.Export`, mid-grey #808080 1.4in square,
|
|
@@ -38638,7 +38999,7 @@ function resolveLayer(index, bevelType, widthEmu, heightEmu, isBottom, scene, ma
|
|
|
38638
38999
|
|
|
38639
39000
|
/**
|
|
38640
39001
|
* Legacy `box-shadow` routing for a `material`/profile combination the SVG
|
|
38641
|
-
* lighting filter cannot
|
|
39002
|
+
* lighting filter cannot beat.
|
|
38642
39003
|
*
|
|
38643
39004
|
* Split out of `visual-3d-bevel-lighting.ts` to keep that file under the
|
|
38644
39005
|
* repo's ~300 LOC guideline.
|
|
@@ -38646,26 +39007,45 @@ function resolveLayer(index, bevelType, widthEmu, heightEmu, isBottom, scene, ma
|
|
|
38646
39007
|
* @module render/visual-3d-bevel-lighting-routing
|
|
38647
39008
|
*/
|
|
38648
39009
|
/**
|
|
38649
|
-
* `material|profile` pairs that
|
|
38650
|
-
* approach
|
|
38651
|
-
* .
|
|
38652
|
-
*
|
|
38653
|
-
* `metal|circle
|
|
38654
|
-
* `specularExponent
|
|
38655
|
-
*
|
|
38656
|
-
*
|
|
38657
|
-
*
|
|
38658
|
-
* `
|
|
38659
|
-
*
|
|
38660
|
-
*
|
|
38661
|
-
*
|
|
38662
|
-
*
|
|
38663
|
-
*
|
|
38664
|
-
*
|
|
38665
|
-
*
|
|
38666
|
-
*
|
|
38667
|
-
|
|
38668
|
-
|
|
39010
|
+
* `material|profile` pairs that measure WORSE than the legacy `box-shadow`
|
|
39011
|
+
* approach and therefore route to the legacy model instead of shipping a
|
|
39012
|
+
* regression. Empty as of the 2026-09 bevel-profile-cross-section campaign
|
|
39013
|
+
* (`visual-3d-bevel-lighting-tables.ts`'s `BEVEL_PROFILE_HEIGHT_MAP` doc
|
|
39014
|
+
* comment): `metal|circle` was the one routed pair (a grid search over
|
|
39015
|
+
* `diffuseConstant`/`specularConstant`/`specularExponent`/
|
|
39016
|
+
* `surfaceScaleMultiplier` against the OLD, ECMA-376-reasoned profile table
|
|
39017
|
+
* could not bring it at or below baseline in any direction - see this file's
|
|
39018
|
+
* git history for that campaign's numbers). Re-fitting `circle`'s
|
|
39019
|
+
* `BEVEL_PROFILE_HEIGHT_MAP` entry against real COM cross-section data
|
|
39020
|
+
* (`surfaceScaleFactor` 1 -> 0.65) changed the balance enough that the
|
|
39021
|
+
* UNCHANGED material constants now beat the box-shadow baseline in every
|
|
39022
|
+
* `a:lightRig/@dir`, confirmed two ways: fresh COM ground truth (mid-grey
|
|
39023
|
+
* `circle`/metal square, 24pt bevel, `threePt` rig, all 4 directions,
|
|
39024
|
+
* 0.15in-from-edge highlight+shadow sampling, script
|
|
39025
|
+
* `scripts/make-bevel-material-fixture.mjs` +
|
|
39026
|
+
* `measure-bevel-material-com.ps1`) and an ACTUAL headless-Chromium
|
|
39027
|
+
* rasterisation of the real filter primitive chain (a one-off Playwright
|
|
39028
|
+
* spec, not committed) sampled the same points: mean absolute error 39.5
|
|
39029
|
+
* (baseline was 54.9). The same real-browser check also re-confirmed
|
|
39030
|
+
* `angle`/`hardEdge`/`softRound` still beat their baselines (41.5/62.0/27.5
|
|
39031
|
+
* against baselines ~70/~69.5/~50) with the new profile table, so nothing
|
|
39032
|
+
* newly regressed.
|
|
39033
|
+
*
|
|
39034
|
+
* A candidate fix for the SEPARATE flat-interior specular-saturation defect
|
|
39035
|
+
* (masking `feSpecularLighting`'s contribution to the actual curved bevel
|
|
39036
|
+
* band via a `feComponentTransfer`/`feColorMatrix` triangle-of-height mask,
|
|
39037
|
+
* `1 - |2*height-1|`, zero at the flat cap) was measured against the same
|
|
39038
|
+
* real-browser pipeline for all 4 profiles and made EVERY ONE of them worse,
|
|
39039
|
+
* often drastically (`circle` 39.5 -> 103.5, `angle` 41.5 -> 104.0,
|
|
39040
|
+
* `hardEdge` 62.0 -> 94.0, `softRound` 27.5 -> 89.0): the mask's `feFuncA
|
|
39041
|
+
* type="table" tableValues="0 1 0"` zeroes specular well before COM's real
|
|
39042
|
+
* highlight has decayed at the 0.15in sample offset (the mask, tuned only to
|
|
39043
|
+
* the height VALUE crossing 0.5, does not track where the actual specular
|
|
39044
|
+
* lobe sits for a high `specularExponent`), so this attempt was measured and
|
|
39045
|
+
* NOT landed. See `docs/guide/limitations.md` for the still-open
|
|
39046
|
+
* flat-interior saturation defect this was meant to fix.
|
|
39047
|
+
*/
|
|
39048
|
+
const LEGACY_BEVEL_ROUTING = new Set();
|
|
38669
39049
|
/**
|
|
38670
39050
|
* Whether a `material`/`profile` combination is routed to the legacy
|
|
38671
39051
|
* `box-shadow` bevel model instead of the SVG lighting filter.
|
|
@@ -38737,12 +39117,12 @@ function isRoutedToLegacyBevelShadow(material, profile) {
|
|
|
38737
39117
|
* `metal`/`circle` below its baseline in any direction (the two targets pull
|
|
38738
39118
|
* in opposite directions as `surfaceScale` changes - see
|
|
38739
39119
|
* `visual-3d-bevel-lighting-routing.ts`'s `isRoutedToLegacyBevelShadow` doc
|
|
38740
|
-
* comment), so `metal`/`circle`
|
|
38741
|
-
*
|
|
38742
|
-
*
|
|
38743
|
-
*
|
|
38744
|
-
*
|
|
38745
|
-
*
|
|
39120
|
+
* comment), so `metal`/`circle` ROUTED to the legacy `box-shadow` model at
|
|
39121
|
+
* the time (SUPERSEDED 2026-09 below; it no longer routes). The "before"
|
|
39122
|
+
* numbers are large because this campaign scores absolute brightness match,
|
|
39123
|
+
* not just highlight/shadow SIGN agreement (all `getBevelShadow`'s
|
|
39124
|
+
* box-shadow output was previously verified against). Scripts: scratch
|
|
39125
|
+
* tooling, same convention as `com-acceptance.mjs`.
|
|
38746
39126
|
*
|
|
38747
39127
|
* ## Re-run against the CURRENT `threePt` elevationDeg (2026-09, post-lightRig-recalibration)
|
|
38748
39128
|
*
|
|
@@ -38800,6 +39180,16 @@ function isRoutedToLegacyBevelShadow(material, profile) {
|
|
|
38800
39180
|
* material against this same COM ground truth. Neither was completed in
|
|
38801
39181
|
* this pass; see `docs/guide/limitations.md`.
|
|
38802
39182
|
*
|
|
39183
|
+
* ## 2026-09 bevel-profile cross-section + specular-masking follow-up
|
|
39184
|
+
*
|
|
39185
|
+
* Kept in the files they most directly touch (LOC budget): the 12
|
|
39186
|
+
* `a:bevelT/@prst` height-map SHAPES were fit against real COM cross-section
|
|
39187
|
+
* curves for the first time (`visual-3d-bevel-lighting-tables.ts`'s doc +
|
|
39188
|
+
* pinned table in its `.test.ts`), changing `circle`'s `surfaceScaleFactor`
|
|
39189
|
+
* enough that `metal`/`circle` now beats baseline unrouted; a follow-up
|
|
39190
|
+
* specular-band-masking attempt at the saturation defect above was measured
|
|
39191
|
+
* and made things WORSE (`visual-3d-bevel-lighting-routing.ts`'s doc).
|
|
39192
|
+
*
|
|
38803
39193
|
* @module render/visual-3d-bevel-lighting
|
|
38804
39194
|
*/
|
|
38805
39195
|
/**
|
|
@@ -39236,19 +39626,13 @@ function applyHomography(h, p) {
|
|
|
39236
39626
|
*
|
|
39237
39627
|
* 1. The shape's flat picture plane is a unit square in its own local XY
|
|
39238
39628
|
* plane (`z=0`), corners at `(-0.5,-0.5) .. (0.5,0.5)`.
|
|
39239
|
-
* 2. Each corner is projected by {@link projectCorner}:
|
|
39240
|
-
*
|
|
39241
|
-
*
|
|
39242
|
-
*
|
|
39243
|
-
*
|
|
39244
|
-
*
|
|
39245
|
-
*
|
|
39246
|
-
* 3. The pinhole secondary term's focal length is `f = 1/tan(fov/2)` (the
|
|
39247
|
-
* same FOV <-> perspective-distance relationship `visual-3d-camera-fov`
|
|
39248
|
-
* already uses), so `lat=lon=rev=0` reproduces an EXACT identity
|
|
39249
|
-
* homography - the same trivial case `orthographicFront` is COM-measured
|
|
39250
|
-
* to produce - by construction (the secondary term is architecturally
|
|
39251
|
-
* zero whenever either axis is zero, so this holds regardless of `fov`).
|
|
39629
|
+
* 2. Each corner is projected by {@link projectCorner}: an ORTHOGRAPHIC
|
|
39630
|
+
* (parallel, no perspective divide) rotation-composition transform - see
|
|
39631
|
+
* that function's own doc comment for the exact formula and its
|
|
39632
|
+
* derivation.
|
|
39633
|
+
* 3. `lat=lon=rev=0` reproduces an EXACT identity homography - the same
|
|
39634
|
+
* trivial case `orthographicFront` is COM-measured to produce - by
|
|
39635
|
+
* construction (`cos(0)=1`, every other term vanishes).
|
|
39252
39636
|
* 4. `rev` (roll about the view axis) commutes with the projection: rolling
|
|
39253
39637
|
* the camera about its own aim axis is exactly a 2D rotation of the
|
|
39254
39638
|
* already-projected image, applied here as a post-projection step rather
|
|
@@ -39259,88 +39643,96 @@ function applyHomography(h, p) {
|
|
|
39259
39643
|
* `visual-3d-camera-homography`'s existing `homographyToMatrix3d`
|
|
39260
39644
|
* embedding unchanged.
|
|
39261
39645
|
*
|
|
39262
|
-
* ## COM validation
|
|
39646
|
+
* ## COM validation, round 2: the 27-point lat x lon x rev grid (2026-09)
|
|
39647
|
+
*
|
|
39648
|
+
* The first campaign (single COM measurement per case, see history) found
|
|
39649
|
+
* the primary per-axis cosine scale exact for any single-axis `a:rot`, but a
|
|
39650
|
+
* damped pinhole-perspective "secondary term" (weighted by
|
|
39651
|
+
* `sin(lat)*sin(lon)`, FOV-dependent) under-predicted a genuinely combined
|
|
39652
|
+
* pose (`lat=35.26deg lon=45deg rev=45deg`) by ~25-29% relative corner error,
|
|
39653
|
+
* repeatably across two independent measurements. That secondary term is
|
|
39654
|
+
* REPLACED here, not patched: a fresh 27-point grid (`lat in {0, 25,
|
|
39655
|
+
* 35.26deg}` x `lon in {0, 25, 45deg}` x `rev in {0, 25, 45deg}`, including
|
|
39656
|
+
* both prior points exactly) was rendered via real PowerPoint COM
|
|
39657
|
+
* (`Slide.Export`, 144px/in, flat 2in `prst="orthographicFront"` + `a:rot`
|
|
39658
|
+
* squares) and each cell's 4 corners extracted by convex-hull fit (the
|
|
39659
|
+
* boundary/hull/quad-simplification method `visual-3d-camera-homography.ts`
|
|
39660
|
+
* already validated, NOT the fragile "4 extreme pixels" shortcut the first
|
|
39661
|
+
* campaign used, which silently mis-ordered corners for any near-45deg `rev`
|
|
39662
|
+
* by matching against UNDISTORTED reference positions - a large rotation's
|
|
39663
|
+
* true nearest axis-aligned corner is not its physical origin; fixed by
|
|
39664
|
+
* matching against a cosine-scale-plus-rev PRIOR position instead).
|
|
39665
|
+
*
|
|
39666
|
+
* Fitting the 27 measured cells against every hypothesis in the task brief
|
|
39667
|
+
* (Euler order lon-then-lat vs lat-then-lon vs the old damped-pinhole model;
|
|
39668
|
+
* orthographic vs a true perspective divide at the override's own FOV;
|
|
39669
|
+
* rotation about the shape centre - confirmed by near-zero centroid shift
|
|
39670
|
+
* uncorrelated with a cell's distance from the canvas centre, ruling out a
|
|
39671
|
+
* slide-centre pivot) found an EXACT closed form: keep `x` as the
|
|
39672
|
+
* already-validated pure cosine scale (COM-confirmed independent of `lat`:
|
|
39673
|
+
* the same `lon=45deg` cells produced identical `x` at `lat=25deg` and
|
|
39674
|
+
* `lat=35.26deg`), and add a rotation-composition cross term to `y` ONLY,
|
|
39675
|
+
* with a NEGATIVE sign relative to the naive `Ry(lon).Rx(lat)` composition
|
|
39676
|
+
* this module's first attempt used:
|
|
39677
|
+
*
|
|
39678
|
+
* ```
|
|
39679
|
+
* x = X * cos(lon)
|
|
39680
|
+
* y = Y * cos(lat) - X * sin(lat) * sin(lon)
|
|
39681
|
+
* ```
|
|
39263
39682
|
*
|
|
39264
|
-
*
|
|
39265
|
-
*
|
|
39266
|
-
*
|
|
39267
|
-
*
|
|
39268
|
-
*
|
|
39269
|
-
*
|
|
39270
|
-
*
|
|
39271
|
-
*
|
|
39272
|
-
*
|
|
39273
|
-
*
|
|
39683
|
+
* Across all 27 grid cells (script: `gen-fixture.mjs` -> `measure.ps1` ->
|
|
39684
|
+
* `solve-corners.mjs` -> `fit-model.mjs`, scratch/one-off, not committed):
|
|
39685
|
+
* average max-corner error 0.61%, median well under 1%, worst 3 cells (all
|
|
39686
|
+
* `rev=25deg`, an "ugly" non-axis-aligned roll angle that maximises
|
|
39687
|
+
* antialiasing-boundary noise at a 288px-side element, not a systematic
|
|
39688
|
+
* lat/lon pattern) at 2.10% / 1.96% / 1.70% - see the raw per-cell table
|
|
39689
|
+
* below. This lands the combined case in the SAME ~1% band as the
|
|
39690
|
+
* single-axis cases, closing the ~25-29% gap the first campaign left open,
|
|
39691
|
+
* with NO fov/zoom dependency at all: the model is purely orthographic, so
|
|
39692
|
+
* `ParametricCameraParams.fovRad` is now unused by {@link projectCorner}
|
|
39693
|
+
* (kept in the type for API stability; `@fov`/`@zoom` were not
|
|
39694
|
+
* independently varied by this campaign, only held at their
|
|
39695
|
+
* `orthographicFront` default, so this does not claim they have no effect
|
|
39696
|
+
* under some other combination this grid did not cover).
|
|
39697
|
+
*
|
|
39698
|
+
* Raw per-cell max-corner error (fraction of the square's own side, sorted
|
|
39699
|
+
* worst-first; `lat=35.26` is the isometric angle `atan(1/sqrt(2))`, reusing
|
|
39700
|
+
* the first campaign's own combined-case angle set):
|
|
39274
39701
|
*
|
|
39275
39702
|
* ```
|
|
39276
|
-
*
|
|
39277
|
-
*
|
|
39278
|
-
*
|
|
39279
|
-
*
|
|
39703
|
+
* lat25_lon0_rev25 2.098% lat25_lon0_rev0 0.390%
|
|
39704
|
+
* lat0_lon45_rev25 1.959% lat25_lon0_rev45 0.362%
|
|
39705
|
+
* lat35.26_lon45_rev25 1.696% lat35.26_lon0_rev45 0.353%
|
|
39706
|
+
* lat0_lon25_rev25 0.776% lat0_lon25_rev45 0.349%
|
|
39707
|
+
* lat25_lon25_rev25 0.756% lat35.26_lon0_rev25 0.347%
|
|
39708
|
+
* lat25_lon25_rev45 0.735% lat35.26_lon45_rev45 0.347%
|
|
39709
|
+
* lat0_lon0_rev45 0.669% lat25_lon45_rev0 0.342%
|
|
39710
|
+
* lat35.26_lon25_rev45 0.654% lat25_lon25_rev0 0.323%
|
|
39711
|
+
* lat35.26_lon25_rev25 0.585% lat35.26_lon45_rev0 0.312%
|
|
39712
|
+
* lat0_lon0_rev0 0.491% lat35.26_lon25_rev0 0.304%
|
|
39713
|
+
* lat0_lon45_rev0 0.450% lat25_lon45_rev25 0.292%
|
|
39714
|
+
* lat25_lon45_rev45 0.420% lat0_lon0_rev25 0.259%
|
|
39715
|
+
* lat0_lon45_rev45 0.404%
|
|
39716
|
+
* lat35.26_lon0_rev0 0.402% (avg 0.610%, max 2.098%)
|
|
39280
39717
|
* ```
|
|
39281
39718
|
*
|
|
39282
|
-
*
|
|
39283
|
-
*
|
|
39284
|
-
*
|
|
39285
|
-
*
|
|
39286
|
-
*
|
|
39287
|
-
*
|
|
39288
|
-
*
|
|
39289
|
-
*
|
|
39290
|
-
*
|
|
39291
|
-
*
|
|
39292
|
-
*
|
|
39293
|
-
*
|
|
39294
|
-
*
|
|
39295
|
-
*
|
|
39296
|
-
* symmetric per-axis scale with NO keystone and NO centre shift, which the
|
|
39297
|
-
* old model could not represent either (it always keystones via
|
|
39298
|
-
* `perspective()`). `lon`'s sign was independently isolated and COM-checked
|
|
39299
|
-
* (a positive `lon` measured a symmetric width shrink, matching this
|
|
39300
|
-
* module).
|
|
39301
|
-
*
|
|
39302
|
-
* `lat`'s sign is NOT independently observable from a single-axis case:
|
|
39303
|
-
* `cos` is an even function, so this module's primary term produces the
|
|
39304
|
-
* IDENTICAL homography for `lat=+25deg` and `lat=-25deg` in isolation (no
|
|
39305
|
-
* `lon`) - proven analytically, and confirmed by a real `lat=25deg only`
|
|
39306
|
-
* COM measurement (2026-09, same 2in-square/144px-in methodology) matching
|
|
39307
|
-
* this module's prediction to within 1px on every one of the 4 measured
|
|
39308
|
-
* corners (predicted top/bottom edge at y=56.7/317.7 vs measured 56/317,
|
|
39309
|
-
* width unchanged both sides). Sign only becomes observable jointly with
|
|
39310
|
-
* `lon` (the secondary term), which the combined case below already
|
|
39311
|
-
* exercises; a single-axis case genuinely cannot add information here.
|
|
39312
|
-
*
|
|
39313
|
-
* `rev`'s sign WAS independently isolated: a real `rev=45deg only` COM
|
|
39314
|
-
* measurement (lat=lon=0) produced a diamond-oriented square whose 4 extreme
|
|
39315
|
-
* points matched this module's predicted corner-to-extreme mapping (which
|
|
39316
|
-
* original corner becomes the new top/right/bottom/left vertex) for a
|
|
39317
|
-
* POSITIVE `rev`, each within about 10 degrees of angle from the shape's own
|
|
39318
|
-
* centre (a small, consistent systematic offset in the SAME rotational
|
|
39319
|
-
* sense across all 4 points, not a sign flip) - this module's `rev` sign
|
|
39320
|
-
* convention is therefore COM-confirmed, not merely architecturally
|
|
39321
|
-
* plausible.
|
|
39322
|
-
*
|
|
39323
|
-
* A second, independent combined-case measurement (a fresh fixture, same
|
|
39324
|
-
* lat=35.26/lon=45/rev=45 angles) reproduced the same ~25-29% relative
|
|
39325
|
-
* corner error as the original campaign above (70.8px average this time, vs
|
|
39326
|
-
* 82px originally, both on a 288px element) - confirming the combined-case
|
|
39327
|
-
* residual is a real, repeatable limitation of this module's secondary term,
|
|
39328
|
-
* not measurement noise from a single run. The fixture/export/pixel-sampling
|
|
39329
|
-
* scripts used for all of this measurement were scratch, one-off tooling
|
|
39330
|
-
* (not committed - see the task report for the methodology if reproducing).
|
|
39719
|
+
* `lon`'s sign was independently isolated and COM-checked in the first
|
|
39720
|
+
* campaign (a positive `lon` measured a symmetric width shrink, matching
|
|
39721
|
+
* this module) and is unaffected by the cross-term replacement (`x` is
|
|
39722
|
+
* unchanged). `lat`'s sign is not independently observable from a
|
|
39723
|
+
* single-axis case (`cos` is even) but IS observable jointly with `lon` via
|
|
39724
|
+
* the cross term; the 27-point grid's fit (rather than an isolated
|
|
39725
|
+
* combined-case check) is itself the confirmation this module's `lat` sign
|
|
39726
|
+
* convention is correct across the whole grid, not just one pose. `rev`'s
|
|
39727
|
+
* sign was independently isolated in the first campaign (a real `rev=45deg
|
|
39728
|
+
* only` measurement matched this module's predicted corner-to-extreme
|
|
39729
|
+
* mapping for a positive `rev`) and is reused unchanged here: it is still
|
|
39730
|
+
* applied as a simple post-projection 2D roll, and the fit above already
|
|
39731
|
+
* exercises every `rev` level jointly with every `lat`/`lon` combination
|
|
39732
|
+
* without needing a different composition order.
|
|
39331
39733
|
*
|
|
39332
39734
|
* @module render/visual-3d-camera-parametric
|
|
39333
39735
|
*/
|
|
39334
|
-
function rotateX(v, angle) {
|
|
39335
|
-
const c = Math.cos(angle);
|
|
39336
|
-
const s = Math.sin(angle);
|
|
39337
|
-
return { x: v.x, y: v.y * c - v.z * s, z: v.y * s + v.z * c };
|
|
39338
|
-
}
|
|
39339
|
-
function rotateY(v, angle) {
|
|
39340
|
-
const c = Math.cos(angle);
|
|
39341
|
-
const s = Math.sin(angle);
|
|
39342
|
-
return { x: v.x * c + v.z * s, y: v.y, z: -v.x * s + v.z * c };
|
|
39343
|
-
}
|
|
39344
39736
|
function rotate2d(p, angle) {
|
|
39345
39737
|
if (angle === 0) {
|
|
39346
39738
|
return p;
|
|
@@ -39353,52 +39745,22 @@ function rotate2d(p, angle) {
|
|
|
39353
39745
|
* Project one local unit-square corner `(x, y)` (already centred, y-up)
|
|
39354
39746
|
* through the camera.
|
|
39355
39747
|
*
|
|
39356
|
-
*
|
|
39357
|
-
*
|
|
39358
|
-
*
|
|
39359
|
-
* `
|
|
39360
|
-
*
|
|
39361
|
-
*
|
|
39362
|
-
*
|
|
39363
|
-
*
|
|
39364
|
-
*
|
|
39365
|
-
*
|
|
39366
|
-
*
|
|
39367
|
-
*
|
|
39368
|
-
* pinhole formula's deviation from the cosine term) is blended in only when
|
|
39369
|
-
* BOTH `lat` and `lon` are nonzero at once (weighted by `sin(lat)*sin(lon)`,
|
|
39370
|
-
* which is exactly 0 for any single-axis rotation, so that COM-validated
|
|
39371
|
-
* case is reproduced UNCHANGED). This mirrors the preset table's own
|
|
39372
|
-
* two-axis families (`*Facing`/`Contrasting*`/`Heroic*`) genuinely needing a
|
|
39373
|
-
* skew a pure scale cannot represent. `fov` modulates this secondary term's
|
|
39374
|
-
* strength (a wider FOV -> a nearer, more exaggerated camera -> more
|
|
39375
|
-
* foreshortening), the only place `@fov`/`@zoom` affect this model: no COM
|
|
39376
|
-
* data varies FOV independently for an override, so treat this coupling as
|
|
39377
|
-
* physically-motivated but NOT independently calibrated, unlike the
|
|
39378
|
-
* COM-validated primary term.
|
|
39748
|
+
* `x` is a pure per-axis cosine foreshortening (`x = X*cos(lon)`), COM-
|
|
39749
|
+
* confirmed independent of `lat` (see the module doc comment): the 27-point
|
|
39750
|
+
* grid's `lon=45deg` cells produced the identical `x` at both `lat=25deg`
|
|
39751
|
+
* and `lat=35.26deg`. `y` gets the SAME cosine scale on its own axis
|
|
39752
|
+
* (`Y*cos(lat)`) plus a rotation-composition cross term, `-X*sin(lat)*
|
|
39753
|
+
* sin(lon)`, that is exactly 0 whenever EITHER axis is 0 (so both the
|
|
39754
|
+
* identity and every single-axis case reproduce their already-COM-validated
|
|
39755
|
+
* result unchanged) and otherwise fits the 27-point grid to within ~1% on
|
|
39756
|
+
* average (see the module doc comment for the full per-cell table). This is
|
|
39757
|
+
* a purely ORTHOGRAPHIC transform (no perspective divide, no `fov`
|
|
39758
|
+
* dependency): a genuine pinhole projection was one of the hypotheses tested
|
|
39759
|
+
* against the grid and fit measurably worse than this cross term.
|
|
39379
39760
|
*/
|
|
39380
39761
|
function projectCorner(localX, localY, params) {
|
|
39381
|
-
const
|
|
39382
|
-
const
|
|
39383
|
-
let x = localX * scaleX;
|
|
39384
|
-
let y = localY * scaleY;
|
|
39385
|
-
const twoAxisWeight = Math.sin(params.latRad) * Math.sin(params.lonRad);
|
|
39386
|
-
if (twoAxisWeight !== 0) {
|
|
39387
|
-
const f = 1 / Math.tan(params.fovRad / 2);
|
|
39388
|
-
const local = { x: localX, y: localY, z: 0 };
|
|
39389
|
-
// R^T * P, where R = Ry(lon) . Rx(lat): apply Ry(-lon) then Rx(-lat).
|
|
39390
|
-
const viewNoTranslate = rotateX(rotateY(local, -params.lonRad), -params.latRad);
|
|
39391
|
-
const viewZ = viewNoTranslate.z - f;
|
|
39392
|
-
// Guard a degenerate camera-through-the-plane case (should not occur
|
|
39393
|
-
// for any realistic lat/lon): skip the secondary term rather than
|
|
39394
|
-
// divide by ~0.
|
|
39395
|
-
if (Math.abs(viewZ) > 1e-6) {
|
|
39396
|
-
const pinholeX = (f * viewNoTranslate.x) / -viewZ;
|
|
39397
|
-
const pinholeY = (f * viewNoTranslate.y) / -viewZ;
|
|
39398
|
-
x += (pinholeX - localX * scaleX) * Math.abs(twoAxisWeight);
|
|
39399
|
-
y += (pinholeY - localY * scaleY) * Math.abs(twoAxisWeight);
|
|
39400
|
-
}
|
|
39401
|
-
}
|
|
39762
|
+
const x = localX * Math.cos(params.lonRad);
|
|
39763
|
+
const y = localY * Math.cos(params.latRad) - localX * Math.sin(params.latRad) * Math.sin(params.lonRad);
|
|
39402
39764
|
return rotate2d({ x, y }, params.revRad);
|
|
39403
39765
|
}
|
|
39404
39766
|
/**
|
|
@@ -43527,6 +43889,7 @@ function deleteTableRow(tableData, rowIdx) {
|
|
|
43527
43889
|
return {
|
|
43528
43890
|
...cc,
|
|
43529
43891
|
text: cell.text || cc.text,
|
|
43892
|
+
textRuns: cell.text ? cell.textRuns : cc.textRuns,
|
|
43530
43893
|
style: cc.style || cell.style,
|
|
43531
43894
|
rowSpan: newRs > 1 ? newRs : undefined,
|
|
43532
43895
|
vMerge: undefined,
|
|
@@ -43658,6 +44021,7 @@ function deleteTableColumn(tableData, colIdx) {
|
|
|
43658
44021
|
adjustedCells[nextColIdx] = {
|
|
43659
44022
|
...nextCell,
|
|
43660
44023
|
text: cell.text || nextCell.text,
|
|
44024
|
+
textRuns: cell.text ? cell.textRuns : nextCell.textRuns,
|
|
43661
44025
|
style: nextCell.style || cell.style,
|
|
43662
44026
|
gridSpan: gs - 1 > 1 ? gs - 1 : undefined,
|
|
43663
44027
|
hMerge: undefined,
|
|
@@ -43734,7 +44098,7 @@ function buildTableDataGrid(element) {
|
|
|
43734
44098
|
* structural edit therefore rebuilds `rawXml` alongside it, exactly as the
|
|
43735
44099
|
* on-canvas cell editor does.
|
|
43736
44100
|
*/
|
|
43737
|
-
function withTableData$1(element, transform) {
|
|
44101
|
+
function withTableData$1(element, transform, edit) {
|
|
43738
44102
|
const tableData = element.tableData;
|
|
43739
44103
|
if (!tableData) {
|
|
43740
44104
|
return element;
|
|
@@ -43745,7 +44109,7 @@ function withTableData$1(element, transform) {
|
|
|
43745
44109
|
}
|
|
43746
44110
|
const updated = { ...element, tableData: next };
|
|
43747
44111
|
if (element.rawXml) {
|
|
43748
|
-
const rawXml = rebuildTableStructureInRawXml(element, next);
|
|
44112
|
+
const rawXml = rebuildTableStructureInRawXml(element, next, edit);
|
|
43749
44113
|
if (rawXml) {
|
|
43750
44114
|
updated.rawXml = rawXml;
|
|
43751
44115
|
}
|
|
@@ -43780,7 +44144,11 @@ function setTableElementCellText(element, rowIndex, colIndex, text) {
|
|
|
43780
44144
|
* @returns A new element with the row inserted.
|
|
43781
44145
|
*/
|
|
43782
44146
|
function insertTableElementRow(element, rowIdx, position) {
|
|
43783
|
-
return withTableData$1(element, (data) => insertTableRow(data, rowIdx, position)
|
|
44147
|
+
return withTableData$1(element, (data) => insertTableRow(data, rowIdx, position), {
|
|
44148
|
+
axis: 'row',
|
|
44149
|
+
action: 'insert',
|
|
44150
|
+
index: position === 'above' ? rowIdx : rowIdx + 1,
|
|
44151
|
+
});
|
|
43784
44152
|
}
|
|
43785
44153
|
/**
|
|
43786
44154
|
* Remove the row at `rowIdx`, preserving merge spans. No-op on the last row.
|
|
@@ -43790,7 +44158,11 @@ function insertTableElementRow(element, rowIdx, position) {
|
|
|
43790
44158
|
* @returns A new element with the row removed.
|
|
43791
44159
|
*/
|
|
43792
44160
|
function removeTableElementRow(element, rowIdx) {
|
|
43793
|
-
return withTableData$1(element, (data) => deleteTableRow(data, rowIdx)
|
|
44161
|
+
return withTableData$1(element, (data) => deleteTableRow(data, rowIdx), {
|
|
44162
|
+
axis: 'row',
|
|
44163
|
+
action: 'delete',
|
|
44164
|
+
index: rowIdx,
|
|
44165
|
+
});
|
|
43794
44166
|
}
|
|
43795
44167
|
/**
|
|
43796
44168
|
* Insert a blank column left or right of `colIdx`, preserving merge spans.
|
|
@@ -43801,7 +44173,11 @@ function removeTableElementRow(element, rowIdx) {
|
|
|
43801
44173
|
* @returns A new element with the column inserted.
|
|
43802
44174
|
*/
|
|
43803
44175
|
function insertTableElementColumn(element, colIdx, position) {
|
|
43804
|
-
return withTableData$1(element, (data) => insertTableColumn(data, colIdx, position)
|
|
44176
|
+
return withTableData$1(element, (data) => insertTableColumn(data, colIdx, position), {
|
|
44177
|
+
axis: 'column',
|
|
44178
|
+
action: 'insert',
|
|
44179
|
+
index: position === 'left' ? colIdx : colIdx + 1,
|
|
44180
|
+
});
|
|
43805
44181
|
}
|
|
43806
44182
|
/**
|
|
43807
44183
|
* Remove the column at `colIdx`, preserving merge spans. No-op on the last one.
|
|
@@ -43811,7 +44187,11 @@ function insertTableElementColumn(element, colIdx, position) {
|
|
|
43811
44187
|
* @returns A new element with the column removed.
|
|
43812
44188
|
*/
|
|
43813
44189
|
function removeTableElementColumn(element, colIdx) {
|
|
43814
|
-
return withTableData$1(element, (data) => deleteTableColumn(data, colIdx)
|
|
44190
|
+
return withTableData$1(element, (data) => deleteTableColumn(data, colIdx), {
|
|
44191
|
+
axis: 'column',
|
|
44192
|
+
action: 'delete',
|
|
44193
|
+
index: colIdx,
|
|
44194
|
+
});
|
|
43815
44195
|
}
|
|
43816
44196
|
/**
|
|
43817
44197
|
* Append a blank row after the last one.
|
|
@@ -45558,8 +45938,17 @@ function computeShadeToTitleFillToRect(title, slideWidthPx, slideHeightPx) {
|
|
|
45558
45938
|
b: clampUnit(1 - (title.y + title.height) / slideHeightPx),
|
|
45559
45939
|
};
|
|
45560
45940
|
}
|
|
45561
|
-
/**
|
|
45562
|
-
|
|
45941
|
+
/**
|
|
45942
|
+
* Matches one `<colour> <position>%` gradient stop token (see
|
|
45943
|
+
* {@link parseGradientCssStops}). The `rgba?(...)` alternative excludes `(`
|
|
45944
|
+
* from its content class (`[^()]*` rather than `[^)]*`): otherwise, on a
|
|
45945
|
+
* string with many repeated unclosed `rgb(` prefixes, each occurrence lets
|
|
45946
|
+
* the content group re-swallow every later `rgb(` before failing to find a
|
|
45947
|
+
* closing `)`, which is `js/polynomial-redos` (quadratic in the number of
|
|
45948
|
+
* repeats). Real `rgba?()` content never contains `(`, so this does not
|
|
45949
|
+
* change what valid input matches.
|
|
45950
|
+
*/
|
|
45951
|
+
const GRADIENT_STOP_TOKEN = /(rgba?\([^()]*\)|#[0-9a-fA-F]{3,8})\s+(-?[\d.]+)%/gu;
|
|
45563
45952
|
function toHexChannel(value) {
|
|
45564
45953
|
return Math.min(255, Math.max(0, Math.round(value)))
|
|
45565
45954
|
.toString(16)
|
|
@@ -96574,8 +96963,20 @@ async function fetchTextIfCrossOriginSafe(url) {
|
|
|
96574
96963
|
return null;
|
|
96575
96964
|
}
|
|
96576
96965
|
}
|
|
96577
|
-
/**
|
|
96578
|
-
|
|
96966
|
+
/**
|
|
96967
|
+
* Every distinct `url(...)` reference inside a `@font-face` CSS block's `src`
|
|
96968
|
+
* list, as a double-quoted, single-quoted, or bare token. The three forms are
|
|
96969
|
+
* separate alternatives (rather than one `["']?...["']?` wrapped around a
|
|
96970
|
+
* single content group) so the surrounding `\s*` never shares characters with
|
|
96971
|
+
* the content group: a naive `\s*["']?([^"')]+)["']?\s*` lets whitespace be
|
|
96972
|
+
* split between the leading `\s*` and the content group in exponentially many
|
|
96973
|
+
* equivalent ways, which is polynomial-time (`js/polynomial-redos`) on an
|
|
96974
|
+
* unclosed `url(` followed by many tabs/spaces (this stylesheet text can come
|
|
96975
|
+
* from a fetched cross-origin `@font-face` CSS file, see
|
|
96976
|
+
* `fetchTextIfCrossOriginSafe`). Each alternative here has a fixed,
|
|
96977
|
+
* non-overlapping character class, so there is only one way to match.
|
|
96978
|
+
*/
|
|
96979
|
+
const FONT_FACE_URL_PATTERN = /url\(\s*(?:"([^"]*)"|'([^']*)'|([^"')\s]+))\s*\)/gu;
|
|
96579
96980
|
/**
|
|
96580
96981
|
* Replace every `url(https://...)` reference in `css` with a `data:` URI of
|
|
96581
96982
|
* the fetched resource, dropping (leaving as-is) any reference that fails to
|
|
@@ -96587,8 +96988,8 @@ const FONT_FACE_URL_PATTERN = /url\(\s*["']?([^"')]+)["']?\s*\)/gu;
|
|
|
96587
96988
|
async function inlineFontFaceUrls(css, fetchDataUrl) {
|
|
96588
96989
|
const urls = new Set();
|
|
96589
96990
|
for (const match of css.matchAll(FONT_FACE_URL_PATTERN)) {
|
|
96590
|
-
const raw = match[1];
|
|
96591
|
-
if (raw.startsWith('http:') || raw.startsWith('https:')) {
|
|
96991
|
+
const raw = match[1] ?? match[2] ?? match[3];
|
|
96992
|
+
if (raw && (raw.startsWith('http:') || raw.startsWith('https:'))) {
|
|
96592
96993
|
urls.add(raw);
|
|
96593
96994
|
}
|
|
96594
96995
|
}
|
|
@@ -96690,10 +97091,25 @@ async function fetchAsDataUrl(url) {
|
|
|
96690
97091
|
return null;
|
|
96691
97092
|
}
|
|
96692
97093
|
}
|
|
97094
|
+
/**
|
|
97095
|
+
* Matches one `url(...)` reference, as a double-quoted, single-quoted, or bare
|
|
97096
|
+
* token. The three forms are matched as separate alternatives (rather than one
|
|
97097
|
+
* `["']?...["']?` wrapped around a single content group) so the surrounding
|
|
97098
|
+
* `\s*` never shares characters with the content group: a naive
|
|
97099
|
+
* `\s*["']?([^"')]+)["']?\s*` lets whitespace be split between the leading
|
|
97100
|
+
* `\s*` and the content group in exponentially many equivalent ways, which is
|
|
97101
|
+
* polynomial-time (`js/polynomial-redos`) on an unclosed `url(` followed by
|
|
97102
|
+
* many tabs/spaces. Each alternative here has a fixed, non-overlapping
|
|
97103
|
+
* character class, so there is only one way to match.
|
|
97104
|
+
*/
|
|
97105
|
+
const CSS_URL_PATTERN = /url\(\s*(?:"([^"]*)"|'([^']*)'|([^"')\s]+))\s*\)/u;
|
|
96693
97106
|
/** Extract a `url(...)` reference from a CSS value; returns `null` when none is present. */
|
|
96694
97107
|
function extractCssUrl(value) {
|
|
96695
|
-
const match =
|
|
96696
|
-
|
|
97108
|
+
const match = CSS_URL_PATTERN.exec(value);
|
|
97109
|
+
if (!match) {
|
|
97110
|
+
return null;
|
|
97111
|
+
}
|
|
97112
|
+
return match[1] ?? match[2] ?? match[3] ?? null;
|
|
96697
97113
|
}
|
|
96698
97114
|
function needsEmbedding(url) {
|
|
96699
97115
|
return url.startsWith('blob:') || url.startsWith('http:') || url.startsWith('https:');
|
|
@@ -100638,6 +101054,21 @@ function groupTilesByRow(tiles) {
|
|
|
100638
101054
|
const rowCount = tiles.reduce((max, t) => Math.max(max, t.row), 0) + 1;
|
|
100639
101055
|
const rows = Array.from({ length: rowCount }, () => []);
|
|
100640
101056
|
for (const tile of tiles) {
|
|
101057
|
+
// `row`/`col` are declared as `number`, but this is a public export: a
|
|
101058
|
+
// caller could pass a `RasterizedTile[]` built from untrusted data whose
|
|
101059
|
+
// `row`/`col` are, at runtime, a string like `__proto__`. Indexing an
|
|
101060
|
+
// array with that string resolves through `Array.prototype` (itself
|
|
101061
|
+
// inherited from `Object.prototype`), so `rows[tile.row][tile.col] = tile`
|
|
101062
|
+
// would assign onto `Array.prototype` rather than `rows`, polluting every
|
|
101063
|
+
// array in the process. Requiring a genuine non-negative integer index
|
|
101064
|
+
// closes that off without changing behaviour for any real tile, which is
|
|
101065
|
+
// always produced by `computeExportTilePlan`'s integer loop counters.
|
|
101066
|
+
if (!Number.isInteger(tile.row) ||
|
|
101067
|
+
!Number.isInteger(tile.col) ||
|
|
101068
|
+
tile.row < 0 ||
|
|
101069
|
+
tile.col < 0) {
|
|
101070
|
+
continue;
|
|
101071
|
+
}
|
|
100641
101072
|
rows[tile.row][tile.col] = tile;
|
|
100642
101073
|
}
|
|
100643
101074
|
return rows;
|
|
@@ -103016,7 +103447,7 @@ function createLocalStorageBackend(namespace) {
|
|
|
103016
103447
|
/** Try IndexedDB first; fall back to localStorage on any failure. */
|
|
103017
103448
|
async function resolveBackend(dbName, namespace) {
|
|
103018
103449
|
try {
|
|
103019
|
-
const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-
|
|
103450
|
+
const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-D_H4MXBh.mjs');
|
|
103020
103451
|
const db = await openChatDb(dbName);
|
|
103021
103452
|
return createIdbBackend(db);
|
|
103022
103453
|
}
|
|
@@ -109650,10 +110081,14 @@ class EditorContextMenuComponent {
|
|
|
109650
110081
|
return;
|
|
109651
110082
|
}
|
|
109652
110083
|
const updated = op(ctx.element, ctx.sel);
|
|
109653
|
-
if (updated.tableData) {
|
|
109654
|
-
|
|
110084
|
+
if (updated !== ctx.element && updated.tableData) {
|
|
110085
|
+
const patch = {
|
|
109655
110086
|
tableData: updated.tableData,
|
|
109656
|
-
}
|
|
110087
|
+
};
|
|
110088
|
+
if (updated.rawXml !== ctx.element.rawXml) {
|
|
110089
|
+
patch.rawXml = updated.rawXml;
|
|
110090
|
+
}
|
|
110091
|
+
this.editor.updateElement(this.slideIndex(), ctx.element.id, patch);
|
|
109657
110092
|
}
|
|
109658
110093
|
}
|
|
109659
110094
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: EditorContextMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
@@ -142944,7 +143379,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
142944
143379
|
}], 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 }] }] } });
|
|
142945
143380
|
|
|
142946
143381
|
// Generated by scripts/inline-shared.mjs from package.json. Do not edit.
|
|
142947
|
-
const PPTX_ANGULAR_VIEWER_VERSION = "3.
|
|
143382
|
+
const PPTX_ANGULAR_VIEWER_VERSION = "3.15.0";
|
|
142948
143383
|
|
|
142949
143384
|
/**
|
|
142950
143385
|
* account-page.component.ts: File > Account content.
|
|
@@ -181406,4 +181841,4 @@ function cn(...values) {
|
|
|
181406
181841
|
*/
|
|
181407
181842
|
|
|
181408
181843
|
export { CollaborationService as $, AFTER_ANIMATION_VALUES as A, AnimationAuthorPanelComponent as B, AnimationPanelComponent as C, AnimationPlaybackService as D, AutosaveRecoveryDialogComponent as E, AutosaveService as F, BroadcastDialogComponent as G, CHART_EDITOR_STYLES as H, CURSOR_PALETTE as I, CanvasFitService as J, ChartAxisOptionsComponent as K, ChartAxisStyleOptionsComponent as L, ChartComboTypeOptionsComponent as M, ChartDataEditorComponent as N, ChartDataLabelOptionsComponent as O, ChartDatapointMarkerOptionsComponent as P, ChartDatapointOptionsComponent as Q, ChartDisplayOptionsComponent as R, ChartElementViewComponent as S, ChartErrorBarOptionsComponent as T, ChartMarkerOptionsComponent as U, ChartPartSelectionService as V, ChartPrimitivesComponent as W, ChartRendererComponent as X, ChartTrendlineOptionsComponent as Y, ChartTypeSelectorComponent as Z, CollaborationCursorsComponent as _, ALIGN_OPTIONS as a, InsertSmartArtDialogComponent as a$, ColorChangedImageComponent as a0, CommentMarkersOverlayComponent as a1, CommentsPanelComponent as a2, CommentsService as a3, ComparePanelComponent as a4, ConnectorRendererComponent as a5, ConnectorTextOverlayComponent as a6, CustomShowsComponent as a7, DEFAULT_BOUNDS as a8, DEFAULT_BROADCAST_SERVER_URL as a9, EditorToolbarComponent as aA, EffectsPanelComponent as aB, ElementRendererComponent as aC, EmbeddedFontsService as aD, EncryptedFileDialogComponent as aE, EquationEditorDialogComponent as aF, EquationRendererComponent as aG, EquationTemplateGalleryComponent as aH, ExportProgressModalComponent as aI, ExportService as aJ, FieldContextService as aK, FindBarComponent as aL, FindReplaceBarComponent as aM, FollowModeBarComponent as aN, FontEmbeddingListComponent as aO, FontEmbeddingPanelComponent as aP, GALLERY_THEME_PRESETS as aQ, GOOGLE_WEBFONTS_LINK_ID as aR, GRIDLINE_COLOR$1 as aS, GoogleWebfontsService as aT, GradientPickerComponent as aU, HANDOUT_OPTIONS as aV, HeaderFooterDialogComponent as aW, HyperlinkDialogComponent as aX, ImagePropertiesPanelComponent as aY, InkDrawingService as aZ, InkRendererComponent as a_, DEFAULT_CANVAS_HEIGHT as aa, DEFAULT_CANVAS_WIDTH as ab, DEFAULT_COLOR_SCHEME as ac, DEFAULT_FILL_COLOR$1 as ad, DEFAULT_LAYOUT as ae, DEFAULT_PALETTE$1 as af, DEFAULT_PATTERN_FILL_PRESET as ag, DEFAULT_PRINT_SETTINGS as ah, DEFAULT_SLIDE_BACKGROUND as ai, DEFAULT_STROKE_COLOR as aj, DEFAULT_STYLE as ak, DEFAULT_TABLE_ROW_HEIGHT as al, DEFAULT_TEXT_COLOR$2 as am, DEFAULT_VIEWER_PROFILE as an, DIRECTIONAL_PRESETS as ao, DIRECTION_OPTIONS as ap, DocumentPropertiesCardComponent as aq, EFFECT_SOUND_CATALOGUE as ar, EMBEDDED_FONTS_STYLE_ID as as, EMPHASIS_PRESETS as at, ENTRANCE_PRESETS as au, TEMPLATES as av, EXIT_PRESETS as aw, EditorContextMenuComponent as ax, EditorHistory as ay, EditorStateService as az, ANIMATION_PRESET_CATEGORIES as b, RibbonArrangeSectionComponent as b$, InspectorPaneHeaderComponent as b0, InspectorPanelComponent as b1, IsMobileService as b2, KeepAnnotationsDialogComponent as b3, LOCALE_CATALOG as b4, LONG_PRESS_DURATION_MS as b5, LONG_PRESS_MOVE_TOLERANCE_PX as b6, LoadContentService as b7, LocalPresencePublisher as b8, MAX_ZOOM_SCALE as b9, PX_PER_INCH as bA, PasswordProtectionDialogComponent as bB, PasswordStrengthMeterComponent as bC, PowerPointViewerComponent as bD, PresentToolbarAutoHide as bE, PresentationAnnotationOverlayComponent as bF, PresentationAnnotationsService as bG, PresentationOverlayComponent as bH, PresentationPropertiesPanelComponent as bI, PresentationSettingsCardComponent as bJ, PresentationSubtitleBarComponent as bK, PresentationToolbarComponent as bL, PresentationTransitionOverlayComponent as bM, PresenterViewComponent as bN, PresenterWindowService as bO, PrintDialogComponent as bP, PrintService as bQ, PrintSettingsPanelComponent as bR, PropertiesDialogComponent as bS, REPEAT_MODE_OPTIONS as bT, RESIZE_HANDLES as bU, RULER_FONT_SIZE as bV, RULER_THICKNESS as bW, ReadingViewOverlayComponent as bX, RemoteSelectionOverlayComponent as bY, RibbonAnimationGalleryComponent as bZ, RibbonAnimationsSectionComponent as b_, MIN_ZOOM_SCALE as ba, MOTION_PATH_COLUMNS as bb, MediaPreviewComponent as bc, MediaPropertiesPanelComponent as bd, MediaRendererComponent as be, MediaTrimTimelineComponent as bf, MobileBottomBarComponent as bg, MobileMenuSheetComponent as bh, MobilePresenterViewComponent as bi, MobileSheetComponent as bj, MobileSlidesSheetComponent as bk, MobileToolbarComponent as bl, ModalDialogComponent as bm, Model3DRendererComponent as bn, NotesHandoutCardComponent as bo, NotesPanelComponent as bp, NotesToolbarComponent as bq, OleRendererComponent as br, OutlineViewOverlayComponent as bs, POWER_POINT_VIEWER_PROVIDERS as bt, PPTX_OPEN_ACCEPT as bu, PRESENTATION_OPEN_EXTENSIONS as bv, PRESENTER_CHANNEL_NAME as bw, PRESENTER_MSG_ORIGIN as bx, PRESENTER_TIMER_SEGMENT_MS as by, PX_PER_CM as bz, AUDIENCE_HASH as c, TABLE_STRUCTURE_TOGGLES as c$, RibbonColorPopoverComponent as c0, RibbonComponent as c1, RibbonDesignSectionComponent as c2, RibbonDrawSectionComponent as c3, RibbonDrawingGroupComponent as c4, RibbonEditingSectionComponent as c5, RibbonFileSectionComponent as c6, RibbonFontControlsComponent as c7, RibbonHomeSectionComponent as c8, RibbonHyperlinkButtonComponent as c9, SettingsAppearanceTabComponent as cA, SettingsDialogComponent as cB, SettingsLanguageTabComponent as cC, ShareDialogComponent as cD, ShortcutPanelComponent as cE, ShowOptionsFieldsetComponent as cF, ShowSlidesFieldsetComponent as cG, SignatureStrippedDialogComponent as cH, SignaturesPanelComponent as cI, SignaturesService as cJ, SlideBackgroundCardComponent as cK, SlideCanvasComponent as cL, SlideDefaultInspectorComponent as cM, SlideDiffChangesComponent as cN, SlideDiffRowComponent as cO, SlideDiffThumbnailsComponent as cP, SlideSizeCardComponent as cQ, SlideSorterOverlayComponent as cR, SlideThemeOverridePanelComponent as cS, SlideTransitionCardComponent as cT, SlidesPanelComponent as cU, SmartArt3DRendererComponent as cV, SmartArt3DService as cW, SmartArtPreviewComponent as cX, SmartArtPropertiesComponent as cY, SmartArtRendererComponent as cZ, StatusBarComponent as c_, RibbonInsertFieldsComponent as ca, RibbonInsertSectionComponent as cb, RibbonMotionPathGalleryComponent as cc, RibbonParagraphControlsComponent as cd, RibbonPrimaryRowComponent as ce, RibbonReviewSectionComponent as cf, RibbonShapeExtrasComponent as cg, RibbonSlideshowSectionComponent as ch, RibbonTransitionsSectionComponent as ci, RibbonViewSectionComponent as cj, RulerGuidesService as ck, SEQUENCE_OPTIONS as cl, SEVERITY_GROUPS as cm, SEVERITY_LABELS as cn, SHORTCUT_REFERENCE_ITEMS as co, SLIDE_TRANSITION_KEYFRAMES as cp, DEFAULT_PALETTE as cq, PALETTES$1 as cr, SMART_ART_COLOR_SCHEMES as cs, SMART_ART_STYLE_OPTIONS as ct, SUB_ITEM_LABEL as cu, SVG_WARP_PRESETS as cv, SWIPE_MAX_VERTICAL_PX as cw, SWIPE_THRESHOLD_PX as cx, SelectionPaneComponent as cy, SetUpSlideShowDialogComponent as cz, AUDIENCE_NONCE_KEY as d, alignPatch as d$, TEXT_3D_BOTTOM_BEVEL_KEYS as d0, TEXT_3D_TOP_BEVEL_KEYS as d1, TEXT_DIRECTION_OPTIONS$1 as d2, THEME_CATALOG as d3, TIMING_CURVE_OPTIONS as d4, TRIGGER_OPTIONS as d5, TYPE_LABELS as d6, TableCellAdvancedFillComponent as d7, TableCellFormattingComponent as d8, TableDataEditorComponent as d9, ViewerExportService as dA, ViewerExtraDialogsComponent as dB, ViewerFileIOService as dC, ViewerFindReplaceService as dD, ViewerFormatPainterService as dE, ViewerInspectorPanelService as dF, ViewerKeyboardService as dG, ViewerMobileSheetService as dH, ViewerPresentationModeService as dI, ViewerThemeGalleryService as dJ, ViewerTouchGesturesService as dK, ViewerZoomService as dL, WEBM_MIME_CANDIDATES as dM, WriteBackScheduler as dN, ZERO_LINE_COLOR as dO, ZoomNavigationService as dP, ZoomRendererComponent as dQ, ZoomTargetService as dR, addCategory as dS, addCommentToList as dT, addGradientStopPatch as dU, addItem as dV, addSeries as dW, addSubItem as dX, advanceStep as dY, affordanceElements as dZ, aiToggleVisible as d_, TablePropertiesComponent as da, TableRendererComponent as db, TableResizeOverlayComponent as dc, TableSelectionService as dd, TagsCardComponent as de, Text3DBevelSectionComponent as df, Text3DPanelComponent as dg, TextAdvancedPanelComponent as dh, ThemeEditorFieldsComponent as di, ThemeGalleryComponent as dj, ThemeSelectorCardComponent as dk, TitleBarComponent as dl, TitleBarSearchComponent as dm, TransitionDirectionPickerComponent as dn, TransitionPreviewComponent as dp, VALIGN_OPTIONS as dq, VIEWER_THEME as dr, VersionHistoryPanelComponent as ds, ViewerCanvasEditingService as dt, ViewerCollabCursorService as du, ViewerCollaborationSessionService as dv, ViewerCompareService as dw, ViewerCustomShowsService as dx, ViewerDialogsService as dy, ViewerDocumentPropertiesService as dz, AVATAR_COLOR_SWATCHES as e, buildStockViewModel as e$, animationFor as e0, animationPresetLabelKey as e1, annotationMapToInkInserts as e2, applyAcceptedDiff as e3, applyAnimationPreset as e4, applyFindReplacements as e5, applyFormatToElement as e6, applyMove as e7, applyResize as e8, asMediaElement as e9, buildEmbeddedFontStyles as eA, buildEquationElement as eB, buildEquationSegment as eC, buildFallbackViewModel as eD, buildFontFaceRule as eE, buildGradientFillCss as eF, buildGridlinesAndLabels as eG, buildHyperlinkPatch as eH, buildInkContainerStyle as eI, buildInkStrokes as eJ, buildLegend as eK, buildLiveInkStrokeView as eL, buildMarkTooltip as eM, buildModel3DContainerStyle as eN, buildModel3DViewModel as eO, buildOleActionModel as eP, buildOleInfoRows as eQ, buildPatternFillCss as eR, buildPieViewModel as eS, buildPrintHtmlDocument as eT, buildPropertiesPatch as eU, buildRadarViewModel as eV, buildRegionMapViewModel as eW, buildSaveSlides as eX, buildShareUrl as eY, buildSmartArtInsertElement as eZ, buildSmartArtNodes as e_, assignUserColor as ea, attachShowVisibilityPause as eb, attachTouchGestures as ec, axisTickValues as ed, beginNodeEdit as ee, bevelSizePatch as ef, boolFromEvent as eg, bringForward as eh, bringToFront as ei, buildBarActions as ej, buildBroadcastConfig as ek, buildBroadcastViewerUrl as el, buildCategoryLabels as em, buildCellParagraphs as en, buildChartViewModel as eo, buildChatLogExport as ep, buildChatLogMarkdown as eq, buildChromeStyle as er, buildClearHyperlinkPatch as es, buildClickGroups as et, buildColStyles as eu, buildCollaborationConfig as ev, buildComboViewModel as ew, buildCssGradientFromShapeStyle as ex, buildDuotoneFilter as ey, buildDuotoneFilterId as ez, AXIS_LABEL_COLOR as f, computePlotLayout as f$, buildSurfaceViewModel as f0, buildTableViewModel as f1, buildTreemapViewModel as f2, buildTrimFragment as f3, buildWaterfallViewModel as f4, buildZeroLine as f5, buildZoomContainerStyle as f6, buildZoomViewModel as f7, bulletIndentPx as f8, canAddTopLevelNode as f9, collectAccessibilityIssues as fA, collectElementText as fB, collectSlideText as fC, collectStoredChats as fD, collectUsedFontFamilies as fE, columnWidthStyle as fF, commitNodeText as fG, computeAlign as fH, computeAxisTitlePrimitives as fI, computeBarRects as fJ, computeBubbleRadius as fK, computeCornerHandle as fL, computeDistribute as fM, computeDrawingViewBox as fN, computeErrorBarPrimitives as fO, computeFocusTargets as fP, computeGridSpacingPx as fQ, computeHandleBoxes as fR, computeHandoutLayout as fS, computeIsMobile as fT, computeIsTablet as fU, computeLinePoints as fV, computeLinearRegression as fW, computePageCount as fX, computePieLayout as fY, computePieSlicePath as fZ, computePieSlices as f_, canEditSmartArtNodes as fa, canGroupSelection as fb, canRemoveTopLevelNode as fc, canSetStrokeWidth as fd, canStartBroadcast as fe, canStartShare as ff, canUngroupSelection as fg, canUseClipboard as fh, captionDisplayText as fi, cellRunStyle as fj, cellStyleToStyleMap as fk, cellTdStyle as fl, changeCountLabel as fm, changeIcon as fn, characterSpacingPatch as fo, chartPreserveAspectRatio as fp, checkFontAvailable as fq, clampCursorPosition as fr, clampGifDimensions as fs, clampIndex as ft, clampNotesFontSize as fu, clampScale as fv, clampStep as fw, clearAllLocalViewerData as fx, clearAudienceContent as fy, cn as fz, AccessibilityPanelComponent as g, focusTargetChips as g$, computeRSquared as g0, computeRadarPoints as g1, computeResizeHandleBoxes as g2, computeRotateHandleBox as g3, computeScatterDots as g4, computeScatterXDomain as g5, computeSelectionBoxes as g6, computeSingleSelected as g7, computeSlideIndices as g8, computeSnap as g9, disableGlowPatch as gA, disableInnerShadowPatch as gB, disableOuterShadowPatch as gC, disableReflectionPatch as gD, disableSoftEdgePatch as gE, duplicateElementById as gF, durationOf as gG, effectsStateOf as gH, enableGlowPatch as gI, enableInnerShadowPatch as gJ, enableOuterShadowPatch as gK, enableReflectionPatch as gL, enableSoftEdgePatch as gM, encodeGif as gN, endShowMediaCleanup as gO, estimatePageCount as gP, exitPresentationFullscreen as gQ, exportAiChatLogs as gR, extractPathPoints as gS, eyedropperAvailable as gT, fillColorOf$1 as gU, findInSlides as gV, findOwningSlideIndex as gW, findSlideIndexByElementId as gX, firstVisibleIndex as gY, fitPolynomial as gZ, fitZoom as g_, computeStackedBarRects as ga, computeStackedValueRange as gb, computeTrendlinePrimitives as gc, computeValueRange as gd, convertOmmlToMathMl as ge, copyFormatFromElement as gf, countAccessibilityIssues as gg, countAnnotationStrokes as gh, createAngularAiBridge as gi, createCustomShow as gj, createSwipeDismissDrag as gk, createWebrtcBundle as gl, createWebsocketBundle as gm, cssObjectToStyleMap as gn, currentColorScheme as go, currentLayout as gp, currentStyle as gq, defaultCssVars as gr, defaultRadius as gs, defaultThemeColors as gt, deleteElementsByIds as gu, deleteVersion as gv, demoteNode as gw, deriveModel3DBlobUrl as gx, derivePresenceList as gy, describeSmartArtBounds as gz, AccessibilityService as h, insertTableElementRow as h$, fontMimeForFormat as h0, fontSizeOf as h1, forgetSessionDeck as h2, formatAxisValue as h3, formatBytes as h4, formatCursorLabel as h5, formatElapsed as h6, formatFileSize as h7, formatPropertyDate as h8, formatTime as h9, getShapeFillStrokeStyle as hA, getSlideBackgroundStyle as hB, getSlideTransitionAnimations as hC, getSmartArtNodeBounds as hD, getSpeechRecognitionCtor as hE, getTextBlockStyle as hF, getTextWarp as hG, getTouchDistance as hH, getWarpCategory as hI, getWarpPath as hJ, gradientStateFromStyle as hK, gradientStateOf as hL, gradientStatePatch as hM, gradientStopColorCommitPatch as hN, gridColumns as hO, groupIssuesBySeverity as hP, hasAnimation as hQ, hasCopyableFormat as hR, hasExistingLink as hS, hasExitedFullscreen as hT, hasGradientFill as hU, hasPressureVariation as hV, hasVisibleSlideAfter as hW, headerLabel as hX, imageDimensions as hY, inkViewBox as hZ, insertTableElementColumn as h_, fpsToFrameIntervalMs as ha, generateBroadcastRoomId as hb, generateCommentId as hc, generateCustomShowId as hd, generatePressureCircles as he, generateTicks as hf, getClrChangeParams as hg, getContainerStyle as hh, getDuotoneFilterDef as hi, getEffectSoundAsset as hj, getEffectSoundState as hk, getImageSrc as hl, getLocalStorageUsageSummary as hm, getOleAriaLabel as hn, getOleBadgeLabel as ho, getOleDisplayName as hp, getOleDownloadFileName as hq, getOleTypeColor as hr, getOleTypeLabel as hs, getPasswordStrength as ht, getPatternSvg as hu, getPlaceholderStyle as hv, getVersions as hw, getResolvedShapeClipPath as hx, getResolvedShapeClipPathFor as hy, getSessionTabId as hz, AccessibilityTextPanelComponent as i, normalizeSlidesPerPage as i$, interpolateWidth as i0, isAudienceTab as i1, isBold as i2, isBrowserOpenableMime as i3, isChildNode as i4, isElementInteractive as i5, isInjectableUrl as i6, isItalic as i7, isLegacyBinaryPresentation as i8, isPpactionUrl as i9, mergeDown as iA, mergeRight as iB, mergeSelection as iC, mergeTablesDirective as iD, moveElementBy as iE, moveNodeDown as iF, moveNodeUp as iG, msToFrameDelayCs as iH, narrowToCircle as iI, narrowToPolygon as iJ, narrowToRect as iK, newChartElement as iL, newEquationElement as iM, newPresetShapeElement as iN, newShapeElement as iO, newSmartArtElement as iP, newTableElement as iQ, newTextElement as iR, nextVisibleIndex as iS, nodeBold as iT, nodeEditBox as iU, nodeFillColor as iV, nodeFontColor as iW, nodeIdFromKey as iX, nodeItalic as iY, nodeStyle as iZ, normalizeFontFormat as i_, isPresenterMessage as ia, isSigned as ib, isSupportedPresentationFile as ic, isTextElement as id, isTwoTableFocus as ie, isUnderline as ig, isUrlSafe as ih, isValidRoomId as ii, isViewportBackgroundPressTarget as ij, isZoomActivationKey as ik, issueTrackKey as il, issueTypeLabel as im, keyToLabel as io, lastVisibleIndex as ip, latexToMathml as iq, layoutConnectorPaints as ir, layoutNodeLabels as is, linePointsToSvgString as it, lineSpacingPatch as iu, loadAudienceContent as iv, loadSessionDeck as iw, mediaFallbackFor as ix, mediaSurfaceFor as iy, mergeCaptionResults as iz, AccountPageComponent as j, resolveParagraphBullet as j$, normalizeValue as j0, numFromEvent as j1, ommlToMathml as j2, ooxmlDashToCssBorderStyle as j3, openNativeEyeDropper as j4, overallStatus as j5, paletteColor as j6, parseAudienceNonce as j7, parseNodeTextarea as j8, partitionSlides as j9, readAsDataUrl as jA, recordWebm as jB, registerCrossSlideAudio as jC, rememberSessionDeck as jD, removeAnimation as jE, removeCategory as jF, removeTableElementColumn as jG, removeCommentFromList as jH, removeElementAnimation as jI, removeGradientStopPatch as jJ, removeNode as jK, removeTableElementRow as jL, removeSeries as jM, renderToCanvas as jN, reorderAnimationDown as jO, reorderAnimationUp as jP, replaceInSlides as jQ, replaceMatch as jR, requestPresentationFullscreen as jS, resizeElement as jT, resolveCaptionTracks as jU, resolveChartKind as jV, resolveFontVariant as jW, resolveHyperlinkHref as jX, resolveInteractiveElementId as jY, resolveMediaSrc as jZ, resolveOleType as j_, patchChartData as ja, patchChartStyle as jb, patchTableData as jc, patchTextStyle as jd, patternPresetOptions as je, pendingElementStyles as jf, pickColorByClickFallback as jg, pickFile as jh, pickSupportedMimeType as ji, planGifFrames as jj, planVideoSegments as jk, pointFromPointerEvent as jl, pointsToSvgPathD as jm, presenceToCursors as jn, presentationBaseName as jo, presentationStageStyle as jp, presenterTimerProgress as jq, presetByLayout as jr, presetsForCategory as js, pressuresToWidths as jt, prevVisibleIndex as ju, projectDrawingShapes as jv, promoteNode as jw, provideViewerTheme as jx, radarAngle as jy, radarRingPoints as jz, ActionSettingsPanelComponent as k, setSequence as k$, resolvePresenterNotes as k0, resolveProfileInitial as k1, resolveRegionCode as k2, resolveRibbonCanGroup as k3, resolveSlideAutoAdvanceMs as k4, resolvePalette as k5, resolveThemeCatalogEntry as k6, resolveTransitionDuration as k7, restoreSessionDeck as k8, revealedElementStyles as k9, setAnimationEmphasis as kA, setAnimationEntrance as kB, setAnimationExit as kC, setAxis as kD, setAxisLogScale as kE, setAxisTitleStyle as kF, setCategoryLabel as kG, setCellText as kH, setColorScheme as kI, setDataLabels as kJ, setDataPointExplosion as kK, setDataPointFill as kL, setDataPointLabel as kM, setDataPointMarker as kN, setDelay as kO, setDirection as kP, setDuration as kQ, setEffectSound as kR, setEffectStockSound as kS, setElementPosition as kT, setGridlineStyle as kU, setLayout as kV, setLegend as kW, setNodeStyle as kX, setNodeText as kY, setRepeatCount as kZ, setRepeatMode as k_, routeOrthogonalConnector as ka, rowStyle as kb, rulerDragToGuidePosition as kc, rulerHighlight as kd, rulerStripTicks as ke, sampleColorFromSlide as kf, sanitizeColor as kg, sanitizeSlideIndex as kh, sanitizeUserName as ki, saveViewerProfile as kj, savedPresentationFileName as kk, scanAvailableFonts as kl, searchSlides as km, seedBroadcastFields as kn, seedHyperlinkDraft as ko, seedPropertiesDraft as kp, seedShareFields as kq, segmentFrameCount as kr, selectValue$3 as ks, sendBackward as kt, sendToBack as ku, sequentialColorScale as kv, serializeWriteBack as kw, seriesColor as kx, setAfterAnimation as ky, setAfterAnimationColor as kz, AdvancedChartEditorComponent as l, updateReflectionPatch as l$, setSeriesChartType as l0, setSeriesColor as l1, setSeriesErrorBars as l2, setSeriesMarker as l3, setSeriesName as l4, setSeriesTrendline as l5, setSeriesValue as l6, setStyle as l7, setTimingCurve as l8, setTitle as l9, strokeWidthOf as lA, styleShadowFilter as lB, surfaceColor as lC, textAdvancedPatch as lD, textAdvancedStateFromStyle as lE, textAdvancedStateOf as lF, textColorOf as lG, textDirectionPatch as lH, textFontSizePatch as lI, textStyleOf as lJ, textStylePatch as lK, themeStyle as lL, themeToCssVars as lM, thumbnailHeight as lN, thumbnailZoom as lO, toggleCommentResolvedInList as lP, toggleNodeBold as lQ, toggleNodeItalic as lR, toggleSheet as lS, topLevelNodeCount as lT, transformSelectedTextCase as lU, translationsEn as lV, updateElementById as lW, updateGlowPatch as lX, updateGradientStopPatch as lY, updateInnerShadowPatch as lZ, updateOuterShadowPatch as l_, setTrigger as la, setTriggerShapeId as lb, shapeStylePatch$1 as lc, sheetAfterNavigate as ld, shouldBlockClickAdvance as le, shouldUseSvgWarp as lf, showDirectionPicker as lg, showsTemplateAffordance as lh, signatureCountLabel as li, signatureKey as lj, signatureTimestamp as lk, signerName as ll, statusLabel as lm, slideNumberOf as ln, slidesWithReappliedLayout as lo, smartArtNodes as lp, paletteColour as lq, snapToGridStep as lr, splitCursorCell as ls, splitMergedCell as lt, statusKind as lu, statusLabel$1 as lv, storeAudienceContent as lw, stringFromEvent$5 as lx, strokeColorOf as ly, strokeToInkElement as lz, AiChangeOverlayComponent as m, vAlignPatch as m0, validatePassword as m1, validatePrintSettings as m2, validateRoomId as m3, valueToY as m4, vermilionDarkColors as m5, vermilionDarkTheme as m6, vermilionLightColors as m7, vermilionLightTheme as m8, vermilionRadius as m9, waypointsToPathD as ma, withManualLayouts as mb, worstStatus as mc, zoomTargetSlideIndex as md, AiChatPanelComponent as n, AiChatService as o, AiComposerComponent as p, AiFocusBarComponent as q, AiFocusHighlightOverlayComponent as r, AiHistoryMenuComponent as s, toChatSummary as t, AiHistoryService as u, AiMessageListComponent as v, AiPanelStore as w, AiProposalCardComponent as x, AiSettingsSectionComponent as y, AiToolCallCardComponent as z };
|
|
181409
|
-
//# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-
|
|
181844
|
+
//# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-wH2ZRE8b.mjs.map
|