feat: Slide / Master / Layout headers-footers public API (Phase 2)#49
Merged
Conversation
Public Python API for the headers/footers/slide-numbers/dates epic (#20). Phase 2 lands the user-facing surface on top of the Phase 1 (PR #48) OOXML primitives. Phase 3 adds Field-based date auto-update; Phase 5 adds the HandoutMaster Python class and watermark helper. Changes: - pptx.slide._HeaderFooterVisibility (NEW) — mixin providing the four `show_*` properties (show_slide_number, show_footer, show_date, show_header) for any template element that carries a `<p:hf>` child. Inherited by SlideLayout, SlideMaster, and NotesMaster. Getter semantics: `<p:hf>` absent → True (PowerPoint default); present → the effective attribute value (each defaults to True per Phase 1's OptionalAttribute(default=True)). Setter semantics: assigning True when `<p:hf>` is absent is a no-op (default-True needs no element); assigning False creates `<p:hf>` via the Phase 1 ZeroOrOne accessor (`get_or_add_hf`) and writes the attribute as "0". An existing `<p:hf>` element is retained when all attrs become True — avoiding low-value XML churn on toggle-back-on. - pptx.slide.Slide — gains `has_footer`, `footer` (str | None, with setter), `has_slide_number` (read-only — auto-filled by PowerPoint), `has_date`, and `date_text` (str | None, with setter, Fixed-mode only; `<a:fld>` auto-update remains Phase 3 scope). Two private helpers centralize the placeholder iteration: `_first_ph_of_type` walks the slide's own placeholders, `_layout_ph_of_type` walks the layout's placeholders for the clone-on-first-write path. Both return the first match in document order. Text getters call `text_frame.text` on the matched placeholder; text setters clone the layout placeholder via `self.shapes.clone_placeholder` when the slide has no matching placeholder yet, mirroring how PowerPoint promotes a layout-level placeholder to slide-level on first edit. Setting None or "" clears the text but does not remove the placeholder shape. Setting a non-empty string when the layout itself has no FOOTER (or DATE) placeholder raises ValueError with a precise message. Design notes: - The mixin lives in pptx.slide (not a separate module) because its three users all live there and the API surface is small. The `_element` annotation on the mixin is a union of the three concrete template element types, gated by a TYPE_CHECKING import so runtime attribute access works on whichever element type the concrete class carries. - Slide accessors lean on `placeholder_format.type` for type discovery rather than poking `element.ph_type`, matching the established `NotesSlide.notes_placeholder` style in this same file. The lookup helpers return `None` rather than raising so callers can use them as `is None` guards. - The footer/date setters intentionally do NOT remove the placeholder on clear. Removing a shape just because its text is empty would be surprising and would also strip layout-derived formatting; clearing text matches what PowerPoint does when the user backspaces footer content. Test counts: - tests/test_slide.py: +41 new test methods covering all 38 ISCs in the working ISA (12 template `show_*` getter/setter cases across SlideLayout / SlideMaster / NotesMaster; Slide.footer/has_footer with cloning, idempotent rewrite, clear-on-None, ValueError on no layout placeholder; Slide.has_slide_number; Slide.has_date and date_text with the parallel set; helper coverage for first-match document-order semantics). - pytest: 3598 passed (3514 baseline + 84 new — includes pytest parameterizations counted by collection rather than by `def`), 0 failed. Wall clock 5.11s. - ruff check: All checks passed. ruff format: 216 files already formatted (no diff). - behave: 1048 scenarios passed, 0 failed (zero regression vs Phase 1 baseline). - uat/uat_headers_footers_phase2.py: PASS — toggles `layout.show_footer = False` on Layout 0 of test.pptx, sets `slide.footer = "Phase 2 round-trip"` on Slide 0, saves, reopens, and asserts both round-trip. Refs #20. Builds on Phase 1 (PR #48 / commit 0223199).
This was referenced May 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 2 of #20 — public Slide / Master / Layout API
Builds on Phase 1 (PR #48). Ships the user-facing surface that sits on top of the OOXML wrappers — what end users will actually call. Phase 3 adds Field-based date auto-update; Phase 5 adds the
HandoutMasterPython class and a watermark helper.Changes
SlideLayout/SlideMaster/NotesMaster— fourshow_*visibility properties each, via a new private_HeaderFooterVisibilitymixin so the twelve property bodies are defined once and inherited three times.show_slide_number,show_footer,show_date,show_header— bool getters/setters backed by<p:hf>from Phase 1.Truewhen<p:hf>is absent (PowerPoint default). Setter creates<p:hf>on first False, never auto-cleans it when it becomes all-True (low-value churn).Slide— per-slide accessors:has_footer,footer(str | None, get/set). Setter clones the FOOTERLayoutPlaceholderfromslide.slide_layouton first set; raisesValueErrorif the layout has no FOOTER placeholder. Setting toNoneor""clears the text, keeps the placeholder.has_slide_number— presence check; no paired text accessor (auto-filled by PowerPoint).has_date,date_text(str | None, get/set) — parallel Fixed-mode semantics tofooter. The<a:fld>auto-update path is Phase 3 scope, not landed here._first_ph_of_type,_layout_ph_of_type) factor placeholder iteration so the public properties stay readable.Out of scope (deliberate)
Fieldclass /Run.add_field()— Phase 3 (port ofscanny#797).HandoutMasterPython class /HandoutMasterPartplumbing — Phase 5.Verification (local, CPython 3.14.4)
41 new test methods (84 pytest items with parameterizations) across
DescribeSlide,DescribeSlideLayout,DescribeSlideMaster, and the newDescribeNotesMaster.Refs #20.