md2star.pptx_assemble module
Seam-D PPTX assembler: build a deck directly on the AI-selected layouts.
md2star.pptx_layout decides, per slide, which of a designer template’s
named layouts best carries that slide’s content. This module is what actually
uses that decision: for every slide it instantiates the chosen layout
(not a fixed handful of curated backgrounds) from the real template and
overlays guaranteed-legible content, so the deck’s visual variety follows the
AI’s per-slide reasoning instead of a hand-picked round-robin.
Design choice, carried over from the PoC that proved it out: fighting a designer layout’s own placeholders is fragile (their colours and geometry vary wildly, some are dark-on-dark, some clutter a small box next to a big void). Instead each slide keeps the chosen layout’s background (its true branding) and places our own guaranteed-safe text/image boxes in it, sized from the layout’s actual placeholder geometry when the catalog recorded one (title / body / picture), and a sensible fractional default otherwise — so it degrades gracefully for template layouts the catalog under-described.
With one exception, reconciled rather than overlaid: the title goes into the layout’s own title placeholder when it has one. A slide’s outline entry and its screen-reader reading order come from placeholders, not from loose text boxes, so a deck built purely of overlays had no titles at all where it counts — and the empty placeholder left underneath each overlay was a “Click to add title” ghost and a guaranteed overlap. Body text keeps its overlay (a designer body placeholder brings its master’s own bullet glyphs, which would double up with the assembler’s), and every placeholder still empty once a slide is finished is removed. See the “placeholder / overlay reconciliation” section.
Text colour is not hard-coded white: with an arbitrary designer layout now in play (not just a curated dark subset), a slide’s background can be light, so the assembler reads the layout’s own background colour and switches to a dark foreground when needed. This is the correctness gap the curated-background PoC never had to close.
A layout is not the whole template, though, and reference_slides=True opts
each slide into Seam E (md2star.pptx_clone) first: where the chosen
layout has an example page in the template that can carry this slide’s content,
that page is cloned and its text replaced, so the slide inherits the
designer’s decorative shapes and typography and not just their background. It
is strictly an addition — every refusal falls through to the Seam-D path above,
which can place anything.
The target-matching Ralph Eyeball Loop (eyeball_slides()) closes the
verification gap the same way md2star.reverse_diagrams does for
diagrams: render the assembled slide, show it and the chosen layout’s own
catalog thumbnail to a vision model, and apply bounded, spec-level fixes
(shrink text, trim bullets, grow an undersized image) rather than ever hand-
editing the picture. Its model-free counterpart is md2star.pptx_audit,
which measures the saved deck for the defects arithmetic can settle on its own.
- class md2star.pptx_assemble.AssembleResult(pptx_path: 'Path', placed: 'int', total: 'int', cloned: 'int' = 0, pages: 'dict[int, int]'=<factory>, unjudged: 'list[int]' = <factory>)[source]
Bases:
object- Parameters:
- cloned: int = 0
How many of the placed slides were carried by Seam E — cloned from one of the designer’s own example pages rather than built on a bare layout. Always
0unlessreference_slides=True, and never all of them: a page that cannot carry its content is refused, not forced.
- pages: dict[int, int]
Slide index -> the example page that carried it, for the slides Seam E took. Reported rather than written back onto the
Slidemodels, so assembly stays a pure function of them; it is what lets the eyeball loop judge a cloned slide against the page it was cloned from and move it to another one. Seeeyeball_slides().
- md2star.pptx_assemble.CREDIT_NAME = 'md2star-credit'
Name every photo credit carries, so
md2star.pptx_auditknows the small type over a picture is deliberate (a credit, like a footer).
- md2star.pptx_assemble.CREDIT_SIZES = (10.0, 9.0)
a long URL comes down to 9 pt, then breaks after a « / » onto a second line rather than being cut.
- Type:
Credit sizes tried in turn, largest first
- class md2star.pptx_assemble.Item(text, level=0, bullet=True)[source]
Bases:
objectOne paragraph of slide text: its words, list depth, and whether it is a bullet.
- md2star.pptx_assemble.LATENT_KINDS = frozenset({'dt', 'ftr', 'sldNum'})
PowerPoint fills the date, the footer and the slide number itself, from field codes, at display time. python-pptx already declines to clone these (“latent” placeholders), so in practice they never reach a slide this module built — the guard is here so that deleting “every empty placeholder” can never mean deleting a deck’s page numbers, whatever path the presentation arrived by.
- Type:
Placeholders whose emptiness is correct
- md2star.pptx_assemble.PHOTO_TITLE_WORDS = 6
the band holds one line.
- Type:
Words a title over a photograph should not exceed
- md2star.pptx_assemble.SMALLEST_BASE_PT = 16
The smallest type the assembler sets on a content slide — its bullets. A
font_deltais one number applied to every size on the slide, so this is the one that binds: a delta computed from an overrunning 26 pt title would take 16 pt bullets to 8 pt, which the audit then reports as illegible (observed, on a real deck, from the first version of the exact correction).
- md2star.pptx_assemble.TITLE_KINDS = {'ctrTitle', 'title'}
Placeholder kinds that are a slide heading. Public because Seam E (
md2star.pptx_clone) classifies a designer page with the same vocabulary the assembler writes with.
- md2star.pptx_assemble.add_code(slide, code, box, *, font, size=13, shrink=True)[source]
A dark rounded panel holding code, every line of it.
It used to keep the first twelve lines and drop the rest unannounced; a long listing now shrinks to fit (or, under
--min-pt, overruns and is reported by the audit) instead of being cut.
- md2star.pptx_assemble.add_credit(slide, picture, text, *, family=None)[source]
Print text small in a corner of picture, on a dark translucent plate.
Bottom right inside the picture’s frame, a small margin in; top right when that corner would cover the title, a caption or a band the designer drew (a full-bleed photo’s title band sits at the bottom). White on a 60 % black plate reads on any photograph. The shape is named
CREDIT_NAME: the audit exempts it from the type-size floors and from the picture-over-text overlap, both of which are its very design. Returns the text box, orNonewhen there is nothing to credit.
- md2star.pptx_assemble.add_image(slide, img, *, box, alt='', credit='')[source]
Contain-fit img in box (inches); credit goes over it (
add_credit()).
- md2star.pptx_assemble.add_table(slide, rows, box, *, size, font)[source]
A real PowerPoint table for a Markdown pipe table.
Tables used to vanish from smart-layout slides: the text parser skipped every
|row and no branch placed them. The template’s default table style supplies colours; only the size (and the face) is set here.
- md2star.pptx_assemble.add_text(slide, blocks, box=None, *, color, center=False, middle=False, into=None, shrink=True, levels=None, plain=None, face=None)[source]
Overlay a text box. blocks = list of (text, pt_size, bold, font).
With into set to an existing shape (a cloned placeholder), writes into that shape instead of adding a new one, and ignores box — the shape’s own geometry is the designer’s and is already what box was derived from.
A
Nonesize, weight, face or color is not written, so the run inherits it from the template (seedeclared_style()): that is how--min-ptkeeps a designer’s 30 pt body instead of overwriting it. shrink allows the renderer to shrink text to fit its shape; off under--min-pt, where shrinking is exactly what the floor forbids. levels sets each paragraph’s list depth and plain marks paragraphs that must not get the placeholder’s bullet glyph (prose, numbered items).Every paragraph’s lines are broken by
md2star.linebreakwhen the box’s width and the text’s size are known: never after « de » or « the », never before a lone « : », never between a number and its unit. face is the family measured when a block inherits its font (None: 0.55 em).
- md2star.pptx_assemble.assemble(slides, catalog, template, out_path, *, image_base_dir=None, render_mermaid=None, default_layout=None, reference_slides=False, avoid_pages=None, min_pt=None)[source]
Build a real
.pptxdirectly on each slide’s AI-selected layout.For every slide, instantiate
slides[i].chosen_layoutfrom template (falling back to default_layout, or the template’s first layout, when the pick is missing or unknown) and overlay title/body/image/code content sized from that layout’s own placeholder geometry when the catalog recorded one. Per-slideoverrides(written byeyeball_slides()) bound font size and image growth. Every paragraph, table and code line of the slide is placed: nothing is trimmed to make it fit.With reference_slides set, each slide is first offered to Seam E (
md2star.pptx_clone): when the chosen layout has an example page in the template that can carry this slide’s content, that page is cloned and its text replaced, so the deck inherits the designer’s decorative shapes and typography instead of only their background. A refusal (no suitable example, a chart on the page, fewer bullets than columns) falls straight through to the Seam-D path below, which can place anything. avoid_pages maps a slide index to example pages it may not use — whateyeball_slides()writes after a vision model has judged one of them not to be carrying that slide.With min_pt (the CLI’s
--min-pt), no slide text is set under that size: body text goes into the layout’s own body placeholders, keeping the size, colour, face and bullets the designer declared whenever they clear the floor; overlay text uses the declared size or the floor; and nothing is allowed to auto-shrink. Overflow is then the audit’s to report and the eyeball loop’s to fix by moving or splitting the slide.- Returns:
The output path plus how many of the slides were placed without error (a per-slide exception is logged and skipped, never aborts the deck), and how many of those a cloned example page carried.
- Return type:
- Parameters:
- md2star.pptx_assemble.background_rgb(layout)[source]
The background a slide on layout shows, when it is one colour.
The layout’s own
<p:bg>first, then its master’s — the step that was missing: a template whose dark background is declared once on the master (the common case) used to read as “unresolvable”, hence light, hence near-black text on a dark slide. A picture background is unknowable and returnsNone; a gradient is judged by its first stop.- Return type:
RGBColor | None
- md2star.pptx_assemble.body_items(body)[source]
Every paragraph of visible text on a slide, in order, nothing capped.
Headings, tables, fences, images and div markers are not text paragraphs (each has its own branch in
assemble()). List items keep their depth (two spaces or a tab per level); a numbered item keeps its number and gets no bullet glyph; wrapped lines join the paragraph they continue.
- md2star.pptx_assemble.clear_slides(prs)[source]
Drop every slide the template ships with, keeping layouts/masters.
- Parameters:
prs (Presentation)
- Return type:
None
- md2star.pptx_assemble.credit_lines(text, max_w_pt, family)[source]
The size and lines a credit is set in, inside max_w_pt points.
One line at the largest of
CREDIT_SIZESthat fits; failing that, two lines at the smallest, broken after the « / » that balances them best. It measures and breaks whatever text it is given. The address is cut to its site byshort_credit()before this is called; the full address survives as the link, so the printed credit can stay short.
- md2star.pptx_assemble.credit_pictures(pptx_path, markdown)[source]
Credit the pictures of a deck Pandoc wrote; returns how many were credited.
Pandoc’s PPTX writer puts an image’s title into the picture’s
descr(its alt text, followed by the image’s path) and prints nothing. Every picture whosedescris one of markdown’s image titles getsadd_credit(), and itsdescrgoes back to the authored alt text: the credit is printed, not read out as the picture’s description.
- md2star.pptx_assemble.declared_style(shape, level=0)[source]
The point size and colour shape inherits at level, from the template.
- Returns:
The inherited size (
Nonewhen nothing on the chain declares one), its colour when resolvable to RGB, and whether a colour is declared at all (a theme colour the reader cannot resolve still counts: the designer chose it, so the assembler should not paint over it).- Return type:
- Parameters:
level (int)
- md2star.pptx_assemble.drop_empty_placeholders(slide)[source]
Delete every placeholder this slide inherited but never filled.
- Returns:
How many were removed — reported by the caller’s debug log, and the invariant
md2star.pptx_audit.audit()re-checks on the saved deck.- Return type:
- md2star.pptx_assemble.eyeball_slides(slides, catalog, template, out_path, *, indices=None, max_iterations=1, chat=None, render_template=None, render_deck=None, image_base_dir=None, render_mermaid=None, default_layout=None, allow_reselect=True, reference_slides=False, min_pt=None)[source]
Target-matching Ralph Eyeball Loop over an assembled deck.
Assembles (or re-uses an already-assembled) deck, renders it, and for each of indices (default: every slide) shows a vision model both the candidate render and the chosen layout’s own catalog thumbnail. On a mismatch it writes a bounded
overridesentry back onto the slide (shrink text, cap bullets, grow an undersized image — never touches the picture itself) and re-assembles, up to max_iterations passes. Any transport/render failure leaves the current best assembly untouched.With reference_slides on, the loop gets sharper on both ends. Its target for a cloned slide is the designer page that slide was cloned from rather than the layout’s representative thumbnail — the tightest comparison available, since the candidate is literally that page with the deck’s own content in it. And it gains a fix it did not have: when a cloned slide is flagged and no bounded override applies, it moves the slide to a different example page of the same layout before considering a layout swap. A designer who demonstrated one layout five ways has given five compositions to try, and reaching for another of theirs is a smaller, truer correction than abandoning the layout they chose. If none of the remaining pages can carry the slide, assembly falls back to Seam D on its own, which is the safe generic this loop has always wanted.
A judge that times out or answers nothing is unknown, not “matches”: the slide is judged on the measured audit alone and listed in
AssembleResult.unjudged.With min_pt, an overflow is never fixed by shrinking: the slide moves to the next layout that holds it at the floor, and when none is left it is split in two (continuation titled “(suite)” / “(cont.)”).
render_deck renders the assembled output deck itself (default:
md2star.pptx_layout.render_deck_pages(), LibreOffice + poppler); injectable so the loop is testable offline without either installed.- Parameters:
catalog (list[LayoutInfo])
template (Path)
out_path (Path)
max_iterations (int)
chat (Callable[[str, list[bytes] | None, dict | None], dict | str | None] | None)
render_template (Callable[[Path, int], bytes | None] | None)
image_base_dir (Path | None)
default_layout (str | None)
allow_reselect (bool)
reference_slides (bool)
min_pt (float | None)
- Return type:
- md2star.pptx_assemble.fallback_bullets(body, cap=None)[source]
The slide’s text paragraphs, markup stripped — all of them unless cap.
There used to be a default cap of four bullets of ten words each, applied silently. That was content loss presented as layout; the full text now goes on the slide, and a slide too long for any layout is split into continuation slides upstream (
md2star.pptx_layout.paginate()).
- md2star.pptx_assemble.image_title(target)[source]
The Pandoc title of one image target (
path "title"), or"".
- md2star.pptx_assemble.is_dark_layout(layout)[source]
True/Falsefor a resolvable one-colour background, elseNone.Resolved through
background_rgb(), so a dark background declared on the master (and only inherited by the layout) counts.- Return type:
bool | None
- md2star.pptx_assemble.is_ornament(text)[source]
Whether text is ornament (or nothing) rather than someone’s words.
- md2star.pptx_assemble.layout_index(prs)[source]
Map every distinct layout name to its
SlideLayoutobject.
- md2star.pptx_assemble.notes_paragraphs(notes)[source]
Speaker notes as
(text, level)paragraphs, list structure kept.The notes pane is read mid-talk, at a glance: one paragraph per bullet, with the bullet drawn as “•” and nested items indented, reads the way the author wrote it — a single blob, or raw “- “ markup, does not.
- md2star.pptx_assemble.parse_alt_text(body)[source]
The first image’s Markdown alt text (
""when it has none).Authored alt text is the accessible description of the picture, and dropping it makes the deck unreadable to a screen reader. Pandoc’s own PPTX writer carries it into the shape’s
descrattribute; this is how the assembler keeps parity with the path it bypasses. md2star can even write this text for you — seemd2star.preprocessing.alt_text.
- md2star.pptx_assemble.parse_code(body)[source]
First non-Mermaid fenced block (the slide’s primary code sample, if any).
Failing a fence, a Pandoc line block whose every line is inline code (``| git push ``) is a listing too: one source line per line.
- md2star.pptx_assemble.parse_image_credit(body)[source]
The first image’s Markdown title: its photo credit (
""when none).puts the credit where Pandoc keeps an image title. It is printed small over the picture (add_credit()), never used as the picture’s description.
- md2star.pptx_assemble.parse_table(body)[source]
The first Markdown pipe table on the slide, as rows of cell text.
- md2star.pptx_assemble.placeholder_kind(shape)[source]
OOXML placeholder type of shape (
""when it is not one).Read off the XML rather than python-pptx’s
PP_PLACEHOLDERenum so the vocabulary is identical to the catalog extractor’s (md2star.pptx_layout._placeholder_kind()) with no enum-name mapping table to keep in sync. A<p:ph>with notypeis OOXML’s generic content placeholder, which pandoc and PowerPoint both callobj.- Return type:
- md2star.pptx_assemble.short_credit(text)[source]
The words a credit prints, and the full address it links to.
Courtesy of https://www.nga.gov/artworks/195605-gallery-drouotprintsCourtesy of nga.gov: the site, not the whole path, which says nothing a reader can use from a screen. The full address is kept as the link, on the credit and on the picture itself, so a click still lands on the source.
- md2star.pptx_assemble.slide_placeholder(slide, kinds)[source]
The slide’s own cloned placeholder of one of kinds, or
None.
- md2star.pptx_assemble.template_fonts(template)[source]
Derive the template’s own DESIGN fonts:
{family, display, body}.Reads every explicit
typeface="..."on slide/layout shapes (not the theme, which carries Office’s long CJK fallback list) and picks the most- common non-system face as body, its heaviest same-family weight as display (titles). Falls back to Arial when the template declares no explicit design font. Read-only: unlike the PoC this is derived from, this never downloads or installs fonts — a missing font degrades to whatever substitute PowerPoint/LibreOffice picks, exactly like every other best-effort AI/rendering feature in this package.