Skip to content

fix(font): the donut-centre KPI renders the weight it declares - #479

Merged
DemchaAV merged 2 commits into
developfrom
fix/font-face-alias
Jul 31, 2026
Merged

fix(font): the donut-centre KPI renders the weight it declares#479
DemchaAV merged 2 commits into
developfrom
fix/font-face-alias

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Closes #451.

Why

A style names a font family and a decoration, and the decoration is what picks the face within that family. The standard-14 face constants are aliases of their family — FontLibrary.resolveBaseFont rewrites each one to its base before any lookup — so naming a face contributes nothing, and a style that names one and sets no decoration renders regular, measured with regular metrics. Nothing announces it: the text lays out and draws.

ChartDefaults.DONUT_CENTER_TEXT_STYLE did exactly that, so every donut chart without an explicit centre style drew its KPI in the wrong weight.

The issue's inventory needs correcting, in both directions

The issue names five library sites. Four of them are fineHeadingBarStyle:21, TimelineBuilder:69, TimelineMarker:74 and ModernProfessional:134 all set .decoration(BOLD) beside the alias and render bold today. So "every chart axis label, heading bar, timeline marker and those preset headings are currently regular weight in shipped output" does not hold for them.

One is genuinely broken: ChartDefaults:101. Measured by rendering the whole example catalogue before and after and comparing pixels page by page, fixing it changes three documents — the engine deck (page 4), the feature catalogue (page 3) and the chart showcase (page 5). Not "every chart".

A hypothesis worth recording as refuted: the four Typography factories that declare a face constant as headlineFont looked like a second, larger source. They are not. Patching BrandTheme.headlineStyle() to BOLD changes nine documents — classic-serif, minimal-underlined, boxed-sections, centered-headline, blue-banner — none of which are those four themes, and all of which are intentionally regular. The four themes' presets style their headlines themselves and never reach the token. It is a dead declaration, not a defect: rewriting all four to the base family moves nothing.

What changed

ChartDefaults names the family and sets decoration(BOLD) — the one real fix.

Twelve other library sites that named a face beside a decoration already doing the work now name their family. This is render-neutral by measurement: the full-catalogue sweep gives the same three changed documents with or without these edits. They are worth changing because the library was teaching the trap in its own source, and because they are exactly what the new warning fires on.

Typography's four factory tokens are not among them. Render-neutral is not the same property as API-neutral: Typography is a public record, headlineFont() is an observable value, and api-stability.md puts the BrandTheme factories and layered presets on the Stable tier — major releases only. Those four are unchanged, and the token remains a dead declaration to resolve in a major.

FontLibrary logs one line per distinct face constant it rewrites — once per name, not per lookup, so a document using the form a thousand times says so once. A full catalogue run emits five lines, one per alias still used in examples/. Warning rather than failing: the form renders correctly when the decoration is right, and rejecting it would break those callers for nothing.

Two tests, because the rule and the site need separate cover. FontFaceResolutionTest pins the rule against the resolved PDFont — the glyph program the page references — rather than a rendered image, so there is no pixel threshold to argue about. It covers the family+decoration path, the alias-without-decoration path, and the case where the name and the decoration disagree: HELVETICA_BOLD with decoration(ITALIC) resolves to Helvetica-Oblique. That last assertion is the one that must change if the alias is ever made a real face, which turns Option 1 into a deliberate edit to a named test instead of a silent behaviour change.

It also asserts the donut default directly, through the glyph program that style resolves to — the rule alone does not catch the site going back, and neither do the chart layout tests, which measure through fake metrics whose width follows the character count and ignores the face. Reverting the fix turns it red with expected "Helvetica-Bold" but was "Helvetica".

FontFaceAliasWarningTest covers the signal itself: one warning per face constant rather than per lookup, naming both the constant and its family, and silence for a style that names a family. It states the warning's limit as an assertion instead of a comment — the key is the alias alone, because the library cannot see the decoration where it rewrites the name, so the first use consumes the warning even when correct and a later broken one is silent. The specific sites are covered by the resolution test and by DocsBoldFaceGuardTest.

Docs: docs/font-coverage.md gains the rule as its opening section, and the DocumentTextStyle Javadoc states it on fontName. The issue's "also update" note for backend-capability-matrix.md does not apply — the resolution rule is unchanged.

Verification

Reactor gate BUILD SUCCESS, 13/13 modules, 2:01, zero errors. javadoc:javadoc exits 0. Core 416 → 420 tests, render-pdf 139 → 143.

Blast radius measured, not predicted — every claim above comes from rendering all 91 documents twice and diffing every page:

Change under test Documents affected
ChartDefaults alone 3 — engine deck p4, feature catalogue p3, chart showcase p5
ChartDefaults + all sixteen redundant rewrites the same 3
BrandTheme.headlineStyle()BOLD (rejected) 9, none of them the four themes it was meant to fix

Three committed previews and the showcase artifacts for those documents are refreshed. The other 96 files a full sync touches differ only by the render date and were reverted — as were the engine-deck and feature-catalog thumbnails, which cannot show this fix: ShowcaseSync renders page 0 and the change lands on pages 4 and 3. Measured rather than assumed — page 0 of both documents is pixel-identical before and after (mean 0.0000, max-pixel 0), so those two images were carrying drift from an earlier run. chart-showcase.png was already byte-identical, which is what prompted the check.

One defect I introduced, caught by a test. The mechanical rewrite matched Standard14Fonts.FontName.HELVETICA_BOLD — PDFBox's enum, not GraphCompose's — and swapped the face the PPTX watermark measures with, which is the seam #450 fixed. PptxChromeTest.theWatermarkFrameOrbitsThePdfBaselineRotationPivot failed. Both renderer files are reverted whole: they are backend internals where a face name carries meaning, not style declarations in a document. No Standard14Fonts reference appears in this diff.

Not in scope

Option 1 from the issue — making the alias resolve to the requested face — is not taken. It changes the 97 example sites that use the form without a decoration, and it needs a precedence rule for a name and a decoration that disagree, which does not exist today. The test above pins the current answer so that decision stays available and explicit.

Only the PDF backend's resolution is pinned, though PPTX resolves through the same FontLibrary. Whether the face constants should be deprecated on fontName(...) is the question the warning raises and does not answer — and the cleanest answer to the warning's alias-only key is probably to move the check to where the name and the decoration are both in hand, which is style construction rather than font resolution.

A style names a font family and a decoration, and the decoration is what picks
the face within the family. The standard-14 face constants — HELVETICA_BOLD,
TIMES_ITALIC, COURIER_BOLD_OBLIQUE and the rest — are aliases of their family:
FontLibrary rewrites each one to its base before any lookup, so naming a face
contributes nothing.

ChartDefaults named the bold face and set no decoration, so every donut chart
without an explicit centre style drew its KPI regular and measured it with
regular metrics. Nothing announced it — the text laid out and drew. That is the
one place in the library where the form actually produced the wrong output;
sixteen others named a face beside the decoration that was already doing the
work, and four Typography tokens declared a weight the theme never applied.
Those sixteen now name their family, which changes no render: sweeping the whole
example catalogue before and after gives the same three documents either way.

FontLibrary logs one line per distinct face constant it rewrites, so the silent
no-op becomes visible without failing a caller whose decoration is correct.
FontFaceResolutionTest pins the rule against the resolved PDFont rather than a
rendered image, including the case where the name and the decoration disagree —
which is the assertion that has to change if the alias is ever made a real face,
making that a deliberate edit instead of a silent behaviour change.

Three rendered documents move: the engine deck, the feature catalogue and the
chart showcase, each on the page carrying a donut.

The issue's own inventory needs correcting: of the five library sites it names,
four already set the decoration and render bold today.
… own site

Typography is a public record and headlineFont() is an observable value on a
Stable surface — api-stability.md puts the BrandTheme factories and the layered
presets there, changeable in major releases only. Rewriting four factories from
a face constant to its family was render-neutral, which is not the same property
as API-neutral, and none of it was needed to fix the chart. Reverted.

FontFaceResolutionTest pinned the rule and not the site, so a style going back to
naming the bold face with no decoration still satisfied every assertion in it.
The chart layout tests do not close that either — they measure through fake
metrics whose width follows the character count and ignores the face. The donut
default is now asserted directly, through the glyph program its own style
resolves to; reverting the fix turns it red with "expected Helvetica-Bold but
was Helvetica".

FontFaceAliasWarningTest covers the signal: one warning per face constant, not
per lookup, naming both the constant and the family it resolves to, and silence
for a style that names a family. It also states the limit in an assertion rather
than in a comment — the warning is keyed on the alias alone, because the library
cannot see the decoration where it rewrites the name, so the first use consumes
the warning even when that use is correct.

The engine-deck and feature-catalog thumbnails are restored. ShowcaseSync renders
page 0 and the fix lands on pages 4 and 3; page 0 of both documents is
pixel-identical before and after, so those two images were carrying drift from
an earlier run, not this change. chart-showcase.png was already byte-identical,
which is what prompted the check.
@DemchaAV
DemchaAV merged commit fbe1c02 into develop Jul 31, 2026
12 checks passed
@DemchaAV
DemchaAV deleted the fix/font-face-alias branch July 31, 2026 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant