diff --git a/CHANGELOG.md b/CHANGELOG.md
index 839d77f..2734ea1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -37,6 +37,8 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
### Fixed
+- Preserved explicit paragraph `defRPr` text colors when a shape also provides a theme
+ `fontRef` color, preventing light theme text from overriding authored paragraph colors.
- Keep near-fit, single-paragraph square-wrapped headings on one line with a conservative 2%
browser-metric correction while preserving deliberate multi-line text.
- Resolve macOS PowerPoint exports and macro hosts by exact full path, close only that presentation,
diff --git a/test/unit/renderer/ShapeRenderer.test.ts b/test/unit/renderer/ShapeRenderer.test.ts
index 063c596..d87e740 100644
--- a/test/unit/renderer/ShapeRenderer.test.ts
+++ b/test/unit/renderer/ShapeRenderer.test.ts
@@ -6034,6 +6034,50 @@ describe('ShapeRenderer', () => {
expect(hasWhiteText).toBe(true);
});
+ it('keeps paragraph defRPr color over shape fontRef color', () => {
+ const xml = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Paragraph Color
+
+
+
+ `;
+
+ const el = renderShape(parseShapeNode(parseXml(xml)), createMockRenderContext());
+ const textSpan = Array.from(el.querySelectorAll('span')).find(
+ (span) => span.textContent === 'Paragraph Color',
+ );
+
+ expect(textSpan).toBeDefined();
+ expect(textSpan!.style.color).toBe('rgb(36, 59, 83)');
+ });
+
// ---------------------------------------------------------------------------
// Dynamic normAutofit scaling (lines ~1142-1162 in ShapeRenderer.ts)
// In jsdom, scrollHeight often equals clientHeight, so the scale branch may not
diff --git a/test/unit/renderer/TextRenderer.branches.test.ts b/test/unit/renderer/TextRenderer.branches.test.ts
index fdf7e96..f9826b5 100644
--- a/test/unit/renderer/TextRenderer.branches.test.ts
+++ b/test/unit/renderer/TextRenderer.branches.test.ts
@@ -1546,6 +1546,82 @@ describe('TextRenderer — branch coverage (uncovered paths)', () => {
expect(span!.style.color).toBe('rgb(255, 0, 0)');
});
+ it('prefers explicit paragraph defRPr color over fontRefColor', () => {
+ const body = makeTextBody({
+ paragraphs: [
+ {
+ properties: xmlNode(
+ '',
+ ),
+ runs: [{ text: 'Paragraph color wins' }],
+ level: 0,
+ },
+ ],
+ });
+ const ctx = createMockRenderContext();
+ const container = document.createElement('div');
+ renderTextBody(body, undefined, ctx, container, {
+ fontRefColor: '#FFFFFF',
+ });
+ const span = container.querySelector('span');
+ expect(span!.style.color).toBe('rgb(36, 59, 83)');
+ });
+
+ it('uses fontRefColor when paragraph defRPr has no color', () => {
+ const body = makeTextBody({
+ paragraphs: [
+ {
+ properties: xmlNode(''),
+ runs: [{ text: 'Shape style color wins' }],
+ level: 0,
+ },
+ ],
+ });
+ const ctx = createMockRenderContext();
+ const container = document.createElement('div');
+ renderTextBody(body, undefined, ctx, container, {
+ fontRefColor: '#FFFFFF',
+ });
+ const span = container.querySelector('span');
+ expect(span!.style.color).toBe('rgb(255, 255, 255)');
+ });
+
+ it('keeps fontRefColor precedence over inherited list style colors', () => {
+ const body = makeTextBody({
+ listStyle:
+ '',
+ paragraphs: [{ runs: [{ text: 'Shape style color wins' }], level: 0 }],
+ });
+ const ctx = createMockRenderContext();
+ const container = document.createElement('div');
+ renderTextBody(body, undefined, ctx, container, {
+ fontRefColor: '#FFFFFF',
+ });
+ const span = container.querySelector('span');
+ expect(span!.style.color).toBe('rgb(255, 255, 255)');
+ });
+
+ it('prefers explicit paragraph defRPr color over cellTextColor', () => {
+ const body = makeTextBody({
+ paragraphs: [
+ {
+ properties: xmlNode(
+ '',
+ ),
+ runs: [{ text: 'Paragraph color wins' }],
+ level: 0,
+ },
+ ],
+ });
+ const ctx = createMockRenderContext();
+ const container = document.createElement('div');
+ renderTextBody(body, undefined, ctx, container, {
+ cellTextColor: '#FF0000',
+ });
+ const span = container.querySelector('span');
+ expect(span!.style.color).toBe('rgb(36, 59, 83)');
+ });
+
it('uses cellTextColor as fallback when no other color set', () => {
const body = makeTextBody({
paragraphs: [