Skip to content

\mathit companion font on MTFont - #270

Merged
kostub merged 3 commits into
masterfrom
feature/mathit-routing-pr2
Aug 4, 2026
Merged

\mathit companion font on MTFont#270
kostub merged 3 commits into
masterfrom
feature/mathit-routing-pr2

Conversation

@kostub

@kostub kostub commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Plan: docs/plans/2026-08-04-mathit-text-italic-routing.md (PR 2 of 3, items 3-4)
LLD: docs/lld/2026-07-27-mathit-text-italic-routing.md

Goal

Every MTFont carries a mathitCTFont — the LLD §3.1 companion table made total over the eight bundled math fonts: bundled Latin Modern Roman Italic for the two Latin Modern-family fonts and as the terminal fallback, name-verified platform faces for the other six.

Nothing consumes it yet; routing lands in PR 3.

Two details worth review attention:

  • The companion rides the existing per-name MTFont cache rather than a second cache in MTFontManager — no (name, size) coherence problem and no MTFont.name accessor needed (LLD §3.3).
  • CTFontCreateWithName substitutes silently rather than failing (asking for a missing face returned Helvetica in the LLD §2.9 probe), so by-name rows create, compare CTFontCopyPostScriptName, and reject on mismatch — falling through to the bundled face rather than drawing a wrong one.

The bundled .otf ships whole and unsubsetted with no .plist sidecar: it is drawn from, never used as a math table. The GUST Font License already covers it and is already vendored.

Commits

  1. [item 3] Bundle lmroman10-italic.otf companion face
  2. [item 4] Resolve \mathit companion font on MTFont

Full swift test green (455 tests).

Stack

  1. Never fuse atoms across a font-style change #269 — Never fuse atoms across a font-style change
  2. This PR

Summary by CodeRabbit

  • New Features

    • Improved italic math rendering with verified system font mappings and bundled Latin Modern Italic support.
    • Added reliable fallback behavior when a dedicated italic font is unavailable.
    • Preserved italic font sizing when fonts are resized or copied.
  • Bug Fixes

    • Prevented substituted or mismatched fonts from being selected for mathematical italics.
    • Improved font selection consistency across supported build environments.

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3fe4b341-b986-4282-8898-2578afbad3fe

📥 Commits

Reviewing files that changed from the base of the PR and between 894f8de and 7a60e77.

📒 Files selected for processing (2)
  • iosMath/render/MTFont.m
  • iosMathTests/MTFontManagerTest.m
🚧 Files skipped from review as they are similar to previous changes (2)
  • iosMathTests/MTFontManagerTest.m
  • iosMath/render/MTFont.m

📝 Walkthrough

Walkthrough

MTFont now creates and manages a companion italic font for \mathit. It verifies system font mappings, loads the bundled Latin Modern italic font when needed, preserves sizing and ownership, and tests fallback and substitution rejection.

Changes

Math italic companion font

Layer / File(s) Summary
Font resolution and contract
iosMath/render/internal/MTFont+Internal.h, iosMath/render/MTFont.m
Defines the mathitCTFont property and verified font creation function. Resolves supported system fonts and the bundled Latin Modern italic fallback.
MTFont lifecycle integration
iosMath/render/MTFont.m
Initializes the companion font, falls back to ctFont when needed, preserves Core Foundation ownership, resizes the companion font, and releases it during deallocation.
Companion font validation
iosMathTests/MTFontManagerTest.m
Tests companion font availability, size, bundled-face selection, resizing, and rejection of substituted PostScript fonts.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MTFont
  participant CoreText
  participant FontBundle
  MTFont->>CoreText: Resolve verified italic PostScript font
  CoreText-->>MTFont: Return matching CTFontRef or reject substitution
  MTFont->>FontBundle: Load bundled Latin Modern italic fallback
  FontBundle-->>MTFont: Return fallback CTFontRef
  MTFont->>MTFont: Store and resize companion font
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding a \mathit companion font to MTFont.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/mathit-routing-pr2

Comment @coderabbitai help to get the list of available commands.

@kostub kostub left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the diff of #270 only (items 3-4), against LLD §2.8/§2.9/§3.1/§6/§7.

CoreFoundation memory management — checked, no defects found

I walked every path that creates, retains or releases a CTFontRef/CGFontRef/CGDataProviderRef, and also ran clang --analyze -fobjc-arc over MTFont.m (clean, exit 0).

  • init, success. MTCreateCompanionForMathFont returns +1; assigned straight to the ivar (not through the setter), so the object holds exactly one reference; dealloc's self.mathitCTFont = nil releases it. Balanced.
  • init, early return at if (!dict) { return nil; }. _mathitCTFont is still NULL at that point, and the setter's if (_mathitCTFont != nil) guard makes the dealloc release a no-op. No over-release.
  • init, assert-fallback. _mathitCTFont = CFRetain(_ctFont) puts +2 on the same object; dealloc releases through both self.ctFont = nil and self.mathitCTFont = nil. Balanced — the ordering in dealloc is safe because setCtFont: only nils _ctFont, it does not touch _mathitCTFont.
  • copyFontWithSize:. CTFontCreateCopyWithAttributes +1 → setter retains (+2) → CFRelease (+1), held by copyFont. Balanced, and the target is a fresh [[MTFont alloc] init] so the setter's release-old branch is not taken on a NULL ivar.
  • MTCreateBundledItalicFont. provider and cgFont are each released on both the success and the failure continuation; the three return NULL points leak nothing.
  • MTCreateVerifiedFontWithPostScriptName. CFBridgingRelease(CTFontCopyPostScriptName(...)) consumes the +1 name; the mismatch branch releases font before returning NULL.

I also empirically checked the one thing the LLD §6 re-entrancy note asserts but the test suite does not cover: whether CTFontCreateCopyWithAttributes preserves a face that was built from an unregistered CGFont (lmroman10-italic.otf is not installed on the system). testCopyFontWithSizePreservesMathitCompanion only exercises XITS, whose companion is a system-installed Times. I ran a scratch test over all eight rows: latinmodern-math and newcm-math both round-trip LMRoman10-Italic at the new size with identical glyph IDs for f/1/Γ. So the copy path is sound for the bundled face too — no finding, just closing the gap I suspected.

Behaviour matches §3.1's table row-for-row (all six by-name keys match the MTFontName* constant values), the fonts group is a folder reference in iosMath.xcodeproj so the new .otf needs no .pbxproj edit, and Package.swift's .copy("fonts") picks it up. Full suite: 455 tests, 0 failures.

Nit — two deletable lines

static CTFontRef MTCreateBundledItalicFont(CGFloat size) CF_RETURNS_RETAINED;
static CTFontRef MTCreateCompanionForMathFont(NSString* name, CGFloat size) CF_RETURNS_RETAINED;

These two forward declarations exist only to carry the annotation — nothing calls either function before its definition. The annotation is not doing work: both names start with MTCreate, so the analyzer already applies the CF create rule by naming convention. I deleted both lines and re-ran clang --analyze: still clean, no new leak or over-release diagnostics. The file already relies on this — MTCreateVerifiedFontWithPostScriptName's own definition at line 26 carries no annotation either (only the MTFont+Internal.h declaration does), so as written the file is inconsistent about a marker that changes nothing. Four lines collapse to zero.

Same size of point, same place: the new test uses @import CoreText; where the neighbouring MTTypesetterTest.m:10 uses #import <CoreText/CoreText.h>. Both work; the #import form is what the rest of iosMathTests/ uses.

Nothing blocking.

kostub added 2 commits August 4, 2026 18:49
Whole, unsubsetted Latin Modern Roman Italic face copied from TeX Live
2025. No .plist sidecar: it is drawn from as a plain CTFont, never
consulted as a math table (LLD 2026-07-27 §2.8, §3.1). Already covered
by the vendored GUST Font License. Not yet referenced by any code;
resolved and carried by MTFont in the next item.
Every MTFont now resolves and carries a mathitCTFont at load: the two
Latin Modern-family fonts (and any failed by-name lookup) fall back to
the bundled lmroman10-italic.otf; the other six resolve a specific
platform face by PostScript name, verified via
MTCreateVerifiedFontWithPostScriptName to reject CoreText's silent
substitution (LLD 2026-07-27 §2.9). copyFontWithSize: resizes the same
face rather than re-resolving it. Nothing consumes mathitCTFont yet;
routing lands in PR 3.
@kostub
kostub force-pushed the feature/mathit-routing-pr2 branch from d4b2927 to 894f8de Compare August 4, 2026 13:19
@kostub
kostub changed the base branch from feature/mathit-routing-pr1 to master August 4, 2026 13:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
iosMathTests/MTFontManagerTest.m (1)

129-133: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prove the substitution branch.

Create the raw font with CTFontCreateWithName, assert it is non-NULL, and assert that CTFontCopyPostScriptName differs from the requested name before asserting that MTCreateVerifiedFontWithPostScriptName returns NULL.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@iosMathTests/MTFontManagerTest.m` around lines 129 - 133, Update
testVerifiedFontCreationRejectsSubstitution to first create the requested font
via CTFontCreateWithName, assert the raw font is non-NULL, and verify
CTFontCopyPostScriptName differs from the requested name; then assert
MTCreateVerifiedFontWithPostScriptName returns NULL while preserving proper
CFRelease cleanup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@iosMath/render/MTFont.m`:
- Around line 169-171: In the CTFontCreateCopyWithAttributes call for the
companion mathitCTFont, replace the size parameter with
CTFontGetSize(copyFont.ctFont) so that the companion font uses the same resolved
size as the primary font instead of preserving its original size when size is 0.
This ensures both fonts maintain equal sizes after copyFontWithSize is called.

In `@iosMathTests/MTFontManagerTest.m`:
- Around line 95-102: Guard the nullable results from fontWithName:size: and
related font-copy operations in the test methods: assert font, copy, and
companion, then return or continue before any Core Text call when a value is
nil. Update the affected checks around mathitCTFont and the corresponding lines
to prevent NULL from reaching Core Text APIs while preserving the existing
assertions for valid objects.

---

Nitpick comments:
In `@iosMathTests/MTFontManagerTest.m`:
- Around line 129-133: Update testVerifiedFontCreationRejectsSubstitution to
first create the requested font via CTFontCreateWithName, assert the raw font is
non-NULL, and verify CTFontCopyPostScriptName differs from the requested name;
then assert MTCreateVerifiedFontWithPostScriptName returns NULL while preserving
proper CFRelease cleanup.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bd1cf13e-533b-4514-af3d-0f195a4b03af

📥 Commits

Reviewing files that changed from the base of the PR and between 76fd773 and 894f8de.

⛔ Files ignored due to path filters (1)
  • iosMath/fonts/lmroman10-italic.otf is excluded by !**/*.otf
📒 Files selected for processing (3)
  • iosMath/render/MTFont.m
  • iosMath/render/internal/MTFont+Internal.h
  • iosMathTests/MTFontManagerTest.m

Comment thread iosMath/render/MTFont.m Outdated
Comment thread iosMathTests/MTFontManagerTest.m
copyFontWithSize: passed its `size` argument to both font constructors,
but they read 0 differently: CTFontCreateWithGraphicsFont maps it to
12pt while CTFontCreateCopyWithAttributes keeps the source size. So
copyFontWithSize:0 — reachable from the public fontWithName:size: —
produced a 12pt math font with a 20pt \mathit companion. Build the
companion from the primary's resolved size instead.

Drop the two CF_RETURNS_RETAINED forward declarations: nothing calls
either function before its definition, and the analyzer already applies
the CF create rule to an MTCreate* name (verified — it still reports an
injected leak through MTCreateBundledItalicFont without them).

Test file switches to #import <CoreText/CoreText.h>, matching the rest
of iosMathTests/, and testVerifiedFontCreationRejectsSubstitution now
pins that CoreText substitutes rather than returning NULL, which is the
premise the whole verify-by-name path exists for.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TmCWfSMYeLRJmUEvSd5XoT
@kostub
kostub merged commit 32e3e9a into master Aug 4, 2026
2 checks passed
@kostub
kostub deleted the feature/mathit-routing-pr2 branch August 20, 2026 07:03
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