diff --git a/MacOSMathExample/AppDelegate.m b/MacOSMathExample/AppDelegate.m index ea457ebe..1307dd34 100644 --- a/MacOSMathExample/AppDelegate.m +++ b/MacOSMathExample/AppDelegate.m @@ -11,6 +11,7 @@ #import "AppDelegate.h" #import "MTMathUILabel.h" +#import "MTFontManager.h" #import "../MathExamples.h" // Flipped NSView so Auto Layout stacks subviews top-to-bottom. @@ -24,6 +25,14 @@ @interface AppDelegate () @property (weak) IBOutlet NSWindow *window; @property (nonatomic, strong) NSMutableArray* demoLabels; @property (nonatomic, strong) NSMutableArray* labels; +// Height constraints + their startup constants, so the size slider can rescale +// each label (and the document view) instead of clipping at larger font sizes. +// Demo labels render at fontSize 15; test labels at the default 20. +@property (nonatomic, strong) NSMutableArray* demoHeightConstraints; +@property (nonatomic, strong) NSMutableArray* testHeightConstraints; +@property (nonatomic, strong) NSMutableArray* demoBaseHeights; +@property (nonatomic, strong) NSMutableArray* testBaseHeights; +@property (nonatomic, weak) NSView* documentContentView; @end @implementation AppDelegate @@ -33,15 +42,55 @@ static CGFloat HeightAtIndex(const CGFloat *heights, NSUInteger count, NSUIntege return (index < count) ? heights[index] : fallback; } +static NSString *const kMacFontNames[] = { + @"Latin Modern Math", @"TeX Gyre Termes", @"XITS Math", + @"New Computer Modern", @"TeX Gyre Pagella", @"STIX Two", + @"Fira Math", @"Noto Sans Math", +}; +static const NSUInteger kMacFontCount = 8; + - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { self.demoLabels = [[NSMutableArray alloc] init]; self.labels = [[NSMutableArray alloc] init]; + self.demoHeightConstraints = [[NSMutableArray alloc] init]; + self.testHeightConstraints = [[NSMutableArray alloc] init]; + self.demoBaseHeights = [[NSMutableArray alloc] init]; + self.testBaseHeights = [[NSMutableArray alloc] init]; NSView* mainView = self.window.contentView; - // Scroll view fills the window. - NSScrollView* scrollView = [[NSScrollView alloc] initWithFrame:mainView.bounds]; + // Header strip: font popup + size slider, pinned to the top of mainView. + CGFloat headerHeight = 36; + NSRect headerFrame = NSMakeRect(0, mainView.bounds.size.height - headerHeight, + mainView.bounds.size.width, headerHeight); + NSView* headerView = [[NSView alloc] initWithFrame:headerFrame]; + headerView.autoresizingMask = NSViewWidthSizable | NSViewMinYMargin; + + NSPopUpButton* fontPopup = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(8, 4, 220, 28) pullsDown:NO]; + for (NSUInteger i = 0; i < kMacFontCount; i++) { + [fontPopup addItemWithTitle:kMacFontNames[i]]; + } + [fontPopup setTarget:self]; + [fontPopup setAction:@selector(changeFont:)]; + fontPopup.autoresizingMask = NSViewMaxXMargin; + [headerView addSubview:fontPopup]; + + NSSlider* sizeSlider = [[NSSlider alloc] initWithFrame:NSMakeRect(236, 8, 200, 20)]; + sizeSlider.minValue = 10; + sizeSlider.maxValue = 40; + sizeSlider.doubleValue = 15; + sizeSlider.target = self; + sizeSlider.action = @selector(changeSize:); + sizeSlider.autoresizingMask = NSViewWidthSizable; + [headerView addSubview:sizeSlider]; + + [mainView addSubview:headerView]; + + // Scroll view fills the window below the header. + NSRect scrollFrame = NSMakeRect(0, 0, mainView.bounds.size.width, + mainView.bounds.size.height - headerHeight); + NSScrollView* scrollView = [[NSScrollView alloc] initWithFrame:scrollFrame]; scrollView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; scrollView.hasVerticalScroller = YES; scrollView.hasHorizontalScroller = NO; @@ -55,6 +104,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification contentView.autoresizingMask = NSViewWidthSizable; contentView.backgroundColor = NSColor.whiteColor; scrollView.documentView = contentView; + self.documentContentView = contentView; // --- Demo formulae — LaTeX strings from MathExamples.h --- static const CGFloat demoHeights[] = { @@ -63,9 +113,12 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification NSArray* demoFormulas = MathDemoFormulas(); for (NSUInteger i = 0; i < demoFormulas.count; i++) { CGFloat height = HeightAtIndex(demoHeights, sizeof(demoHeights)/sizeof(CGFloat), i, 60); - MTMathUILabel* label = [self createMathLabel:demoFormulas[i] withHeight:height]; + MTMathUILabel* label = [[MTMathUILabel alloc] init]; + label.latex = demoFormulas[i]; label.fontSize = 15; [self.demoLabels addObject:label]; + [self.demoHeightConstraints addObject:[self setHeight:height forView:label]]; + [self.demoBaseHeights addObject:@(height)]; } [self addLabelAsSubview:self.demoLabels[0] to:contentView]; @@ -95,7 +148,11 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification NSArray* testFormulas = MathTestFormulas(); for (NSUInteger i = 0; i < testFormulas.count; i++) { CGFloat height = HeightAtIndex(testHeights, sizeof(testHeights)/sizeof(CGFloat), i, 40); - [self.labels addObject:[self createMathLabel:testFormulas[i] withHeight:height]]; + MTMathUILabel* label = [[MTMathUILabel alloc] init]; + label.latex = testFormulas[i]; + [self.labels addObject:label]; + [self.testHeightConstraints addObject:[self setHeight:height forView:label]]; + [self.testBaseHeights addObject:@(height)]; } CGFloat documentHeight = 10; @@ -123,9 +180,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification self.labels[6].contentInsets = NSEdgeInsetsMake(0, 20, 0, 0); self.labels[7].backgroundColor = highlight; self.labels[7].labelMode = kMTMathUILabelModeText; - self.labels[8].fontSize = 30; self.labels[8].textAlignment = kMTTextAlignmentCenter; - self.labels[9].fontSize = 10; self.labels[9].textAlignment = kMTTextAlignmentCenter; self.labels[17].labelMode = kMTMathUILabelModeText; self.labels[18].labelMode = kMTMathUILabelModeText; @@ -143,16 +198,56 @@ - (void)applicationWillTerminate:(NSNotification *)aNotification { } -#pragma mark - Label creation helpers +#pragma mark - Font and size actions -- (MTMathUILabel*)createMathLabel:(NSString*)latex withHeight:(CGFloat)height +- (void)changeFont:(NSPopUpButton *)sender { - MTMathUILabel* label = [[MTMathUILabel alloc] init]; - [self setHeight:height forView:label]; - label.latex = latex; - return label; + NSInteger i = sender.indexOfSelectedItem; + if (i < 0 || (NSUInteger)i >= kMacFontCount) return; + // Local (non-static) array: the public MTFontName* constants are runtime + // `extern NSString *const` values, not compile-time constants, so they can't + // initialize a file-scope static array. + NSString *const kMacFontKeys[] = { + MTFontNameLatinModern, MTFontNameTermes, MTFontNameXITS, + MTFontNameNewComputerModern, MTFontNamePagella, MTFontNameSTIXTwo, + MTFontNameFiraMath, MTFontNameNotoSansMath, + }; + NSString* key = kMacFontKeys[i]; + for (MTMathUILabel* label in self.demoLabels) { + label.font = [[MTFontManager fontManager] fontWithName:key size:label.font.fontSize]; + } + for (MTMathUILabel* label in self.labels) { + label.font = [[MTFontManager fontManager] fontWithName:key size:label.font.fontSize]; + } +} + +- (void)changeSize:(NSSlider *)sender +{ + CGFloat size = (CGFloat)sender.doubleValue; + // Scale each label's height from its startup baseline (demo 15, test 20) and + // grow the document view to match so formulas aren't clipped at larger sizes. + CGFloat documentHeight = 10; // top inset + for (NSUInteger i = 0; i < self.demoLabels.count; i++) { + self.demoLabels[i].fontSize = size; + CGFloat h = self.demoBaseHeights[i].doubleValue * (size / 15.0); + self.demoHeightConstraints[i].constant = h; + documentHeight += h + 10; + } + documentHeight += 30; // gap between sections + for (NSUInteger i = 0; i < self.labels.count; i++) { + self.labels[i].fontSize = size; + CGFloat h = self.testBaseHeights[i].doubleValue * (size / 20.0); + self.testHeightConstraints[i].constant = h; + documentHeight += h + 10; + } + NSView* contentView = self.documentContentView; + NSRect frame = contentView.frame; + frame.size.height = documentHeight; + contentView.frame = frame; } +#pragma mark - Label creation helpers + - (void)addLabelWithIndex:(NSUInteger)idx inArray:(NSArray*)array toView:(NSView*)contentView { NSAssert(idx > 0, @"Index should be greater than 0. For the first label add manually."); @@ -172,15 +267,17 @@ - (void)addLabelAsSubview:(NSView*)label to:(NSView*)parent options:0 metrics:nil views:views]]; } -- (void)setHeight:(CGFloat)height forView:(NSView*)view +- (NSLayoutConstraint*)setHeight:(CGFloat)height forView:(NSView*)view { view.translatesAutoresizingMaskIntoConstraints = NO; - [NSLayoutConstraint constraintWithItem:view + NSLayoutConstraint* constraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute - multiplier:1 constant:height].active = YES; + multiplier:1 constant:height]; + constraint.active = YES; + return constraint; } - (void)setVerticalGap:(CGFloat)gap between:(NSView*)view1 and:(NSView*)view2 diff --git a/MacOSMathExample/Base.lproj/MainMenu.xib b/MacOSMathExample/Base.lproj/MainMenu.xib index 6177a890..0300faba 100644 --- a/MacOSMathExample/Base.lproj/MainMenu.xib +++ b/MacOSMathExample/Base.lproj/MainMenu.xib @@ -15,8 +15,6 @@ - - @@ -690,29 +688,6 @@ - - - - - - - - - - - - - - diff --git a/SwiftMathExample/ContentView.swift b/SwiftMathExample/ContentView.swift index f0bbd805..34b2c8cb 100644 --- a/SwiftMathExample/ContentView.swift +++ b/SwiftMathExample/ContentView.swift @@ -32,14 +32,16 @@ struct ContentView: View { /// Selected math font, shared across all tabs — switching it in the /// Playground re-renders the Examples and Gallery too, mirroring the ObjC example. @State private var font: MathFont = .latinModern + /// Global font size, shared across all tabs (range 10–40, default 15). + @State private var fontSize: CGFloat = 15 var body: some View { TabView { - ExamplesTab(font: font) + ExamplesTab(font: font, fontSize: fontSize) .tabItem { Label("Examples", systemImage: "function") } - PlaygroundTab(font: $font) + PlaygroundTab(font: $font, fontSize: $fontSize) .tabItem { Label("Playground", systemImage: "pencil.and.scribble") } - GalleryTab(font: font) + GalleryTab(font: font, fontSize: fontSize) .tabItem { Label("Gallery", systemImage: "square.grid.2x2") } } } @@ -51,41 +53,37 @@ private struct NamedFormula { let title: String let latex: String var mode: MTMathUILabelMode = .display - var fontSize: CGFloat = 20 - var height: CGFloat = 60 } /// Curated examples — keep in sync with MathDemoFormulas() in MathExamples.h. /// LaTeX strings are sourced from MathDemoFormulas() so content stays consistent; -/// titles and per-formula display metadata live here. +/// titles live here. private struct NamedFormulaMeta { let title: String var mode: MTMathUILabelMode = .display - var fontSize: CGFloat = 20 - var height: CGFloat = 60 } private let namedExampleMeta: [NamedFormulaMeta] = [ - NamedFormulaMeta(title: "Quadratic formula", height: 80), - NamedFormulaMeta(title: "Cosine addition formula", height: 60), - NamedFormulaMeta(title: "Rogers–Ramanujan continued fraction", height: 130), - NamedFormulaMeta(title: "Standard deviation", height: 80), - NamedFormulaMeta(title: "De Morgan's law", height: 60), - NamedFormulaMeta(title: "Change of base", height: 70), - NamedFormulaMeta(title: "Compound interest limit", height: 70), - NamedFormulaMeta(title: "Gaussian integral", height: 70), - NamedFormulaMeta(title: "AM-GM inequality", height: 80), - NamedFormulaMeta(title: "Cauchy integral formula", height: 80), - NamedFormulaMeta(title: "Schrödinger's equation", fontSize: 16, height: 80), - NamedFormulaMeta(title: "Cauchy-Schwarz inequality", height: 80), - NamedFormulaMeta(title: "Stirling numbers", height: 80), - NamedFormulaMeta(title: "Fourier transform", height: 70), - NamedFormulaMeta(title: "Lorenz system", height: 110), - NamedFormulaMeta(title: "Cross product", fontSize: 16, height: 140), - NamedFormulaMeta(title: "Maxwell's equations", fontSize: 16, height: 200), - NamedFormulaMeta(title: "2×2 matrix multiplication", fontSize: 16, height: 90), - NamedFormulaMeta(title: "EM algorithm Q-function", height: 130), - NamedFormulaMeta(title: "Piecewise function", height: 100), + NamedFormulaMeta(title: "Quadratic formula"), + NamedFormulaMeta(title: "Cosine addition formula"), + NamedFormulaMeta(title: "Rogers–Ramanujan continued fraction"), + NamedFormulaMeta(title: "Standard deviation"), + NamedFormulaMeta(title: "De Morgan's law"), + NamedFormulaMeta(title: "Change of base"), + NamedFormulaMeta(title: "Compound interest limit"), + NamedFormulaMeta(title: "Gaussian integral"), + NamedFormulaMeta(title: "AM-GM inequality"), + NamedFormulaMeta(title: "Cauchy integral formula"), + NamedFormulaMeta(title: "Schrödinger's equation"), + NamedFormulaMeta(title: "Cauchy-Schwarz inequality"), + NamedFormulaMeta(title: "Stirling numbers"), + NamedFormulaMeta(title: "Fourier transform"), + NamedFormulaMeta(title: "Lorenz system"), + NamedFormulaMeta(title: "Cross product"), + NamedFormulaMeta(title: "Maxwell's equations"), + NamedFormulaMeta(title: "2×2 matrix multiplication"), + NamedFormulaMeta(title: "EM algorithm Q-function"), + NamedFormulaMeta(title: "Piecewise function"), ] private let namedExamples: [NamedFormula] = { @@ -93,20 +91,21 @@ private let namedExamples: [NamedFormula] = { precondition(formulas.count == namedExampleMeta.count, "namedExampleMeta (\(namedExampleMeta.count)) must match MathDemoFormulas (\(formulas.count))") return zip(namedExampleMeta, formulas).map { meta, latex in - NamedFormula(title: meta.title, latex: latex, mode: meta.mode, fontSize: meta.fontSize, height: meta.height) + NamedFormula(title: meta.title, latex: latex, mode: meta.mode) } }() /// Curated, named examples — suitable as a quick-start reference. private struct ExamplesTab: View { let font: MathFont + let fontSize: CGFloat var body: some View { NavigationView { ScrollView { VStack(alignment: .leading, spacing: 20) { ForEach(namedExamples.indices, id: \.self) { i in - ExampleCard(formula: namedExamples[i], font: font) + ExampleCard(formula: namedExamples[i], font: font, fontSize: fontSize) } } .padding() @@ -125,6 +124,7 @@ private struct ExamplesTab: View { private struct ExampleCard: View { let formula: NamedFormula let font: MathFont + let fontSize: CGFloat var body: some View { VStack(alignment: .leading, spacing: 6) { @@ -134,10 +134,12 @@ private struct ExampleCard: View { // Horizontal scroll so a formula wider than the screen (e.g. the // Rogers–Ramanujan fraction in Latin Modern) scrolls within its card // instead of stretching the whole column and clipping every card's left edge. + // No fixed height: the label reports its intrinsic content height (see + // MathLabel.sizeThatFits), so tall formulae aren't vertically clipped at + // any font size. ScrollView(.horizontal, showsIndicators: false) { - MathLabel(latex: formula.latex, fontSize: formula.fontSize, mode: formula.mode, - font: font.font(size: formula.fontSize)) - .frame(height: formula.height) + MathLabel(latex: formula.latex, fontSize: fontSize, mode: formula.mode, + font: font.font(size: fontSize)) } } .padding() @@ -153,10 +155,9 @@ private struct ExampleCard: View { /// live. Mirrors the LaTeX text field and font switcher in the ObjC iosMathExample. private struct PlaygroundTab: View { @Binding var font: MathFont + @Binding var fontSize: CGFloat @State private var latex = #"x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}"# - private let fontSize: CGFloat = 24 - var body: some View { NavigationView { VStack(alignment: .leading, spacing: 16) { @@ -176,13 +177,20 @@ private struct PlaygroundTab: View { .clipShape(RoundedRectangle(cornerRadius: 10)) .shadow(color: .black.opacity(0.06), radius: 4, x: 0, y: 2) - // Font switcher. - Picker("Font", selection: $font) { - ForEach(MathFont.allCases) { font in - Text(font.rawValue).tag(font) + // Font switcher + size stepper. Spacer + fixedSize keep the + // stepper pinned to the trailing edge so it doesn't shift as the + // font picker's width changes with the selected font's name. + HStack { + Picker("Font", selection: $font) { + ForEach(MathFont.allCases) { font in + Text(font.rawValue).tag(font) + } } + .pickerStyle(.menu) + Spacer() + Stepper("Size: \(Int(fontSize))", value: $fontSize, in: 10...40) + .fixedSize() } - .pickerStyle(.segmented) // LaTeX editor. Text("LaTeX") @@ -220,6 +228,7 @@ private struct PlaygroundTab: View { /// Full typesetter test suite. Curated real-math formulae live in the Examples tab. private struct GalleryTab: View { let font: MathFont + let fontSize: CGFloat private static let testHeights: [CGFloat] = [ 40, 40, 40, 40, 40, 60, 60, 60, 90, 30, 40, 90, 40, 60, 60, 60, @@ -240,15 +249,17 @@ private struct GalleryTab: View { ForEach(testFormulas.indices, id: \.self) { i in MathLabel( latex: testFormulas[i], - fontSize: testFontSize(at: i), + fontSize: fontSize, mode: testMode(at: i), alignment: testAlignment(at: i), highlighted: [0, 1, 3, 6, 7].contains(i), leftInset: i == 6 ? 20 : 0, rightInset: i == 3 ? 20 : 0, - font: font.font(size: testFontSize(at: i)) + font: font.font(size: fontSize) ) - .frame(height: testHeight(at: i)) + // Scale the row height with the font, relative to the size + // each entry's height was tuned for, so nothing clips. + .frame(height: testHeight(at: i) * (fontSize / testBaselineFontSize(at: i))) .padding(.horizontal, 10) } } @@ -265,7 +276,13 @@ private struct GalleryTab: View { #endif } - private func testFontSize(at i: Int) -> CGFloat { + private func testHeight(at i: Int) -> CGFloat { + Self.testHeights.indices.contains(i) ? Self.testHeights[i] : 40 + } + + /// Font size each entry's tuned height assumes, used to scale heights with + /// the slider. Indices 8 and 9 were originally shown larger/smaller. + private func testBaselineFontSize(at i: Int) -> CGFloat { switch i { case 8: return 30 case 9: return 10 @@ -273,10 +290,6 @@ private struct GalleryTab: View { } } - private func testHeight(at i: Int) -> CGFloat { - Self.testHeights.indices.contains(i) ? Self.testHeights[i] : 40 - } - private func testMode(at i: Int) -> MTMathUILabelMode { [5, 7, 17, 18, 26, 28].contains(i) ? .text : .display } diff --git a/SwiftMathExample/MathLabel.swift b/SwiftMathExample/MathLabel.swift index f5629f6c..41ba96bb 100644 --- a/SwiftMathExample/MathLabel.swift +++ b/SwiftMathExample/MathLabel.swift @@ -43,14 +43,24 @@ enum MathFont: String, CaseIterable, Identifiable { case latinModern = "Latin Modern" case termes = "TeX Gyre Termes" case xits = "XITS" + case newComputerModern = "New Computer Modern" + case pagella = "TeX Gyre Pagella" + case stixTwo = "STIX Two" + case firaMath = "Fira Math" + case notoSansMath = "Noto Sans Math" var id: String { rawValue } var fontName: String { switch self { - case .latinModern: return MTFontNameLatinModern - case .termes: return MTFontNameTermes - case .xits: return MTFontNameXITS + case .latinModern: return MTFontNameLatinModern + case .termes: return MTFontNameTermes + case .xits: return MTFontNameXITS + case .newComputerModern: return MTFontNameNewComputerModern + case .pagella: return MTFontNamePagella + case .stixTwo: return MTFontNameSTIXTwo + case .firaMath: return MTFontNameFiraMath + case .notoSansMath: return MTFontNameNotoSansMath } } diff --git a/iosMathExample/View.xib b/iosMathExample/View.xib index 9cbe2e93..03c336cf 100644 --- a/iosMathExample/View.xib +++ b/iosMathExample/View.xib @@ -22,17 +22,10 @@ - - @@ -60,11 +53,9 @@ - - - + diff --git a/iosMathExample/example/ViewController.m b/iosMathExample/example/ViewController.m index df139b95..ab202d4f 100644 --- a/iosMathExample/example/ViewController.m +++ b/iosMathExample/example/ViewController.m @@ -32,10 +32,19 @@ @interface ViewController () @property (nonatomic, nonnull) NSMutableArray* demoLabels; @property (nonatomic, nonnull) NSMutableArray* labels; +// Height constraints + their startup constants, so the size slider can rescale +// each label (and the content view) instead of clipping at larger font sizes. +// Demo labels render at fontSize 15; test labels at the default 20. +@property (nonatomic, nonnull) NSMutableArray* demoHeightConstraints; +@property (nonatomic, nonnull) NSMutableArray* testHeightConstraints; +@property (nonatomic, nonnull) NSMutableArray* demoBaseHeights; +@property (nonatomic, nonnull) NSMutableArray* testBaseHeights; +@property (nonatomic) NSLayoutConstraint* contentHeightConstraint; @property (weak, nonatomic) IBOutlet UITextField *fontField; @property (nonatomic) FontPickerDelegate* pickerDelegate; @property (weak, nonatomic) IBOutlet UITextField *colorField; @property (nonatomic) ColorPickerDelegate* colorPickerDelegate; +@property (nonatomic) UILabel* sizeLabel; @property (weak, nonatomic) IBOutlet MTMathUILabel *mathLabel; @property (weak, nonatomic) IBOutlet UITextField *latexField; @@ -56,6 +65,10 @@ - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil if (self) { self.demoLabels = [[NSMutableArray alloc] init]; self.labels = [[NSMutableArray alloc] init]; + self.demoHeightConstraints = [[NSMutableArray alloc] init]; + self.testHeightConstraints = [[NSMutableArray alloc] init]; + self.demoBaseHeights = [[NSMutableArray alloc] init]; + self.testBaseHeights = [[NSMutableArray alloc] init]; } return self; } @@ -85,11 +98,50 @@ - (void)viewDidLoad self.latexField.delegate = self; + // Global font-size control in the top row, beside the font + colour fields. + // A stepper (rather than a slider) shows the exact point size and keeps a + // fixed footprint, so it doesn't drift as the selected font name changes. + UIView* fontsPanel = self.fontField.superview; + self.sizeLabel = [[UILabel alloc] init]; + self.sizeLabel.translatesAutoresizingMaskIntoConstraints = NO; + self.sizeLabel.font = [UIFont systemFontOfSize:14]; + [fontsPanel addSubview:self.sizeLabel]; + + UIStepper* sizeStepper = [[UIStepper alloc] init]; + sizeStepper.translatesAutoresizingMaskIntoConstraints = NO; + sizeStepper.minimumValue = 10; + sizeStepper.maximumValue = 40; + sizeStepper.stepValue = 1; + sizeStepper.value = 15; + [sizeStepper addTarget:self action:@selector(sizeChanged:) forControlEvents:UIControlEventValueChanged]; + [fontsPanel addSubview:sizeStepper]; + // Row order: font field → colour field → size label → stepper. The stepper is + // anchored to the trailing safe area and the colour/size controls have fixed + // (intrinsic) widths, so the font field — which had its fixed width removed in + // the XIB — absorbs the remaining space. Everything stays on-screen and + // tappable at any width (iPhone 16 Pro included). + [NSLayoutConstraint activateConstraints:@[ + [self.sizeLabel.leadingAnchor constraintEqualToAnchor:self.colorField.trailingAnchor constant:12], + [self.sizeLabel.centerYAnchor constraintEqualToAnchor:self.colorField.centerYAnchor], + [sizeStepper.leadingAnchor constraintEqualToAnchor:self.sizeLabel.trailingAnchor constant:8], + [sizeStepper.centerYAnchor constraintEqualToAnchor:self.colorField.centerYAnchor], + [sizeStepper.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor constant:-12], + ]]; + // Let the font field shrink to fit the row rather than the size controls. + [self.fontField setContentHuggingPriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal]; + [self.fontField setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal]; + [self updateSizeLabel:sizeStepper.value]; + UIView* contentView = [[UIView alloc] init]; [self addFullSizeView:contentView to:self.scrollView]; - // set the size of the content view - // Disable horizontal scrolling. - [self setEqualWidths:contentView andView:self.scrollView]; + // Let the content view grow wider than the viewport so wide formulae (e.g. + // Rogers–Ramanujan in Latin Modern, or any formula at a large font size) can + // be reached by scrolling horizontally instead of being clipped. It still + // fills the viewport when every formula is narrower (low-priority equal width). + [contentView.widthAnchor constraintGreaterThanOrEqualToAnchor:self.scrollView.widthAnchor].active = YES; + NSLayoutConstraint* contentFillsWidth = [contentView.widthAnchor constraintEqualToAnchor:self.scrollView.widthAnchor]; + contentFillsWidth.priority = UILayoutPriorityDefaultLow; + contentFillsWidth.active = YES; // Demo formulae — LaTeX strings from MathExamples.h static const CGFloat demoHeights[] = { 60, 40, 120, 60, 40, 40, 40, 40, 60, 40, 40, 60, 60, 60, 70, 70, 140, 60, 90, 60 @@ -97,9 +149,12 @@ - (void)viewDidLoad NSArray* demoFormulas = MathDemoFormulas(); for (NSUInteger i = 0; i < demoFormulas.count; i++) { CGFloat height = HeightAtIndex(demoHeights, sizeof(demoHeights)/sizeof(CGFloat), i, 60); - MTMathUILabel* label = [self createMathLabel:demoFormulas[i] withHeight:height]; + MTMathUILabel* label = [[MTMathUILabel alloc] init]; + label.latex = demoFormulas[i]; label.fontSize = 15; [self.demoLabels addObject:label]; + [self.demoHeightConstraints addObject:[self setHeight:height forView:label]]; + [self.demoBaseHeights addObject:@(height)]; } [self addLabelAsSubview:self.demoLabels[0] to:contentView]; @@ -129,7 +184,11 @@ - (void)viewDidLoad NSArray* testFormulas = MathTestFormulas(); for (NSUInteger i = 0; i < testFormulas.count; i++) { CGFloat height = HeightAtIndex(testHeights, sizeof(testHeights)/sizeof(CGFloat), i, 40); - [self.labels addObject:[self createMathLabel:testFormulas[i] withHeight:height]]; + MTMathUILabel* label = [[MTMathUILabel alloc] init]; + label.latex = testFormulas[i]; + [self.labels addObject:label]; + [self.testHeightConstraints addObject:[self setHeight:height forView:label]]; + [self.testBaseHeights addObject:@(height)]; } CGFloat totalHeight = 10; // top inset @@ -142,7 +201,7 @@ - (void)viewDidLoad totalHeight += HeightAtIndex(testHeights, sizeof(testHeights)/sizeof(CGFloat), i, 40); totalHeight += 10; } - [self setHeight:totalHeight forView:contentView]; + self.contentHeightConstraint = [self setHeight:totalHeight forView:contentView]; // Rendering properties that are not shared (alignment, mode, color, insets, fontSize). UIColor* highlight = [UIColor colorWithHue:0.15 saturation:0.2 brightness:1.0 alpha:1.0]; @@ -157,9 +216,7 @@ - (void)viewDidLoad self.labels[6].contentInsets = UIEdgeInsetsMake(0, 20, 0, 0); self.labels[7].backgroundColor = highlight; self.labels[7].labelMode = kMTMathUILabelModeText; - self.labels[8].fontSize = 30; self.labels[8].textAlignment = kMTTextAlignmentCenter; - self.labels[9].fontSize = 10; self.labels[9].textAlignment = kMTTextAlignmentCenter; self.labels[17].labelMode = kMTMathUILabelModeText; self.labels[18].labelMode = kMTMathUILabelModeText; @@ -187,14 +244,6 @@ - (void) addLabelWithIndex:(NSUInteger) idx inArray:(NSArray*) a [self setVerticalGap:10 between:array[idx - 1] and:array[idx]]; } --(MTMathUILabel*) createMathLabel:(NSString*) latex withHeight:(CGFloat) height -{ - MTMathUILabel* label = [[MTMathUILabel alloc] init]; - [self setHeight:height forView:label]; - label.latex = latex; - return label; -} - #pragma mark Constraints - (void)addFullSizeView:(UIView *)view to:(UIView*) parent { @@ -211,7 +260,7 @@ - (void)addFullSizeView:(UIView *)view to:(UIView*) parent views:views]]; } -- (void) setHeight:(CGFloat) height forView:(UIView*) view +- (NSLayoutConstraint*) setHeight:(CGFloat) height forView:(UIView*) view { view.translatesAutoresizingMaskIntoConstraints = false; // Add height constraint @@ -221,15 +270,7 @@ - (void) setHeight:(CGFloat) height forView:(UIView*) view attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:height]; constraint.active = YES; -} - -- (void) setEqualWidths:(UIView*) view1 andView:(UIView*) view2 -{ - NSLayoutConstraint* constraint = [NSLayoutConstraint constraintWithItem:view1 - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual toItem:view2 - attribute:NSLayoutAttributeWidth multiplier:1 constant:0]; - constraint.active = YES; + return constraint; } - (void) addLabelAsSubview:(UIView*) label to:(UIView*) parent @@ -237,7 +278,10 @@ - (void) addLabelAsSubview:(UIView*) label to:(UIView*) parent label.translatesAutoresizingMaskIntoConstraints = NO; NSDictionary *views = NSDictionaryOfVariableBindings(label); [parent addSubview:label]; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(10)-[label]-(10)-|" + // Pin the label's leading edge; leave the trailing as a >= gap so the label + // keeps its natural (intrinsic) width and pushes the content view wider than + // the viewport when needed, enabling horizontal scrolling instead of clipping. + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(10)-[label]-(>=10)-|" options:0 metrics:nil views:views]]; @@ -254,6 +298,36 @@ - (void) setVerticalGap:(CGFloat) gap between:(UIView*) view1 and:(UIView*) view constraint.active = YES; } +#pragma mark Actions + +- (void)updateSizeLabel:(CGFloat)size +{ + self.sizeLabel.text = [NSString stringWithFormat:@"%dpt", (int)size]; +} + +- (void)sizeChanged:(UIStepper *)sender +{ + CGFloat size = (CGFloat)sender.value; + [self updateSizeLabel:size]; + // Scale each label's height from its startup baseline so formulas grow with + // the font instead of being clipped by the fixed startup heights. + CGFloat total = 10; // top inset + for (NSUInteger i = 0; i < self.demoLabels.count; i++) { + self.demoLabels[i].fontSize = size; + CGFloat h = self.demoBaseHeights[i].doubleValue * (size / 15.0); + self.demoHeightConstraints[i].constant = h; + total += h + 10; + } + total += 30; // gap between sections + for (NSUInteger i = 0; i < self.labels.count; i++) { + self.labels[i].fontSize = size; + CGFloat h = self.testBaseHeights[i].doubleValue * (size / 20.0); + self.testHeightConstraints[i].constant = h; + total += h + 10; + } + self.contentHeightConstraint.constant = total; +} + #pragma mark Buttons - (void)applyFontWithName:(NSString *)name { @@ -301,7 +375,9 @@ - (instancetype)init { self = [super init]; if (self) { - self.fontNames = @[@"Latin Modern Math", @"TeX Gyre Termes", @"XITS Math"]; + self.fontNames = @[@"Latin Modern Math", @"TeX Gyre Termes", @"XITS Math", + @"New Computer Modern", @"TeX Gyre Pagella", @"STIX Two", + @"Fira Math", @"Noto Sans Math"]; } return self; } @@ -327,6 +403,8 @@ - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComp // Not static: extern const NSString* values aren't compile-time constants. NSString *const kFontKeys[] = { MTFontNameLatinModern, MTFontNameTermes, MTFontNameXITS, + MTFontNameNewComputerModern, MTFontNamePagella, MTFontNameSTIXTwo, + MTFontNameFiraMath, MTFontNameNotoSansMath, }; self.controller.fontField.text = self.fontNames[row]; [self.controller.fontField resignFirstResponder];