From 8b5f8b94f080b4717cb578ff237c877b13c55c31 Mon Sep 17 00:00:00 2001 From: Kostub D Date: Sat, 30 May 2026 22:09:56 +0530 Subject: [PATCH 1/8] [item 9] Add public MTFontName* font-name constants --- iosMath/render/MTFontManager.h | 9 ++++++++ iosMath/render/MTFontManager.m | 9 ++++++++ iosMathSwiftTests/iosMathSwiftAPITests.swift | 23 +++++++++----------- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/iosMath/render/MTFontManager.h b/iosMath/render/MTFontManager.h index ee2e131..3a3fd40 100644 --- a/iosMath/render/MTFontManager.h +++ b/iosMath/render/MTFontManager.h @@ -17,6 +17,15 @@ NS_ASSUME_NONNULL_BEGIN +extern NSString *const MTFontNameLatinModern; +extern NSString *const MTFontNameXITS; +extern NSString *const MTFontNameTermes; +extern NSString *const MTFontNameNewComputerModern; +extern NSString *const MTFontNamePagella; +extern NSString *const MTFontNameSTIXTwo; +extern NSString *const MTFontNameFiraMath; +extern NSString *const MTFontNameNotoSansMath; + /** A manager to load font files from disc and keep them in memory. */ @interface MTFontManager : NSObject diff --git a/iosMath/render/MTFontManager.m b/iosMath/render/MTFontManager.m index a6877ba..9e010cc 100644 --- a/iosMath/render/MTFontManager.m +++ b/iosMath/render/MTFontManager.m @@ -14,6 +14,15 @@ const int kDefaultFontSize = 20; +NSString *const MTFontNameLatinModern = @"latinmodern-math"; +NSString *const MTFontNameXITS = @"xits-math"; +NSString *const MTFontNameTermes = @"texgyretermes-math"; +NSString *const MTFontNameNewComputerModern = @"newcm-math"; +NSString *const MTFontNamePagella = @"texgyrepagella-math"; +NSString *const MTFontNameSTIXTwo = @"stixtwo-math"; +NSString *const MTFontNameFiraMath = @"firamath"; +NSString *const MTFontNameNotoSansMath = @"notosansmath"; + @interface MTFontManager () @property (nonatomic, nonnull) NSMutableDictionary* nameToFontMap; diff --git a/iosMathSwiftTests/iosMathSwiftAPITests.swift b/iosMathSwiftTests/iosMathSwiftAPITests.swift index 6c702d3..ff4be0e 100644 --- a/iosMathSwiftTests/iosMathSwiftAPITests.swift +++ b/iosMathSwiftTests/iosMathSwiftAPITests.swift @@ -49,19 +49,16 @@ final class MTFontManagerTests: XCTestCase { XCTAssertNotNil(font) } - func testLatinModernFont() { - let font = MTFontManager().latinModernFont(withSize: 18) - XCTAssertNotNil(font) - } - - func testXitsFont() { - let font = MTFontManager().xitsFont(withSize: 16) - XCTAssertNotNil(font) - } - - func testTermesFont() { - let font = MTFontManager().termesFont(withSize: 16) - XCTAssertNotNil(font) + func testFontByName() { + let manager = MTFontManager() + let names = [ + MTFontNameLatinModern, MTFontNameXITS, MTFontNameTermes, + MTFontNameNewComputerModern, MTFontNamePagella, MTFontNameSTIXTwo, + MTFontNameFiraMath, MTFontNameNotoSansMath, + ] + for name in names { + XCTAssertNotNil(manager.font(withName: name, size: 18), "Font \(name) failed to load") + } } } From 11c7b28452de8fd3d7484c7da9694363e25703e2 Mon Sep 17 00:00:00 2001 From: Kostub D Date: Sat, 30 May 2026 22:10:46 +0530 Subject: [PATCH 2/8] [item 10] Remove per-font convenience methods; route defaultFont via constant --- iosMath/render/MTFontManager.h | 9 --------- iosMath/render/MTFontManager.m | 17 +---------------- iosMathTests/MTTypesetterTest.m | 4 ++-- 3 files changed, 3 insertions(+), 27 deletions(-) diff --git a/iosMath/render/MTFontManager.h b/iosMath/render/MTFontManager.h index 3a3fd40..3620434 100644 --- a/iosMath/render/MTFontManager.h +++ b/iosMath/render/MTFontManager.h @@ -45,15 +45,6 @@ extern NSString *const MTFontNameNotoSansMath; */ - (MTFont *) fontWithName:(NSString *)name size:(CGFloat)size; -/** Helper function to return the Xits Math font. */ -- (MTFont *) xitsFontWithSize:(CGFloat)size; - -/** Helper function to return the Tex Gyre Termes Math font. */ -- (MTFont *) termesFontWithSize:(CGFloat)size; - -/** Helper function to return the Latin Modern Math font. */ -- (MTFont *) latinModernFontWithSize:(CGFloat)size; - /** Returns a CoreText font suitable for `\text*` rendering. The caller owns the returned reference (CF_RETAINED) and must `CFRelease` it. diff --git a/iosMath/render/MTFontManager.m b/iosMath/render/MTFontManager.m index 9e010cc..9852564 100644 --- a/iosMath/render/MTFontManager.m +++ b/iosMath/render/MTFontManager.m @@ -63,24 +63,9 @@ - (MTFont *)fontWithName:(NSString *)name size:(CGFloat)size } } -- (MTFont *)latinModernFontWithSize:(CGFloat)size -{ - return [self fontWithName:@"latinmodern-math" size:size]; -} - -- (MTFont *)xitsFontWithSize:(CGFloat)size -{ - return [self fontWithName:@"xits-math" size:size]; -} - -- (MTFont *)termesFontWithSize:(CGFloat)size -{ - return [self fontWithName:@"texgyretermes-math" size:size]; -} - - (MTFont *)defaultFont { - return [self latinModernFontWithSize:kDefaultFontSize]; + return [self fontWithName:MTFontNameLatinModern size:kDefaultFontSize]; } + (CTFontRef) textCTFontForStyle:(MTTextStyle) style diff --git a/iosMathTests/MTTypesetterTest.m b/iosMathTests/MTTypesetterTest.m index 5642368..0557841 100644 --- a/iosMathTests/MTTypesetterTest.m +++ b/iosMathTests/MTTypesetterTest.m @@ -2027,7 +2027,7 @@ - (void)testOverrightarrowNarrow // horizontal glyph assembly. - (void)testStretchyArrowAssemblyOnlyFont { - MTFont* xits = [MTFontManager.fontManager xitsFontWithSize:20]; + MTFont* xits = [MTFontManager.fontManager fontWithName:MTFontNameXITS size:20]; XCTAssertNotNil(xits); for (NSString* latex in @[@"\\overrightarrow{x}", @"\\overrightarrow{ABCD}", @@ -2057,7 +2057,7 @@ - (void)testStretchyArrowAssemblyOnlyFont // vertical glyph assembly instead. - (void)testStretchyVerticalArrowAssemblyOnlyFont { - MTFont* xits = [MTFontManager.fontManager xitsFontWithSize:20]; + MTFont* xits = [MTFontManager.fontManager fontWithName:MTFontNameXITS size:20]; XCTAssertNotNil(xits); // Tall content (a fraction) forces the boundary delimiter to stretch, exercising From ee8612e94ddaa2f96c59914e6b4d7877fa25e0c7 Mon Sep 17 00:00:00 2001 From: Kostub D Date: Sat, 30 May 2026 22:20:39 +0530 Subject: [PATCH 3/8] [item 11] Migrate iOS example to fontWithName: + applyFontWithName: --- iosMathExample/example/ViewController.m | 49 +++++-------------------- 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/iosMathExample/example/ViewController.m b/iosMathExample/example/ViewController.m index e3a9ef4..242a836 100644 --- a/iosMathExample/example/ViewController.m +++ b/iosMathExample/example/ViewController.m @@ -39,6 +39,8 @@ @interface ViewController () @property (weak, nonatomic) IBOutlet MTMathUILabel *mathLabel; @property (weak, nonatomic) IBOutlet UITextField *latexField; +- (void)applyFontWithName:(NSString *)name; + @end @implementation ViewController @@ -253,33 +255,13 @@ - (void) setVerticalGap:(CGFloat) gap between:(UIView*) view1 and:(UIView*) view } #pragma mark Buttons -- (void)latinButtonPressed:(id)sender -{ - for (MTMathUILabel* label in self.demoLabels) { - label.font = [[MTFontManager fontManager] latinModernFontWithSize:label.font.fontSize]; - } - for (MTMathUILabel* label in self.labels) { - label.font = [[MTFontManager fontManager] latinModernFontWithSize:label.font.fontSize]; - } -} - -- (void)termesButtonPressed:(id)sender -{ - for (MTMathUILabel* label in self.demoLabels) { - label.font = [[MTFontManager fontManager] termesFontWithSize:label.font.fontSize]; - } - for (MTMathUILabel* label in self.labels) { - label.font = [[MTFontManager fontManager] termesFontWithSize:label.font.fontSize]; - } -} - -- (void)xitsButtonPressed:(id)sender +- (void)applyFontWithName:(NSString *)name { for (MTMathUILabel* label in self.demoLabels) { - label.font = [[MTFontManager fontManager] xitsFontWithSize:label.font.fontSize]; + label.font = [[MTFontManager fontManager] fontWithName:name size:label.font.fontSize]; } for (MTMathUILabel* label in self.labels) { - label.font = [[MTFontManager fontManager] xitsFontWithSize:label.font.fontSize]; + label.font = [[MTFontManager fontManager] fontWithName:name size:label.font.fontSize]; } } @@ -341,24 +323,13 @@ - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row f - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { + // Display names (self.fontNames) map 1:1 to these loader keys. + static NSString *const kFontKeys[] = { + @"latinmodern-math", @"texgyretermes-math", @"xits-math", + }; self.controller.fontField.text = self.fontNames[row]; [self.controller.fontField resignFirstResponder]; - switch (row) { - case 0: - [self.controller latinButtonPressed:nil]; - break; - - case 1: - [self.controller termesButtonPressed:nil]; - break; - - case 2: - [self.controller xitsButtonPressed:nil]; - break; - - default: - break; - } + [self.controller applyFontWithName:kFontKeys[row]]; } @end From e05e4f922964eeaed25e19bf73e6c3a079fed24b Mon Sep 17 00:00:00 2001 From: Kostub D Date: Sat, 30 May 2026 22:22:58 +0530 Subject: [PATCH 4/8] [item 12] Migrate SwiftMathExample MathFont to fontWithName: + constants --- SwiftMathExample/MathLabel.swift | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/SwiftMathExample/MathLabel.swift b/SwiftMathExample/MathLabel.swift index d16213c..0c1d64e 100644 --- a/SwiftMathExample/MathLabel.swift +++ b/SwiftMathExample/MathLabel.swift @@ -38,7 +38,7 @@ struct MathLabel: View { } } -/// The three math fonts bundled with iosMath, exposed for the font switcher. +/// The math fonts bundled with iosMath, exposed for the font switcher. enum MathFont: String, CaseIterable, Identifiable { case latinModern = "Latin Modern" case termes = "TeX Gyre Termes" @@ -46,14 +46,17 @@ enum MathFont: String, CaseIterable, Identifiable { var id: String { rawValue } - func font(size: CGFloat) -> MTFont? { - let manager = MTFontManager.fontManager() + var fontName: String { switch self { - case .latinModern: return manager.latinModernFont(withSize: size) - case .termes: return manager.termesFont(withSize: size) - case .xits: return manager.xitsFont(withSize: size) + case .latinModern: return MTFontNameLatinModern + case .termes: return MTFontNameTermes + case .xits: return MTFontNameXITS } } + + func font(size: CGFloat) -> MTFont? { + MTFontManager().font(withName: fontName, size: size) + } } // MARK: - Platform representables From 7cdf37447d0aad5196a1137b497a19638e99c0a8 Mon Sep 17 00:00:00 2001 From: Kostub D Date: Sat, 30 May 2026 23:28:39 +0530 Subject: [PATCH 5/8] [item 13] Update README usage snippet to fontWithName: + constants --- README.md | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f96da90..d6c4859 100755 --- a/README.md +++ b/README.md @@ -200,22 +200,41 @@ label.fontSize = 30 ### Font -The default font is *Latin Modern Math*. Three fonts are bundled; you can -also use any OTF math font: +The default font is *Latin Modern Math*. Eight fonts are bundled; you can +also use any OTF math font. Select a font using `MTFontName*` constants and +`font(withName:size:)`: ```swift -label.font = MTFontManager().termesFont(withSize: 20) +label.font = MTFontManager().font(withName: MTFontNameTermes, size: 20) ```
Objective-C ```objective-c -label.font = [[MTFontManager fontManager] termesFontWithSize:20]; +label.font = [MTFontManager.fontManager fontWithName:MTFontNameTermes size:20]; ```
+The three per-font convenience methods (`latinModernFontWithSize:`, +`xitsFontWithSize:`, `termesFontWithSize:`) were removed. Use +`fontWithName:size:` with one of the bundled constants instead. +`defaultFont` is unchanged and returns Latin Modern Math at 20pt. + +Available `MTFontName*` constants: + +| Constant | Font | +|---|---| +| `MTFontNameLatinModern` | Latin Modern Math | +| `MTFontNameXITS` | XITS Math | +| `MTFontNameTermes` | TeX Gyre Termes Math | +| `MTFontNameNewComputerModern` | New Computer Modern Math | +| `MTFontNamePagella` | TeX Gyre Pagella Math | +| `MTFontNameSTIXTwo` | STIX Two Math | +| `MTFontNameFiraMath` | Fira Math | +| `MTFontNameNotoSansMath` | Noto Sans Math | + ### Color ```swift From 8b36f91b669b59d19c10adb88e9ca4744ed917dc Mon Sep 17 00:00:00 2001 From: Kostub D Date: Sat, 30 May 2026 23:29:29 +0530 Subject: [PATCH 6/8] [item 14] Note breaking font-API change and new fonts in CHANGELOG --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 496a863..293120d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ ## Changelog +### v2.3.0 (unreleased) — Breaking API change + +**Breaking:** Removed `latinModernFontWithSize:`, `xitsFontWithSize:`, and +`termesFontWithSize:` from `MTFontManager`. Migrate to the generic accessor +with the new public name constants: + +```objc +// Before +label.font = [[MTFontManager fontManager] termesFontWithSize:20]; + +// After +label.font = [[MTFontManager fontManager] fontWithName:MTFontNameTermes size:20]; +``` + +`defaultFont` is unchanged and returns Latin Modern Math at 20pt. + +**New fonts:** Added five new OpenType MATH fonts — New Computer Modern Math, +TeX Gyre Pagella Math, STIX Two Math, Fira Math, and Noto Sans Math. XITS Math +updated to v1.302 (final upstream release). + +**New public constants** (`MTFontManager.h`): `MTFontNameLatinModern`, +`MTFontNameXITS`, `MTFontNameTermes`, `MTFontNameNewComputerModern`, +`MTFontNamePagella`, `MTFontNameSTIXTwo`, `MTFontNameFiraMath`, +`MTFontNameNotoSansMath`. + ### v2.2.0 (2026-05-16) * Add `\text{}`, `\textrm{}`, `\textbf{}`, `\textit{}`, `\textsf{}`, `\texttt{}` for rendering non-Latin text alongside math — supports CJK, Devanagari, Arabic, Hebrew, Cyrillic, and any other script handled by CoreText system-font cascade. * Add prime shorthand: `f'` parses as `f^{\prime}`, `f''` as `f^{\prime\prime}`, etc. From 73e3fc4ae1fd308f6ba14c8fd5f08be3463b07cb Mon Sep 17 00:00:00 2001 From: Kostub D Date: Sun, 31 May 2026 00:29:17 +0530 Subject: [PATCH 7/8] [review] Use fontManager singleton + name constants in examples/docs Address PR #210 review: - MathLabel.swift / README: call MTFontManager.fontManager() instead of MTFontManager(), so the shared nameToFontMap cache is reused rather than re-parsing the font file on every font(size:) call. - iOS example: key the picker off the public MTFontName* constants instead of hardcoded loader-key literals. Drops `static` since extern const NSString* are not compile-time-constant array initializers. Co-Authored-By: Claude Opus 4.8 --- README.md | 2 +- SwiftMathExample/MathLabel.swift | 2 +- iosMathExample/example/ViewController.m | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d6c4859..6c76688 100755 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ also use any OTF math font. Select a font using `MTFontName*` constants and `font(withName:size:)`: ```swift -label.font = MTFontManager().font(withName: MTFontNameTermes, size: 20) +label.font = MTFontManager.fontManager().font(withName: MTFontNameTermes, size: 20) ```
diff --git a/SwiftMathExample/MathLabel.swift b/SwiftMathExample/MathLabel.swift index 0c1d64e..dc208c6 100644 --- a/SwiftMathExample/MathLabel.swift +++ b/SwiftMathExample/MathLabel.swift @@ -55,7 +55,7 @@ enum MathFont: String, CaseIterable, Identifiable { } func font(size: CGFloat) -> MTFont? { - MTFontManager().font(withName: fontName, size: size) + MTFontManager.fontManager().font(withName: fontName, size: size) } } diff --git a/iosMathExample/example/ViewController.m b/iosMathExample/example/ViewController.m index 242a836..df139b9 100644 --- a/iosMathExample/example/ViewController.m +++ b/iosMathExample/example/ViewController.m @@ -324,8 +324,9 @@ - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row f - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { // Display names (self.fontNames) map 1:1 to these loader keys. - static NSString *const kFontKeys[] = { - @"latinmodern-math", @"texgyretermes-math", @"xits-math", + // Not static: extern const NSString* values aren't compile-time constants. + NSString *const kFontKeys[] = { + MTFontNameLatinModern, MTFontNameTermes, MTFontNameXITS, }; self.controller.fontField.text = self.fontNames[row]; [self.controller.fontField resignFirstResponder]; From 1956418a192edef24be6b8aad9cb7f32bac55746 Mon Sep 17 00:00:00 2001 From: Kostub D Date: Sun, 31 May 2026 12:35:37 +0530 Subject: [PATCH 8/8] Make MTFontManager a true singleton; expose via class property MTFontManager() in Swift bypassed the singleton: the ObjC factory method +fontManager was imported as init(), so MTFontManager() called -init and produced a fresh, cache-bypassing instance, while the explicit fontManager() spelling was marked unavailable. Expose the shared instance as a class property instead of a +fontManager factory method so Swift imports it as MTFontManager.fontManager (no parens, no init() collapse), and mark init/new NS_UNAVAILABLE so the bypass can't be written. The getter now builds the instance via dispatch_once + a private -initPrivate. ObjC callers using [MTFontManager fontManager] are unaffected. Update the Swift callers (MathLabel, README, Swift API tests) to the property form and assert singleton identity. Co-Authored-By: Claude Opus 4.8 --- README.md | 2 +- SwiftMathExample/MathLabel.swift | 2 +- iosMath/render/MTFontManager.h | 14 ++++++++++++-- iosMath/render/MTFontManager.m | 13 ++++++++----- iosMathSwiftTests/iosMathSwiftAPITests.swift | 11 ++++++----- 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 6c76688..8c49b19 100755 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ also use any OTF math font. Select a font using `MTFontName*` constants and `font(withName:size:)`: ```swift -label.font = MTFontManager.fontManager().font(withName: MTFontNameTermes, size: 20) +label.font = MTFontManager.fontManager.font(withName: MTFontNameTermes, size: 20) ```
diff --git a/SwiftMathExample/MathLabel.swift b/SwiftMathExample/MathLabel.swift index dc208c6..f5629f6 100644 --- a/SwiftMathExample/MathLabel.swift +++ b/SwiftMathExample/MathLabel.swift @@ -55,7 +55,7 @@ enum MathFont: String, CaseIterable, Identifiable { } func font(size: CGFloat) -> MTFont? { - MTFontManager.fontManager().font(withName: fontName, size: size) + MTFontManager.fontManager.font(withName: fontName, size: size) } } diff --git a/iosMath/render/MTFontManager.h b/iosMath/render/MTFontManager.h index 3620434..1292e1f 100644 --- a/iosMath/render/MTFontManager.h +++ b/iosMath/render/MTFontManager.h @@ -30,8 +30,18 @@ extern NSString *const MTFontNameNotoSansMath; in memory. */ @interface MTFontManager : NSObject -/** Get the singleton instance of MTFontManager. */ -+ (instancetype) fontManager; +/** The shared font manager. + + Declared as a class property (not a `+fontManager` factory method) so that + Swift imports it as `MTFontManager.fontManager` instead of collapsing it into + `init()`. In Objective-C it is still reached via `[MTFontManager fontManager]` + or `MTFontManager.fontManager`. */ +@property (class, readonly, strong) MTFontManager *fontManager; + +/** MTFontManager is a singleton; use +fontManager. Constructing your own + instance bypasses the shared font cache, so init/new are unavailable. */ ++ (instancetype) new NS_UNAVAILABLE; +- (instancetype) init NS_UNAVAILABLE; /** Returns the default font, which is Latin Modern Math with 20pt */ - (MTFont *) defaultFont; diff --git a/iosMath/render/MTFontManager.m b/iosMath/render/MTFontManager.m index 9852564..5c832de 100644 --- a/iosMath/render/MTFontManager.m +++ b/iosMath/render/MTFontManager.m @@ -31,16 +31,19 @@ @interface MTFontManager () @implementation MTFontManager -+ (instancetype) fontManager ++ (MTFontManager *) fontManager { static MTFontManager* manager = nil; - if (manager == nil) { - manager = [MTFontManager new]; - } + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + manager = [[self alloc] initPrivate]; + }); return manager; } -- (instancetype)init +// init/new are NS_UNAVAILABLE so callers can't bypass the singleton; the +// shared instance is built through this private initializer instead. +- (instancetype)initPrivate { self = [super init]; if (self) { diff --git a/iosMathSwiftTests/iosMathSwiftAPITests.swift b/iosMathSwiftTests/iosMathSwiftAPITests.swift index ff4be0e..16d14d3 100644 --- a/iosMathSwiftTests/iosMathSwiftAPITests.swift +++ b/iosMathSwiftTests/iosMathSwiftAPITests.swift @@ -39,18 +39,19 @@ final class MTMathUILabelTests: XCTestCase { } final class MTFontManagerTests: XCTestCase { - func testFontManagerInit() { - let mgr = MTFontManager() - XCTAssertNotNil(mgr) + func testFontManagerSingleton() { + // fontManager is a class property, so repeated access returns the same + // shared instance (and there is no MTFontManager() to bypass it). + XCTAssertTrue(MTFontManager.fontManager === MTFontManager.fontManager) } func testDefaultFont() { - let font = MTFontManager().defaultFont() + let font = MTFontManager.fontManager.defaultFont() XCTAssertNotNil(font) } func testFontByName() { - let manager = MTFontManager() + let manager = MTFontManager.fontManager let names = [ MTFontNameLatinModern, MTFontNameXITS, MTFontNameTermes, MTFontNameNewComputerModern, MTFontNamePagella, MTFontNameSTIXTwo,