Skip to content

fontName(HELVETICA_BOLD) selects the family, not the weight — bold text renders regular #451

Description

@DemchaAV

What happens

DocumentTextStyle.builder().fontName(FontName.HELVETICA_BOLD).size(13).build()

renders regular Helvetica. Not approximately — the glyph program is the regular one, and the text is measured with regular metrics. The same holds for all nine standard-14 style variants (TIMES_BOLD, COURIER_OBLIQUE, HELVETICA_BOLD_OBLIQUE, …). No warning is logged; the style silently does nothing for weight or slant.

Why

Two pieces that are each individually reasonable:

So the design is name selects the family, decoration selects the face, and .fontName(HELVETICA_BOLD) expresses the family twice and the face never. The working form is:

DocumentTextStyle.builder()
        .fontName(FontName.HELVETICA)
        .decoration(DocumentTextDecoration.BOLD)
        .size(13)
        .build()

Measured difference on a real deck heading — GraphCompose Engine at 13pt is 135.84 pt as rendered today and 144.47 pt as the author intended, 6.4% narrower than the bold it asks for.

Where it bites today

ChartDefaults:101, HeadingBarStyle:21, TimelineBuilder:69, TimelineMarker:74 and ModernProfessional:134 all use the non-working form, plus 111 call sites across examples/. Every chart axis label, heading bar, timeline marker and those preset headings are currently regular weight in shipped output.

This is also what #450 traced: the PPTX backend used to read the weight off the name string, so it asked the viewer for a bold face the layout had never measured and the text overran its frame. That is fixed by matching the engine's rule, which means PPTX now renders regular too — correct parity, and the same missing bold.

Options

  1. Make the alias a real fallback. Look the requested variant up first and only fall back to the base family when nothing is registered. The standard-14 families already declare all four faces (DefaultFonts passes "HELVETICA_BOLD" etc. to FontFamilyDefinition.standard14), so the bold program is available — it is only unreachable.
  2. Reject the form. Log once per style, or fail fast, when a style names a variant that resolves to a different face than it asks for, so the silent no-op becomes visible.
  3. Leave the behaviour, document it. Cheapest, and the weakest — the API reads as if it works.

Option 1 is the one that matches what callers already write, but it changes rendered output wherever the form is used: text gets wider, lines re-wrap, and every layout snapshot and pixel baseline covering those sites moves. That is why it was kept out of the 2.1.0 release week rather than folded into #450.

Also update

docs/architecture/backend-capability-matrix.md if the resolution rule changes, and the font documentation, which does not currently state that the face comes from the decoration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions