md2star.pptx_audit module

Model-free audit of an assembled deck: what a reviewer would catch by eye.

md2star.pptx_fit decides, before assembly, whether a slide’s content should fit a candidate layout. This module asks the complementary question after assembly, of the saved .pptx itself: given the shapes that actually exist, with the geometry and the font sizes they actually carry, is anything wrong with this deck? Six checks, all arithmetic on OOXML — no model, no render, no network:

off-slide

A shape extends past the canvas. Content that nobody will ever see.

overlap

Two text shapes cover the same region, or a picture covers part of a text shape: words with a photograph laid over them (or under them) are as unreadable as words over words. A picture filling most of the canvas (BACKDROP_SHARE) is a backdrop, the photo-hero composition, and is not counted. A one-glyph ornament (md2star.pptx_assemble.is_ornament()) does not count as one of the two — a “01” inside a title’s generous box is a designer’s composition too, and cloned pages are full of them.

ghost-placeholder

A placeholder the slide inherited from its layout and never filled. The assembler removes these (see md2star.pptx_assemble.drop_empty_placeholders()); this is the invariant re-checked on the file, which is the only place it matters.

illegible-type

A run below LEGIBLE_MIN_PT. Not the 30 pt ideal — the size below which a projected slide stops being readable at all.

low-contrast

Text a reader cannot separate from what is behind it, by the WCAG ratio (sRGB-linearised relative luminance), against the 3:1 floor large type earns and the 4.5:1 floor small type does not. The backdrop is resolved the way a renderer resolves it — shapes under the text, then the slide’s background, then the layout’s shapes and background, then the master’s — because on a real template the colour panel a heading sits on is a shape on the layout while the layout’s own background is the other half of the slide. Only runs that state their own colour are judged, and a backdrop this module cannot read (a photograph, a gradient) is left unjudged rather than guessed at.

under-min-pt

Only with min_pt (md2star audit --min-pt 30): slide content set under that size — Kawasaki’s 30 pt, enforced instead of observed. Footer chrome is not content: date / footer / slide-number placeholders, any shape holding a field (<a:fld>, e.g. the page number), and a single line of text in the bottom 12 % of the slide are exempt. A photo credit (a shape named md2star-credit, see md2star.pptx_assemble.add_credit()) is exempt from this, from illegible-type, overrun and overlap: it is small and over its picture by design. Its contrast is still judged.

overrun

The text needs more room than its box gives it at the size it declares. Reported with the size that would fit, because PowerPoint’s auto-shrink means the consequence is quiet (smaller type), not visible (clipped text). An observation about the deck’s density, in the same spirit as md2star.pptx_fit.KAWASAKI_MIN_PT: a finding, never an error.

Why bother, when md2star.pptx_assemble.eyeball_slides() already shows each slide to a vision model? Because that loop needs a resolved VLM, LibreOffice and poppler, while every failure above is measurable. A geometry bug deserves arithmetic, not an opinion — and this way the quality gate still runs on a laptop with no model at all, which is md2star’s whole offline-by-default posture. The dimensions are the ones PPTEval scores a deck on by eye (Design: “proper layout … element placement, and absence of overlaps”, arXiv 2501.03936); this is the half of that rubric a machine can settle on its own.

Author

[Warith HARCHAOUI](https://linkedin.com/in/warith-harchaoui/)

md2star.pptx_audit.BACKDROP_SHARE = 0.8

Share of the canvas above which a picture is a backdrop (a full-bleed photo with a title on it, by design) rather than a picture placed beside words.

md2star.pptx_audit.BLEED_TOLERANCE = 0.01

How far past the canvas edge a shape may sit before it counts as bleeding off the slide, as a fraction of the canvas. Designer templates routinely bleed a background band a hair past the edge on purpose, and rounding EMU through inches leaves sub-point residue, so an exact test would cry wolf.

md2star.pptx_audit.CHROME_BAND = 0.12

Share of the slide’s height, from the bottom, where a single line of text is footer chrome (a course name, a page number) for under-min-pt.

md2star.pptx_audit.CONTRAST_LARGE_PT = 18.0

1 of it against its background instead of the 4.5:1 it asks of body copy. A slide is mostly large type by that definition, which is why the floor here is usually the lenient one — and why a run that falls under it is a real problem rather than a strict reading of a web rule.

Type:

WCAG 2.x calls type large at 18 pt, or 14 pt when bold, and asks 3

md2star.pptx_audit.DEFECT_KINDS = frozenset({'ghost-placeholder', 'illegible-type', 'low-contrast', 'off-slide', 'overlap', 'under-min-pt'})

The kinds that are defects rather than observations, and so decide the exit status. An overrun is left out on purpose: PowerPoint auto-shrinks the text, so a dense deck is a choice, not a breakage. –strict counts it too.

class md2star.pptx_audit.Finding(slide, kind, detail, shape='', measured_pt=None, fits_pt=None)[source]

Bases: object

One thing wrong with one slide.

Parameters:
slide

1-based slide number, as PowerPoint shows it.

Type:

int

kind

One of KINDS.

Type:

str

detail

A sentence a human can act on, with the measurement in it.

Type:

str

shape

The offending shape’s name, when a single shape owns the finding.

Type:

str

measured_pt, fits_pt

An overrun’s declared size and the size that would fit. None for every other kind, where the question does not arise.

Type:

float or None

detail: str
fits_pt: float | None = None
kind: str
measured_pt: float | None = None

the size the text declares, and the size that would fit. Carried as numbers, not only inside detail, because the eyeball loop consumes this (see md2star.pptx_assemble._measured_verdict()) and a loop that reads them can apply the exact correction instead of nudging toward it.

Type:

For an overrun

shape: str = ''
slide: int
md2star.pptx_audit.KINDS: tuple[str, ...] = ('off-slide', 'overlap', 'ghost-placeholder', 'illegible-type', 'under-min-pt', 'low-contrast', 'overrun')

Every finding kind this module can report, in descending severity. Used to order a report and to keep the vocabulary closed (a typo’d kind in a caller would otherwise sort and summarise silently).

md2star.pptx_audit.LEGIBLE_MIN_PT = 12.0

Point size below which a projected slide stops being readable from the back of a room. Deliberately far below md2star.pptx_fit.KAWASAKI_MIN_PT (30 pt): that is the ideal this project reports against, while this is the floor under which something has gone wrong — a caption nobody can read.

md2star.pptx_audit.OVERLAP_TOLERANCE = 0.1

Shared area, as a fraction of the smaller shape’s own area, above which two text shapes are judged to collide. A few percent is a touching corner; a tenth of a shape is a legibility problem.

md2star.pptx_audit.audit(deck, *, canvas=None, min_pt=None)[source]

Audit a saved .pptx, slide by slide.

Parameters:
  • deck (pathlib.Path) – The assembled presentation to inspect. Read-only; nothing is written.

  • canvas (tuple of int, optional) – (width, height) in EMU to measure against. Defaults to the deck’s own slide size, which is what you want unless you are checking a deck against a different target canvas.

  • min_pt (float, optional) – Also report content text under this size (under-min-pt).

Returns:

Sorted by slide, then by descending severity (KINDS order). Empty means the deck passed every check.

Return type:

list of Finding

Examples

>>> findings = audit(Path("deck.pptx"))
>>> print(summarise(findings))
layout audit: clean (12 slides)
md2star.pptx_audit.log_findings(findings)[source]

Send each finding to the logger at a level that matches its severity.

Parameters:

findings (list[Finding])

Return type:

None

md2star.pptx_audit.run(deck_path, *, as_json=False, strict=False, min_pt=None)[source]

md2star audit <deck.pptx> — measure a deck and report what is wrong.

Works on any presentation, not only the ones md2star built: everything it checks is read off the OOXML, so a deck somebody made in PowerPoint is just as auditable. No model, no render, no network.

The flags arrive parsed: since 3.10.0 this package has exactly one command line, built with click in md2star.cli, so nothing here re-parses an argv.

Parameters:
  • deck_path (str) – The .pptx to inspect. Never modified.

  • as_json (bool, optional) – Emit the findings as JSON instead of one line each plus a summary.

  • strict (bool, optional) – Also fail on overrun, which is otherwise an observation.

  • min_pt (float, optional) – Fail on content text under this size (under-min-pt); footer chrome is exempt.

Returns:

0 when nothing but observations were found, 1 when a defect was (see DEFECT_KINDS, and strict), 2 on a usage or read error.

Return type:

int

md2star.pptx_audit.summarise(findings, *, slides=None)[source]

One line fit for stdout: what the audit found, or that it found nothing.

Parameters:
  • findings (list of Finding) – As returned by audit().

  • slides (int, optional) – Deck length, for the “clean” wording.

Returns:

A single line, never empty.

Return type:

str

Examples

>>> summarise([], slides=12)
'layout audit: clean (12 slides)'
>>> summarise([Finding(3, "overlap", "x")])
'layout audit: 1 finding on 1 slide (1 overlap)'