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
41 changes: 41 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/

Expand Down
68 changes: 68 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
13 changes: 13 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
Expand Down
7 changes: 4 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
}
Expand Down Expand Up @@ -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",
Expand Down
26 changes: 1 addition & 25 deletions examples/build.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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.
*/
Expand Down
34 changes: 34 additions & 0 deletions examples/decks.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

/*
* The decks the examples are written as.
*
* A file of its own because two things need it: the script that writes the decks, and
* the test that checks what it wrote. A list of its own in either of them would be a
* second answer to a question this file answers, and the two would drift.
*
* Required rather than included, so that a second caller in the same run does not
* declare the constants again.
*/

/**
* The sources this script does not convert, and why.
*
* A deck is here because converting it is the failure it exists to demonstrate.
*/
const NOT_BUILT = [
'10-rejected' => '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']],
];
10 changes: 5 additions & 5 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 8 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
<testsuite name="unit">
<directory suffix=".php">tests/Unit</directory>
</testsuite>
<!--
The decks the library writes, checked against the schema Microsoft's own
validator reads. It needs the .NET SDK, which a machine without it does not
have, so the test skips itself there.
-->
<testsuite name="conformance">
<directory suffix=".php">tests/Conformance</directory>
</testsuite>
<!--
Every fenced example in README.md, run. The mechanism is described where
it is written down — in tests/Support/Doctest.php.
Expand Down
Loading
Loading