fix(ios): synthesize oblique for italic on single-style custom fonts#12
Merged
Conversation
`italic` on a custom font with no real italic face rendered upright on iOS but slanted on Android: SwiftUI's `.italic()` works by requesting the italic symbolic trait, which is a silent no-op when the family has nothing to satisfy it, while Android's text stack fakes the slant (textSkewX). Single-style fonts — most Google Fonts display faces, e.g. Audiowide — hit this on every italic use. The resolver now takes an `italic` flag: when CoreText can't produce a real italic face for the font (checked via CTFontCreateCopyWithSymbolicTraits, cached per PostScript name), it returns the font skewed by tan(14°) — matching Android's fake-italic slant — via a UIFontDescriptor matrix. Fonts with a true italic face are still returned upright so the existing `Text.italic()` / `Font.italic()` call sites select the real face; both leaf and composed-run text paths pass the flag through. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
shanerbaner82
added a commit
that referenced
this pull request
Jul 23, 2026
…t clip (#13) The font-matrix oblique from #12 leans glyphs past their advance widths, and SwiftUI's text renderer clips each line to those advances — the last glyph of a synthetically-obliqued line lost its top-right corner (visible on jump's 72pt "JUMP" wordmark: the P's bowl was sliced vertically). No outside padding can reveal it; the raster itself is cut. Leaf text now keeps the UPRIGHT font — complete raster, nothing to clip — and slants the rendered view with a `transformEffect` skew instead, the same draw-time model as Android's textSkewX. The transform is anchored at the first baseline (ascender below the view top) so the baseline stays put and glyph tops lean right; layout metrics are untouched. On wrapped text, later baselines drift slightly left of the anchor — the trade against a hard clip at every line end, and only for single-style fonts asked to render italic. Inline composed runs keep the font-matrix path (a per-view transform can't target one run); mid-line the overhang lands harmlessly in the next run's space, so only a line ENDING in an oblique run can still clip — noted on the resolver. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.
Problem
<text font="accent" class="italic">renders slanted on Android but upright on iOS whenever the custom font has no real italic face (true of most single-style Google Fonts display faces — Audiowide, DynaPuff, Rock Salt, and jump's wordmark is the live example).TextRenderer.kt): Compose getsFontStyle.Italic; when the family lacks an italic font, the platform synthesizes one (textSkewX = -0.25).NativeUITextRenderer.swift):Text.italic()requests the italic symbolic trait; when the family can't satisfy it, CoreText silently falls back and the text stays upright. No synthesis anywhere in the chain.Fix
NativeUIFontResolver.font(_:size:)gains anitalic:flag:CTFontCreateCopyWithSymbolicTraitsnon-nil, cached per PostScript name), the font is returned upright as before — the existingText.italic()/Font.italic()call sites select the true face.UIFontDescriptormatrix — deliberately matching Android's fake-italic slant so the two platforms lean the same amount.Both text paths pass the flag through: the leaf path via a new
italic:parameter onnuiScaledFont(defaulted, so all other call sites are untouched), and the composed-run path directly. The theme-default font (fonts.default) benefits too, since resolution runs through the same code.No prop/contract changes; Android untouched.
Testing
swiftc -parseclean on the three edited files (full type-check requires the generated Xcode project).font="accent" italic→ Audiowide).🤖 Generated with Claude Code