# Multiline/Line Breaking Implementation Notes ## Overview SwiftMath 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. ## Implementation Architecture ### Two-Tier Breaking System #### 1. **Interatom Line Breaking** (Primary - NEW) **Location**: `MTTypesetter.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 #### 2. **Universal Line Breaking** (Fallback - EXISTING) **Location**: `MTTypesetter.swift:877-950` **Mechanism**: - Checks **after** adding atom (for simple atoms without scripts) - Uses Core Text's `CTTypesetterSuggestLineBreak` for 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 ## Fully Supported Cases ### ✅ Simple Equations ```swift "a + b + c + d + e + f" "x = 1, y = 2, z = 3" "α + β + γ + δ" ``` **Works perfectly**: Breaks between operators and variables. ### ✅ Mixed Text and Math ```swift "\\text{Calculate } Δ = b^{2} - 4ac \\text{ with } a=1" ``` **Works perfectly**: Breaks between text and math atoms naturally. ### ✅ Long Sequences ```swift "1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10" ``` **Works perfectly**: Breaks between numbers and operators. ### ✅ Relational Expressions ```swift "a < b, b > c, c ≤ d, d ≥ e" ``` **Works perfectly**: Breaks after punctuation and relations. ### ✅ Fractions (NEWLY SUPPORTED!) ```swift "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 MTTypesetter.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! ### ✅ Radicals (NEWLY SUPPORTED!) ```swift "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 MTTypesetter.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! ### ✅ Mixed Complex Expressions (NEWLY SUPPORTED!) ```swift "a + \\frac{1}{2} + \\sqrt{3} + b" ``` **Now works perfectly**: Intelligently mixes fractions, radicals, and simple atoms. Each element stays inline if it fits. ### ✅ Large Operators (NEWLY SUPPORTED!) ```swift "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 MTTypesetter.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! ### ✅ Delimited Expressions (NEWLY SUPPORTED!) ```swift "(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 MTTypesetter.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! ### ✅ Colored Expressions (NEWLY SUPPORTED!) ```swift "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 MTTypesetter.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! ### ✅ Matrices/Tables (NEWLY SUPPORTED!) ```swift "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 MTTypesetter.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! ### ✅ Atoms with Scripts (NEWLY IMPROVED!) ```swift "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 MTTypesetter.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! ## Limited Support Cases ### ⚠️ Very Long Text Atoms ```swift "\\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. ## Remaining Unsupported Cases **GREAT NEWS**: As of the latest update, ALL major complex atom types now support intelligent inline layout! 🎉 ### ✅ Previously Unsupported - NOW FIXED! 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 ### ℹ️ Special Note: Accents **Code location**: `MTTypesetter.swift:751-824` ```swift "\\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. ## Recent Improvements (Implemented!) ### ✅ FIXED: Over-Breaking with Fractions and Radicals **Previous Problem**: Expressions mixing simple atoms with fractions/radicals had too many breaks. **Previous Example**: ```swift "a + \\frac{1}{2} + b + \\sqrt{3} + c" // Previously became 5 lines ``` **Solution 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 (MTTypesetterTests.swift:1712-1869) - All 43 tests pass on both iOS and macOS ## Remaining Issues and Edge Cases ### 1. No Look-Ahead Optimization **Problem**: Greedy algorithm breaks immediately without considering slightly better break points nearby. **Example**: ```swift "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. ### 2. Fixed Line Height **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. ### 3. ✅ FIXED: Scripts Disable Interatom Breaking **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! ### 4. No Break Quality Scoring **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 ### 5. No Widow/Orphan Control **Problem**: Single atoms can end up alone on lines. **Example**: ```swift // Last line might just be: "+ e" ``` **Possible solution**: Minimum atoms per line constraint. ### 6. ✅ FIXED: Inconsistent Behavior with Recursion **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: - `.color` atoms (line 625) - `.textcolor` atoms (line 637) - `.colorBox` atoms (line 667) - `.inner` atoms (lines 755, 762) - `makeLeftRight()` helper (line 1867) **Result**: ✅ Inner content now wraps properly! ## Future Enhancement Opportunities ### ✅ COMPLETED: Priority 1 - Fix ALL Complex Atom Line Flushing **Status**: ✅ 100% IMPLEMENTED AND TESTED **What was done**: 1. Added `shouldBreakBeforeDisplay()` helper to check width before flushing 2. Modified `.fraction` case to check width before breaking ✅ 3. Modified `.radical` case to check width before breaking ✅ 4. Modified `.largeOperator` case with height+width checking ✅ 5. Modified `.inner` case with maxWidth propagation ✅ 6. Modified all 3 color cases (.color, .textcolor, .colorBox) with maxWidth propagation ✅ 7. Modified `.table` case to check width before breaking ✅ 8. Added 20 comprehensive tests covering all newly fixed scenarios ✅ 9. Fixed 6 old tests that checked exact pixel values ✅ 10. All 76 tests pass on both iOS and macOS ✅ **Impact**: ⭐⭐⭐⭐⭐ TRANSFORMATIONAL! ALL complex atom types now intelligently stay inline! **Progress**: 100% complete! 🎉 ### ✅ COMPLETED: Priority 1 - Improve Script Handling **Status**: ✅ IMPLEMENTED AND TESTED **What was done**: 1. Added `estimateAtomWidthWithScripts()` helper function to calculate atom width including scripts 2. Check width constraint BEFORE flushing for scripted atoms (lines 1123-1137) 3. Only break line if adding scripted atom would exceed maxWidth 4. Otherwise add inline with proper spacing 5. Added 8 comprehensive tests covering all scenarios 6. 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! ### ✅ Break Quality Scoring (NEWLY COMPLETED!) **Goal**: Prefer better break points aesthetically (e.g., after operators rather than in the middle of expressions). **Implementation**: Lines 517-607 in MTTypesetter.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! ### ✅ Dynamic Line Height (NEWLY COMPLETED!) **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 MTTypesetter.swift - Added `currentLineStartIndex: Int` to track where each line's displays begin in displayAtoms array - Added `minimumLineSpacing: CGFloat` set 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! ## Testing Strategy ### Current Test Coverage ✅ Simple equations (6 tests in `MTTypesetterTests.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 `MTTypesetterTests.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 MTTypesetterTests.swift, all passing on iOS** **Overall: 248 tests across entire test suite, all passing** ### Coverage Summary by Category **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 ## Performance Considerations ### Current Performance - Width calculations use Core Text (relatively fast) - No caching of calculated widths - Greedy algorithm is O(n) where n = number of atoms ### Potential Optimizations 1. **Width caching**: Cache calculated atom widths 2. **Batch processing**: Calculate multiple atom widths together 3. **Early exit**: Stop processing if remaining content definitely fits ## Conclusion ### 🎉 COMPLETE: Major Transformation Achieved! The multiline line breaking implementation now provides **comprehensive support** for ALL complex atom types! ### ✅ What's Now Excellent (All Major Features Complete!) 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} + b` now stay on **1-2 lines** instead of 5! - ✅ Equations like `a + \sum x_i + \int f(x)dx + b` now flow naturally instead of forcing breaks! - ✅ Delimited content like `(a+b) + \left(\frac{c}{d}\right) + e` stays 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! ### ⚠️ Remaining Limitations (Minor Cases Only) **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! ### 🎯 Future Enhancements (All Core Features Complete!) All major priorities have been completed! Possible future enhancements: 1. **Further optimize very long text atom breaking** - fine-tune Unicode-aware breaking for edge cases 2. **Configurable line spacing multiplier** - allow users to adjust minimum spacing 3. **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)