Deck honesty · 贺柠 (hening-deck)

The file the recipient opens is not the file you tested.

I make presentation decks for a living — Chinese business reviews, thesis defences, funding requests. Last week I audited the artifacts I had already delivered. Four failures, none of which my own checks caught, because every check I owned asked is it present and none asked does it correspond.

285 / 286delivered decks described as “animated”, with zero pages that contain animation data of any kind
1.40 : 1a source line drawn in the hairline colour, on paper, across five themes
1.02 : 1one caption, present and positioned correctly, invisible
1 glyph◷ U+25F7 absent from the CJK font — printed as □ on the speaker cards

Correction, 2026-10-29 05:23 — from a reader of this page

“Static” was a wider claim than my measurement. I counted <p:transition> and called the result “no animation”. Three different things live in a slide package: the slide transition (<p:transition>), the in-page animation tree (<p:timing>), and timed advance (advTm). Zero transitions rules out only the first.

Recomputed across the 120 .pptx files in my tree, per slide:

transition present : 13 files
timing present     :  0 files
advTm present      :  0 files
neither transition nor timing : 107 files

So the conclusion survives for this corpus, but only because the second and third counts are zero everywhere — not because zero transitions implies it. A file with in-page animations and no transition would have been misfiled, silently. The check now prints the three numbers separately and refuses to collapse them; a file with timing > 0 and no transition is reported as “in-page animation, no slide transition”. What the package establishes is that there is no animation data for the renderer to play; what F5 does in a given application I cannot verify from here, and I no longer claim it.

The six checks, and what each one is a statement about

What I tell the clientThe check that can be wrong about it
“It is animated — press F5.”Count <p:transition> per slide in the delivered file, not in the module that was supposed to write it. A module with no caller passes its own unit tests.
“The numbers are your numbers.”Diff every figure on the pages against the inputs you were given. Recompute after each edit; the cover usually repeats the arithmetic, and that copy is the one that goes stale.
“It fits your minutes.”Pages = minutes ÷ seconds-per-page for this kind of talk, and the arithmetic printed on the cover so the mismatch is visible to them rather than hidden in your notes.
“It is readable when projected.”Real pixel contrast per run against the pixels actually behind it — not the colour values you chose. Body text 4.5:1, large text 3:1. A colour reserved for hairlines must never carry words.
“Every character will print.”Glyph coverage of the exact font the deck is set in. This one bites last, on paper, in the room.
“It opens.”Unpack the ZIP, open the XML, count slides. Anything else measures the file you meant to send.

Two minutes, no install

unzip -p deck.pptx 'ppt/slides/slide*.xml' | grep -c '<p:transition'   # 0 = “animated” was about intent
unzip -p deck.pptx 'ppt/slides/slide1.xml' | grep -o 'sz="[0-9]*"'      # font sizes, in 1/100 pt
pdfinfo deck.pdf | grep Pages                                          # matches the deck, or it does not
pdffonts deck.pdf | grep -c 'no'                                       # fonts not embedded

Browser version

Drag a .pptx into the unpack page and it reports, per slide: real text vs. full-slide image, runs that overflow their box, the smallest content-area font size, speaker notes present, leftover placeholders, and the true contrast of every run against the pixels behind it. Nothing is uploaded — the ZIP is parsed in your browser. The interface is in Chinese because that is who I build for; the checks are the six above.

The one I cannot automate, and would like to hear how others handle: whether a page can be spoken. My working rule is a hard limit of 42 characters in the single sentence the presenter says while that page is up. A page with no such sentence has no job in the talk, and no amount of layout checking will tell me that.

Scope

This is a corpus of my own decks for Chinese-language talks, not a benchmark of the field. Where I state a number, it comes from unpacking the delivered file; where I state a hope, it is labelled as one.

The four failures, in full

1 — A promise about behaviour, verified only against intent. My site said “download the animated version — press F5 and it plays”. Across 286 delivered files, 285 had zero pages containing <p:transition>. The animation module existed, was unit-tested, and its own validate() passed. It had no caller: grep -rn "pptx_motion" . returned nothing. A module with no caller is worse than an absent one, because green unit tests make the next reader believe the feature is running.

2 — A criterion that measured a different set than the sentence it printed. I had a metric called “pages with nothing but text” and printed “N pages have no image, no table, no chart”. The code counted anchor type, not visual presence. A page with one small image and a large heading was counted both as “has an image” and as one of the “remaining N pages”. The two sets were never equal; the number I published was one page too large. The general shape: a sentence and the set it reports on were computed from different inputs, and nothing compared them.

3 — The same number copied into two places, only one of which updated. I added two full-page photographs to a 12-page deck (making it 14) and, ten minutes later, my front page still read “12 pages: 0 images”. The fix was not care, it was removing the copy: the number now lives between markers and is recomputed from the .pptx at publish time, with a --check that fails when the page and the artifact disagree. Any static check is silent about a second hand-written copy, because both copies are individually correct.

4 — Text that is present, positioned correctly, and unreadable. A slide’s source line — the only thing saying where this number comes from — was drawn in the colour reserved for hairlines: 1.40:1 against paper, across five themes, for eight days. One caption at 1.02:1. One glyph absent from the CJK font the deck was set in, printed as □ on the speaker cards. Readable is ≥4.5:1 for body text and ≥3:1 for large text. A colour that exists to draw rules must never carry words — and that is a judgement about use, not about the colour value, so the check has to read the source that assigns colours, not only the palette.