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. diff --git a/README.md b/README.md index f96da90..8c49b19 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.fontManager.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 diff --git a/SwiftMathExample/MathLabel.swift b/SwiftMathExample/MathLabel.swift index d16213c..f5629f6 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.fontManager.font(withName: fontName, size: size) + } } // MARK: - Platform representables diff --git a/iosMath/render/MTFontManager.h b/iosMath/render/MTFontManager.h index ee2e131..1292e1f 100644 --- a/iosMath/render/MTFontManager.h +++ b/iosMath/render/MTFontManager.h @@ -17,12 +17,31 @@ 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 -/** 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; @@ -36,15 +55,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (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 a6877ba..5c832de 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; @@ -22,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) { @@ -54,24 +66,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/iosMathExample/example/ViewController.m b/iosMathExample/example/ViewController.m index e3a9ef4..df139b9 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,14 @@ - (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. + // 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]; - 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 diff --git a/iosMathSwiftTests/iosMathSwiftAPITests.swift b/iosMathSwiftTests/iosMathSwiftAPITests.swift index 6c702d3..16d14d3 100644 --- a/iosMathSwiftTests/iosMathSwiftAPITests.swift +++ b/iosMathSwiftTests/iosMathSwiftAPITests.swift @@ -39,29 +39,27 @@ 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 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.fontManager + 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") + } } } 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