diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 83c92b7..22e01b7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -81,6 +81,47 @@ 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. + # + # The rasteriser is installed because the deck that carries a vector needs one to be + # written at all — the library refuses a picture it cannot draw rather than writing a + # diagram that has lost its words. Whether the runner's Imagick can draw one is the + # runner's business: where it cannot, that deck's test says so and skips itself, and + # the rest of the gallery is checked all the same. + 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 validator is a .NET program, so the SDK is what runs it. The step says so + # where it is missing rather than leaving the gate to skip itself: a skipped test + # is a green tick for a check that never ran. + - name: The .NET SDK is there + run: dotnet --version + + - name: Install + run: composer install --prefer-dist --no-interaction --no-progress + + - name: The decks are valid OOXML + run: vendor/bin/pest --testsuite=conformance + 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..827dbe9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,73 @@ 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). +## [0.3.1] - 2026-10-31 + +### Added + +- **The decks the library writes are 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. + + The one deck that carries a vector needs a rasteriser to be written at all — the library + refuses a picture it cannot draw rather than writing a diagram that has lost its words + — so a machine without one says which deck it did not check and checks the rest. + **The continuous integration runner is such a machine**, so the gate checks eighteen of + the nineteen decks there, and the nineteenth is checked where a vector can be drawn. + +### Changed + +- **The patch carries four more fixes 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)). + + Three of them are charts that carried less than they were given: the area, the line, + the radar and the scatter dropped the fill of a single data point + ([#1048](https://github.com/PHPOffice/PHPPresentation/issues/1048), + [#1052](https://github.com/PHPOffice/PHPPresentation/pull/1052)), a doughnut, a pie + and a pie in three dimensions dropped the fill of the series itself + ([#1049](https://github.com/PHPOffice/PHPPresentation/issues/1049), + [#1051](https://github.com/PHPOffice/PHPPresentation/pull/1051)), and a category axis + wrote its major unit as the number of labels to skip — and as a fraction, where the + schema wants a count, so the file was outside the schema + ([#1047](https://github.com/PHPOffice/PHPPresentation/issues/1047), + [#1050](https://github.com/PHPOffice/PHPPresentation/pull/1050)). + + 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. + +- **The charts are checked too, and the gate knows what a chart has to carry.** The + patch is the library that writes the charts, and a patch that is re-rolled without a + fix is a patch that has lost it — nothing else in this suite would have noticed. So + the gate writes a chart of every kind through the patched library, and checks two + things about it: that the file is within the schema, which the validator reads, and + that it carries what it was given, which the validator cannot see. + + A chart that is missing a data point is a valid chart. The validator reads what a + file holds and says nothing about what it does not, so the elements themselves are + asserted on: every kind of series carries the fill it was given, and every kind of + series carries the fill of the point it was given. + + Three defects were found this way and are fixed upstream: + [#1047](https://github.com/PHPOffice/PHPPresentation/issues/1047) — a category axis + wrote its major unit as the number of labels to skip, and wrote it as a fraction + where the schema wants a count, so the file was outside the schema; + [#1048](https://github.com/PHPOffice/PHPPresentation/issues/1048) — the area, line, + radar and scatter charts dropped the fill of a single data point; + [#1049](https://github.com/PHPOffice/PHPPresentation/issues/1049) — a doughnut, a pie + and a pie in three dimensions dropped the fill of the series itself. + ## [0.3.0] - 2026-10-30 ### Added @@ -2500,5 +2567,6 @@ slide begins and which theme a deck means, and renders the deck to a `.pptx`. [0.2.1]: https://github.com/markdown-office/markdown-presentation/releases/tag/v0.2.1 [0.2.2]: https://github.com/markdown-office/markdown-presentation/releases/tag/v0.2.2 [0.3.0]: https://github.com/markdown-office/markdown-presentation/releases/tag/v0.3.0 +[0.3.1]: https://github.com/markdown-office/markdown-presentation/releases/tag/v0.3.1 [composer-patches]: https://github.com/cweagans/composer-patches [the-patch]: https://github.com/markdown-office/PHPPresentation/blob/patches/1.2.0-to-master/patches/phpoffice-phppresentation-1.2.0-to-master.patch diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a1711a3..a31ff1c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -146,6 +146,19 @@ 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 +the decks 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 those tests and says what is +missing; the job that runs them in continuous integration installs it, so the gate cannot +skip itself there. The one deck that carries a vector needs a rasteriser to be written at +all, and a machine without one says which deck it did not check. + ## What a change has to pass ``` diff --git a/composer.json b/composer.json index 4064e28..d028a04 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, that write a chart series in the order the schema names it, that write the data points of a series and the fill of the series itself, and that give a category axis a tick label skip of its own", + "url": "https://raw.githubusercontent.com/markdown-office/PHPPresentation/605bc7c9724ac4a0781cc1692ff7ae279a33bb8a/patches/phpoffice-phppresentation-1.2.0-to-master.patch", + "sha256": "46862cef4470ae8e755a03f1f8afd94c7e772cc1ee4ab0a39441c2cb392d5146" } ] } @@ -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..9181279 --- /dev/null +++ b/examples/decks.php @@ -0,0 +1,34 @@ + '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. + */ +const VARIANTS = [ + '03-themes' => ['theme' => ['default', 'corporate-dark', 'minimal']], + '11-type-scale' => ['theme' => ['default', 'dense']], +]; diff --git a/phpstan.neon.dist b/phpstan.neon.dist index b4ac8f7..411e456 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -2,11 +2,11 @@ # tools/phpstan.sh for the version and the hash. # # Level 9, with no `ignoreErrors`, no baseline and no suppression comment anywhere -# in this repository. `tests/Unit` and `tests/Readme` are not analysed: every one of -# their tests is a closure handed to `it()` and every assertion goes through -# `expect()`, both of which resolve at run time, so an analyser given neither can -# only report that the functions are unknown. The support code those tests lean on -# *is* analysed, and it is where the array shapes live. +# in this repository. `tests/Unit`, `tests/Conformance` and `tests/Readme` are not +# analysed: every one of their tests is a closure handed to `it()` and every assertion +# goes through `expect()`, both of which resolve at run time, so an analyser given +# neither can only report that the functions are unknown. The support code those tests +# lean on *is* analysed, and it is where the array shapes live. parameters: level: 9 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 + + + + + + + +