Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 45 additions & 10 deletions .github/workflows/pr-validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,9 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Install PDF test dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq poppler-utils qpdf fonts-dejavu-core
# PDF visual regression tests run on a dedicated ubuntu-22.04 hosted
# runner (see pdf-visual job below) so goldens are pixel-matched against
# a reproducible image. They self-skip here because pdftoppm is absent.

- name: Setup
run: just setup
Expand Down Expand Up @@ -142,11 +141,8 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Install PDF test dependencies
if: matrix.hypervisor == 'kvm'
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq poppler-utils qpdf fonts-dejavu-core
# PDF visual regression tests run on a dedicated ubuntu-22.04 hosted
# runner (see pdf-visual job). They self-skip here because pdftoppm is absent.

- name: Setup
run: just setup
Expand All @@ -165,6 +161,45 @@ jobs:
path: dist/
retention-days: 7

# PDF visual regression tests — run on a GitHub-hosted ubuntu-22.04 image
# so goldens are pixel-matched against a reproducible, fork-accessible runner.
# The update-golden.yml workflow uses the same image to regenerate baselines.
pdf-visual:
name: PDF Visual Regression
needs: [docs-pr]
if: needs.docs-pr.outputs.docs-only != 'true'
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v6

- uses: actions/setup-node@v6
with:
node-version: "22"

- uses: hyperlight-dev/ci-setup-workflow@v1.9.0
with:
rust-toolchain: "1.89"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Install PDF test dependencies
run: just install-pdf-deps

- name: Setup
run: just setup

- name: Run PDF visual regression tests
run: npx vitest run tests/pdf-visual.test.ts

- name: Upload diff images on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: pdf-visual-diffs
path: /tmp/pdf-visual-test/
if-no-files-found: ignore
retention-days: 7

# Build Docker image (just setup builds deps + creates symlinks for Dockerfile COPY)
build-docker:
name: Build Docker Image
Expand Down Expand Up @@ -212,7 +247,7 @@ jobs:
# Gate PR merges on all jobs passing
ci-status:
name: CI Status
needs: [docs-pr, lint-and-test, build-and-test, build-docker]
needs: [docs-pr, lint-and-test, build-and-test, pdf-visual, build-docker]
if: always()
runs-on: ubuntu-latest
steps:
Expand Down
13 changes: 2 additions & 11 deletions .github/workflows/update-golden.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,7 @@ concurrency:
jobs:
update-goldens:
name: Regenerate PDF Goldens
runs-on:
[
self-hosted,
Linux,
X64,
"1ES.Pool=hld-kvm-amd",
"JobId=update-golden-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}",
]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v6
with:
Expand All @@ -60,9 +53,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Install PDF test dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq poppler-utils qpdf fonts-dejavu-core
run: just install-pdf-deps

- name: Setup
run: just setup
Expand Down
44 changes: 37 additions & 7 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -256,14 +256,37 @@ fmt-all: fmt fmt-analysis-guest fmt-runtime
test-all: test test-analysis-guest
@echo "✅ All tests passed"

# PDF visual regression tests
# Install PDF visual test dependencies (poppler-utils + fonts-dejavu-core).
# On Windows, installs into WSL. Pass a distro name to target a specific one
# that matches the CI runner (e.g. just install-pdf-deps Ubuntu-22.04).
[linux]
install-pdf-deps:
sudo apt-get update -qq && sudo apt-get install -y -qq poppler-utils qpdf fonts-dejavu-core

[windows]
install-pdf-deps distro="":
{{ if distro == "" { "wsl" } else { "wsl -d " + distro } }} bash -c "sudo apt-get update -qq && sudo apt-get install -y -qq poppler-utils qpdf fonts-dejavu-core"

# PDF visual regression tests.
# On Windows, pass a WSL distro name to match CI (e.g. just test-pdf-visual Ubuntu-22.04).
[linux]
test-pdf-visual:
npx vitest run tests/pdf-visual.test.ts

# Update PDF golden baselines (run after intentional visual changes)
[windows]
test-pdf-visual distro="":
{{ if distro == "" { "" } else { "$env:PDF_WSL_DISTRO = '" + distro + "';" } }} npx vitest run tests/pdf-visual.test.ts

# Update PDF golden baselines (run after intentional visual changes).
# On Windows, pass a WSL distro name to match CI (e.g. just update-pdf-golden Ubuntu-22.04).
[linux]
update-pdf-golden:
UPDATE_GOLDEN=1 npx vitest run tests/pdf-visual.test.ts

[windows]
update-pdf-golden distro="":
{{ if distro == "" { "" } else { "$env:PDF_WSL_DISTRO = '" + distro + "';" } }} $env:UPDATE_GOLDEN = "1"; npx vitest run tests/pdf-visual.test.ts

# ── OOXML Validation ─────────────────────────────────────────────────

# Validate a PPTX file against the OpenXML SDK schema.
Expand Down Expand Up @@ -604,7 +627,7 @@ mcp-setup-everything:
echo "✅ MCP 'everything' server configured in $CONFIG_FILE"
echo " Start the agent and run: /plugin enable mcp && /mcp enable everything"

# Set up the MCP GitHub server (requires GITHUB_TOKEN env var)
# Set up the MCP GitHub server (uses GITHUB_TOKEN — get one via: gh auth token)
[unix]
mcp-setup-github:
#!/usr/bin/env bash
Expand All @@ -614,9 +637,16 @@ mcp-setup-github:
mkdir -p "$CONFIG_DIR"

if [ -z "${GITHUB_TOKEN:-}" ]; then
echo "⚠️ GITHUB_TOKEN not set. The GitHub MCP server needs it at runtime."
echo " export GITHUB_TOKEN=ghp_your_token_here"
echo " Continuing with config anyway..."
echo "⚠️ GITHUB_TOKEN not set. Trying 'gh auth token'..."
if command -v gh &>/dev/null; then
export GITHUB_TOKEN=$(gh auth token 2>/dev/null || true)
fi
if [ -z "${GITHUB_TOKEN:-}" ]; then
echo " Could not get token. Run: export GITHUB_TOKEN=\$(gh auth token)"
echo " Continuing with config anyway..."
else
echo " ✅ Got token from gh CLI"
fi
fi

node -e "
Expand All @@ -638,7 +668,7 @@ mcp-setup-github:
fs.writeFileSync(path, JSON.stringify(cfg, null, 2) + '\n');
"
echo "✅ MCP 'github' server configured in $CONFIG_FILE"
echo " Requires: export GITHUB_TOKEN=ghp_..."
echo " Tip: export GITHUB_TOKEN=\$(gh auth token)"
echo " Start the agent and run: /plugin enable mcp && /mcp enable github"

# Set up the MCP filesystem server (read-only access to a directory)
Expand Down
4 changes: 2 additions & 2 deletions builtin-modules/pdf.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"description": "PDF 1.7 document generation — text, graphics, metadata, standard fonts. Flow-based layout for auto-paginating documents.",
"author": "system",
"mutable": false,
"sourceHash": "sha256:c8716bcb3295f5bc",
"dtsHash": "sha256:f30fba88bfe5f977",
"sourceHash": "sha256:fedd6d3bc7c638e8",
"dtsHash": "sha256:e56bc4be1f1d0cd1",
"importStyle": "named",
"hints": {
"overview": "Generate PDF documents with text, shapes, and metadata. Uses PDF's 14 standard fonts (no embedding required). Coordinates are in points (72 points = 1 inch), with top-left origin.",
Expand Down
2 changes: 1 addition & 1 deletion builtin-modules/pptx-tables.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "Styled tables for PPTX presentations - headers, borders, alternating rows",
"author": "system",
"mutable": false,
"sourceHash": "sha256:e03a2365c45ab0e6",
"sourceHash": "sha256:4fd269d16f32d3ec",
"dtsHash": "sha256:130d021921083af6",
"importStyle": "named",
"hints": {
Expand Down
45 changes: 24 additions & 21 deletions builtin-modules/src/pdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3015,8 +3015,6 @@ export interface TableStyle {
borderColor: string;
/** Border line width in points. */
borderWidth: number;
/** Page background colour (set internally for contrast checking). */
_pageBg?: string;
}

/** Built-in table styles matching PPTX table styles. */
Expand Down Expand Up @@ -3378,9 +3376,11 @@ export function comparisonTable(opts: ComparisonTableOptions): PdfElement {
return _createPdfElement("comparisonTable", data);
}

/** Resolve a style name or object to a TableStyle. */
/** Resolve a style name or object to a TableStyle.
* Preset styles are shallow-cloned so that contrast auto-correction
* in renderTable never mutates the shared TABLE_STYLES singletons. */
function resolveTableStyle(style?: string | TableStyle): TableStyle {
if (!style) return TABLE_STYLES.default;
if (!style) return { ...TABLE_STYLES.default };
if (typeof style === "string") {
const resolved = TABLE_STYLES[style];
if (!resolved) {
Expand All @@ -3389,7 +3389,7 @@ function resolveTableStyle(style?: string | TableStyle): TableStyle {
`Unknown table style "${style}". Valid styles: ${valid}.`,
);
}
return resolved;
return { ...resolved };
}
return style;
}
Expand Down Expand Up @@ -3487,16 +3487,22 @@ function renderTable(
const rowH = tableRowHeight(fontSize, compact);
const headerH = rowH;

// Ensure page background is available for contrast checking
if (!style._pageBg) {
style._pageBg = doc.theme.bg;
}

// ── Contrast auto-correction ─────────────────────────────────────
// Automatically fix text colors that have poor contrast against the
// page background. No errors — just silently correct to readable.
// Use a local pageBg derived from the current doc theme rather than
// caching on the style object (which could be stale across renders).
const MIN_CONTRAST = 3.0;
const pageBg = style._pageBg || "FFFFFF";
const pageBg = doc.theme.bg || "FFFFFF";

// If headerBg is too similar to pageBg, the header row won't stand out.
// Swap to theme accent1 so the header is visually distinct.
if (style.headerBg) {
const headerVsPage = contrastRatio(style.headerBg, pageBg);
if (headerVsPage < 1.5 && doc.theme.accent1) {
style.headerBg = doc.theme.accent1;
}
Comment thread
simongdavies marked this conversation as resolved.
}

if (style.bodyFg) {
const bodyRatio = contrastRatio(style.bodyFg, pageBg);
Expand Down Expand Up @@ -3641,17 +3647,14 @@ function renderTable(
});
}

// Alternating row background FIRST
if (style.altRowBg && r % 2 === 1) {
doc.drawRect(x, curY, totalWidth, rowH, { fill: style.altRowBg });
}

// Auto-contrast body text against effective row background
// EVERY row gets an explicit fill — no transparent rows, no guessing
const isAlt = !!(style.altRowBg && r % 2 === 1);
const rowBg = isAlt
? style.altRowBg
: (style._pageBg || "FFFFFF");
const rowFg = autoTextColor(rowBg);
const rowBg = isAlt ? style.altRowBg : pageBg;
doc.drawRect(x, curY, totalWidth, rowH, { fill: rowBg });
Comment thread
simongdavies marked this conversation as resolved.

// Prefer the validated body text color; fall back to an automatic
// contrast color if no explicit body foreground is configured.
const rowFg = style.bodyFg ?? autoTextColor(rowBg);

// Cell text AFTER background
const isBoldRow = rowBold?.[r] ?? false;
Expand Down
19 changes: 13 additions & 6 deletions builtin-modules/src/pptx-tables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ export function table(opts: TableOptions): ShapeFragment {
// ── Theme-aware defaults ────────────────────────────────────────────
// If opts.theme is passed, auto-compute colors for dark/light backgrounds.
// Style overrides always take precedence over theme-computed values.
const theme = opts.theme || {};
const theme = (opts.theme || {}) as Partial<Theme>;
const darkMode = theme.bg ? isDark(theme.bg) : false;

// Dark mode defaults: light text on dark alt-rows
Expand All @@ -285,7 +285,14 @@ export function table(opts: TableOptions): ShapeFragment {
const defaultAltRowColor = darkMode ? "2D333B" : "F5F5F5";
const defaultBorderColor = darkMode ? "444C56" : "CCCCCC";

const headerBg = style.headerBg || "2196F3";
let headerBg = style.headerBg || "2196F3";

// If headerBg matches the slide bg, the header won't stand out — use accent
const slideBgForCheck = theme.bg || (darkMode ? "1A1A1A" : "FFFFFF");
if (theme.accent1 && contrastRatio(headerBg, slideBgForCheck) < 1.5) {
headerBg = theme.accent1;
}

const headerColor = style.headerColor || autoTextColor(headerBg);
const headerFontSize = style.headerFontSize || 13;
const styleFontSize = style.fontSize || 12;
Expand Down Expand Up @@ -324,10 +331,10 @@ export function table(opts: TableOptions): ShapeFragment {
// Build data rows
// ALWAYS auto-contrast text against each row's effective background
// to prevent unreadable text on dark themes or image backgrounds.
// If no theme.bg is provided, give non-alt rows an explicit fill
// matching the alt-row scheme so text is always readable.
// ALWAYS give every row an explicit fill — never rely on slide background
// inheritance, because we can't guarantee we know the actual slide bg.
// This ensures autoTextColor always computes against the real fill.
const slideBg = theme.bg || (darkMode ? "1A1A1A" : "FFFFFF");
const nonAltFill = darkMode ? slideBg : undefined;
const dataRows = rows
.map((row, rowIdx) => {
const isAlt = altRows && rowIdx % 2 === 1;
Expand All @@ -337,7 +344,7 @@ export function table(opts: TableOptions): ShapeFragment {
const cells = row
.map((cell) =>
cellXml(cell, {
fillColor: isAlt ? altRowColor : nonAltFill,
fillColor: rowBg, // Always explicit fill — never undefined
color: rowTextColor,
fontSize: styleFontSize,
borderColor,
Expand Down
2 changes: 0 additions & 2 deletions builtin-modules/src/types/ha-modules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1102,8 +1102,6 @@ declare module "ha:pdf" {
borderColor: string;
/** Border line width in points. */
borderWidth: number;
/** Page background colour (set internally for contrast checking). */
_pageBg?: string;
}
/** Built-in table styles matching PPTX table styles. */
export declare const TABLE_STYLES: Record<string, TableStyle>;
Expand Down
4 changes: 2 additions & 2 deletions docs/MCP.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,8 @@ MCP tools with native PPTX generation in a single workflow.
### Setup

```bash
# Set your GitHub token
export GITHUB_TOKEN="ghp_your_token_here"
# Use your existing GitHub CLI auth (no PAT needed)
export GITHUB_TOKEN=$(gh auth token)

# Configure the GitHub MCP server
just mcp-setup-github
Expand Down
Loading
Loading