You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(a11y): reading_order + accessibility_issues + is_hidden_from_accessibility (Phase B)
Phase B of issue #22 (Accessibility epic). Phase A — `Shape.alt_text`,
`Shape.alt_title`, `Shape.is_decorative` — shipped in PR #31. Phase B
adds the remaining items the issue called out, finishing the epic
modulo Microsoft Accessibility Checker's deeper diagnostics (color
contrast, font size, etc.) which are outside python-pptx's scope.
Phase A regression fix (rolled into this PR)
--------------------------------------------
PR #31's `<adec:decorative>` extension used the wrong `<a:ext uri="...">`
GUID — it shipped with `{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}`, but
PowerPoint's accessibility-rendering pipeline only recognizes the
extension under `{C183D7F6-B498-43B3-948B-1728B52AA6E4}`. With the
wrong GUID, the XML round-tripped within python-pptx (so unit tests
passed) but PowerPoint silently ignored the extension and the
"Mark as Decorative" checkbox in the Alt Text pane stayed unchecked
on reopen. Confirmed empirically by diffing a maintainer-authored
PowerPoint deck (where the checkbox was set in the UI, then saved)
against a python-pptx-emitted deck — the only material difference was
the GUID. Both `is_decorative` getter and setter, plus their unit
tests, are updated to the correct GUID.
New public API (Phase B)
------------------------
- `Shape.is_hidden_from_accessibility` (boolean, read/write)
Convenience alias for `is_decorative`. Mirrors the wording used in
some accessibility documentation and third-party tools so the API
reads naturally for either audience. Backed by the same
`<adec:decorative val="1"/>` extension.
- `Slide.shapes.reading_order` (read/write)
Getter returns a tuple of shapes in z-order — the order screen
readers will narrate them on a slide that does not declare an
explicit `<p:tabLst>`. Setter accepts a permutation of the slide's
existing shapes and reorders the underlying `<p:spTree>` to match.
Raises `ValueError` on wrong length or unknown shapes.
- `Slide.shapes.accessibility_issues()` (returns `list[BaseShape]`)
Lint helper. Returns shapes that lack alt text (neither `alt_text`
nor `alt_title` is set) AND are not marked decorative. Returned in
reading order. A fully-tagged slide reports `[]`. Treat it as a
fast first-pass — color-contrast / font-size / explicit-tab-order
checks are not covered.
Phase B scope decisions
-----------------------
- `<p:tabLst>` (the OOXML element for explicit reading order on a
per-slide basis) is rare in practice — PowerPoint's UI doesn't
expose it, and the OOXML spec calls out that the fallback for
reading order is shape z-order. We deliberately model
`reading_order` as z-order rather than emitting `<p:tabLst>`.
This keeps the API simple and works correctly for the
overwhelming majority of decks. If a real-world need for explicit
`<p:tabLst>` emerges, that's a Phase C addition that would
preserve the existing API as the no-tabLst fallback.
Test coverage
-------------
- 20 new unit tests in `tests/test_a11y_phase_b.py`
- 4 new behave scenarios in `features/a11y-phase-b.feature`
- Existing `tests/shapes/test_base.py` `is_decorative` cases
updated to the corrected GUID; semantic test surface unchanged.
- New `uat_a11y_phase_b.py` (untracked per repo §6) builds a 2-slide
deck demonstrating the lint helper and reading_order setter.
UAT signoff: ✓ on the corrected URI (verified end-to-end against
a PowerPoint-authored reference deck).
Verification
------------
```
$ python3 -m pytest tests/ -q | tail -3
3242 passed in 4.42s
$ ruff check src tests | tail -3
All checks passed!
$ python3 -m behave features/ --no-color | tail -3
1003 scenarios passed, 0 failed, 0 skipped
3011 steps passed, 0 failed, 0 skipped
```
Closes#22
0 commit comments