Skip to content

Commit a03edea

Browse files
committed
v0.7.8: fix text/background contrast across all themes
- Add WCAG 2.1 luminance helpers (luminance, pickTextColor) to JS renderer - Fix hardcoded colors in addSlideHeader, renderTitleSlide, renderTimelineSlide, renderFunnelSlide, renderProcessSlide, renderSwotSlide - Funnel labels now auto-select light/dark text per segment fill color - Process step fills now follow active theme instead of hardcoded pastels - SWOT quadrant colors now derive from chartColors - Python pptx_renderer _get_colors() merges full theme palette from templates - Add color/contrast docs to SKILL.md and USER_GUIDE (en/zh-CN/ja) - Bump version to 0.7.8
1 parent 6d3dd9b commit a03edea

13 files changed

Lines changed: 103 additions & 21 deletions

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ The format is based on Keep a Changelog, and this project follows Semantic Versi
66

77
## [Unreleased]
88

9+
## [0.7.8] - 2026-03-24
10+
11+
### Fixed
12+
13+
- **Text/Background Contrast Across Themes** — Added WCAG-based luminance helpers (`luminance()`, `pickTextColor()`) to JS renderer. Fixed hardcoded text colors in `addSlideHeader()`, `renderTitleSlide()`, `renderTimelineSlide()`, `renderFunnelSlide()`, `renderProcessSlide()`, and `renderSwotSlide()` to use theme-aware colors instead of always using dark `closingBg`. Funnel stage labels now dynamically choose light or dark text based on each segment's fill color. Process step backgrounds now follow the active theme instead of hardcoded pastels. SWOT quadrant colors now derive from `chartColors`. Python renderer `_get_colors()` now merges full theme palette (text, background, closing colors) from templates. All 6 built-in themes (including dark-executive and tech) now render with correct contrast.
14+
915
## [0.7.7] - 2026-03-23
1016

1117
### Changed

ROADMAP.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
# Project Status
22

3-
Current version: **v0.7.7**
3+
Current version: **v0.7.8**
44

55
Product positioning: **to-B PPT generation engine** — embeddable into enterprise AI workflows with private deployment, API-driven generation (MCP / CLI / HTTP), and multi-language support (CJK + English). Core differentiators are data security (no data leaves the enterprise), content intelligence (AI-driven layout and visualization selection), and cross-platform output (PowerPoint, Keynote, Google Slides).
66

77
Current release posture: **published in its current scope**. This repository is being released as a personal open-source project in maintenance mode. No additional roadmap items are currently committed.
88

99
---
1010

11-
## Current Capabilities (v0.7.7)
11+
## Current Capabilities (v0.7.8)
1212

1313
| Area | What's Included |
1414
|------|----------------|
@@ -43,6 +43,7 @@ This repository is published as a stable personal-project snapshot.
4343

4444
| Version | Milestone |
4545
|---------|-----------|
46+
| v0.7.8 | Fix text/background contrast across all themes; WCAG luminance-aware rendering |
4647
| v0.7.7 | Current-scope release alignment; roadmap converted to project status / maintenance posture |
4748
| v0.7.6 | Release metadata audit cleanup and public baseline verification |
4849
| v0.7.5 | README positioning update for personal-project release |

SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Examples include:
2929

3030
## Implemented Mitigations
3131

32-
As of v0.7.7, the following security controls are in place:
32+
As of v0.7.8, the following security controls are in place:
3333

3434
- **Path traversal prevention**: file paths are resolved and validated to stay within the project directory
3535
- **SSRF blocking**: URL targets are validated to reject private/internal network addresses

SKILL.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,16 @@ When using chart layout, always populate concrete data:
139139
- If no real data is available, use realistic placeholders and note it in assumptions
140140
- Never leave categories or series as empty arrays
141141

142-
The system automatically validates chart data after generation. If a chart slide has invalid data (empty categories, missing series, or non-numeric values), it falls back to a bullet layout and records the fallback in assumptions.
142+
The system automatically validates chart data after generation.
143+
144+
## Color and Contrast
145+
146+
The renderer uses WCAG 2.1 relative-luminance detection to pick readable text colors automatically. When choosing themes or creating custom ones:
147+
148+
- **Light themes** (business-clean, corporate-blue, warm-modern, minimal): dark text on light slide backgrounds
149+
- **Dark themes** (dark-executive, tech): light text on dark slide backgrounds
150+
- Chart labels and funnel stage text auto-select light or dark based on the segment fill color
151+
- Custom themes should ensure all text/background pairs meet a minimum 4.5:1 contrast ratio If a chart slide has invalid data (empty categories, missing series, or non-numeric values), it falls back to a bullet layout and records the fallback in assumptions.
143152

144153
## Narrative Guidance
145154

docs/USER_GUIDE.en.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,17 @@ Create a JSON file following the schema in `assets/themes/theme-schema.json`:
304304

305305
Pass a custom theme file path via `--theme /path/to/my-brand.json`.
306306

307+
### Color and Accessibility
308+
309+
The renderer uses WCAG 2.1 luminance detection to ensure readable text on all backgrounds. This works automatically for all 6 built-in themes, including dark themes like `dark-executive` and `tech`.
310+
311+
When creating custom themes, ensure text/background color pairs meet WCAG AA contrast (minimum 4.5:1 for normal text, 3:1 for large text):
312+
313+
- **Light theme example**: `text: "1A202C"` on `slideBg: "FFFFFF"` — 16.1:1 contrast ✓
314+
- **Dark theme example**: `text: "E2E8F0"` on `slideBg: "0F172A"` — 12.6:1 contrast ✓
315+
316+
Chart labels and funnel stage text automatically switch between light and dark based on each segment's fill color.
317+
307318
## Chart Data Handling
308319

309320
When slides use the `chart` layout, the system validates chart data automatically:

docs/USER_GUIDE.ja.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,17 @@ npm run revise:mock
233233

234234
`assets/themes/theme-schema.json` のスキーマに従って JSON ファイルを作成します。`--theme /path/to/my-brand.json` でカスタムテーマファイルを指定できます。
235235

236+
### カラーとアクセシビリティ
237+
238+
レンダラーは WCAG 2.1 の輝度検出を使用して、すべての背景上でテキストが読みやすくなるよう自動調整します。`dark-executive``tech` などのダークテーマを含む 6 つのビルトインテーマすべてに自動対応しています。
239+
240+
カスタムテーマを作成する際は、テキスト/背景の色ペアが WCAG AA コントラスト基準(通常テキスト最低 4.5:1、大テキスト 3:1)を満たすようにしてください:
241+
242+
- **ライトテーマ例**: `text: "1A202C"` × `slideBg: "FFFFFF"` — コントラスト比 16.1:1 ✓
243+
- **ダークテーマ例**: `text: "E2E8F0"` × `slideBg: "0F172A"` — コントラスト比 12.6:1 ✓
244+
245+
チャートラベルやファネルステージのテキストは、各セグメントの塗りつぶし色に基づいてライト/ダークを自動切り替えします。
246+
236247
## チャートデータの処理
237248

238249
スライドが `chart` レイアウトを使う場合、システムが自動的にデータを検証します:

docs/USER_GUIDE.zh-CN.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,17 @@ npm run revise:mock
276276

277277
通过 `--theme /path/to/my-brand.json` 传入自定义主题文件。
278278

279+
### 颜色与无障碍
280+
281+
渲染器使用 WCAG 2.1 亮度检测,确保所有背景上的文字可读。6 个内置主题(包括 `dark-executive``tech` 等深色主题)均已自动适配。
282+
283+
创建自定义主题时,请确保文字/背景颜色对的对比度满足 WCAG AA 标准(普通文字最低 4.5:1,大文字 3:1):
284+
285+
- **浅色主题示例**`text: "1A202C"``slideBg: "FFFFFF"` — 对比度 16.1:1 ✓
286+
- **深色主题示例**`text: "E2E8F0"``slideBg: "0F172A"` — 对比度 12.6:1 ✓
287+
288+
图表标签和漏斗阶段文字会根据每个色块的填充色自动切换深色或浅色。
289+
279290
## 图表数据处理
280291

281292
当 slide 使用 `chart` 布局时,系统会自动验证图表数据:

generate-ppt.js

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,33 @@ function mergeTheme(partial) {
7373
};
7474
}
7575

76+
// ---------------------------------------------------------------------------
77+
// Color contrast helpers (WCAG 2.1 relative luminance)
78+
// ---------------------------------------------------------------------------
79+
80+
/**
81+
* Relative luminance per WCAG 2.1.
82+
* @param {string} hex 6-char hex color (no #)
83+
*/
84+
function luminance(hex) {
85+
const r = parseInt(hex.slice(0, 2), 16) / 255;
86+
const g = parseInt(hex.slice(2, 4), 16) / 255;
87+
const b = parseInt(hex.slice(4, 6), 16) / 255;
88+
const [sr, sg, sb] = [r, g, b].map(c =>
89+
c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
90+
);
91+
return 0.2126 * sr + 0.7152 * sg + 0.0722 * sb;
92+
}
93+
94+
/**
95+
* Pick a readable text color for the given background.
96+
* Returns theme text color (dark) for light backgrounds,
97+
* theme textLight (white/light) for dark backgrounds.
98+
*/
99+
function pickTextColor(bgHex) {
100+
return luminance(bgHex) > 0.4 ? _t.colors.text : _t.colors.textLight;
101+
}
102+
76103
// Active theme — set at the start of buildDeck(), used by all render helpers
77104
let _t = DEFAULT_THEME;
78105

@@ -181,7 +208,7 @@ function addSlideHeader(slide, deck, current) {
181208
h: 0.5,
182209
fontSize: 24,
183210
bold: true,
184-
color: _t.colors.closingBg
211+
color: _t.colors.text
185212
});
186213

187214
if (current.objective) {
@@ -308,7 +335,7 @@ function renderTitleSlide(slide, deck, current) {
308335
h: 1.1,
309336
fontSize: 28,
310337
bold: true,
311-
color: _t.colors.closingBg
338+
color: pickTextColor(_t.colors.titleBg)
312339
});
313340

314341
addTextBox(slide, current.subtitle || deck.scenario || deck.audience || '', {
@@ -519,7 +546,7 @@ function renderTimelineSlide(slide, deck, current) {
519546
fontSize: 10,
520547
align: 'center',
521548
bold: true,
522-
color: _t.colors.closingBg
549+
color: _t.colors.text
523550
});
524551

525552
addTextBox(slide, item, {
@@ -547,7 +574,7 @@ function renderProcessSlide(slide, deck, current) {
547574
w: width,
548575
h: 1.6,
549576
rectRadius: 0.05,
550-
fill: { color: index % 2 === 0 ? 'ECFDF5' : 'EFF6FF' },
577+
fill: { color: index % 2 === 0 ? _t.colors.background : _t.colors.headerBg },
551578
line: { color: _t.colors.border, pt: 1 }
552579
});
553580

@@ -873,11 +900,12 @@ function renderKpiSlide(slide, deck, current) {
873900
function renderSwotSlide(slide, deck, current) {
874901
addSlideHeader(slide, deck, current);
875902
const q = current.quadrants || {};
903+
const cc = _t.chartColors;
876904
const sections = [
877-
{ label: 'Strengths', items: q.strengths || [], color: '059669' },
878-
{ label: 'Weaknesses', items: q.weaknesses || [], color: 'DC2626' },
879-
{ label: 'Opportunities', items: q.opportunities || [], color: '2563EB' },
880-
{ label: 'Threats', items: q.threats || [], color: 'D97706' },
905+
{ label: 'Strengths', items: q.strengths || [], color: cc[0] || '059669' },
906+
{ label: 'Weaknesses', items: q.weaknesses || [], color: cc[3] || 'DC2626' },
907+
{ label: 'Opportunities', items: q.opportunities || [], color: cc[1] || '2563EB' },
908+
{ label: 'Threats', items: q.threats || [], color: cc[2] || 'D97706' },
881909
];
882910

883911
const gridX = 0.9, gridY = 1.8;
@@ -975,7 +1003,7 @@ function renderFunnelSlide(slide, deck, current) {
9751003
const label = stage.value ? `${stage.label}${stage.value}` : stage.label;
9761004
addTextBox(slide, label, {
9771005
x, y, w, h: stageH,
978-
fontSize: 13, bold: true, color: 'FFFFFF', align: 'center', valign: 'mid'
1006+
fontSize: 13, bold: true, color: pickTextColor(color), align: 'center', valign: 'mid'
9791007
});
9801008
});
9811009
}

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "auto-ppt-engine",
3-
"version": "0.7.7",
3+
"version": "0.7.8",
44
"description": "Python-first AI-agent-ready PowerPoint workflow for planning, revising, validating, and rendering PPTX decks.",
55
"license": "Apache-2.0",
66
"main": "generate-ppt.js",

0 commit comments

Comments
 (0)