@@ -465,7 +465,8 @@ function snapshotFields(element: SlideElement): unknown {
465465function registerElementSource (
466466 element : SlideElement ,
467467 rawXml : string | undefined ,
468- slidePath : string
468+ slidePath : string ,
469+ theme ?: ThemeColors
469470) : void {
470471 if ( ! rawXml ) return ;
471472 // Skip elements whose source XML relies on placeholder geometry
@@ -480,7 +481,7 @@ function registerElementSource(
480481 snapshot : snapshotElement ( element ) ,
481482 slidePath,
482483 } ) ;
483- stampPristineOoxml ( element , rawXml ) ;
484+ stampPristineOoxml ( element , rawXml , theme ) ;
484485}
485486
486487/**
@@ -491,14 +492,48 @@ function registerElementSource(
491492 * instead of re-synthesising from `path.d` — synthesis can't represent OOXML
492493 * even-odd winding, which is what blanks complex vectors like the eon bicycle.
493494 *
494- * Restricted to shapes whose XML carries no external references
495- * (`r:embed` / `r:id` / `r:link` images, `a:schemeClr` theme colours) so the
496- * fragment stays valid without the source archive or its theme.
495+ * Theme colours (`<a:schemeClr>`) are resolved to literal `<a:srgbClr>` against
496+ * the slide's theme so brand-coloured vectors (the common case — e.g. E.ON red
497+ * is a theme accent) become self-contained and still qualify; the swap is
498+ * lossless because both elements accept the same child transforms. Shapes that
499+ * reference media (`r:embed` / `r:id` / `r:link`) or carry an unresolvable
500+ * colour are left to the synth path (they'd be invalid without the archive).
497501 */
498- function stampPristineOoxml ( element : SlideElement , rawXml : string ) : void {
502+ function stampPristineOoxml (
503+ element : SlideElement ,
504+ rawXml : string ,
505+ theme ?: ThemeColors
506+ ) : void {
499507 if ( element . type !== "shape" || ! element . path ) return ;
500- if ( / \b r : ( e m b e d | i d | l i n k ) = | < a : s c h e m e C l r \b / . test ( rawXml ) ) return ;
501- element . pristineOoxml = { xml : rawXml , snapshot : snapshotElement ( element ) } ;
508+ if ( / \b r : ( e m b e d | i d | l i n k ) = / . test ( rawXml ) ) return ;
509+ const xml = theme ? resolveSchemeColorsInXml ( rawXml , theme ) : rawXml ;
510+ // Any unresolved scheme colour left over → not self-contained → synth.
511+ if ( / < a : s c h e m e C l r \b / . test ( xml ) ) return ;
512+ element . pristineOoxml = { xml, snapshot : snapshotElement ( element ) } ;
513+ }
514+
515+ /**
516+ * Rewrite `<a:schemeClr val="accent2">` → `<a:srgbClr val="EA1B0A">` (and the
517+ * self-closing / closing-tag forms) using the baked theme. `schemeClr` and
518+ * `srgbClr` accept identical child transforms (`lumMod`, `alpha`, …), so the
519+ * swap preserves tints/shades exactly — only the colour source changes from a
520+ * theme reference to a literal. Tokens not present in the theme (e.g. `phClr`)
521+ * are left untouched so the caller can detect "still has schemeClr" and bail.
522+ */
523+ export function resolveSchemeColorsInXml ( xml : string , theme : ThemeColors ) : string {
524+ return xml . replace (
525+ / < a : s c h e m e C l r \b ( [ ^ > ] * ?) \b v a l = " ( [ ^ " ] + ) " ( [ ^ > ] * ?) ( \/ ? ) > / g,
526+ ( whole , pre : string , token : string , post : string , selfClose : string ) => {
527+ const hex = ( theme as unknown as Record < string , string > ) [ token ] ;
528+ // Only swap when the theme gives a literal #RRGGBB — anything else
529+ // (missing token, "transparent", …) is left so the caller bails out.
530+ if ( ! hex || ! / ^ # [ 0 - 9 a - f A - F ] { 6 } $ / . test ( hex ) ) return whole ;
531+ const val = hex . slice ( 1 ) . toUpperCase ( ) ;
532+ // `pre` already carries the whitespace that separated the tag from
533+ // `val=`, so don't add another space (would double it).
534+ return `<a:srgbClr${ pre } val="${ val } "${ post } ${ selfClose } >` ;
535+ }
536+ ) . replace ( / < \/ a : s c h e m e C l r > / g, "</a:srgbClr>" ) ;
502537}
503538
504539function hasExplicitXfrm ( xml : string ) : boolean {
@@ -746,7 +781,7 @@ async function walkUnderlay(
746781 const registerFromNode = ( node : any , el : SlideElement | null ) => {
747782 if ( ! el ) return ;
748783 const rawSrc = ( node as any ) ?. _elementRawSrc as string | undefined ;
749- registerElementSource ( el , rawSrc , ctx . slidePath ) ;
784+ registerElementSource ( el , rawSrc , ctx . slidePath , ctx . theme ) ;
750785 out . push ( el ) ;
751786 } ;
752787 for ( const sp of asArray ( spTree [ "p:sp" ] ) ) {
@@ -923,25 +958,25 @@ async function parseSpTree(
923958 if ( tag === "p:sp" ) {
924959 const el = await parseSpOrText ( node , ctx , outer ) ;
925960 if ( el ) {
926- registerElementSource ( el , rawSrc , ctx . slidePath ) ;
961+ registerElementSource ( el , rawSrc , ctx . slidePath , ctx . theme ) ;
927962 out . push ( el ) ;
928963 }
929964 } else if ( tag === "p:pic" ) {
930965 const el = await parsePic ( node , ctx , outer ) ;
931966 if ( el ) {
932- registerElementSource ( el , rawSrc , ctx . slidePath ) ;
967+ registerElementSource ( el , rawSrc , ctx . slidePath , ctx . theme ) ;
933968 out . push ( el ) ;
934969 }
935970 } else if ( tag === "p:cxnSp" ) {
936971 const el = parseCxn ( node , ctx , outer ) ;
937972 if ( el ) {
938- registerElementSource ( el , rawSrc , ctx . slidePath ) ;
973+ registerElementSource ( el , rawSrc , ctx . slidePath , ctx . theme ) ;
939974 out . push ( el ) ;
940975 }
941976 } else if ( tag === "p:graphicFrame" ) {
942977 const el = await parseGraphicFrame ( node , ctx , outer ) ;
943978 if ( el ) {
944- registerElementSource ( el , rawSrc , ctx . slidePath ) ;
979+ registerElementSource ( el , rawSrc , ctx . slidePath , ctx . theme ) ;
945980 out . push ( el ) ;
946981 }
947982 } else if ( tag === "p:grpSp" ) {
@@ -954,7 +989,7 @@ async function parseSpTree(
954989 // and image exactly, plus the group transform itself. Once any
955990 // descendant is edited the snapshot diverges (see snapshotElement)
956991 // and the synth path re-emits the group instead.
957- registerElementSource ( group , rawSrc , ctx . slidePath ) ;
992+ registerElementSource ( group , rawSrc , ctx . slidePath , ctx . theme ) ;
958993 out . push ( group ) ;
959994 }
960995 }
0 commit comments