Notable changes to deckwright, newest first. The project is pre-1.0 — pin to a tagged
release; main is the development line.
- On pf-core 0.25.
codemeasures its lines in the theme's mono face, inside the plate's padding and rounded corners. A listing with a line wider than its plate is refused unless it setswrap: true, and a wrapped listing's plate is as deep as the rows its lines wrap to.codedraws a tab as spaces to the next multiple of four columns, does not charge a line's trailing spaces, and charges a space its face may lack a full em.codesets its rows exactly 1.55 times the type size apart, the advance its plate is sized for, and moves the leading above its first row from the plate's top margin to its bottom one.- Courier New, Liberation Mono and Cousine have a width table of their own, covering
accents, Greek, Cyrillic, arrows and box drawing. A
monoface without it is charged the widest monospace advance measured for Latin, and the widest glyph of its class beyond it. coderefuses a placement too narrow for its plate's padding to leave room for a line.
- Fit estimates break a line where LibreOffice does: never beside a no-break, figure or narrow no-break space unless a hyphen or en dash comes before it, nor after a hyphen before a digit, between two hyphens, or after a hyphen opening a word. An overlong word in text carrying CJK keeps its kinsoku.
overflowreports a line set on a plate — acodelisting — that the render does not hold inside that plate.
- On pf-core 0.24.
- An unquoted
yes,no,onoroffin copy — a table cell, bullet, chart category or title — prints as written. Fields that taketrueorfalseread those words by the truth they spell.banding,wrap, a cell'semphasisand aversusside'shighlightrefuse a value that is not a boolean, where a quoted'no'turned them on. - Optional copy written
nooroff— a bullets heading, a stat label or caption, a callout or flow body, aversusnote, an image'sovertext — is set instead of dropped. - A key YAML reads as a date, a boolean or a number, written beside a misspelt key, is refused with the unknown-key message, naming the key as YAML read it, in a spec and a theme alike.
- A theme with a non-finite chart number; a NUL, over-long name or symlink loop in
template:oricons:;nan%orinf%inscale:; a type size at or below zero,line_weight_pt: 0included; areference_heighttoo small to divide by; or a face that is not one name is aThemeErrornaming the key and its value. conformskips an accent that stands off the page it derived by neither 3:1 contrast nor 35 ΔE, as it skips unedited stock accents, and its report names each accent skipped. Re-adopting a template whose accent matches its page changes that theme's accents.conformloads its theme and flattens a template's master picture once per run instead of once per exercise.conformreports each pair of accents it binds that sit closer than 35 ΔE.- A theme that binds its own
pageand nolinegets a line derived from that page. On a dark or coloured page, rules, table rules and edges drawn in the default line change colour: a rule is drawn inmuted, as it is on a light page.
equation:sets one display equation from a LaTeX subset, written as PowerPoint stores an equation: Office Math in anmc:AlternateContentChoice, and a Fallback of one line of UnicodeMath, which readers without Office Math and every check read.- A
document:card's corners and shadow blend on a dark or coloured slide, where they sat on a white matte. - A
document:card shows the pictures its markdown names from the markdown's own folder:, an<img>orsrcset, an SVG<image>, a<video>poster, or a CSSurl(). One named from anywhere else is refused. - A
document:card draws a markdown table as ruled, padded cells. - A
bulletslist is measured item by item as its items wrap, with the space after each item, and a list whose tallest column would run past its placement is refused, naming the bullets and the lines they wrap to. swatchesreadscolumns:: 1 to 8 chips per row, clamped to the number of roles. Any other value is refused. A row after the first starts below the labels of the row above.divergepaints each direction in the first accent, then the label ink, that stands off the ground behind its bars.
highlight: trueshows on a pie or doughnut: the marked wedge keeps the first accent and the rest fade that same accent toward the chart's ground, at two alternating depths, each still standing off the ground itself. On a chart with several series the marked row keeps each series' colour and the other rows fade. A highlight needs no second accent.- No two touching pie or doughnut wedges share a colour, the last and first included. Where the accents run short, a wedge takes a tint or shade of an accent.
- A
ChartSpecbuilt in code with a highlight its kind cannot show is refused. - A pie's data labels are pinned to
ctr, so a small wedge's label no longer lands outside the pie in the ink chosen for the wedge it named.
- A click-to-reveal trigger is written in PowerPoint's own form: its sequence advances on a click of the trigger alone, not on the slide's next click.
- A slide takes
transition: morph, written as PowerPoint's Morph transition with a fade as its fallback, and a placement takesmorph: <name>, which names its shapes alike on each slide that carries the name, for Morph to pair.
renderrasterises each page to a whole number of pixels, so a dark slide no longer shows a light line down its right edge.qareportsseries-colourwhen two series in one chart share a fill.qareportsmorph-unpaired: amorph:name with no namesake on the slide before, or a morph slide that names nothing.qareportslink-contrast: the template's link colour, which Keynote draws, against each linked shape's ground.renderlinks each typeface the deck names that fontconfig has installed under that name into LibreOffice's profile, so those faces, and CJK text, render in their own type.renderwrites a stamp beside its output, andqareuses a render whose stamp matches the deck on disk instead of converting the deck again.qa'stext-fitmeasuresbulletscolumns and counts paragraph spacing; the manifest recordsspace_after_pt.qa'sshape-idandshape-namecount ids permc:AlternateContentbranch.extractreads a wrapped shape from its Fallback, and a build's paragraph builds judge a wrapped shape by its Choice.qa's package checks resolve a relationship target that names its part from the package root.
diffreads a copy saved under another name against its build, when the copy sits beside the manifest: a built deck carries its build id.diffno longer reports a table's cells or a chart's parts gone from an untouched deck, and reports a slide deleted, pasted in, duplicated or moved as one line.diffreports a shaperestyledorrelabelled, and a slideretimed: its build's clicks, revealed shapes or triggers changed, or its transition did.- The manifest records the transition each slide arrives on.
- The manifest records both lines of a
swatcheslabel, the role and its hex, sodiffno longer reports every chip retyped. extractdrafts a deck deckwright built as the placements that built it — card, bullets, prose, table, chart, image, rule and panel — each at the box its manifest recorded, with itsid:,morph:,reveals:and a plate'spair:, and the slide'sanimate:andtransition: morph. Another component comes back as bullets of its words, or as an image when it holds only a picture, and the draft says so; so does a reveal a spec cannot state, which is left out.extractdrafts a chart from any deck as achart:block with its kind and data, and a filled shape holding a heading and a line, or a text box lying on one, as acard:.extractdrafts a picture as animage:with its alt text or decorative flag, a photo in a picture placeholder included, and writes its file to a<draft>.mediafolder beside the draft. An existing folder is refused without--force, and replaced with it. A picture that would push a slide's words off it is named instead.- A slide holding more than the grid bands names each table row, chart and picture left over; a slide read back by placement names what was added to it by hand.
extract --as mdlists each picture's alt text, a card's words, and a chart's data.
bin/testruns the suite in parallel when pytest-xdist is installed;bin/test -n0runs it serially.lxmlandrichare declared dependencies, andpytest-xdistis a dev dependency.
image,icon,documentandcharttakealt:, written as the shape's alternative text, anddecorative: true, written as Office's decorative flag. Giving both is refused.- A slide's background image or theme art, and a
card's picture icon beside its words, are marked decorative.cardtakesalt:anddecorative:for its icon. - Pictures no longer carry their file name as alternative text.
qareportsalt-text: a picture or chart with neither alt text nor the decorative flag, or with only a file name. WARN.- The manifest records
altanddecorativeper shape. extractwrites each dropped figure's alt text into the draft as a comment.
- A slide takes
id:, and a placement takesgoto:naming a slide id or a relative jump (first,previous,next,last). Every shape the placement drew jumps when clicked. A goto naming no slide, its own slide, or sitting on areveals:trigger is refused. qareportslink: a jump to a slide the show no longer contains, an unknown relative jump, or a web link that is not anhttp,httpsormailtoaddress.- Copy takes
[words](address)links tohttp,httpsormailtoaddresses, in the line's own ink and underlined.codekeeps markup as written; a chart refuses a link;\[escapes a bracket.extractwrites linked runs back as markup. - The manifest records
gotoandlinksper shape, and a linked line's text as its words. relationshipno longer reports an emptyr:id, which PowerPoint writes for click actions that relate to nothing.
- Chinese, Japanese and Korean text is measured at one em per ideograph, kana, hangul syllable and fullwidth form, instead of the widest Latin glyph, and wraps where the renderer breaks it: between ideographs and kana, at spaces in Korean, with kinsoku (closing punctuation and small kana never start a line, opening brackets never end one) and a trailing full stop or comma hanging past a full line.
- Each run carrying CJK is written with its
langand the theme's face for its script. The theme takes those faces fromtype.ea(ja,ko,zh-Hans,zh-Hant), else from the template'sfontSchemescript entries. - The deck and each slide take
lang:. Han-only text with nolang:is refused on a theme that sets Japanese and Chinese in different faces, which most brand templates do. qa'sfont-substitutedreads the fonts the rendered PDF embeds (Popplerpdffonts), falling back tofc-list. A face installed but unreachable by LibreOffice is now reported.qareportscjk-unrendered: a slide with CJK text whose render embeds no CJK font. ERROR.
inspectanddiffread a shape stored insidemc:AlternateContentfrom its fallback.inspectlisted nothing for one, anddiffreported itgone.
- Licensed Apache-2.0, replacing MIT, with a
NOTICEfile. The distribution'slicensemetadata isApache-2.0. python-pptxis pinned to~=1.0.2.
conformderivesinverseagainst the page a slide shows. A dark master gets a lightinverseand its owninverse-ink; a light-page template derives as before. Re-adopting a dark-page template changes its inverse surfaces.- Loading a theme whose
inverseis within 3:1 of itspagelogstheme_inverse_matches_page. Re-adopting its template rebindsinverseandinverse-ink, bound or left at the default;conformwithout--adoptreports it. - Re-adopting a theme keeps its comments and layout: only the lines whose values change
are rewritten. A change a line edit cannot make, such as a flow-style
bind:, rewrites the file and logstheme_comments_droppedwhen it had comments. conform's report names thepage,inkandinverseof the theme it wrote, each with its contrast, instead of the colour scheme's pair; an unbound one is marked(default).- A template outside the theme directory whose filename is already taken there is refused
without printing an
mvover it, byconform --adoptand by atheme:naming it. - A template symlinked into the theme directory is adopted where the link is.
- A number in a spec prints as it was written:
1.10and2.50stay as typed in a table or a stat. What YAML 1.1 reads in another base —007,0x1F,1:30— loads as the text written, and an unquotedcrop: 16:9is that aspect. A whole-number field such as a column index refuses one by name.
- Text a component sets on the slide is inked for what is painted under it: prose, bullets,
callouts, captions,
nav,fanout,diverge,code,gridand a chart's labels, axis text and legend over apanelor a picture, and a table's body cells on an inverse band. The manifest records that ground, and a picture's scrim is part of it. - A
bleed:card, disc, callout dot,versusorfanoutplate,divergeplate or bar, orgridbar is the ground for text laid over it, as a panel is. A disc covers its inscribed square. - Text over a picture laid on top of a panel is inked for the picture.
- Width tables for Poppins, Open Sans, Montserrat, Amatic, Sniglet, Bebas Neue and Barlow Semi Condensed. Decks in those faces lay out against their real widths, so prose, cards, tables and titles take less room than the conservative estimate gave them.
- A fit refusal whose estimate ran on a face with no width table says so.
- A
cardheading or body, astatsvalue or label, or aflowstep holding a run wider than its frame is refused, naming the run and the width it needs, measured without the sizing margin. A run ends at a space, after a hyphen or dash, and at each CJK character. A word that fits its line is no longer counted as two lines by the wrap estimate that sizes cards, flow steps, discs, chrome lines, prose, table rows, picture text and swatch captions, and thatqa'stext-fitcheck measures against.
navwith noitems:takes the deck'ssections:, and with noactive:marks the slide's ownsection:when it is one of the items.SlideCtx.text_inkandSlideCtx.accent_atreturn an ink for a box and the ground painted under it, for components written outside the package;ctx.paintedtakes aDiscfor a round fill.
animate: one_at_a_timereveals a single-columnbulletslist one bullet per click, as a PowerPoint build by paragraph in one text box. A list with several columns still reveals a column per click, andtogetheris unchanged.- Every component reports a motion role for each shape it reveals, so
motion.rolesbinds how text, surfaces, lines, figures and charts enter; a chart's own build takes thedatumentrance. The lines in afanoutand betweenflowsteps now wipe by default, asruleandconnectordo; a vertical stroke wipes along its length.
- Area, doughnut and bubble charts print their data labels.
- An area chart's first and last data labels are moved in off the plot edge, so they do not sit on the value axis.
xy-scatter-smoothandxy-scatter-smooth-no-markersdraw curves.- A data label drawn on a series' fill (pie and doughnut wedges, area bands, stacked bars,
inside_endbars) is inked for that fill, per wedge where wedges differ. - A chart with one named series is not titled with that series' name.
- A chart's data labels, axis text and legend are recorded in the manifest with the ink and
ground they are drawn on, so
qa'scontrastandmin-fontchecks cover them. - A bar or column chart's value axis starts at zero when every value is zero or more.
y_minstill overrides it; line, area, radar, scatter and bubble keep automatic scaling.
qawarnsfill-groundwhen a shape filled to stand off its ground (an inverse or accent panel, card or disc, a plate, a bar, a dot) is separated from it by neither luminance nor colour.
- Slides whose chapters run out of the order
sections:lists are refused, naming both. - A
theme:naming a.pptx, at a path or by bare filename, is refused with the theme already adopted from it, or else theconform --adoptcommand that onboards it, paths quoted and the move into the theme directory included. A theme file that is not UTF-8 text is aThemeError. - Commands printed by
sampleand byconform --adopt's refusal quote their paths. - An exception from a component's own code keeps its traceback and ends with a line
naming the slide and component. A component's
LayoutErroris unchanged.
- The project is
deckwright. The package, thedeckwrightcommand, theDECKWRIGHT_*variables and the.deckwright-cachedirectory follow the name. A.envcarryingPPTXKIT_*needs its prefix changed; a.pptxkit-cacheleft behind can be deleted. Decks and manifests already built are unaffected — a.pptxnever carried the package name. - On pf-core 0.22.
-
deckwright extract <deck>.pptxdrafts a.deck.yamlfrom a deck deckwright did not build. Kickers, titles, subtitles, body text, tables and speaker notes convert; every shape it cannot turn into words is named in a# not converted:comment on the slide it came from, repeats collapsed intoN × kind.--as mdwrites a plain transcript instead,--outchooses where it lands, and--themeis loaded, so the draft'srows:spans that theme's grid and an unknown name fails before anything is written.out:defaults toout/<slug>/<title> v1.pptx, the same slugdeckwright newuses. -
extractwalks grouped shapes, reads chrome back out of the shape namesbuildwrites, so a deck deckwright built keeps its kicker, title and subtitle, and takes a slide's first block as its title when it is one line set larger than everything else on the slide. Shapes overlapping vertically are one row, read left to right. An element python-pptx cannot build —p:contentPart,mc:AlternateContent— is named by its tag. -
extractrecovers a slide'sbackground:when the colour painting the whole canvas is one the named theme declares. A colour it does not declare is written into the draft as a comment naming the hex. -
The bullet marker
buildwrites is taken off again on the way in, so a deck survives any number of extract-and-rebuild round trips. The draft is not otherwise a fixed point — a columned list comes back as one placement per column — but its words are. A leading dot in a deck deckwright did not build is left as content. -
buildrefuses an--out(or anout:) that is the spec being compiled, or that names any.yaml/.ymlpath — a deck written over its spec leaves nothing to rebuild it from. Rebuilding over an existing.pptxis unchanged. The spec snapshot under.build/is now taken before the deck is written rather than after. -
extractrefuses a destination that already holds a file, so the second run over a deck does not replace the draft you edited after the first;--forceoverwrites. That refusal and an--asthat is neitheryamlnormdare both raised before the deck is read. -
An extracted draft builds as written. A table placement gets the rows its own height will demand at build; every other placement gets a fixed two. A slide holding more than the grid can band runs its blocks together and columns a long list; whatever still does not fit is written in as comment lines.
-
qareads a deck's timing:beatsreports each animated slide's rhythm,beat-sizewarns when one beat of a staged build reveals more thanDECKWRIGHT_MAX_BEAT_SHAPES(default 6), anddead-triggerreports an interactive reveal that cannot fire or that reveals what is already on screen.animate: together, a chart build and areveals:trigger are exempt frombeat-size: one click is what each of them declares. -
Every build writes
<deck>.beats.mdbeside<deck>.content.md— the reveal order in words, click by click, each section headed by its slide's title. -
The manifest records the clicks an animation spends, which is not the number of beats its components grouped:
animate: togetherand anafter_previouschain spend one, a chart build spends one per part plus one for its axes, and an interactivereveals:spends none and names its trigger. -
A ring of
reveals:is refused at build. Chains still build — click one to reveal the next is real staging.
-
A pie or doughnut carrying more than one series is refused by name. python-pptx keeps only the first, so the second reached a strict
zipand came out as a bare traceback. -
A radar series carries no
c:smooth. python-pptx writes one for every connected kind andCT_RadarSerhas no such child, so the chart part did not validate. -
A chart's
labels:names the series that print their data labels, so a flat reference series stops stamping the same number over every point. -
A chart's value axis takes the same number format its data labels do, so a line whose points read
11.2%sits against a scale reading0.0%. -
Chart data labels keep their decimal places, read off the data —
11.2, 9.6, 5.1, 4.8labels to one place, whole numbers to none.decimals:overrides it,0included, and the*-stacked-100kinds are no exception: their axis shows the computed share while each label prints its own series value. -
qawarns aschart-datapointswhen a bar or column chart plots fewer than four values, quotingchoosing.md's rule. A warning, not a refusal. -
A chart legend goes under the plot on every kind but the bar family, which keeps the column it reserves beside its category labels. The bar family's legend gets a measured column out of the same budget as those labels, and the plot keeps at least 45% of the frame.
-
A data label takes a position only where the chart group offers one:
inside_endis written for the bar family and a pie, the line and scatter kinds sit above their point, and area, doughnut and radar are left without one. A theme naming alabel_positiondoes not get to write it into a group that has none. -
versussizes each plate in proportion to its value, so a side 60% larger is 60% wider. Values with no number keep the even split, and so do two written in different units —2 daysagainst4 hoursreads 2 against 4 and would draw the longer span smaller. A magnitude suffix is part of the unit:$1.2Mand$480Kdo not compare, and neither does a singular against its own plural: the unit is matched whole, because no string rule separateshrs/hrfromms/m. The smaller plate never drops below the width its longest word needs, so a lopsided pair widens rather than breaking type mid-word; a placement too narrow for both sides is refused. -
The chart block's
annotate:key is gone; a deck still carrying one is refused by name. For a callout on one point, place acalloutsorprosebeside the chart.
-
A typo in a theme's
scale:,scale.margin:ortype:block is refused by name. Only the top level was checked, so a misspelled key was dropped and the built-in default stood — the theme read as though it had been honoured. -
The packaged
basetheme sets larger type. At its 7.5in reference height: body 14 → 16pt, caption 12 → 14, subtitle 16 → 18, lead 18 → 20, head 19 → 22, stat 32 → 34.kicker,title,displayandheroare unchanged. A deck ontheme: basereflows on its next build: copy that only just fitted may overflow, and a placement whose contents no longer fit their band fails the build rather than shipping short. -
A theme with no
template:sets colours:bind:accepts a literalRRGGBBfor any role, so a palette is a theme of five lines. A slot name in such a theme is refused, naming the two ways out;marks:still needs a template. -
A
bind:to a literal accent is kept even where it equals a colour Microsoft ships. The stock-accent guard applies to template slots only. -
A type-ramp rung reaches the theme's monospace face with
face: mono, alongsideface: bodyandface: heading. Aface:matching an alias only in case is a literal typeface and warns. -
The
captionrung takes a half step of the modular scale — 14.3pt at the default reference height, up from 12.8pt — so it no longer shares a step with the bold, capitalisedkicker.
-
swatchesreserves the depth its caption actually wraps to rather than a flat two label lines, so a placement that would have held it is no longer refused. -
A placement's
anchor:applies to the shapes that placement drew and to nothing else, so editing one slide cannot move another. -
A mark's contrast plate stays inside the placement that asked for it.
-
A chrome box may declare
h: autoand take the depth its text wraps to. -
prunematches layouts by part name, so it cannot drop a layout a slide is still on. -
A
section:that resumes after another chapter has begun is refused, naming both slides. A slide carrying nosection:of its own does not break the run it falls in.
-
deckwright qaaddsplaceholder: recorded text and speaker notes that read like copy nobody meant to ship — four phrasesdeckwright newseeds, pluslorem,ipsum,TODO,FIXME,[insertand a run of three or morexin either case. WARN. -
deckwright qaaddsfont-substituted: one finding per theme face the rendering machine cannot set. It runs only whenqarenders, and is silent when fontconfig cannot be asked. WARN. -
A line the rendered page does not hold is asked for again inside the shape's own box before
overflowreports it. pdftotext merges side-by-side placements row by row, so twoprosein adjacent columns spliced each other's wrapped lines and the slide was reported twice over. -
qafalls back to thethemename a manifest records when thetheme_pathit records is not there, so a deck handed over on its own is checked against the packagedbaserather than refused. A name resolves against the reader's own theme directory first, sotheme-substitutedreports a file that answered to the name but hashes differently from the one the deck was built against. -
deckwright doctorreports whether this machine has the faces thebasetheme sets type in; without fontconfig, or whenbasedid not load, that row is aSKIP. -
deckwright doctorloads thebasetheme it resolved. One that resolves but will not parse is aWARNnaming the file and the loader's message.
-
deckwright conformresolves the+mj-ltand+mn-ltfont references a template may carry against its own font scheme.load_themewarns when a theme on disk already holds one and names the template to re-adopt. -
Counts and margins in
scale:, point sizes intype:, millisecond timings inmotion:, and every block that must be a mapping are checked at load; a value that will not convert raisesThemeErrornaming the key and the value.deckwright doctorprints its full table even when a check raises.
-
renderresolves the deck path as well as--outdir, so a relative input works from any directory, and a relative--outdirlands where the render created it. An input that is not there is reported as itself. -
A conversion that writes no PDF is reported instead of rasterising the previous run's, and the failure names the leftover process.
-
renderleaves norender/sharedirectory behind: LibreOffice starts in the throwaway profile it already owns. -
deckwright glyphs find <substring>searches the 4,001 glyph names and the alias tables. Hyphens and underscores match either way;--limitcaps the output and a capped run says how many it dropped. -
Build errors from inside a composite name the component the author wrote — a
flowreportedcomponent 'card'— and the chrome-box error names its slide. -
fc-list (fontconfig) is a new optional external tool; the
doctorfonts row and thefont-substitutedcheck are silent without it.DECKWRIGHT_FC_LISTandDECKWRIGHT_FC_LIST_TIMEOUT_Spoint at another binary and cap the call. -
Every external tool deckwright reads — pdftotext, fc-list, LibreOffice, pdftoppm — is decoded as UTF-8 rather than the platform locale, and the CLI's own output degrades instead of raising on a console that cannot encode a deck's em dashes.
-
treatments.mdsays the claim should land a beat before the evidence, and applies the run test toanimate:.choosing.mdgives the recipe for staging a table's total row into its own beat. -
motion.mdmarksmotion.advanceas a theme key beside the slide-levelanimate:andreveals:;placement.mdstates thatanchormoves what a placement drew inside its rect, with the measured positions;components.mdgives the icon sizing arithmetic;qa.mdsays#Ncounts shapes as drawn, so a contrast plate takes#1.
Initial public release.
- Python 3.12+, on pf-core 0.21.
deckwright buildcompiles a declarative.deck.yamlagainst a theme into a branded.pptx, a build manifest, and a.content.mdof the deck's words.- Twenty-two slide components, 29 native chart kinds, imagery with fit/crop and scrim solving, HTML panels rendered through headless Chrome, and animation — builds, click-to-reveal and slide transitions.
- The built-in
basetheme ships inside the package, sotheme: baseresolves with no checkout. A file of the same name inDECKWRIGHT_THEME_DIRtakes precedence. - Default faces are Helvetica and Courier New, which resolve to metric clones in Keynote, PowerPoint and LibreOffice alike.
deckwright conform <template>.pptx --adopt <name>derives a theme from a brand template and drives every capability through it.deckwright samplewrites a small brand template to conform against, so the walkthrough needs no brand file. It lands in the theme directory, where--adoptcan read it.deckwright qachecks geometry bounds, reserved regions, WCAG contrast, minimum font size and render-based overflow against a built deck's manifest.renderandqawrite intorender/<deck>/beside the deck, so two decks in one directory never overwrite each other's slides.- A built deck carries only the slide layouts it uses;
build --keep-layoutsretains the rest, and the media only they reach. - Speaker notes declare their notes master on the presentation, which Keynote requires to open the file.
deckwright doctorreports the version, the glyph bundle, theme resolution and the external tools, naming the install command for anything missing.--versionprints it on its own.- An absent external tool names the binary, the
DECKWRIGHT_*variable that overrides it and the install command for the platform;qaalso names--no-render, which runs every check that needs no tool. - ~4,000 Material Symbols ship as one archive;
deckwright glyphs verifychecks it against its manifest anddeckwright glyphs syncre-vendors it from upstream — the one command that uses the network. - Supporting commands:
render,shot,inspect,diff,new,demo. - One directory for a brand:
templates/holds the.pptxand the theme derived from it, side by side.theme: <name>resolves<name>.theme.yamlthere, and a theme names its template by bare filename — nothing is ever copied. A template is adopted where it lives; adopting one from elsewhere is refused. - Re-running
conform --adopton the same template is a refresh that keeps hand edits;--forcere-derives and discards them. - The suite's primary guard drives every template in that directory
(
tests/test_templates.py,DECKWRIGHT_TEMPLATES_MINto require a minimum). - Headless Chrome runs sandboxed.
DECKWRIGHT_CHROME_NO_SANDBOX=1passes--no-sandbox, which is implied when running as root. - Every rendered card carries a content policy: no frames, objects or embeds, no
script but deckwright's own height probe, and images and fonts from
data:/http(s):only. Afile://URL in card markdown no longer renders a local file into the deck. - Importing
deckwrightno longer touches the root logger; handlers land on thedeckwrightlogger, and an application that configured logging first keeps its own. - All text is read and written as UTF-8 regardless of the platform locale.
- Every XML part of a
.pptxis parsed with entity expansion and network access refused, so a package from someone else cannot amplify or forge through a DTD. - The sdist ships a runnable suite:
tests/,docs/andexamples/travel with it, and brand templates and derived brand themes are excluded from both dists.