md2star.pptx_layout module

Template-intelligent PPTX layout catalog and per-slide AI selection.

Pandoc’s PPTX writer only ever reaches the ~7 named layouts its bundled template.pptx ships (Title Slide, Section Header, Title and Content, …); it has no notion of a designer template’s much richer layout vocabulary (a real marketing deck template routinely ships 20-40 named layouts: covers, section dividers, big-statement slides, photo heroes, multi-column splits, …). This module is the missing primitive: given any designer PPTX template, it builds a layout catalog (every distinct named layout, its placeholder geometry, and — when a vision model is available — a short caption of what kind of slide it is), then selects the best-fitting catalog layout for each slide of an authored Markdown deck.

Selection is two-staged, mirroring how a human designer would actually work:

  1. Text reasoning (LLM): read each slide’s Markdown content plus the catalog (names + captions) and choose the layout whose role best fits — a cover, a section divider, a photo hero, bulleted content, … This stage alone is cheap and works from captions only, never looking at a candidate render.

  2. Visual confirmation (VLM): the text stage never actually looks at the candidate layouts, so it can pick a layout whose caption sounds right but whose actual composition does not suit the slide (a “photo-hero” caption on a layout whose picture placeholder is tiny, say). This stage shows the vision model the slide’s short-listed candidate thumbnails (the text pick plus a couple of archetype-diverse alternates) side by side and asks which one actually carries the content best, overriding the text pick on disagreement. This is the dimension a pure-LLM, caption-only selector cannot provide.

Both stages, and the template-page renderer they rely on, are threaded through injectable seams (chat / render) so the whole pipeline is unit-testable offline with fakes, exactly like md2star.reverse_diagrams. Every call degrades gracefully: with no reachable engine the catalog still builds (deterministic layout/placeholder extraction never needs AI) and selection falls back to a safe, rule-based default layout per slide.

Author

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

class md2star.pptx_layout.LayoutInfo(name, example_pages=<factory>, placeholders=<factory>, archetype='bulleted-content', background='', caption='', is_content_layout=True)[source]

Bases: object

One distinct named layout the designer template exposes.

Parameters:
archetype: str = 'bulleted-content'
background: str = ''
caption: str = ''
example_pages: list[int]
has_kind(kind)[source]
Parameters:

kind (str)

Return type:

bool

is_content_layout: bool = True
name: str
placeholders: list[Placeholder]
property rep_page: int | None

The first example slide page this layout appears on, if any.

class md2star.pptx_layout.Placeholder(idx, kind, emu=None)[source]

Bases: object

One placeholder shape on a layout: its role and its box in EMU.

Parameters:
emu: tuple[int, int, int, int] | None = None
idx: int
kind: str
class md2star.pptx_layout.Slide(index, title, body='', features=<factory>, chosen_layout=None, chosen_confidence=None, chosen_why='', overrides=<factory>)[source]

Bases: object

One Markdown slide plus the cheap structural cues a selector keys on.

Parameters:
body: str = ''
chosen_confidence: float | None = None
chosen_layout: str | None = None
chosen_why: str = ''
features: list[str]
index: int
overrides: dict
title: str
md2star.pptx_layout.build_catalog(template, *, chat=None, render=None, use_vision=True, cache_path=None, refresh=False)[source]

Build the full layout catalog for template: geometry + (optional) captions.

Parameters:
  • template (Path) – The designer PPTX template to catalog.

  • chat (ChatFn, optional) – Vision transport for captioning; defaults to the real engine-backed one. Ignored when use_vision is False.

  • render (RenderFn, optional) – Template-page renderer; defaults to the real LibreOffice+poppler one.

  • use_vision (bool, default True) – When False (or when rendering/chat is unavailable), every layout gets a rule-based caption instead of a VLM one — the catalog still builds, just without visual captions.

  • cache_path (Path, optional) – Where to persist/read the catalog JSON. Defaults to a content-hashed path under md2star’s cache dir, so re-running on the same template is free.

  • refresh (bool, default False) – Force a rebuild even if a cache file exists.

Returns:

One entry per distinct designer layout name.

Return type:

list[LayoutInfo]

md2star.pptx_layout.default_chat(*, kind, model=None)[source]

Build the real engine-backed transport for kind ("llm" or "vlm").

Routes through best_engine_ai_helper.llm.chat using md2star’s resolved brief -> engine descriptor, exactly like md2star.reverse_diagrams. Returns None on any failure so callers degrade gracefully.

Parameters:
  • kind (str)

  • model (str | None)

Return type:

Callable[[str, list[bytes] | None, dict | None], dict | str | None]

md2star.pptx_layout.default_render()[source]

Build the real page renderer, caching all pages per template (by hash).

Return type:

Callable[[Path, int], bytes | None]

md2star.pptx_layout.extract_layouts(template)[source]

Deterministically extract every distinct layout name and its geometry.

No AI, no rendering: pure zipfile + ElementTree over the template’s OOXML parts. Always available, and the first stage of build_catalog().

Enumerates every ppt/slideLayouts/slideLayoutN.xml part directly (not only the layouts some slide happens to use) so a template distributed with no example slides at all — just its layouts, e.g. python-pptx’s own bundled default template — still yields a full catalog; example_pages is simply empty for a layout no slide currently demonstrates (no thumbnail/caption for it, but its geometry is still extracted and it can still be selected).

Parameters:

template (Path)

Return type:

list[LayoutInfo]

md2star.pptx_layout.layout_pages(template)[source]

Public wrapper over _layout_example_pages() (used by callers/tests).

Parameters:

template (Path)

Return type:

dict[str, list[int]]

md2star.pptx_layout.render_deck_pages(path, *, dpi=110)[source]

Render every page of a PPTX/PDF path to PNG bytes, keyed by 1-based page.

Public wrapper over _render_all_pages(), used by md2star.pptx_assemble’s eyeball loop to render its own assembled output (as opposed to default_render(), which caches per template page and is meant for the catalog/tie-break stages).

Parameters:
Return type:

dict[int, bytes]

md2star.pptx_layout.segment(markdown)[source]

Split markdown into slides.

Prefers explicit horizontal-rule slide breaks (*** / --- / ___ on their own line, the common reveal.js/Marp/this-project convention); when the document has none, falls back to splitting on every H1/H2 heading, mirroring Pandoc’s own default slide-level behaviour.

Parameters:

markdown (str)

Return type:

list[Slide]

md2star.pptx_layout.select_layouts(slides, catalog, *, chat=None, batch=12)[source]

Fill slide.chosen_layout for every slide via batched LLM text calls.

Only content layouts are offered (brand-reference pages are dropped), each tagged with its archetype so the model matches by role. On any transport failure, slides in that batch fall back to the best archetype-matching content layout (never left unassigned).

Parameters:
Return type:

None

md2star.pptx_layout.smart_layout_available()[source]

Return True when the full AI-assisted pipeline (LLM + VLM) can run.

Mirrors md2star.reverse_diagrams.diagrams_available(): a light “does the engine resolve and expose both model kinds?” probe, cheap enough to call before offering --smart-layout in the CLI. The deterministic catalog extraction and a rule-based selection fallback work regardless.

Return type:

bool

md2star.pptx_layout.visual_confirm(slides, catalog, *, chat=None, render=None, template=None, k=3, only_low_confidence=False, confidence_threshold=0.7)[source]

Visually confirm (or override) each slide’s text-stage layout pick.

This is the VLM dimension the pure-text select_layouts() cannot provide: it renders each candidate’s example page, shows the vision model the actual thumbnails side by side with the slide’s content, and asks which one genuinely carries that content best. Disagreements override slide.chosen_layout; any transport/render failure leaves the text stage’s pick untouched (best-effort, never load-bearing).

Parameters:
  • only_low_confidence (bool, default False) – When True, only re-check slides whose text-stage confidence is missing or below confidence_threshold (cheaper, fewer VLM calls); when False (default) every slide gets a visual check.

  • slides (list[Slide])

  • catalog (list[LayoutInfo])

  • chat (Callable[[str, list[bytes] | None, dict | None], dict | str | None] | None)

  • render (Callable[[Path, int], bytes | None] | None)

  • template (Path | None)

  • k (int)

  • confidence_threshold (float)

Return type:

None