From ca8b125a20f09130579253d43181423f01fc1fdf Mon Sep 17 00:00:00 2001 From: Kostub D Date: Sat, 30 May 2026 23:33:37 +0530 Subject: [PATCH 1/7] [item 15] iOS example: 8-font wheel + global font-size slider --- iosMathExample/example/ViewController.m | 53 +++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/iosMathExample/example/ViewController.m b/iosMathExample/example/ViewController.m index df139b9..398d177 100644 --- a/iosMathExample/example/ViewController.m +++ b/iosMathExample/example/ViewController.m @@ -85,6 +85,42 @@ - (void)viewDidLoad self.latexField.delegate = self; + // Add a global font-size slider above the scroll view. + // The XIB pins scrollView.top to the Render panel's bottom; find that + // constraint, capture the Render panel reference, lower its priority, and + // insert the slider between the Render panel and the scroll view. + UIView* renderPanelRef = nil; + for (NSLayoutConstraint* c in self.view.constraints) { + if (c.firstItem == self.scrollView && c.firstAttribute == NSLayoutAttributeTop) { + renderPanelRef = c.secondItem; + c.priority = UILayoutPriorityDefaultLow; + break; + } else if (c.secondItem == self.scrollView && c.secondAttribute == NSLayoutAttributeTop) { + renderPanelRef = c.firstItem; + c.priority = UILayoutPriorityDefaultLow; + break; + } + } + UISlider* sizeSlider = [[UISlider alloc] init]; + sizeSlider.translatesAutoresizingMaskIntoConstraints = NO; + sizeSlider.minimumValue = 10; + sizeSlider.maximumValue = 40; + sizeSlider.value = 15; + [sizeSlider addTarget:self action:@selector(sizeChanged:) forControlEvents:UIControlEventValueChanged]; + [self.view addSubview:sizeSlider]; + NSMutableArray* sliderConstraints = [NSMutableArray arrayWithArray:@[ + [sizeSlider.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor constant:10], + [sizeSlider.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor constant:-10], + [self.scrollView.topAnchor constraintEqualToAnchor:sizeSlider.bottomAnchor constant:4], + ]]; + if (renderPanelRef) { + [sliderConstraints addObject:[sizeSlider.topAnchor constraintEqualToAnchor:renderPanelRef.bottomAnchor constant:4]]; + } else { + // Fallback: pin slider to the scroll view's existing top position. + [sliderConstraints addObject:[sizeSlider.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor constant:180]]; + } + [NSLayoutConstraint activateConstraints:sliderConstraints]; + UIView* contentView = [[UIView alloc] init]; [self addFullSizeView:contentView to:self.scrollView]; // set the size of the content view @@ -157,9 +193,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; @@ -254,6 +288,15 @@ - (void) setVerticalGap:(CGFloat) gap between:(UIView*) view1 and:(UIView*) view constraint.active = YES; } +#pragma mark Actions + +- (void)sizeChanged:(UISlider *)sender +{ + CGFloat size = (CGFloat)sender.value; + for (MTMathUILabel* label in self.demoLabels) { label.fontSize = size; } + for (MTMathUILabel* label in self.labels) { label.fontSize = size; } +} + #pragma mark Buttons - (void)applyFontWithName:(NSString *)name { @@ -301,7 +344,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 +372,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]; From 9878053d2aa390ef327a2a8f7037f16372caea8d Mon Sep 17 00:00:00 2001 From: Kostub D Date: Sat, 30 May 2026 23:34:58 +0530 Subject: [PATCH 2/7] [item 16] SwiftMathExample: 8 fonts (.menu) + shared font-size slider --- SwiftMathExample/ContentView.swift | 46 +++++++++++++++--------------- SwiftMathExample/MathLabel.swift | 16 +++++++++-- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/SwiftMathExample/ContentView.swift b/SwiftMathExample/ContentView.swift index f0bbd80..8fc302c 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") } } } @@ -100,13 +102,14 @@ private let namedExamples: [NamedFormula] = { /// 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 +128,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) { @@ -135,8 +139,8 @@ private struct ExampleCard: View { // Rogers–Ramanujan fraction in Latin Modern) scrolls within its card // instead of stretching the whole column and clipping every card's left edge. ScrollView(.horizontal, showsIndicators: false) { - MathLabel(latex: formula.latex, fontSize: formula.fontSize, mode: formula.mode, - font: font.font(size: formula.fontSize)) + MathLabel(latex: formula.latex, fontSize: fontSize, mode: formula.mode, + font: font.font(size: fontSize)) .frame(height: formula.height) } } @@ -153,10 +157,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 +179,17 @@ 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 slider. + HStack { + Picker("Font", selection: $font) { + ForEach(MathFont.allCases) { font in + Text(font.rawValue).tag(font) + } } + .pickerStyle(.menu) + Slider(value: $fontSize, in: 10...40) + .frame(maxWidth: 160) } - .pickerStyle(.segmented) // LaTeX editor. Text("LaTeX") @@ -220,6 +227,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,13 +248,13 @@ 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)) .padding(.horizontal, 10) @@ -265,14 +273,6 @@ private struct GalleryTab: View { #endif } - private func testFontSize(at i: Int) -> CGFloat { - switch i { - case 8: return 30 - case 9: return 10 - default: return 15 - } - } - private func testHeight(at i: Int) -> CGFloat { Self.testHeights.indices.contains(i) ? Self.testHeights[i] : 40 } diff --git a/SwiftMathExample/MathLabel.swift b/SwiftMathExample/MathLabel.swift index f5629f6..41ba96b 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 } } From 3ba000c51a80403549e5817b6d41eecee15a448d Mon Sep 17 00:00:00 2001 From: Kostub D Date: Sat, 30 May 2026 23:36:40 +0530 Subject: [PATCH 3/7] [item 17] macOS example: code-built font popup + font-size slider --- MacOSMathExample/AppDelegate.m | 70 ++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/MacOSMathExample/AppDelegate.m b/MacOSMathExample/AppDelegate.m index ea457eb..45c0242 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. @@ -33,6 +34,18 @@ 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 NSString *const kMacFontKeys[] = { + @"latinmodern-math", @"texgyretermes-math", @"xits-math", + @"newcm-math", @"texgyrepagella-math", @"stixtwo-math", + @"firamath", @"notosansmath", +}; +static const NSUInteger kMacFontCount = 8; + - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { self.demoLabels = [[NSMutableArray alloc] init]; @@ -40,8 +53,37 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 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; @@ -123,9 +165,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,6 +183,28 @@ - (void)applicationWillTerminate:(NSNotification *)aNotification { } +#pragma mark - Font and size actions + +- (void)changeFont:(NSPopUpButton *)sender +{ + NSInteger i = sender.indexOfSelectedItem; + if (i < 0 || (NSUInteger)i >= kMacFontCount) return; + 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; + for (MTMathUILabel* label in self.demoLabels) { label.fontSize = size; } + for (MTMathUILabel* label in self.labels) { label.fontSize = size; } +} + #pragma mark - Label creation helpers - (MTMathUILabel*)createMathLabel:(NSString*)latex withHeight:(CGFloat)height From 090db84d98457f8cf2761af47e6fd845b0ded924 Mon Sep 17 00:00:00 2001 From: Kostub D Date: Sun, 31 May 2026 12:58:16 +0530 Subject: [PATCH 4/7] [review] Fix font-size slider clipping + iOS render-panel constraint match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses gemini-code-assist review on PR #211: - iOS: restrict the render-panel constraint search to scrollView.top == .bottom (checking the other end's attribute) so it can't match a scrollView.top-to-safe-area-top constraint and pin the slider off-screen. - iOS/macOS/SwiftUI: the size slider only changed fontSize, leaving the fixed startup label/content heights, which clipped formulas at larger sizes. Track each label's height constraint and rescale it (and the content/document height) from its startup baseline font size — demo labels 15, test labels 20 on iOS/macOS; per-entry baselines in SwiftUI. All three example apps build. --- MacOSMathExample/AppDelegate.m | 62 ++++++++++++++++++------ SwiftMathExample/ContentView.swift | 17 ++++++- iosMathExample/example/ViewController.m | 64 ++++++++++++++++++------- 3 files changed, 110 insertions(+), 33 deletions(-) diff --git a/MacOSMathExample/AppDelegate.m b/MacOSMathExample/AppDelegate.m index 45c0242..7897cb2 100644 --- a/MacOSMathExample/AppDelegate.m +++ b/MacOSMathExample/AppDelegate.m @@ -25,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 @@ -50,6 +58,10 @@ - (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; @@ -97,6 +109,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[] = { @@ -105,9 +118,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]; @@ -137,7 +153,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; @@ -201,20 +221,30 @@ - (void)changeFont:(NSPopUpButton *)sender - (void)changeSize:(NSSlider *)sender { CGFloat size = (CGFloat)sender.doubleValue; - for (MTMathUILabel* label in self.demoLabels) { label.fontSize = size; } - for (MTMathUILabel* label in self.labels) { label.fontSize = size; } + // 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 -- (MTMathUILabel*)createMathLabel:(NSString*)latex withHeight:(CGFloat)height -{ - MTMathUILabel* label = [[MTMathUILabel alloc] init]; - [self setHeight:height forView:label]; - label.latex = latex; - return label; -} - - (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."); @@ -234,15 +264,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/SwiftMathExample/ContentView.swift b/SwiftMathExample/ContentView.swift index 8fc302c..479eb67 100644 --- a/SwiftMathExample/ContentView.swift +++ b/SwiftMathExample/ContentView.swift @@ -141,7 +141,8 @@ private struct ExampleCard: View { ScrollView(.horizontal, showsIndicators: false) { MathLabel(latex: formula.latex, fontSize: fontSize, mode: formula.mode, font: font.font(size: fontSize)) - .frame(height: formula.height) + // Scale the card height with the font so larger sizes aren't clipped. + .frame(height: formula.height * (fontSize / formula.fontSize)) } } .padding() @@ -256,7 +257,9 @@ private struct GalleryTab: View { rightInset: i == 3 ? 20 : 0, 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) } } @@ -277,6 +280,16 @@ private struct GalleryTab: View { 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 + default: return 15 + } + } + private func testMode(at i: Int) -> MTMathUILabelMode { [5, 7, 17, 18, 26, 28].contains(i) ? .text : .display } diff --git a/iosMathExample/example/ViewController.m b/iosMathExample/example/ViewController.m index 398d177..9bab29e 100644 --- a/iosMathExample/example/ViewController.m +++ b/iosMathExample/example/ViewController.m @@ -32,6 +32,14 @@ @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; @@ -56,6 +64,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; } @@ -91,11 +103,16 @@ - (void)viewDidLoad // insert the slider between the Render panel and the scroll view. UIView* renderPanelRef = nil; for (NSLayoutConstraint* c in self.view.constraints) { - if (c.firstItem == self.scrollView && c.firstAttribute == NSLayoutAttributeTop) { + // Match only scrollView.top == .bottom. Checking the other + // end's attribute avoids accidentally matching scrollView.top anchored to + // the safe area layout guide's top, which would pin the slider off-screen. + if (c.firstItem == self.scrollView && c.firstAttribute == NSLayoutAttributeTop + && c.secondAttribute == NSLayoutAttributeBottom) { renderPanelRef = c.secondItem; c.priority = UILayoutPriorityDefaultLow; break; - } else if (c.secondItem == self.scrollView && c.secondAttribute == NSLayoutAttributeTop) { + } else if (c.secondItem == self.scrollView && c.secondAttribute == NSLayoutAttributeTop + && c.firstAttribute == NSLayoutAttributeBottom) { renderPanelRef = c.firstItem; c.priority = UILayoutPriorityDefaultLow; break; @@ -133,9 +150,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]; @@ -165,7 +185,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 @@ -178,7 +202,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]; @@ -221,14 +245,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 { @@ -245,7 +261,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 @@ -255,6 +271,7 @@ - (void) setHeight:(CGFloat) height forView:(UIView*) view attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:height]; constraint.active = YES; + return constraint; } - (void) setEqualWidths:(UIView*) view1 andView:(UIView*) view2 @@ -293,8 +310,23 @@ - (void) setVerticalGap:(CGFloat) gap between:(UIView*) view1 and:(UIView*) view - (void)sizeChanged:(UISlider *)sender { CGFloat size = (CGFloat)sender.value; - for (MTMathUILabel* label in self.demoLabels) { label.fontSize = size; } - for (MTMathUILabel* label in self.labels) { label.fontSize = 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 From cb98a7d2da0287db51b18e52b7f0308d6f777506 Mon Sep 17 00:00:00 2001 From: Kostub D Date: Sun, 31 May 2026 14:21:43 +0530 Subject: [PATCH 5/7] [review] Deactivate XIB constraint instead of mutating priority; macOS font keys via public constants iOS: changing an active required (1000) XIB constraint's priority to/from required throws at runtime; deactivate it instead. macOS: replace hardcoded font-key string literals with the public MTFontName* constants (local array, since they're runtime extern consts), matching the iOS example. Co-Authored-By: Claude Opus 4.8 --- MacOSMathExample/AppDelegate.m | 13 ++++++++----- iosMathExample/example/ViewController.m | 10 ++++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/MacOSMathExample/AppDelegate.m b/MacOSMathExample/AppDelegate.m index 7897cb2..1307dd3 100644 --- a/MacOSMathExample/AppDelegate.m +++ b/MacOSMathExample/AppDelegate.m @@ -47,11 +47,6 @@ static CGFloat HeightAtIndex(const CGFloat *heights, NSUInteger count, NSUIntege @"New Computer Modern", @"TeX Gyre Pagella", @"STIX Two", @"Fira Math", @"Noto Sans Math", }; -static NSString *const kMacFontKeys[] = { - @"latinmodern-math", @"texgyretermes-math", @"xits-math", - @"newcm-math", @"texgyrepagella-math", @"stixtwo-math", - @"firamath", @"notosansmath", -}; static const NSUInteger kMacFontCount = 8; - (void)applicationDidFinishLaunching:(NSNotification *)aNotification @@ -209,6 +204,14 @@ - (void)changeFont:(NSPopUpButton *)sender { 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]; diff --git a/iosMathExample/example/ViewController.m b/iosMathExample/example/ViewController.m index 9bab29e..c809894 100644 --- a/iosMathExample/example/ViewController.m +++ b/iosMathExample/example/ViewController.m @@ -99,8 +99,10 @@ - (void)viewDidLoad // Add a global font-size slider above the scroll view. // The XIB pins scrollView.top to the Render panel's bottom; find that - // constraint, capture the Render panel reference, lower its priority, and - // insert the slider between the Render panel and the scroll view. + // constraint, capture the Render panel reference, deactivate it, and insert + // the slider between the Render panel and the scroll view. (Deactivating + // rather than lowering priority: XIB constraints are required (1000), and + // mutating an active constraint's priority to/from required throws.) UIView* renderPanelRef = nil; for (NSLayoutConstraint* c in self.view.constraints) { // Match only scrollView.top == .bottom. Checking the other @@ -109,12 +111,12 @@ - (void)viewDidLoad if (c.firstItem == self.scrollView && c.firstAttribute == NSLayoutAttributeTop && c.secondAttribute == NSLayoutAttributeBottom) { renderPanelRef = c.secondItem; - c.priority = UILayoutPriorityDefaultLow; + c.active = NO; break; } else if (c.secondItem == self.scrollView && c.secondAttribute == NSLayoutAttributeTop && c.firstAttribute == NSLayoutAttributeBottom) { renderPanelRef = c.firstItem; - c.priority = UILayoutPriorityDefaultLow; + c.active = NO; break; } } From 31546fb32899e159e488feaa6053d713c0f952f7 Mon Sep 17 00:00:00 2001 From: Kostub D Date: Sun, 31 May 2026 14:29:57 +0530 Subject: [PATCH 6/7] [review] SwiftMathExample: right-anchored size stepper + intrinsic-height cards Testing feedback on the Playground/Examples tabs: - Size control drifted as the font picker's width changed; replace the Slider with a Stepper pinned to the trailing edge (Spacer + fixedSize). - The stepper also shows the current size ("Size: NN"), which the slider did not. - Examples cards used a guessed fixed-height table that clipped tall formulae (e.g. Rogers-Ramanujan) at every font size; drop the fixed frame and let the label size to its intrinsic content height. Removes the now-unused per-formula height/fontSize metadata. Co-Authored-By: Claude Opus 4.8 --- SwiftMathExample/ContentView.swift | 62 +++++++++++++++--------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/SwiftMathExample/ContentView.swift b/SwiftMathExample/ContentView.swift index 479eb67..34b2c8c 100644 --- a/SwiftMathExample/ContentView.swift +++ b/SwiftMathExample/ContentView.swift @@ -53,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] = { @@ -95,7 +91,7 @@ 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) } }() @@ -138,11 +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: fontSize, mode: formula.mode, font: font.font(size: fontSize)) - // Scale the card height with the font so larger sizes aren't clipped. - .frame(height: formula.height * (fontSize / formula.fontSize)) } } .padding() @@ -180,7 +177,9 @@ private struct PlaygroundTab: View { .clipShape(RoundedRectangle(cornerRadius: 10)) .shadow(color: .black.opacity(0.06), radius: 4, x: 0, y: 2) - // Font switcher + size slider. + // 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 @@ -188,8 +187,9 @@ private struct PlaygroundTab: View { } } .pickerStyle(.menu) - Slider(value: $fontSize, in: 10...40) - .frame(maxWidth: 160) + Spacer() + Stepper("Size: \(Int(fontSize))", value: $fontSize, in: 10...40) + .fixedSize() } // LaTeX editor. From b578164b2049963df0ccd6361117d429e348473a Mon Sep 17 00:00:00 2001 From: Kostub D Date: Sun, 31 May 2026 22:04:21 +0530 Subject: [PATCH 7/7] [review] iOS example: top-row size stepper + horizontal scroll; macOS: drop orphan XIB views iOS testing feedback: - Replaced the font-size slider with a stepper in the top row beside the font and colour pickers, showing the current point size ("15pt"). The font field's fixed width was removed so it absorbs the row's slack and the stepper stays fully on-screen and tappable at any width (incl. iPhone 16 Pro). Removed the redundant "Font" label to make room. - Formulae no longer clip horizontally when the font size grows: the content view may now exceed the viewport width (driven by each label's intrinsic width) so wide formulae scroll horizontally instead of being cut off. macOS testing feedback: - Removed the stray blue box: an orphaned MTMathUILabel (plus an old "Update!" button and text field) left in MainMenu.xib from the original template, which AppDelegate never used. Also removed the matching stale outlet/action connections (screen, inputTextField, clickUpdateButton:). Co-Authored-By: Claude Opus 4.8 --- MacOSMathExample/Base.lproj/MainMenu.xib | 25 ------ iosMathExample/View.xib | 11 +-- iosMathExample/example/ViewController.m | 107 +++++++++++------------ 3 files changed, 53 insertions(+), 90 deletions(-) diff --git a/MacOSMathExample/Base.lproj/MainMenu.xib b/MacOSMathExample/Base.lproj/MainMenu.xib index 6177a89..0300fab 100644 --- a/MacOSMathExample/Base.lproj/MainMenu.xib +++ b/MacOSMathExample/Base.lproj/MainMenu.xib @@ -15,8 +15,6 @@ - - @@ -690,29 +688,6 @@ - - - - - - - - - - - - - - diff --git a/iosMathExample/View.xib b/iosMathExample/View.xib index 9cbe2e9..03c336c 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 c809894..ab202d4 100644 --- a/iosMathExample/example/ViewController.m +++ b/iosMathExample/example/ViewController.m @@ -44,6 +44,7 @@ @interface ViewController () @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; @@ -97,54 +98,50 @@ - (void)viewDidLoad self.latexField.delegate = self; - // Add a global font-size slider above the scroll view. - // The XIB pins scrollView.top to the Render panel's bottom; find that - // constraint, capture the Render panel reference, deactivate it, and insert - // the slider between the Render panel and the scroll view. (Deactivating - // rather than lowering priority: XIB constraints are required (1000), and - // mutating an active constraint's priority to/from required throws.) - UIView* renderPanelRef = nil; - for (NSLayoutConstraint* c in self.view.constraints) { - // Match only scrollView.top == .bottom. Checking the other - // end's attribute avoids accidentally matching scrollView.top anchored to - // the safe area layout guide's top, which would pin the slider off-screen. - if (c.firstItem == self.scrollView && c.firstAttribute == NSLayoutAttributeTop - && c.secondAttribute == NSLayoutAttributeBottom) { - renderPanelRef = c.secondItem; - c.active = NO; - break; - } else if (c.secondItem == self.scrollView && c.secondAttribute == NSLayoutAttributeTop - && c.firstAttribute == NSLayoutAttributeBottom) { - renderPanelRef = c.firstItem; - c.active = NO; - break; - } - } - UISlider* sizeSlider = [[UISlider alloc] init]; - sizeSlider.translatesAutoresizingMaskIntoConstraints = NO; - sizeSlider.minimumValue = 10; - sizeSlider.maximumValue = 40; - sizeSlider.value = 15; - [sizeSlider addTarget:self action:@selector(sizeChanged:) forControlEvents:UIControlEventValueChanged]; - [self.view addSubview:sizeSlider]; - NSMutableArray* sliderConstraints = [NSMutableArray arrayWithArray:@[ - [sizeSlider.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor constant:10], - [sizeSlider.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor constant:-10], - [self.scrollView.topAnchor constraintEqualToAnchor:sizeSlider.bottomAnchor constant:4], + // 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], ]]; - if (renderPanelRef) { - [sliderConstraints addObject:[sizeSlider.topAnchor constraintEqualToAnchor:renderPanelRef.bottomAnchor constant:4]]; - } else { - // Fallback: pin slider to the scroll view's existing top position. - [sliderConstraints addObject:[sizeSlider.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor constant:180]]; - } - [NSLayoutConstraint activateConstraints:sliderConstraints]; + // 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 @@ -276,21 +273,15 @@ - (NSLayoutConstraint*) setHeight:(CGFloat) height forView:(UIView*) view return constraint; } -- (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; -} - - (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]]; @@ -309,9 +300,15 @@ - (void) setVerticalGap:(CGFloat) gap between:(UIView*) view1 and:(UIView*) view #pragma mark Actions -- (void)sizeChanged:(UISlider *)sender +- (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