diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 61a9c3d..2af1dbf 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -1,14 +1,6 @@
name: publish
on:
- pull_request:
- types:
- - opened
- - reopened
- - synchronize
- - labeled
- - unlabeled
- - ready_for_review
workflow_dispatch:
push:
branches:
@@ -18,32 +10,7 @@ env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
- release-intent:
- if: github.event_name == 'pull_request'
- runs-on: ubuntu-latest
- permissions:
- pull-requests: read
- steps:
- - uses: actions/github-script@v8
- with:
- script: |
- const labels = context.payload.pull_request.labels.map((label) => label.name);
- const releaseLabels = labels.filter((label) =>
- ["release:patch", "release:minor", "release:major", "release:skip"].includes(label)
- );
-
- if (releaseLabels.length !== 1) {
- core.setFailed(
- `Expected exactly one release label on the PR. Found: ${releaseLabels.join(", ") || "none"}. ` +
- "Use one of: release:patch, release:minor, release:major, release:skip."
- );
- return;
- }
-
- core.notice(`Release intent: ${releaseLabels[0]}`);
-
publish:
- if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: write
@@ -62,6 +29,11 @@ jobs:
cache: pnpm
registry-url: "https://registry.npmjs.org"
+ - name: Upgrade npm for trusted publishing
+ run: |
+ npm install -g npm@^11.5.1
+ npm --version
+
- run: pnpm install --frozen-lockfile
- id: package
run: |
diff --git a/.github/workflows/release-intent.yml b/.github/workflows/release-intent.yml
new file mode 100644
index 0000000..2ba9eef
--- /dev/null
+++ b/.github/workflows/release-intent.yml
@@ -0,0 +1,38 @@
+name: release-intent
+
+on:
+ pull_request:
+ types:
+ - opened
+ - reopened
+ - synchronize
+ - labeled
+ - unlabeled
+ - ready_for_review
+
+env:
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
+
+jobs:
+ release-intent:
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: read
+ steps:
+ - uses: actions/github-script@v8
+ with:
+ script: |
+ const labels = context.payload.pull_request.labels.map((label) => label.name);
+ const releaseLabels = labels.filter((label) =>
+ ["release:patch", "release:minor", "release:major", "release:skip"].includes(label)
+ );
+
+ if (releaseLabels.length !== 1) {
+ core.setFailed(
+ `Expected exactly one release label on the PR. Found: ${releaseLabels.join(", ") || "none"}. ` +
+ "Use one of: release:patch, release:minor, release:major, release:skip."
+ );
+ return;
+ }
+
+ core.notice(`Release intent: ${releaseLabels[0]}`);
diff --git a/scripts/generate-visual-fixtures.mjs b/scripts/generate-visual-fixtures.mjs
index 7c719c2..343f9a5 100644
--- a/scripts/generate-visual-fixtures.mjs
+++ b/scripts/generate-visual-fixtures.mjs
@@ -14,8 +14,18 @@ await Promise.all([
writeVisualFixture("bullets-numbering", createBulletsNumberingSlide()),
writeVisualFixture("text-insets-anchor", createTextInsetsAnchorSlide()),
writeVisualFixture("paragraph-spacing", createParagraphSpacingSlide()),
- writeVisualFixture("theme-colors", createThemeColorsSlide()),
- writeVisualFixture("theme-luminance", createThemeLuminanceSlide()),
+ writeVisualFixture("theme-colors", createThemeColorsSlide(), {
+ relsXml: createThemeFixtureRelationships(),
+ }),
+ writeVisualFixture("theme-luminance", createThemeLuminanceSlide(), {
+ relsXml: createThemeFixtureRelationships(),
+ }),
+ writeVisualFixture("theme-style-refs", createThemeStyleRefsSlide(), {
+ relsXml: createThemeFixtureRelationships(),
+ files: {
+ "ppt/theme/theme1.xml": createThemeStyleRefsTheme(),
+ },
+ }),
writeVisualFixture("group-transform", createGroupTransformSlide()),
writeVisualFixture("nested-groups", createNestedGroupsSlide()),
writeVisualFixture("table-layout", createTableLayoutSlide()),
@@ -333,6 +343,98 @@ function createThemeLuminanceSlide() {
`);
}
+function createThemeStyleRefsSlide() {
+ return slideXml(`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Theme Style Refs
+
+
+
+ Fill, line, and font defaults come from p:style.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Secondary Box
+
+
+
+ `);
+}
+
+function createThemeStyleRefsTheme() {
+ return `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+}
+
function createGroupTransformSlide() {
return slideXml(`
@@ -701,6 +803,13 @@ function createEmptyRelationships() {
`;
}
+function createThemeFixtureRelationships() {
+ return `
+
+
+`;
+}
+
function hexToRgb(hex) {
const clean = hex.replace("#", "");
return [
diff --git a/scripts/visual-cases.mjs b/scripts/visual-cases.mjs
index 861900d..ed84e9b 100644
--- a/scripts/visual-cases.mjs
+++ b/scripts/visual-cases.mjs
@@ -91,6 +91,17 @@ export const visualCases = [
},
enforce: true,
},
+ {
+ id: "theme-style-refs",
+ sourceFile: ".tmp/visual-fixtures/theme-style-refs.pptx",
+ browserFixturePath: "/.tmp/visual-fixtures/theme-style-refs.pptx",
+ slides: [1],
+ thresholds: {
+ maxDiffRatio: 0.35,
+ meanDiffRatio: 0.2,
+ },
+ enforce: true,
+ },
{
id: "group-transform",
sourceFile: ".tmp/visual-fixtures/group-transform.pptx",
diff --git a/src/parser.ts b/src/parser.ts
index d07c603..fda077d 100644
--- a/src/parser.ts
+++ b/src/parser.ts
@@ -1,4 +1,5 @@
import JSZip from "jszip";
+import type { GradientFill } from "./types";
import type {
PptxSlide,
PptxParseResult,
@@ -37,7 +38,7 @@ export async function parsePptx(arrayBuffer: ArrayBuffer | Uint8Array): Promise<
const imageMap = await extractImages(zip);
// Load theme colors for resolving schemeClr references
- const themeColors = await loadThemeColors(zip);
+ const theme = await loadThemeData(zip);
// Parse each slide
const slides: PptxSlide[] = [];
@@ -58,7 +59,7 @@ export async function parsePptx(arrayBuffer: ArrayBuffer | Uint8Array): Promise<
relationships,
imageMap,
zip,
- themeColors,
+ theme,
);
slides.push(slide);
}
@@ -85,8 +86,26 @@ function assertParseApisAvailable(): void {
/**
* Load theme colors from theme1.xml
*/
-async function loadThemeColors(zip: JSZip): Promise