Line breaking for fractions and radicals fixes

Implement smart width-checking for complex mathematical displays to enable
  inline rendering when space permits, dramatically improving multiline layout.

  Changes:
  - Add shouldBreakBeforeDisplay() helper to check width before line breaks
  - Add performLineBreak() helper for clean line transitions
  - Modify fraction handling to stay inline when they fit within maxWidth
  - Modify radical handling to stay inline when they fit within maxWidth
  - Support radicals with degrees (cube roots, nth roots, etc.)
This commit is contained in:
Nicolas Guillot
2025-11-13 15:39:54 +01:00
parent cd9c3f7a37
commit c5b737d9bb
4 changed files with 655 additions and 120 deletions

View File

@@ -74,6 +74,40 @@ SwiftMath now supports automatic line breaking (multiline display) for mathemati
```
**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.
## Limited Support Cases
### ⚠️ Atoms with Scripts
@@ -94,36 +128,11 @@ SwiftMath now supports automatic line breaking (multiline display) for mathemati
**Limitation**: Breaks within the text atom, not between atoms.
## Unsupported Cases (Forced Line Breaks)
## Remaining Unsupported Cases (Still Force Line Breaks)
These atom types **always** flush the current line before rendering, meaning they start on their own line:
These atom types still **always** flush the current line before rendering. They are candidates for future optimization:
### ❌ Fractions
**Code location**: `MTTypesetter.swift:669-682`
```swift
"a + \\frac{1}{2} + b"
// Results in 3 lines:
// Line 1: "a +"
// Line 2: "½"
// Line 3: "+ b"
```
**Why**: Fractions require complex vertical layout (numerator/denominator) and force a line flush.
**Impact**: Expressions with multiple fractions have excessive line breaks.
### ❌ Radicals (Square Roots)
**Code location**: `MTTypesetter.swift:645-668`
```swift
"x + \\sqrt{2} + y"
// Results in 3 lines
```
**Why**: Radicals require special rendering (radical sign + vinculum) and force line flush.
### ❌ Large Operators
### ⚠️ Large Operators (Not Yet Optimized)
**Code location**: `MTTypesetter.swift:684-693`
```swift
@@ -134,7 +143,7 @@ These atom types **always** flush the current line before rendering, meaning the
**Impact**: Each operator gets its own line.
### Inner Lists (Delimiters)
### ⚠️ Inner Lists (Delimiters) (Not Yet Optimized)
**Code location**: `MTTypesetter.swift:694-709`
```swift
@@ -143,7 +152,7 @@ These atom types **always** flush the current line before rendering, meaning the
**Why**: `\left...\right` pairs create inner lists that flush the line for proper delimiter sizing.
### Matrices/Tables
### ⚠️ Matrices/Tables (Not Yet Optimized)
**Code location**: `MTTypesetter.swift:757-770`
```swift
@@ -152,7 +161,7 @@ These atom types **always** flush the current line before rendering, meaning the
**Why**: Matrices require complex 2D layout.
### Colored Expressions
### ⚠️ Colored Expressions (Not Yet Optimized)
**Code locations**:
- `MTTypesetter.swift:590-600` (`.color`)
- `MTTypesetter.swift:602-630` (`.textcolor`)
@@ -164,7 +173,7 @@ These atom types **always** flush the current line before rendering, meaning the
**Why**: Color atoms recursively create displays and flush the line.
### Accents
### ⚠️ Accents (Partially Supported)
**Code location**: `MTTypesetter.swift:711-755`
```swift
@@ -173,22 +182,32 @@ These atom types **always** flush the current line before rendering, meaning the
**Why**: Accents require special vertical positioning and may flush lines.
## Potential Issues and Edge Cases
## Recent Improvements (Implemented!)
### 1. Over-Breaking with Complex Atoms
**Problem**: Expressions mixing simple and complex atoms have too many breaks.
### ✅ FIXED: Over-Breaking with Fractions and Radicals
**Previous Problem**: Expressions mixing simple atoms with fractions/radicals had too many breaks.
**Example**:
**Previous Example**:
```swift
"a + \\frac{1}{2} + b + \\sqrt{3} + c"
// Becomes 5 lines instead of ideally 1-2
// Previously became 5 lines
```
**Root cause**: Each complex atom flushes the line independently.
**Solution Implemented**: Check if complex atom + current line width fits within constraint before flushing.
**Possible solution**: Check if complex atom + current line width fits within constraint before flushing.
**Current Behavior**: Now stays on 1-2 lines as expected! ✅
### 2. No Look-Ahead Optimization
**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**:
@@ -204,14 +223,14 @@ These atom types **always** flush the current line before rendering, meaning the
**Possible solution**: Implement k-atom look-ahead with break quality scoring.
### 3. Fixed Line Height
### 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.
### 4. Scripts Disable Interatom Breaking
### 3. Scripts Disable Interatom Breaking
**Problem**: Atoms with superscripts/subscripts fall back to universal breaking.
**Example**:
@@ -223,7 +242,7 @@ These atom types **always** flush the current line before rendering, meaning the
**Possible solution**: Refactor script handling to not require immediate line flush, or handle scripted atoms specially in interatom breaking.
### 5. No Break Quality Scoring
### 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.
@@ -233,7 +252,7 @@ These atom types **always** flush the current line before rendering, meaning the
- Medium penalty: after ordinary atoms
- High penalty: after opening brackets, before closing brackets
### 6. No Widow/Orphan Control
### 5. No Widow/Orphan Control
**Problem**: Single atoms can end up alone on lines.
**Example**:
@@ -243,7 +262,7 @@ These atom types **always** flush the current line before rendering, meaning the
**Possible solution**: Minimum atoms per line constraint.
### 7. Inconsistent Behavior with Recursion
### 6. Inconsistent Behavior with Recursion
**Problem**: Nested math lists (inner, color, etc.) create their own displays recursively, potentially without width constraints.
**Example**:
@@ -258,17 +277,34 @@ These atom types **always** flush the current line before rendering, meaning the
## Future Enhancement Opportunities
### Priority 1: Fix Complex Atom Line Flushing
**Goal**: Allow fractions, radicals, etc. to coexist on lines with other atoms.
### ✅ COMPLETED: Fix Complex Atom Line Flushing (Fractions & Radicals)
**Status**: ✅ IMPLEMENTED AND TESTED
**Approach**:
1. Check if complex atom width + current line width fits
2. If yes, add to line without flushing
3. If no, flush current line, add complex atom to new line
**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. Added 8 comprehensive tests covering all scenarios
5. All tests pass on iOS and macOS
**Implementation**: Modify switch cases for `.fraction`, `.radical`, `.largeOperator` to check width before flushing.
**Impact**: ⭐⭐⭐⭐⭐ HUGE improvement achieved!
**Impact**: ⭐⭐⭐⭐⭐ (Huge improvement for mathematical expressions)
**Remaining work**: Apply same pattern to `.largeOperator`, `.inner`, `.color`, `.table`
### Priority 1: Apply Same Fix to Remaining Complex Atoms
**Goal**: Extend the width-checking approach to large operators, delimiters, colors, and matrices.
**Approach**: Use the same `shouldBreakBeforeDisplay()` pattern that now works for fractions and radicals.
**Implementation**: Already proven to work! Just need to apply to:
- `.largeOperator` (lines 723-730)
- `.inner` (lines 732-751)
- `.color` (lines 622-632)
- `.textcolor` (lines 634-662)
- `.colorBox` (lines 664-675)
- `.table` (lines 858-871)
**Impact**: ⭐⭐⭐⭐ (Very good - complete the transformation)
### Priority 2: Improve Script Handling
**Goal**: Make atoms with scripts work with interatom breaking.
@@ -320,26 +356,56 @@ These atom types **always** flush the current line before rendering, meaning the
## Testing Strategy
### Current Test Coverage
✅ Simple equations (6 tests in `MTTypesetterTests.swift:1577-1709`)
✅ 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 current behavior** (NEW - line 2000)
**Nested delimiters current behavior** (NEW - line 2015)
**Colored sections current behavior** (NEW - line 2030)
**Matrices with surrounding content** (NEW - line 2045)
**Real-world: Quadratic formula** (NEW - line 2060)
**Real-world: Complex nested fractions** (NEW - line 2075)
**Real-world: Multiple fractions** (NEW - line 2090)
### Recommended Additional Tests
- [ ] Fractions in equations
- [ ] Radicals in equations
- [ ] Large operators with breaking
- [ ] Nested expressions
- [ ] Colored sections
- [ ] Very narrow widths (edge cases)
- [ ] Very wide atoms (overflow handling)
- [ ] Mixed scripts and non-scripts
- [ ] Matrices with surrounding content
- [ ] Multiple line breaks (3+ lines)
- [ ] Unicode text wrapping
- [ ] Number protection across languages
**Total: 56 tests, all passing on iOS and macOS** (35 original + 8 fractions/radicals + 13 comprehensive)
### Coverage Summary by Category
**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
**Current Behavior Documentation:** (4 tests)
- Large operators (∑, ∫) - documents forced breaks
- Nested delimiters (\left...\right) - documents forced breaks
- Colored expressions - documents forced breaks
- Matrices - documents forced breaks
**Real-World Examples:** (3 tests)
- Quadratic formula
- Complex nested fractions (continued fractions)
- Multiple fractions in sequence
## Performance Considerations
@@ -355,19 +421,32 @@ These atom types **always** flush the current line before rendering, meaning the
## Conclusion
The current implementation provides **excellent support** for:
### ✅ What's Now Excellent (After Recent Improvements)
The implementation now provides **excellent support** for:
- ✅ Simple equations with operators
- ✅ Text and math mixing
- ✅ Long sequences of variables/numbers
-**Fractions inline** (NEWLY SUPPORTED!)
-**Radicals/square roots inline** (NEWLY SUPPORTED!)
-**Mixed complex expressions** (NEWLY SUPPORTED!)
**Limitations exist** for:
- ⚠️ Expressions with fractions, radicals, large operators
- ⚠️ Nested/colored expressions
- ⚠️ Scripted atoms (superscripts/subscripts)
**Major achievement**: Expressions like `a + \frac{1}{2} + \sqrt{3} + b` now stay on **1-2 lines** instead of breaking into 5 lines!
The most impactful improvements would be:
1. **Fix complex atom flushing** (allow fractions/radicals inline)
### ⚠️ Remaining Limitations
**Still need work** for:
- ⚠️ Large operators (∑, ∫, ∏, lim) - still force line breaks
- ⚠️ Delimited expressions (\left...\right) - still force line breaks
- ⚠️ Colored expressions - still force line breaks
- ⚠️ Matrices/tables - still force line breaks
- ⚠️ Scripted atoms (superscripts/subscripts) - use fallback mechanism
### 🎯 Next Priorities
The most impactful remaining improvements:
1. **Apply same fix to remaining complex atoms** (large operators, delimiters, colors, matrices) - proven approach!
2. **Improve script handling** (include in interatom breaking)
3. **Add break quality scoring** (prefer better break points)
These enhancements would significantly expand the range of expressions that break naturally and aesthetically across multiple lines.
**Progress**: We've implemented 40% of the complex atom fixes (fractions & radicals). The pattern is proven and can be easily applied to the remaining 60%.