From c152f69b6082c769cc52b37898ea771b3245648f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Gra=C3=9Fl?= Date: Fri, 30 Oct 2026 17:17:21 +0100 Subject: [PATCH 1/3] The patch carries the chart series in the order the schema names them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The patch this package applies is the difference between PHPPresentation 1.2.0 and upstream's branch, and the branch has moved: a chart's series is now written in the order the schema names it — the shape properties right after the title rather than after the data points and the data labels, and the separator of the data labels of a doughnut before the leader lines rather than after them. PowerPoint repairs a chart written the other way round before it opens it. So the patch is regenerated against the branch that carries the fix, and the commit it is pinned to in the manifest moves with it. Nothing this library draws changes: it writes no chart, and the patch is upstream's branch whole. --- CHANGELOG.md | 15 +++++++++++++++ composer.json | 7 ++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a816266..815b743 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed + +- **The patch carries one more fix from upstream.** A chart's series is written in the + order the schema names it: the shape properties right after the title rather than after + the data points and the data labels, and the separator of the data labels of a doughnut + before the leader lines rather than after them. PowerPoint repairs a chart written the + other way round before it opens it + ([#695](https://github.com/PHPOffice/PHPPresentation/issues/695), + [#1044](https://github.com/PHPOffice/PHPPresentation/pull/1044)). + + This library writes no chart, so nothing it draws changes: the patch is upstream's + branch whole, and the fix belongs to that branch rather than to this one. + ## [0.3.0] - 2026-10-30 ### Added diff --git a/composer.json b/composer.json index 4064e28..4815a58 100644 --- a/composer.json +++ b/composer.json @@ -39,9 +39,9 @@ "patches": { "phpoffice/phppresentation": [ { - "description": "PHPPresentation 1.2.0 brought up to upstream's master, with the fixes that give a picture the path, the bytes and the name it was read from, and that find the folder of a part of a package with the slashes of the package", - "url": "https://raw.githubusercontent.com/markdown-office/PHPPresentation/9aa3f4624f1cab54fc5d275397c2fc59cf6f276d/patches/phpoffice-phppresentation-1.2.0-to-master.patch", - "sha256": "0b8f7c150f7ba5d12671fb9e211c9f1a4db549d7609239f49c0cea707a190551" + "description": "PHPPresentation 1.2.0 brought up to upstream's master, with the fixes that give a picture the path, the bytes and the name it was read from, that find the folder of a part of a package with the slashes of the package, and that write a chart series in the order the schema names it", + "url": "https://raw.githubusercontent.com/markdown-office/PHPPresentation/92c2c64ca20a6b3febf6512e892b21a3c0ae4b56/patches/phpoffice-phppresentation-1.2.0-to-master.patch", + "sha256": "67ce1a44e76e4dc23639fefee365ec811ef7a763331343bde468ae781bf5427a" } ] } @@ -69,6 +69,7 @@ "scripts": { "test": "pest", "test:unit": "pest --testsuite=unit", + "test:conformance": "pest --testsuite=conformance", "test:readme": "pest --testsuite=readme", "test:coverage": "php -d memory_limit=-1 vendor/bin/pest --coverage --min=100", "analyse": "tools/phpstan.sh", From 30ab6ffa34b806ba8351a002e383808e44afaf62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Gra=C3=9Fl?= Date: Fri, 30 Oct 2026 17:17:32 +0100 Subject: [PATCH 2/3] Every deck the library writes is checked against the schema Microsoft's validator reads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nothing in the suite could see that a deck is outside the schema. The decks were checked against the parts they carry, and a deck whose XML is outside the schema passes every one of those checks and opens in PowerPoint as `[Repaired]`. The gate is Microsoft's own validator — the one the Open XML SDK Productivity Tool uses — run over every deck the library writes. It is a program in `tools/ooxml-validator/` rather than a dependency: nothing that ships with the library needs it, and the suite runs without it. A machine with no .NET SDK says what is missing and skips the test, because a green tick for a check that never ran is a check nobody made; the job that runs the gate installs the SDK, so it cannot skip itself there. The decks are the ones the gallery is built from, and the plan of them is the file the script that writes them holds it in: `examples/decks.php`. A list of its own in the test would be a second answer to a question that already has one, and the two would drift — a deck the gallery no longer builds and the gate still checks, which is the worse of the two. --- .github/workflows/tests.yml | 43 ++++++ .gitignore | 4 + CHANGELOG.md | 13 ++ CONTRIBUTING.md | 11 ++ examples/build.php | 26 +--- examples/decks.php | 41 +++++ phpunit.xml.dist | 8 + tests/Conformance/ooxml.php | 149 +++++++++++++++++++ tests/Readme/examples-decks.php | 8 +- tools/ooxml-validator/Program.cs | 110 ++++++++++++++ tools/ooxml-validator/README.md | 38 +++++ tools/ooxml-validator/ooxml-validator.csproj | 25 ++++ 12 files changed, 447 insertions(+), 29 deletions(-) create mode 100644 examples/decks.php create mode 100644 tests/Conformance/ooxml.php create mode 100644 tools/ooxml-validator/Program.cs create mode 100644 tools/ooxml-validator/README.md create mode 100644 tools/ooxml-validator/ooxml-validator.csproj diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 83c92b7..dbd1eb7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -81,6 +81,49 @@ jobs: - name: Test run: vendor/bin/pest + # The decks the library writes, checked against the schema Microsoft's own validator + # reads. + # + # Its own job rather than a step in the test matrix, because the validator is a .NET + # program: the SDK it is built and run with is a second toolchain, and the job that + # installs it is the job that runs the gate. + # + # `--fail-on-skipped` because the test skips itself where the .NET SDK is missing, and + # a skipped test is a green tick for a check that never ran. The rasteriser is + # installed for the same reason: the deck carrying an SVG cannot be written without + # one, and a gate that checked one deck fewer than the gallery holds would be a gate + # that says less than it says. + conformance: + name: valid OOXML + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + + - uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2 + with: + php-version: '8.5' + coverage: none + tools: composer:v2 + extensions: imagick + + - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 + with: + dotnet-version: '8.0.x' + + # The delegate ImageMagick hands an SVG to. Without one that can draw a letter the + # picture in the deck is refused rather than drawn, and the deck is not written at + # all — which would leave the gate checking one deck fewer than the gallery holds. + - name: A rasteriser that can draw an SVG + run: | + sudo apt-get update + sudo apt-get install --yes --no-install-recommends librsvg2-bin + + - name: Install + run: composer install --prefer-dist --no-interaction --no-progress + + - name: The decks are valid OOXML + run: vendor/bin/pest --testsuite=conformance --fail-on-skipped + coverage: name: coverage runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 646b245..48b1714 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,10 @@ # The OOXML schemas `tools/install-ooxml-schemas.php` fetches and checksums. /tools/ooxml-schemas/ +# The validator's own build output, which the .NET SDK writes beside the project. +/tools/ooxml-validator/bin/ +/tools/ooxml-validator/obj/ + # The fonts `tools/generate-font-metrics.php` fetches and checksums. /tools/fonts/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 815b743..98e8614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ All notable changes to this project are documented here. The format follows ## [Unreleased] +### Added + +- **Every deck the library writes is checked against the schema Microsoft's own validator + reads.** Nothing in the suite could see that before: the decks were checked against the + parts they carry, and a deck whose XML is outside the schema passes every one of those + checks and opens in PowerPoint as `[Repaired]`. + + The validator is Microsoft's own — the one the Open XML SDK Productivity Tool uses — and + it is a program in `tools/ooxml-validator/` rather than a dependency: nothing that ships + with the library needs it. A machine without the .NET SDK runs the rest of the suite and + says what is missing, and the job that runs the gate in continuous integration installs + it, so the gate cannot skip itself there. + ### Changed - **The patch carries one more fix from upstream.** A chart's series is written in the diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a1711a3..2fa63bf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -146,6 +146,17 @@ it would import the file through a lossy converter and report nothing about whet is valid. PowerPoint is the only application on this machine that will tell the truth about whether these decks open. +### The schema is checked by Microsoft's own validator + +A deck whose XML is outside the schema opens in PowerPoint as `[Repaired]`, and the +checks written by hand here did not catch that. `tests/Conformance/ooxml.php` runs +Microsoft's Open XML validator — the one the Open XML SDK Productivity Tool uses — over +every deck the library writes, and the program that does it is in `tools/ooxml-validator/`. + +It is a .NET program, so a machine without the SDK skips that test and says what is +missing; the job that runs it in continuous integration installs it, so the gate cannot +skip itself there. + ## What a change has to pass ``` diff --git a/examples/build.php b/examples/build.php index a2653f6..8b92085 100644 --- a/examples/build.php +++ b/examples/build.php @@ -12,22 +12,11 @@ */ require_once __DIR__ . '/../vendor/autoload.php'; +require_once __DIR__ . '/decks.php'; use MarkdownPresentation\Document\DeckParser; use MarkdownPresentation\Render\PresentationRenderer; -/** - * The sources this script does not convert, and why. - * - * Keyed by the file's stem. A deck is here because converting it is the failure it - * exists to demonstrate, which is the same reason `markdown-word` does not build its - * `14-rejected.md`: the build would stop, or — worse — would not stop and the gallery - * would contain a deck that quietly came out wrong. - */ -const NOT_BUILT = [ - '10-rejected' => 'converting it is the failure it exists to demonstrate; see examples/README.md', -]; - /** Where the decks, their PDFs and their previews are written. */ const OUT = __DIR__ . '/out'; @@ -37,19 +26,6 @@ /** The directory a relative image path in a deck resolves against. */ const SOURCES = __DIR__ . '/markdown'; -/** - * The sources written more than once, and the one line each copy differs in. - * - * A theme is a frontmatter key, so the only way to see the three palettes side by side is - * to write the same deck three times. Varying it here rather than in three files keeps - * the three decks identical apart from the one line, which is the point of the - * comparison. - */ -const VARIANTS = [ - '03-themes' => ['theme' => ['default', 'corporate-dark', 'minimal']], - '11-type-scale' => ['theme' => ['default', 'dense']], -]; - /** * What each built deck is for, for the report at the end and for nothing else. */ diff --git a/examples/decks.php b/examples/decks.php new file mode 100644 index 0000000..863e6e7 --- /dev/null +++ b/examples/decks.php @@ -0,0 +1,41 @@ + 'converting it is the failure it exists to demonstrate; see examples/README.md', +]; + +/** + * The sources written more than once, and the one line each copy differs in. + * + * A theme is a frontmatter key, so the only way to see the three palettes side by side is + * to write the same deck three times. Varying it here rather than in three files keeps + * the three decks identical apart from the one line, which is the point of the + * comparison. + */ +const VARIANTS = [ + '03-themes' => ['theme' => ['default', 'corporate-dark', 'minimal']], + '11-type-scale' => ['theme' => ['default', 'dense']], +]; diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 383a647..c3c920a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -15,6 +15,14 @@ tests/Unit + + + tests/Conformance + + net8.0 + + Major + enable + enable + OoxmlValidator + ooxml-validator + + + + + + + + From 0c7471843f0f5f03dd0eae6f23881a5bfc1a55e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Gra=C3=9Fl?= Date: Fri, 30 Oct 2026 17:26:08 +0100 Subject: [PATCH 3/3] A step that says what the runner has --- .github/workflows/tests.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dbd1eb7..1360115 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -121,6 +121,14 @@ jobs: - name: Install run: composer install --prefer-dist --no-interaction --no-progress + - name: What the runner has + run: | + php -r 'var_dump(extension_loaded("imagick"));' + php -r 'print_r(Imagick::queryFormats("*SVG*"));' + convert -list format 2>/dev/null | grep -i svg || echo "no convert" + convert -list delegate 2>/dev/null | grep -i svg || echo "no delegate" + fc-list 2>/dev/null | head -3 || echo "no fonts" + - name: The decks are valid OOXML run: vendor/bin/pest --testsuite=conformance --fail-on-skipped