md2star.pptx_clone module

Seam-E assembler: clone the designer’s own example slide, then replace its text.

md2star.pptx_assemble (Seam D) instantiates a layout and overlays guaranteed-safe boxes in it. That is predictable, and it is also the ceiling of what a layout can give you: a layout carries the background and the placeholder boxes, and nothing else. The accent bars, the numbered step chevrons, the decorative rules, the little bespoke shapes that make a deck look like itself live on the designer’s example slides, one level further in.

So this module takes the other half of the template. For a slide whose chosen layout has an example page in the catalog (LayoutInfo.example_pages), it clones that page and replaces its content: the designer’s filler title becomes yours, their filler bullets become yours, their stand-in picture slot takes your image, and everything they drew around that content stays exactly where they drew it. Typography is inherited rather than forced, which is the whole point — a cloned paragraph keeps the designer’s face, size, colour and bullet glyph, so the result is their composition with your words in it.

Three rules keep that from being a liability.

Nothing the designer wrote may ship. This is the hard one, and it is the same bug class as speaker notes printed on a slide: a template’s filler text is their copy (“Lorem ipsum”, “Client name here”), and publishing it inside your deck is worse than an empty box. So every text frame on a cloned slide is either filled with your content or retired (_retire()) — there is no third outcome — and a page whose content is not restatable at all (a chart, a table, SmartArt, a group with words in it) is refused outright rather than cloned with the designer’s numbers still in it.

A bad carrier is refused, not forced. can_carry() decides, before anything is cloned, whether an example page can hold this slide’s content: a three-column design needs at least three bullets, an image slide needs a picture to replace. When it cannot, compose() returns None and the caller falls back to Seam D, which can place anything. A predictable slide beats a gutted composition.

Legibility is still enforced. Inheriting the designer’s typography is the feature; inheriting unreadable typography is not. Any run that declares a size under md2star.pptx_audit.LEGIBLE_MIN_PT is lifted to that floor. A run that declares no size is left alone: it inherits from the designer’s own master, which is presentation-grade by construction — and the audit reads declared sizes for the same reason.

Dependency direction is one-way, like the audit’s: this module imports the assembler’s placeholder vocabulary and image placement, and md2star.pptx_assemble.assemble() imports this one inside its own body, so the pair never forms an import cycle.

Specified in docs/design/pptx-template-intelligence.md §10.4 (and §4.3 for the A-D seams this one extends).

Author

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

md2star.pptx_clone.CONTENT_PICTURE_AREA = 0.04

A picture covering at least this share of the canvas is a content slot (the designer’s stand-in photo), so it is replaced by the slide’s own image, or removed with it when the slide has none. Anything smaller is furniture — a logo, an icon, a rule — and stays exactly where the designer put it.

class md2star.pptx_clone.Content(title, bullets=<factory>, image=None, alt='', credit='')[source]

Bases: object

What a Markdown slide wants placed, independent of how it gets placed.

The assembler has made every content decision by the time Seam E is asked — which branch the slide took, how many bullets survived the eyeball loop’s cap, where a Mermaid diagram was rendered to — so this carries the outcome and both seams consume the same resolved payload.

Parameters:
alt: str = ''
bullets: list[str]
credit: str = ''
image: Path | None = None
title: str
md2star.pptx_clone.PEER_AREA_RATIO = 0.4

A text frame counts as a peer of the biggest one on the slide — one column of a multi-column design, rather than a caption or a footnote — when it has at least this share of the biggest one’s area. Peers split the bullets between them; everything else is retired.

class md2star.pptx_clone.Roles(title=None, bodies=<factory>, pictures=<factory>, furniture=<factory>, foreign=<factory>)[source]

Bases: object

Every shape of a slide, sorted into what Seam E may do with it.

Parameters:
bodies: list

Text frames that can carry bullets, in reading order (reading_order(): columns left to right, rows top to bottom).

foreign: list

Content Seam E cannot restate — a chart, a table, SmartArt, a group with words in it. One of these is enough to disqualify the whole page, since cloning it would publish the designer’s own numbers (see can_carry()).

furniture: list

decorative shapes, small pictures, and the latent date / footer / slide-number placeholders. Never touched.

Type:

Design, not content

pictures: list

Pictures big enough to be content slots, biggest first.

title: object | None = None

the designer’s own title placeholder when the page has one, else its largest, highest type.

Type:

Where the slide’s heading goes

md2star.pptx_clone.can_carry(roles, content)[source]

Whether a page classified as roles can actually hold content.

The refusals are the interesting part, and each one is a composition this seam would otherwise break:

  • no title frame — nowhere to put the heading, and a cloned page with the designer’s heading still on it is the leak this module exists to avoid;

  • content that cannot be restated (roles.foreign) — a chart or a table would ship with the designer’s numbers in it;

  • an image with no slot to put it in — the picture would land on top of a composition that was not drawn around it;

  • a slot with no image to put in it — the mirror case, and the one a vision model catches first: a page the designer drew around a photograph keeps the shape of that photograph, so taking it out (which we must, it is their content) leaves the void the rest of the page was balanced against. A text slide belongs on a page the designer built for text;

  • fewer bullets than columns — a three-column design with one filled column reads as broken;

  • text that would have to shrink past the legibility floor to fit the frames the designer sized for their own line (fits()) — the page that taught this one put three bullets in a kicker meant for four words, and the renderer dutifully set them at 9 pt.

Every refusal is the same move: the page is declined and Seam D, which can place anything, takes the slide instead.

Parameters:
Return type:

bool

md2star.pptx_clone.classify(slide, canvas)[source]

Sort every shape of slide into a Roles.

Parameters:
  • slide – The page being classified — a designer example, or the clone of one.

  • canvas (tuple[int, int]) – (width, height) of the deck in EMU; a picture’s share of it is what separates a content slot from a logo.

Return type:

Roles

md2star.pptx_clone.clone_slide(prs, src)[source]

Duplicate src — a slide of prs — and return the new slide.

python-pptx has no slide-copy API; this is the shape-tree deepcopy plus relationship re-pointing that stands in for one. It stays inside one presentation on purpose: the deck Seam E builds is opened from the designer template itself, so their example pages are already in the same package and no cross-file part surgery is needed.

md2star.pptx_clone.compose(prs, example, content, *, font_delta=0, min_pt=None)[source]

Clone example and put content in it; None if it cannot carry it.

Classification runs twice on purpose: once on example to decide feasibility before anything is written, and once on the clone, whose shapes are different objects. Deciding first is what makes a refusal free of side effects — the caller gets None and a presentation it can still build a Seam-D slide on.

Returns:

The new slide, or None when the page was refused.

Return type:

object | None

Parameters:
md2star.pptx_clone.distribute(bullets, frames)[source]

Split bullets across frames columns in reading order.

Near-equal contiguous chunks, so a three-column design stays balanced instead of overflowing its first column and leaving the other two thin. can_carry() guarantees there are at least as many bullets as frames, so no column comes out empty.

Parameters:
Return type:

list[list[str]]

md2star.pptx_clone.drop_template_slides(prs, ids)[source]

Remove the example slides ids, leaving only what the build appended.

The images on those pages are not dropped with them when a clone uses one: _copy_rels() gives every clone its own relationship to the same image part, so the part stays referenced and gets written.

Parameters:

ids (list)

Return type:

None

md2star.pptx_clone.example_slides(prs)[source]

Map each example page number to the slide object that is that page.

Keyed by the number in the slide’s part name rather than by position in the deck, because that is how md2star.pptx_layout.layout_pages() numbers the pages it records in LayoutInfo.example_pages. The two agree even in a template whose slides have been reordered in PowerPoint since they were created, where part order and presentation order no longer match.

Snapshot it before cloning anything: the clones are slides of the same presentation, and a caller that re-read this map mid-build would start finding them in it.

Return type:

dict[int, object]

md2star.pptx_clone.fits(shape, lines)[source]

Whether lines can sit in shape without dropping under the floor.

The designer sized that frame for the designer’s own line. Ours may be three times as long, and the renderer answers overflow by shrinking the type — quietly, which is the point: a title that falls from 38 pt to 27 pt is still a title, while three bullets squeezed into a one-line kicker come out at 9 pt and nobody in the room can read them.

So the question is not “does it overflow” but “how small would it have to get”. Same character model as the rest of the suite (0.5 em mean advance, 1.2 line spacing) and the same square-root implied-size arithmetic as md2star.pptx_audit._check_overrun() — capacity scales with the square of the point size, since line height and characters per line both do. A frame that declares no size at all is left alone: its typography comes from the designer’s master, and no signal is not a bad signal.

Parameters:

lines (list[str])

Return type:

bool

md2star.pptx_clone.peers(bodies)[source]

The body frames that are columns of one design, not captions of it.

Parameters:

bodies (list)

Return type:

list

md2star.pptx_clone.pick_example(examples, pages, content, *, used, canvas, avoid=())[source]

Choose which example page to clone for content, or None.

Candidates are the chosen layout’s own example_pages, in the designer’s order, narrowed to those that can_carry() the content. Among those, a page this deck has not cloned yet wins — so a layout demonstrated by three pages gives three different-looking slides instead of the same one three times — and otherwise the first feasible page is reused. A slide with an image then takes the page offering the biggest picture slot, since contain-fitting means that slot is the size the reader actually sees.

avoid is the hard exclusion the Ralph Eyeball Loop writes: a page a vision model has already judged not to be carrying this slide. used is only a preference (variety across the deck); avoid is a refusal. When every feasible page has been refused, the answer is None and the slide falls back to Seam D, which is the safe generic the loop wants anyway.

Parameters:
Return type:

tuple[int, object] | None

md2star.pptx_clone.reading_order(shapes)[source]

Order frames the way a reader walks them: columns first, then rows.

Sorting by (top, left) is right for a row of columns and wrong for a split design, where it interleaves the two halves — the left panel’s one line landing between the right panel’s two. Found on a real template, not reasoned about: a cloned Jellysmack split page took bullet 2 into its lime panel and bullets 1 and 3 into the black half.

So frames are grouped into columns (overlapping vertical bands), the columns run left to right, and each column runs top to bottom. A single row of boxes degenerates to left-to-right, a single stack to top-to-bottom, and the split design comes out in the order a reader actually reads it.

Parameters:

shapes (list)

Return type:

list

md2star.pptx_clone.template_slide_ids(prs)[source]

The <p:sldId> entries the template shipped with.

Seam E has to read the designer’s example slides while it builds, so unlike Seam D it cannot clear them up front (md2star.pptx_assemble.clear_slides()). It records them here, appends its own slides after them, and drops these at the end — see drop_template_slides(). Authored order survives either way, since both seams append.

Return type:

list