fix(examples): render the weights the styles declare - #486
Merged
Conversation
Every one of the 115 font styles in this module named a face constant — HELVETICA_BOLD, TIMES_BOLD, COURIER_BOLD — and set no decoration. The library rewrites such a name to its base family before the lookup, so the constant contributed nothing; the same defect the donut KPI had, one module over and a hundred times. It was invisible until the PPTX backend started taking the face from the decoration in the 2.1 line. From that point a fresh render of the decks came out regular while the committed previews, rendered earlier, still showed bold — and nothing regenerated them, so nobody saw it. Three decks lost weight that way; a fourth gained twenty-one runs it had always asked for and never received. The styles now name a family and a decoration. Where a chain already set one, only the constant is renamed: it was redundant, not load-bearing. Three helpers chose the constant through a ternary and needed the flag moved to the decoration instead. Two consequences worth naming. Bold text is wider, so four layout snapshots move — widths and vertical positions only, no page breaks. And the weekly schedule stopped fitting: its day notes span four fixed sub-columns, a spanned cell cannot borrow width from fixed neighbours, and the board is printed. Its columns are recomputed from the printable width rather than adjusted by eye — 87.38 + 7 x 105.5 against 825.89 available, where the old 90 + 7 x 105 left 0.89pt unused. Thirty-one committed previews are re-rendered, chosen by comparing content rather than bytes so nothing churns on a fresh /ID alone. PptxCanonicalContentTest keeps that comparison: a deck is compared by its package parts, since zip timestamps make byte equality a coin toss.
CI failed the comparator on six decks, including social-card, which this branch never touched and which matched locally. The cause was neither a missing asset nor a stale render: POI ends the XML declaration with the platform's line separator, so every XML part of a deck written on Windows differs from the same deck written on Linux by one byte. Sixteen parts of an untouched deck differed for that reason alone. The digest normalises line endings in XML parts and leaves binary parts alone, where a stray CR would be content. The comparator also walked from the generated tree to the committed one and skipped whatever had no pair, so it could not see a deck added to the catalogue and never committed, or a committed deck whose example is gone — both leave every surviving pair matching. The curated subset is written down and asserted as a set, and two generated decks sharing a basename now fail rather than one standing in for the other: the committed gallery is flat. The schedule's column widths are derived from the page and margins rather than written out, and the comment that described the first attempt's numbers is gone with them. The values are unchanged, so no asset moves.
Normalising line endings closed four of the six decks the runner rejected; the last two came from the same root and needed the same treatment. In one, a freeform's path had every point shifted by a constant 272 EMU across and 489 down while its declared box shrank by exactly as much — the path landed on the same place on the slide to the unit, and only the normalisation moved. Points are now read where they land, and the box that says how they were normalised is dropped. In the other, an embedded image of a text watermark differed along the glyph edges alone: same glyphs, same positions, different antialiasing. Block-averaging leaves deltas of 138 of 255 behind, so no tolerance absorbs that honestly; images are compared by their dimensions and pixel layout, and the Javadoc says which changes that misses. Both are checked against a run of the deck on the runner rather than argued from: all six committed decks match under the comparison. Dropping the box is the load-bearing half, so it is asserted in both directions — the same line normalised two ways is one shape, a line that moved is not — and against real markup as well as written-here markup, so that a reordered attribute cannot leave the rewrite matching nothing.
…more The six assets that carry a version or a date are re-rendered from the merged sources at the released version, so they hold the deterministic render and the restored weights at once instead of one or the other. The showcase deck had come back with the day it was rendered on printed in its header; it reads Q2 2026 again, and still shows the 21 runs of emphasis its styles ask for. The deck comparison no longer holds a committed asset against a fresh render. It cannot: a committed asset is rendered at the released version and a working tree renders the next one, so the two are different documents before anything else is considered, and a committed asset is not otherwise guaranteed to be level with the code that produces it. Comparing the two needs the display version as an input and a way to refresh whatever has drifted — one job, and not a unit test's. What the class pins is what a deck can be compared by, proven in both directions, which is the part the asset gate needs to exist before it can run. Four decks lost their weights, not three; the CHANGELOG said three.
DemchaAV
force-pushed
the
fix/example-bold-faces
branch
from
August 2, 2026 14:54
58e0054 to
d913930
Compare
…t renders Reading a raster part by its dimensions absorbed one measured artefact by giving up on images entirely: a logo swapped, a screenshot replaced or a watermark retyped at the same size all compared equal. Images are now compared pixel for pixel, decoded into one colour model so that how a part was stored does not count as content, and the single part measured as machine-dependent is named in an allowlist rather than inferred from its folder. The two renders of that watermark — Windows and the runner — are committed as fixtures, so the entry has to keep earning itself: they must still differ as images, and must still be the same size for the allowlist to be reading them honestly. A misspelled entry exempts nothing, so the allowlist is checked against the parts that exist. The deck lists cover the catalogue instead of sitting inside it. Asserting that the curated decks are among the generated ones let a new example ship a deck nobody had decided to publish or leave out — the same silence the pairing this replaced used to keep. Both categories are written down, they may not overlap, and together they must equal what the catalogue renders. The class no longer opens by claiming it proves the committed decks match a fresh render; that is the one thing it says it cannot do.
…unt what changed The two image checks called the pixel reader directly, so a wrong media prefix or a mistyped allowlist key was covered by neither: the part would have fallen through to raw bytes and failed on a runner instead. Both now go through the same entry point the digest uses, and the negative one names a deck the allowlist does not, so the key has to be read as deck and part rather than part alone. The allowlisted part is checked in the rendered deck as well as the committed one — the rendered one is what a gate compares. Line endings were the one canonicalisation nothing pinned. It is also the one that cannot fail here, since every test runs on a single machine where both sides carry the same ending, so it is asserted directly. The CHANGELOG said 115 styles. That is how many named a face constant in a plain fontName call; another 23 reached one through a ternary or a helper argument, and all 138 changed. The class summary said every image is read pixel for pixel, one paragraph above the exception it names.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Found while building the asset-comparison gate for #461: the comparator's first run reported committed decks disagreeing with a fresh render, and the disagreement was weight.
Why
This module named a face constant —
HELVETICA_BOLD,TIMES_BOLD,COURIER_BOLD— in 138 places and set no decoration. 115 of them were a plainfontName(FontName.FACE)call; the other 23 reached the constant through a ternary or a helper argument. The library rewrites such a name to its base family before the lookup, so the constant contributed nothing. It is the defect #479 fixed in the library, one module over and a hundred times; none of the 115 was paired with adecoration(...).It stayed invisible until #450 taught the PPTX backend to take the face from the decoration for standard-14 names — correct, and the same rule PDF already followed. From that day a fresh render of the decks came out regular, while the committed previews, rendered before it, still showed bold. Nothing regenerates those previews, so nobody saw it.
Bold runs per deck, counting
develop's committed file againstdevelop's own examples rendered fresh:developfinancial-report.pptxbusiness-report.pptxmaven-banner.pptxtwin-output.pptxmaster-showcase.pptxsocial-card.pptxweb/showcaseis regenerated on every cut, so the next release would have published the de-bolded decks.What
The styles name a family and a decoration. Where a chain already set one, only the constant is renamed — it was redundant, not load-bearing. The helpers that picked the constant through a ternary (
bold ? HELVETICA_BOLD : HELVETICA) needed the flag moved to the decoration instead; those were invisible to a search for the literal call form, which is how the first pass missed them.The four decks that had lost their weights render them again, at the counts their committed previews already showed.
master-showcaseis the one that moves further: it gains 21 bold runs againstdevelop, emphasis its styles had always asked for and never received.The weekly schedule stopped fitting, and it is the one place where restoring intent was not enough. Its day notes span four fixed sub-columns; bold needed 107.38pt in a 105.5pt span, and a spanned cell cannot borrow width from fixed neighbours. The columns are recomputed from the printable width rather than nudged:
87.38 + 7 × 105.5comes within 0.01pt of the825.89available — the name column is floored to the hundredth — where the previous90 + 7 × 105left 0.89pt unused on a board meant to be printed. The name column is derived from the page and the margins rather than written down, so the two cannot drift apart again. The note size drops 5.9 → 5.75 to clear the span.Thirty-one previews and the README hero re-rendered, chosen by comparing content — extracted text plus a rasterised page for PDFs, package parts for decks — so nothing churns on a fresh
/IDalone. The other 36 were left alone: this change does not touch them, and bringing every committed asset level with the code is the asset gate's job, not this PR's.PptxCanonicalContentTestdefines what a deck can be compared by, which is the part the asset gate needs settled before it can run. Zip timestamps rule out bytes, and three further differences turned out to be decided by the machine rather than the document — the platform's line separator after the XML declaration, the box a freeform's path is normalised against, and the antialiasing of rasterised text. Each was measured against a run on the runner: line endings alone put a one-byte difference into sixteen parts of an untouched deck, and one freeform came out with every point shifted by a constant 272 EMU across and 489 down while its declared box shrank by exactly as much, landing the path on the same place on the slide to the unit. So a deck is compared as the document decides it: paths read where they land, images pixel for pixel after decoding into one colour model, everything else verbatim. The watermark is the single part an allowlist exempts, by name rather than by folder, with both renders of it committed as fixtures so the entry keeps having to earn itself. Dropping the box is the load-bearing half, so it is asserted in both directions, and against real markup as well as written-here markup.It does not hold a committed asset against a fresh render. A committed asset is rendered at the released version and a working tree renders the next one, so the two are different documents before anything else is weighed; and a committed asset is not otherwise guaranteed to be level with the code that produces it. Comparing them needs the display version as an input and a way to refresh whatever has drifted — one job, belonging to the asset gate rather than to a unit test.
Tests
examples64 tests andqa688, green locally; the CI run is green, including Examples Generation Smoke Test, CI Gate, Architecture and Documentation Guards and CodeQL. The whole catalogue renders: 102 documents, no failures.Four layout snapshots are re-baselined. The diff is widths and vertical positions only — no
startPage/endPagemoved, so nothing repaginated: