diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 83c92b7..1360115 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -81,6 +81,57 @@ 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: 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 + 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 a816266..98e8614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,34 @@ 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] + +### 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 + 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/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/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", 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 + + + + + + + +