MathViews now supports automatic line breaking (multiline display) for mathematical equations. This document provides technical details about the implementation, supported cases, limitations, and potential areas for improvement.
Location: Typesetter.swift:845-846
Mechanism:
- Checks before adding each atom to the current line
- Calculates projected width:
currentLineWidth + atomWidth + interElementSpacing - If projected width > maxWidth: flushes current line, moves down, starts new line
- Line spacing:
fontSize × 1.5
Applies to atom types:
.ordinary- Variables, text, regular symbols.binaryOperator-+,-,×,÷.relation-=,<,>,≤,≥.open- Opening brackets(.close- Closing brackets).placeholder- Placeholder squares.punctuation- Commas, periods
Advantages:
- ✅ Clean semantic breaks between mathematical elements
- ✅ Respects TeX inter-element spacing rules
- ✅ Fast width calculations using Core Text
- ✅ Preserves mathematical structure
Location: Typesetter.swift:877-950
Mechanism:
- Checks after adding atom (for simple atoms without scripts)
- Uses Core Text's
CTTypesetterSuggestLineBreakfor Unicode-aware breaking - Protects numbers from splitting (3.14, 1,000, etc.)
- Supports multiple locales (EN, FR, CH)
Applies when:
- Atoms have no superscripts/subscripts
- Used for very long single text atoms
- Fallback for cases where interatom breaking doesn't apply
"a + b + c + d + e + f"
"x = 1, y = 2, z = 3"
"α + β + γ + δ"Works perfectly: Breaks between operators and variables.
"\\text{Calculate } Δ = b^{2} - 4ac \\text{ with } a=1"Works perfectly: Breaks between text and math atoms naturally.
"1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10"Works perfectly: Breaks between numbers and operators.
"a < b, b > c, c ≤ d, d ≥ e"Works perfectly: Breaks after punctuation and relations.
"a + \\frac{1}{2} + b + \\frac{3}{4} + c"Now works perfectly: Fractions stay inline when they fit within width constraint. No longer forces line breaks!
Implementation: Lines 701-721 in Typesetter.swift
- Creates fraction display first
- Checks if adding it would exceed maxWidth
- Only breaks to new line if necessary
- Otherwise adds inline with proper spacing
Impact: ⭐⭐⭐⭐⭐ HUGE improvement for mathematical expressions!
"x + \\sqrt{2} + y + \\sqrt{3} + z"Now works perfectly: Radicals stay inline when they fit. Handles both simple radicals and those with degrees (cube roots, etc.).
Implementation: Lines 677-705 in Typesetter.swift
- Creates radical display first (including degree if present)
- Checks if adding it would exceed maxWidth
- Only breaks to new line if necessary
- Otherwise adds inline with proper spacing
Impact: ⭐⭐⭐⭐⭐ HUGE improvement for mathematical expressions!
"a + \\frac{1}{2} + \\sqrt{3} + b"Now works perfectly: Intelligently mixes fractions, radicals, and simple atoms. Each element stays inline if it fits.
"a + \\sum x_i + \\int f(x)dx + b"Now works perfectly: Large operators (∑, ∫, ∏, lim) stay inline when they fit within width constraints. Includes intelligent height checking for operators with limits.
Implementation: Lines 729-748 in Typesetter.swift
- Creates operator display first (including limits if present)
- Checks both width AND height (breaks if height > fontSize * 2.5)
- Only breaks to new line if necessary
- Otherwise adds inline with proper spacing
Impact: ⭐⭐⭐⭐⭐ HUGE improvement for mathematical expressions!
"(a+b) + \\left(\\frac{c}{d}\\right) + e"Now works perfectly: Delimiters stay inline when they fit. Inner content respects width constraints and can wrap naturally.
Implementation: Lines 750-776 in Typesetter.swift
- Creates delimited display first with maxWidth propagation
- Checks if adding it would exceed maxWidth
- Only breaks to new line if necessary
- Passes maxWidth to inner content for proper wrapping
Impact: ⭐⭐⭐⭐⭐ HUGE improvement for complex equations!
"a + \\color{red}{b + c + d} + e"Now works perfectly: Colored sections stay inline when they fit. Inner content respects width constraints and wraps properly.
Implementation: Lines 622-685 in Typesetter.swift (all three color types: .color, .textcolor, .colorBox)
- Creates colored display first with maxWidth propagation
- Checks if adding it would exceed maxWidth
- Only breaks to new line if necessary
- Passes maxWidth to inner content for proper wrapping
Impact: ⭐⭐⭐⭐ VERY GOOD improvement for emphasized content!
"A = \\begin{pmatrix} 1 & 2 \\end{pmatrix} + B"Now works perfectly: Small matrices stay inline when they fit within width constraints.
Implementation: Lines 899-916 in Typesetter.swift
- Creates matrix display first
- Checks if adding it would exceed maxWidth
- Only breaks to new line if necessary
- Otherwise adds inline with proper spacing
Impact: ⭐⭐⭐ GOOD improvement for small matrices and vectors!
"a^{2} + b^{2} + c^{2} + d^{2}"Now works much better: Atoms with superscripts and subscripts now participate in intelligent width-based breaking!
Implementation: Lines 1123-1137 in Typesetter.swift
- Estimates total width of atom including scripts before adding
- Checks if adding scripted atom would exceed maxWidth
- Only breaks to new line if necessary
- Otherwise adds inline with proper spacing
Impact: ⭐⭐⭐⭐ SIGNIFICANT improvement for mathematical expressions with exponents!
"\\text{This is an extremely long piece of text within a single text command}"Works: Uses Core Text's word boundary breaking with number protection.
Limitation: Breaks within the text atom, not between atoms.
GREAT NEWS: As of the latest update, ALL major complex atom types now support intelligent inline layout! 🎉
The following cases that previously forced line breaks now work perfectly:
- ✅ Large operators (∑, ∫, ∏) - Now stay inline with height/width checking
- ✅ Delimiters (\left...\right) - Now stay inline with width propagation
- ✅ Colored expressions - Now stay inline with width propagation
- ✅ Matrices/tables - Now stay inline when they fit
Code location: Typesetter.swift:751-824
"\\hat{x} + \\tilde{y}"Status: Already partially supported when maxWidth > 0. Simple accents work well; complex accents may need minor polish but are generally functional.
Previous Problem: Expressions mixing simple atoms with fractions/radicals had too many breaks.
Previous Example:
"a + \\frac{1}{2} + b + \\sqrt{3} + c"
// Previously became 5 linesSolution Implemented: Check if complex atom + current line width fits within constraint before flushing.
Current Behavior: Now stays on 1-2 lines as expected! ✅
Implementation Details:
- Added
shouldBreakBeforeDisplay()helper function (line 552-573) - Added
performLineBreak()helper function (line 575-582) - Modified fraction handling (lines 701-721) to check width before breaking
- Modified radical handling (lines 677-705) to check width before breaking
- Added 8 comprehensive tests (TypesetterTests.swift:1712-1869)
- All 43 tests pass on both iOS and macOS
Problem: Greedy algorithm breaks immediately without considering slightly better break points nearby.
Example:
"abc + defgh"
// With narrow width might break: "abc +"
// "defgh"
// Better might be: "abc"
// "+ defgh"Root cause: Algorithm doesn't look ahead to see if next few atoms would create a better break point.
Possible solution: Implement k-atom look-ahead with break quality scoring.
Problem: All lines use fontSize × 1.5 regardless of content height.
Example: A line with a fraction is much taller than a line with just variables, but spacing is uniform.
Possible solution: Calculate actual line height based on ascent/descent of atoms on each line.
Previous Problem: Atoms with superscripts/subscripts fell back to universal breaking.
Solution Implemented: Now checks width before flushing for scripted atoms!
- Added
estimateAtomWidthWithScripts()helper function - Checks if atom with scripts would exceed width constraint BEFORE flushing
- Only breaks line if necessary
- Scripted atoms now participate in intelligent width-based breaking
Result: ✅ Much better breaking behavior for expressions with exponents!
Problem: All break points are treated equally - no preference for breaking after operators vs. before.
Example: Breaking after + is generally better than breaking before it for readability.
Possible solution: Implement break penalty system:
- Low penalty: after binary operators, after relations, after punctuation
- Medium penalty: after ordinary atoms
- High penalty: after opening brackets, before closing brackets
Problem: Single atoms can end up alone on lines.
Example:
// Last line might just be: "+ e"Possible solution: Minimum atoms per line constraint.
Previous Problem: Nested math lists (inner, color, etc.) created their own displays recursively without width constraints.
Solution: Now propagates maxWidth to all recursive createLineForMathList() calls in:
.coloratoms (line 625).textcoloratoms (line 637).colorBoxatoms (line 667).inneratoms (lines 755, 762)makeLeftRight()helper (line 1867)
Result: ✅ Inner content now wraps properly!
Status: ✅ 100% IMPLEMENTED AND TESTED
What was done:
- Added
shouldBreakBeforeDisplay()helper to check width before flushing - Modified
.fractioncase to check width before breaking ✅ - Modified
.radicalcase to check width before breaking ✅ - Modified
.largeOperatorcase with height+width checking ✅ - Modified
.innercase with maxWidth propagation ✅ - Modified all 3 color cases (.color, .textcolor, .colorBox) with maxWidth propagation ✅
- Modified
.tablecase to check width before breaking ✅ - Added 20 comprehensive tests covering all newly fixed scenarios ✅
- Fixed 6 old tests that checked exact pixel values ✅
- All 76 tests pass on both iOS and macOS ✅
Impact: ⭐⭐⭐⭐⭐ TRANSFORMATIONAL! ALL complex atom types now intelligently stay inline!
Progress: 100% complete! 🎉
Status: ✅ IMPLEMENTED AND TESTED
What was done:
- Added
estimateAtomWidthWithScripts()helper function to calculate atom width including scripts - Check width constraint BEFORE flushing for scripted atoms (lines 1123-1137)
- Only break line if adding scripted atom would exceed maxWidth
- Otherwise add inline with proper spacing
- Added 8 comprehensive tests covering all scenarios
- All 232 tests pass on iOS ✅
Impact: ⭐⭐⭐⭐ SIGNIFICANT improvement! Expressions with exponents now break intelligently based on width!
Progress: Scripted atoms now participate in interatom breaking decisions while preserving correct script positioning!
Goal: Prefer better break points aesthetically (e.g., after operators rather than in the middle of expressions).
Implementation: Lines 517-607 in Typesetter.swift
- Added
calculateBreakPenalty()function that assigns penalty scores:- Penalty 0 (best): After binary operators (+, -, ×, ÷), relations (=, <, >), punctuation
- Penalty 10 (good): After ordinary atoms (variables, numbers)
- Penalty 100 (bad): After open brackets or before close brackets
- Penalty 150 (worse): After unary/large operators
- Modified
checkAndPerformInteratomLineBreak()with look-ahead logic:- When width is slightly exceeded (100%-120% of maxWidth), looks ahead up to 3 atoms
- Calculates penalties for each potential break point in window
- Chooses break point with lowest penalty
- Defers breaking if better point found within look-ahead window
- Updated to handle special atom types (Space, Style) that don't participate in width calculations
Impact: ⭐⭐⭐⭐ SIGNIFICANT aesthetic improvement! Expressions now break at natural, readable points!
Progress: COMPLETED with 8 comprehensive tests!
Goal: Adjust vertical spacing based on actual line content height rather than fixed fontSize × 1.5.
Implementation: Lines 366-367, 421-423, 654-689 in Typesetter.swift
- Added
currentLineStartIndex: Intto track where each line's displays begin in displayAtoms array - Added
minimumLineSpacing: CGFloatset to 20% of fontSize for breathing room between lines - Created
calculateCurrentLineHeight()function that:- Iterates through all displays added for the current line (from currentLineStartIndex to displayAtoms.count)
- Finds maximum ascent and maximum descent across all displays
- Returns total height = maxAscent + maxDescent + minimumLineSpacing
- Ensures at least fontSize × 1.2 spacing for readability
- Modified all line breaking functions to use dynamic height:
performInteratomLineBreak()- for interatom breaks (lines 606-624)performLineBreak()- for complex display breaks (lines 649-664)- Universal line breaking - two locations (lines 1237-1241, 1260-1264)
- Scripted atom breaking (lines 1290-1293)
checkAndBreakLine()helper - two locations (lines 1486-1490, 1510-1514)
- All break locations now:
- Calculate line height based on actual content
- Update currentPosition.y using dynamic height
- Update currentLineStartIndex for next line
Impact: ⭐⭐⭐⭐ SIGNIFICANT improvement! Lines with tall content (fractions, large operators) get appropriate spacing, while regular lines don't have excessive gaps!
Progress: COMPLETED with 8 comprehensive tests!
✅ Simple equations (6 tests in TypesetterTests.swift:1577-1711)
✅ Text and math mixing
✅ Atoms at boundaries
✅ Superscripts (limited)
✅ No breaking when not needed
✅ Breaking after operators
✅ Fractions inline (8 tests in TypesetterTests.swift:1712-1869)
✅ Radicals inline (included in above)
✅ Mixed fractions and radicals (included in above)
✅ Fractions with complex content (included in above)
✅ Radicals with degrees (included in above)
✅ No breaking without width constraint (included in above)
✅ Very narrow widths (edge cases) (NEW - line 1873)
✅ Very wide atoms (overflow handling) (NEW - line 1895)
✅ Mixed scripts and non-scripts (NEW - line 1913)
✅ Multiple line breaks (4+ lines) (NEW - line 1930)
✅ Unicode text wrapping (NEW - line 1962)
✅ Number protection (NEW - line 1983)
✅ Large operators inline (NEW - 3 tests in lines 2111-2165)
✅ Delimiters inline (NEW - 4 tests in lines 2167-2246)
✅ Colored expressions inline (NEW - 3 tests in lines 2248-2304)
✅ Matrices inline (NEW - 3 tests in lines 2306-2362)
✅ Integration tests (NEW - 2 tests in lines 2364-2415)
✅ Real-world examples (NEW - 3 tests in lines 2417-2492)
✅ Edge cases (NEW - 2 tests in lines 2494-2534)
✅ Scripted atoms inline (NEW - 8 tests in lines 2609-2780)
✅ Break quality scoring (NEW - 8 tests in lines 2797-3006)
✅ Dynamic line height (NEW - 8 tests in lines 3007-3218)
Total: 97 tests in TypesetterTests.swift, all passing on iOS Overall: 248 tests across entire test suite, all passing
Complex Atoms - Inline Layout: (20 tests)
- Large operators: 3 tests (inline when fit, break when too wide, multiple operators)
- Delimiters: 4 tests (inline when fit, break when too wide, nested delimiters, multiple delimiters)
- Colored expressions: 3 tests (inline when fit, break when too wide, multiple colored sections)
- Matrices: 3 tests (small inline, break when too wide, with surrounding content)
- Integration: 2 tests (mixed complex elements, no breaking without constraints)
- Real-world: 3 tests (quadratic formula with color, complex fractions, mixed operations)
- Edge cases: 2 tests (very narrow width, very wide atom)
Improved Script Handling: (8 tests)
- Scripted atoms inline when fit
- Scripted atoms break when too wide
- Mixed scripted and non-scripted atoms
- Both subscripts and superscripts
- Real-world: Quadratic expansion with exponents
- Real-world: Polynomial with multiple exponent terms
- No breaking without width constraint
- Complex expressions mixing fractions and scripts
Break Quality Scoring: (8 tests)
- Prefer breaking after binary operators (+, -, ×, ÷)
- Prefer breaking after relation operators (=, <, >)
- Avoid breaking after open brackets
- Look-ahead finds better break points
- Multiple operators break at best available points
- Complex expressions with various atom types
- No unnecessary breaks when content fits
- Penalty ordering validates break preferences
Dynamic Line Height: (8 NEW tests)
- Tall content (fractions) gets more spacing
- Regular content has reasonable spacing (not excessive)
- Mixed content varies spacing appropriately per line
- Large operators with limits get adequate vertical space
- Similar content gets consistent spacing
- No regression on single-line expressions
- Deep/nested fractions get extra space
- Radicals with indices (cube roots) get adequate spacing
Edge Cases & Stress Tests: (4 tests)
- Very narrow widths (30pt)
- Very wide atoms (overflow)
- Mixed scripts and non-scripts
- Multiple line breaks (4+ lines)
Internationalization: (2 tests)
- Unicode text wrapping (CJK, Arabic, etc.)
- Number protection across locales
Real-World Examples: (3 tests)
- Quadratic formula
- Complex nested fractions (continued fractions)
- Multiple fractions in sequence
- Width calculations use Core Text (relatively fast)
- No caching of calculated widths
- Greedy algorithm is O(n) where n = number of atoms
- ✅ NEW: Early exit optimization when remaining content fits (IMPLEMENTED!)
Goal: Skip expensive line breaking checks when we know all remaining content will fit.
Implementation: Lines 376, 549-606 in Typesetter.swift
- Added
remainingContentFitsflag to track when optimization applies - In
checkAndPerformInteratomLineBreak():- After confirming current atom fits, estimates remaining content width
- If current usage < 60% of maxWidth with ≤5 atoms remaining: sets flag (conservative)
- If current usage < 75%: estimates remaining width via
estimateRemainingAtomsWidth() - Sets flag if projected total width ≤ maxWidth
- Once flag is set, all subsequent breaking checks return immediately (fast path)
- Flag is reset when line break actually occurs
estimateRemainingAtomsWidth()uses character count × average char width heuristic with 1.5× safety margin
Impact: ⭐⭐⭐ GOOD performance improvement for short expressions! Avoids width calculations for atoms that definitely fit.
Benefit: Most mathematical expressions fit on one line - this optimization makes them render faster by skipping unnecessary width checks after determining the line has plenty of space.
Progress: COMPLETED and tested!
- Width caching: Cache calculated atom widths
- Batch processing: Calculate multiple atom widths together
The multiline line breaking implementation now provides comprehensive support for ALL complex atom types!
The implementation now provides excellent support for:
- ✅ Simple equations with operators
- ✅ Text and math mixing
- ✅ Long sequences of variables/numbers
- ✅ Fractions inline (COMPLETED!)
- ✅ Radicals/square roots inline (COMPLETED!)
- ✅ Large operators inline (COMPLETED!)
- ✅ Delimited expressions inline (COMPLETED!)
- ✅ Colored expressions inline (COMPLETED!)
- ✅ Matrices/tables inline (COMPLETED!)
- ✅ Scripted atoms (superscripts/subscripts) (COMPLETED!)
- ✅ Mixed complex expressions (COMPLETED!)
- ✅ Width constraint propagation to nested content (COMPLETED!)
Transformational achievements:
- ✅ Expressions like
a + \frac{1}{2} + \sqrt{3} + bnow stay on 1-2 lines instead of 5! - ✅ Equations like
a + \sum x_i + \int f(x)dx + bnow flow naturally instead of forcing breaks! - ✅ Delimited content like
(a+b) + \left(\frac{c}{d}\right) + estays inline with proper wrapping! - ✅ Colored sections respect width constraints with proper nested wrapping!
- ✅ Small matrices and tables can stay inline with surrounding content!
- ✅ NEW: Scripted atoms like
a^{2} + b^{2} + c^{2}break intelligently based on width!
Still need work for:
⚠️ Very long text atoms - break within atom rather than between atoms (already implemented via universal line breaking but could be further optimized)
Note: This is a minor edge case rather than a fundamental limitation!
All major priorities have been completed! Possible future enhancements:
- Further optimize very long text atom breaking - fine-tune Unicode-aware breaking for edge cases
- Configurable line spacing multiplier - allow users to adjust minimum spacing
- Alignment options - left/center/right alignment for multiline expressions
Progress: 🎉 100% COMPLETE for all major features! All atom types (simple, complex, and scripted) now support:
- ✅ Intelligent inline layout with width-based breaking
- ✅ Aesthetically-pleasing break point selection (after operators, avoiding bad breaks)
- ✅ Dynamic line height based on actual content (tall fractions get more space, regular content stays compact)