Skip to content

Add opt-in synthetic font weight for fonts without bold faces#854

Draft
atani wants to merge 1 commit into
readium:developfrom
atani:feature/synthetic-font-weight
Draft

Add opt-in synthetic font weight for fonts without bold faces#854
atani wants to merge 1 commit into
readium:developfrom
atani:feature/synthetic-font-weight

Conversation

@atani

@atani atani commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Fixes #853.

Problem

EPUBPreferences.fontWeight maps to a CSS font-weight override injected into the page. WebKit can only apply a heavier weight when the font actually ships a bold face. For single-face fonts — most notably custom fonts declared through fontFamilyDeclarations — WebKit does not synthesize bolding, so fontWeight has no visible effect. Rendering the same passage at fontWeight 1.0 and 1.5 with a single-face font produces pixel-identical output.

This is an engine limitation, so font-weight alone cannot fix it. We need a separate channel to thicken glyphs.

Approach

Add an opt-in fontWeightSynthesis: Bool EPUB preference, defaulting to off (nil / false). When it is enabled and fontWeight is greater than 1.0, ReadiumCSS emits a -webkit-text-stroke-width override alongside the existing font-weight entry:

(min(fontWeight, 2.5) - 1.0) / 1.5 * 0.035  // em

Notes on the design:

  • Opt-in, zero default change. With the preference off (the default), no stroke is emitted and the output is identical to before, so existing apps are unaffected.
  • em-based. The stroke width scales with the font size instead of being a fixed pixel value.
  • Only thickens. A text stroke can add to glyph outlines but cannot thin them, so synthesis is limited to weights above 1.0.
  • No stroke color. -webkit-text-stroke-color is intentionally left unset; it defaults to currentColor, which tracks the text color across the light, dark and sepia themes.
  • Reuses the existing injection channel. User properties are injected inline on <html> and updated live through readium.setCSSProperties. -webkit-text-stroke inherits, so applying it on <html> covers all text, and an empty value clears it on live settings changes (mirroring the font-weight entry). Both call sites handle the new key without changes.

The preference is wired through EPUBPreferences, EPUBSettings, EPUBDefaults and EPUBPreferencesEditor, following the exact pattern of textNormalization.

Tests

Added ReadiumCSSTests cases covering the update(with:) mapping: synthesis off emits no stroke; on with weight not above 1.0 or unset emits none; on with weight 1.75 gives 0.0175em; on with weight 2.5 gives 0.0350em; and an out-of-range weight is clamped to the 2.5 result.

Verification

  • xcodebuild test -scheme Readium-Package -destination 'platform=iOS Simulator,name=iPhone 17' -only-testing:ReadiumNavigatorTests — 110 tests pass, including the 5 new cases.
  • swift run --package-path BuildTools swiftformat --lint . — clean.

@mickael-menu

Copy link
Copy Markdown
Member

Currently we're focusing on migrating the toolkit to Swift 6 with strict concurrency support in the swift6 branch. We accept bug fixes but new features are frozen until this is done (we're almost there). I'm putting your PR as a draft for now, thank you for your understanding. 🙏

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.

fontWeight preference has no effect on single-face fonts declared with fontFamilyDeclarations

2 participants