add documentation
This commit is contained in:
373
MULTILINE_IMPLEMENTATION_NOTES.md
Normal file
373
MULTILINE_IMPLEMENTATION_NOTES.md
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
## Limited Support Cases
|
||||||
|
|
||||||
|
### ⚠️ Atoms with Scripts
|
||||||
|
```swift
|
||||||
|
"a^{2} + b^{2} + c^{2} + d^{2}"
|
||||||
|
```
|
||||||
|
**Works but suboptimal**: Falls back to universal breaking which breaks within accumulated text rather than at clean atom boundaries.
|
||||||
|
|
||||||
|
**Why**: Atoms with scripts still trigger line flushing for script positioning, which interrupts the interatom breaking flow.
|
||||||
|
|
||||||
|
**Impact**: May not break at the most aesthetically pleasing positions.
|
||||||
|
|
||||||
|
### ⚠️ 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.
|
||||||
|
|
||||||
|
## Unsupported Cases (Forced Line Breaks)
|
||||||
|
|
||||||
|
These atom types **always** flush the current line before rendering, meaning they start on their own line:
|
||||||
|
|
||||||
|
### ❌ 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
|
||||||
|
**Code location**: `MTTypesetter.swift:684-693`
|
||||||
|
|
||||||
|
```swift
|
||||||
|
"\\sum_{i=1}^{n} x_i + \\int_{0}^{1} f(x)dx"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Large operators (∑, ∫, ∏, lim) with subscripts/superscripts require special vertical positioning.
|
||||||
|
|
||||||
|
**Impact**: Each operator gets its own line.
|
||||||
|
|
||||||
|
### ❌ Inner Lists (Delimiters)
|
||||||
|
**Code location**: `MTTypesetter.swift:694-709`
|
||||||
|
|
||||||
|
```swift
|
||||||
|
"a + \\left( \\frac{b}{c} \\right) + d"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: `\left...\right` pairs create inner lists that flush the line for proper delimiter sizing.
|
||||||
|
|
||||||
|
### ❌ Matrices/Tables
|
||||||
|
**Code location**: `MTTypesetter.swift:757-770`
|
||||||
|
|
||||||
|
```swift
|
||||||
|
"A = \\begin{pmatrix} 1 & 2 \\\\ 3 & 4 \\end{pmatrix}"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Matrices require complex 2D layout.
|
||||||
|
|
||||||
|
### ❌ Colored Expressions
|
||||||
|
**Code locations**:
|
||||||
|
- `MTTypesetter.swift:590-600` (`.color`)
|
||||||
|
- `MTTypesetter.swift:602-630` (`.textcolor`)
|
||||||
|
- `MTTypesetter.swift:632-643` (`.colorBox`)
|
||||||
|
|
||||||
|
```swift
|
||||||
|
"a + \\color{red}{b + c} + d"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Color atoms recursively create displays and flush the line.
|
||||||
|
|
||||||
|
### ❌ Accents
|
||||||
|
**Code location**: `MTTypesetter.swift:711-755`
|
||||||
|
|
||||||
|
```swift
|
||||||
|
"\\hat{x} + \\tilde{y}"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Accents require special vertical positioning and may flush lines.
|
||||||
|
|
||||||
|
## Potential Issues and Edge Cases
|
||||||
|
|
||||||
|
### 1. Over-Breaking with Complex Atoms
|
||||||
|
**Problem**: Expressions mixing simple and complex atoms have too many breaks.
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```swift
|
||||||
|
"a + \\frac{1}{2} + b + \\sqrt{3} + c"
|
||||||
|
// Becomes 5 lines instead of ideally 1-2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Root cause**: Each complex atom flushes the line independently.
|
||||||
|
|
||||||
|
**Possible solution**: Check if complex atom + current line width fits within constraint before flushing.
|
||||||
|
|
||||||
|
### 2. 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.
|
||||||
|
|
||||||
|
### 3. 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
|
||||||
|
**Problem**: Atoms with superscripts/subscripts fall back to universal breaking.
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```swift
|
||||||
|
"a^{2} + b^{2} + c^{2}"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Root cause**: Scripts cause line flushing for vertical positioning (line 892-908), interrupting interatom flow.
|
||||||
|
|
||||||
|
**Possible solution**: Refactor script handling to not require immediate line flush, or handle scripted atoms specially in interatom breaking.
|
||||||
|
|
||||||
|
### 5. 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
|
||||||
|
|
||||||
|
### 6. 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.
|
||||||
|
|
||||||
|
### 7. Inconsistent Behavior with Recursion
|
||||||
|
**Problem**: Nested math lists (inner, color, etc.) create their own displays recursively, potentially without width constraints.
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```swift
|
||||||
|
"\\color{red}{a + b + c + d + e + f + g}"
|
||||||
|
// The entire colored portion might render on one line even if too wide
|
||||||
|
```
|
||||||
|
|
||||||
|
**Root cause**: Recursive calls to `createLineForMathList` at lines 596, 608, 638 don't pass `maxWidth`.
|
||||||
|
|
||||||
|
**Possible solution**: Propagate `maxWidth` to recursive calls.
|
||||||
|
|
||||||
|
## Future Enhancement Opportunities
|
||||||
|
|
||||||
|
### Priority 1: Fix Complex Atom Line Flushing
|
||||||
|
**Goal**: Allow fractions, radicals, etc. to coexist on lines with other atoms.
|
||||||
|
|
||||||
|
**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
|
||||||
|
|
||||||
|
**Implementation**: Modify switch cases for `.fraction`, `.radical`, `.largeOperator` to check width before flushing.
|
||||||
|
|
||||||
|
**Impact**: ⭐⭐⭐⭐⭐ (Huge improvement for mathematical expressions)
|
||||||
|
|
||||||
|
### Priority 2: Improve Script Handling
|
||||||
|
**Goal**: Make atoms with scripts work with interatom breaking.
|
||||||
|
|
||||||
|
**Approach**:
|
||||||
|
1. Calculate total width including scripts
|
||||||
|
2. Include in interatom breaking decision
|
||||||
|
3. Defer script positioning until after line breaking decision
|
||||||
|
|
||||||
|
**Implementation**: Refactor `makeScripts` to be non-flushing.
|
||||||
|
|
||||||
|
**Impact**: ⭐⭐⭐⭐ (Significant improvement for common cases)
|
||||||
|
|
||||||
|
### Priority 3: Implement Break Quality Scoring
|
||||||
|
**Goal**: Prefer better break points (e.g., after operators).
|
||||||
|
|
||||||
|
**Approach**:
|
||||||
|
1. Assign penalty scores to different break point types
|
||||||
|
2. When projected width slightly exceeds maxWidth, look ahead 1-3 atoms
|
||||||
|
3. Choose break point with lowest penalty within acceptable width range
|
||||||
|
|
||||||
|
**Implementation**: Add `calculateBreakPenalty()` method, modify `checkAndPerformInteratomLineBreak()`.
|
||||||
|
|
||||||
|
**Impact**: ⭐⭐⭐ (Nice aesthetic improvement)
|
||||||
|
|
||||||
|
### Priority 4: Dynamic Line Height
|
||||||
|
**Goal**: Adjust vertical spacing based on actual line content height.
|
||||||
|
|
||||||
|
**Approach**:
|
||||||
|
1. Track maximum ascent/descent for each line
|
||||||
|
2. Use actual measurements for vertical positioning
|
||||||
|
3. Add configurable minimum line spacing
|
||||||
|
|
||||||
|
**Implementation**: Modify `addDisplayLine()` to calculate and store line height.
|
||||||
|
|
||||||
|
**Impact**: ⭐⭐ (Better vertical spacing)
|
||||||
|
|
||||||
|
### Priority 5: Width Constraint Propagation
|
||||||
|
**Goal**: Apply width constraints to nested/recursive displays.
|
||||||
|
|
||||||
|
**Approach**:
|
||||||
|
1. Pass `maxWidth` to all recursive `createLineForMathList` calls
|
||||||
|
2. Adjust for nesting level (reduce maxWidth for inner content)
|
||||||
|
|
||||||
|
**Implementation**: Update all recursive calls with `maxWidth` parameter.
|
||||||
|
|
||||||
|
**Impact**: ⭐⭐ (More consistent behavior)
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### Current Test Coverage
|
||||||
|
✅ Simple equations (6 tests in `MTTypesetterTests.swift:1577-1709`)
|
||||||
|
✅ Text and math mixing
|
||||||
|
✅ Atoms at boundaries
|
||||||
|
✅ Superscripts (limited)
|
||||||
|
✅ No breaking when not needed
|
||||||
|
✅ Breaking after operators
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
The current implementation provides **excellent support** for:
|
||||||
|
- ✅ Simple equations with operators
|
||||||
|
- ✅ Text and math mixing
|
||||||
|
- ✅ Long sequences of variables/numbers
|
||||||
|
|
||||||
|
**Limitations exist** for:
|
||||||
|
- ⚠️ Expressions with fractions, radicals, large operators
|
||||||
|
- ⚠️ Nested/colored expressions
|
||||||
|
- ⚠️ Scripted atoms (superscripts/subscripts)
|
||||||
|
|
||||||
|
The most impactful improvements would be:
|
||||||
|
1. **Fix complex atom flushing** (allow fractions/radicals inline)
|
||||||
|
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.
|
||||||
218
README.md
218
README.md
@@ -193,7 +193,7 @@ struct MathView: NSViewRepresentable {
|
|||||||
|
|
||||||
### Automatic Line Wrapping
|
### Automatic Line Wrapping
|
||||||
|
|
||||||
`SwiftMath` supports automatic line wrapping for text and simple math expressions. When the content exceeds the available width, it will wrap at word boundaries to fit within the constrained space.
|
`SwiftMath` supports automatic line wrapping (multiline display) for mathematical content. The implementation uses **interatom line breaking** which breaks equations at atom boundaries (between mathematical elements) rather than within them, preserving the semantic structure of the mathematics.
|
||||||
|
|
||||||
#### Using Line Wrapping with UIKit/AppKit
|
#### Using Line Wrapping with UIKit/AppKit
|
||||||
|
|
||||||
@@ -201,18 +201,17 @@ For direct `MTMathUILabel` usage, set the `preferredMaxLayoutWidth` property:
|
|||||||
|
|
||||||
```swift
|
```swift
|
||||||
let label = MTMathUILabel()
|
let label = MTMathUILabel()
|
||||||
label.latex = "\\(\\text{Remember the conversion: 1 km equals 1000 meters.}\\)"
|
label.latex = "\\text{Calculer le discriminant }\\Delta=b^{2}-4ac\\text{ avec }a=1\\text{, }b=-1\\text{, }c=-5"
|
||||||
label.font = MTFontManager.fontManager.defaultFont
|
label.font = MTFontManager.fontManager.defaultFont
|
||||||
label.labelMode = .text
|
|
||||||
|
|
||||||
// Enable line wrapping by setting a maximum width
|
// Enable line wrapping by setting a maximum width
|
||||||
label.preferredMaxLayoutWidth = 300
|
label.preferredMaxLayoutWidth = 235
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also use `sizeThatFits` to calculate the size with a width constraint:
|
You can also use `sizeThatFits` to calculate the size with a width constraint:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
let constrainedSize = label.sizeThatFits(CGSize(width: 300, height: .greatestFiniteMagnitude))
|
let constrainedSize = label.sizeThatFits(CGSize(width: 235, height: .greatestFiniteMagnitude))
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Using Line Wrapping with SwiftUI
|
#### Using Line Wrapping with SwiftUI
|
||||||
@@ -222,64 +221,205 @@ The `MathView` examples above include `sizeThatFits()` which automatically enabl
|
|||||||
```swift
|
```swift
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
MathView(
|
MathView(
|
||||||
equation: "\\(\\text{Remember the conversion: 1 km equals 1000 meters.}\\)",
|
equation: "\\text{Calculer le discriminant }\\Delta=b^{2}-4ac\\text{ avec }a=1\\text{, }b=-1\\text{, }c=-5",
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
labelMode: .text
|
labelMode: .text
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: 300) // The text will wrap to fit within 300pt
|
.frame(maxWidth: 235) // The equation will break across multiple lines
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Line Wrapping Behavior
|
#### Line Wrapping Behavior and Capabilities
|
||||||
|
|
||||||
- **Works for**: Text content (`\text{...}`), mixed text with simple math, and simple equations
|
SwiftMath implements **two complementary line breaking mechanisms**:
|
||||||
- **Breaks at**: Word boundaries (spaces)
|
|
||||||
- **Preserves**: Complex math layout (fractions, superscripts, matrices remain on single lines)
|
##### 1. Interatom Line Breaking (Primary)
|
||||||
- **Respects**: Unicode text including CJK characters with proper word boundaries
|
Breaks equations **between atoms** (mathematical elements) when content exceeds the width constraint. This is the preferred method as it maintains semantic integrity.
|
||||||
|
|
||||||
|
##### 2. Universal Line Breaking (Fallback)
|
||||||
|
For very long text within single atoms, breaks at Unicode word boundaries using Core Text with number protection (prevents splitting numbers like "3.14").
|
||||||
|
|
||||||
|
#### Fully Supported Cases
|
||||||
|
|
||||||
|
These atom types work perfectly with interatom line breaking:
|
||||||
|
|
||||||
|
**✅ Variables and ordinary text:**
|
||||||
|
```swift
|
||||||
|
label.latex = "a b c d e f g h i j k l m n o p"
|
||||||
|
label.preferredMaxLayoutWidth = 150
|
||||||
|
// Breaks between individual variables at natural boundaries
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Binary operators (+, -, ×, ÷):**
|
||||||
|
```swift
|
||||||
|
label.latex = "a+b+c+d+e+f+g+h"
|
||||||
|
label.preferredMaxLayoutWidth = 100
|
||||||
|
// Breaks cleanly: "a+b+c+d+"
|
||||||
|
// "e+f+g+h"
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Relations (=, <, >, ≤, ≥, etc.):**
|
||||||
|
```swift
|
||||||
|
label.latex = "a=1, b=2, c=3, d=4, e=5"
|
||||||
|
label.preferredMaxLayoutWidth = 120
|
||||||
|
// Breaks after commas and operators
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Mixed text and simple math:**
|
||||||
|
```swift
|
||||||
|
label.latex = "\\text{Calculer }\\Delta=b^{2}-4ac\\text{ avec }a=1\\text{, }b=-1"
|
||||||
|
label.preferredMaxLayoutWidth = 200
|
||||||
|
// Breaks between text and math atoms naturally
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Punctuation (commas, periods):**
|
||||||
|
```swift
|
||||||
|
label.latex = "\\text{First, second, third, fourth, fifth}"
|
||||||
|
label.preferredMaxLayoutWidth = 150
|
||||||
|
// Breaks at commas and spaces
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Brackets and parentheses (simple):**
|
||||||
|
```swift
|
||||||
|
label.latex = "(a+b)+(c+d)+(e+f)"
|
||||||
|
label.preferredMaxLayoutWidth = 120
|
||||||
|
// Breaks between parenthesized groups
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ Greek letters and symbols:**
|
||||||
|
```swift
|
||||||
|
label.latex = "\\alpha+\\beta+\\gamma+\\delta+\\epsilon+\\zeta"
|
||||||
|
label.preferredMaxLayoutWidth = 150
|
||||||
|
// Breaks between Greek letters
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Limited Support Cases
|
||||||
|
|
||||||
|
These cases work but with some constraints:
|
||||||
|
|
||||||
|
**⚠️ Atoms with superscripts/subscripts:**
|
||||||
|
```swift
|
||||||
|
label.latex = "a^{2}+b^{2}+c^{2}+d^{2}+e^{2}"
|
||||||
|
label.preferredMaxLayoutWidth = 150
|
||||||
|
// Works, but uses fallback breaking mechanism
|
||||||
|
// May not break at the most optimal positions
|
||||||
|
```
|
||||||
|
**Note**: Scripted atoms (with superscripts/subscripts) trigger the universal breaking mechanism which breaks within accumulated text rather than at atom boundaries. This still works but may not be as clean as pure interatom breaking.
|
||||||
|
|
||||||
|
**⚠️ Very long single text atoms:**
|
||||||
|
```swift
|
||||||
|
label.latex = "\\text{This is an extremely long piece of text within a single text command}"
|
||||||
|
label.preferredMaxLayoutWidth = 200
|
||||||
|
// Uses Unicode word boundary breaking with Core Text
|
||||||
|
// Protects numbers from being split (e.g., "3.14" stays together)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Unsupported/Forced Line Break Cases
|
||||||
|
|
||||||
|
These atom types **always start on a new line** because they flush the current line before rendering. This can lead to excessive line breaks:
|
||||||
|
|
||||||
|
**❌ Fractions:**
|
||||||
|
```swift
|
||||||
|
label.latex = "a + \\frac{1}{2} + b"
|
||||||
|
// Results in:
|
||||||
|
// Line 1: "a +"
|
||||||
|
// Line 2: "½" (fraction on own line)
|
||||||
|
// Line 3: "+ b"
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ Radicals (square roots):**
|
||||||
|
```swift
|
||||||
|
label.latex = "x + \\sqrt{2} + y"
|
||||||
|
// Results in:
|
||||||
|
// Line 1: "x +"
|
||||||
|
// Line 2: "√2" (radical on own line)
|
||||||
|
// Line 3: "+ y"
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ Large operators (∑, ∫, ∏, lim):**
|
||||||
|
```swift
|
||||||
|
label.latex = "\\sum_{i=1}^{n} x_i + \\int_{0}^{1} f(x)dx"
|
||||||
|
// Each operator forces a new line
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ Matrices and tables:**
|
||||||
|
```swift
|
||||||
|
label.latex = "A = \\begin{pmatrix} 1 & 2 \\\\ 3 & 4 \\end{pmatrix}"
|
||||||
|
// Matrix always on own line
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ Delimited expressions (\left...\right):**
|
||||||
|
```swift
|
||||||
|
label.latex = "\\left(\\frac{a}{b}\\right) + c"
|
||||||
|
// The parenthesized group forces line breaks
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ Colored expressions:**
|
||||||
|
```swift
|
||||||
|
label.latex = "a + \\color{red}{b} + c"
|
||||||
|
// Colored portion causes line break
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ Math accents:**
|
||||||
|
```swift
|
||||||
|
label.latex = "\\hat{x} + \\tilde{y} + \\bar{z}"
|
||||||
|
// Accents may cause line breaks
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Best Practices
|
||||||
|
|
||||||
|
**DO:**
|
||||||
|
- Use interatom breaking for simple equations with operators and relations
|
||||||
|
- Use for mixed text and math where you want natural breaks
|
||||||
|
- Use for long sequences of variables, numbers, and operators
|
||||||
|
- Set appropriate `preferredMaxLayoutWidth` based on your layout needs
|
||||||
|
|
||||||
|
**DON'T:**
|
||||||
|
- Expect natural breaking in expressions with many fractions
|
||||||
|
- Expect natural breaking in expressions with many radicals
|
||||||
|
- Expect natural breaking in expressions with large operators
|
||||||
|
- Use extremely narrow widths (less than ~80pt) which may cause poor breaks
|
||||||
|
|
||||||
#### Examples
|
#### Examples
|
||||||
|
|
||||||
**Simple text wrapping:**
|
**Excellent use case (discriminant formula):**
|
||||||
```swift
|
```swift
|
||||||
// Long text will wrap to multiple lines
|
label.latex = "\\text{Calculer le discriminant }\\Delta=b^{2}-4ac\\text{ avec }a=1\\text{, }b=-1\\text{, }c=-5"
|
||||||
label.latex = "\\(\\text{The quadratic formula is used to solve equations of the form } ax^2 + bx + c = 0\\)"
|
label.preferredMaxLayoutWidth = 235
|
||||||
label.preferredMaxLayoutWidth = 250
|
// ✅ Breaks naturally at good points between atoms
|
||||||
```
|
```
|
||||||
|
|
||||||
**Simple equation with operators:**
|
**Good use case (simple arithmetic):**
|
||||||
```swift
|
```swift
|
||||||
// Long equations can break between operators if too long
|
label.latex = "5+10+15+20+25+30+35+40+45+50"
|
||||||
label.latex = "\\(5 + 10 + 15 + 20 + 25 + 30\\)"
|
|
||||||
label.preferredMaxLayoutWidth = 150
|
label.preferredMaxLayoutWidth = 150
|
||||||
// Will wrap: "5 + 10 + 15 + 20 +"
|
// ✅ Breaks between operators cleanly
|
||||||
// "25 + 30"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Mixed text and math:**
|
**Problematic use case (many fractions):**
|
||||||
```swift
|
```swift
|
||||||
// Text wraps but math expressions stay intact
|
label.latex = "\\frac{1}{2}+\\frac{3}{4}+\\frac{5}{6}+\\frac{7}{8}"
|
||||||
label.latex = "\\(\\text{Result: } 5 \\times 1000 = 5000 \\text{ meters}\\)"
|
|
||||||
label.preferredMaxLayoutWidth = 200
|
label.preferredMaxLayoutWidth = 200
|
||||||
// Will wrap at spaces between text and operators
|
// ⚠️ Each fraction on separate line, not ideal
|
||||||
|
// Better to avoid line breaking for such expressions
|
||||||
```
|
```
|
||||||
|
|
||||||
**Multiple lines in SwiftUI:**
|
**Alternative for complex expressions:**
|
||||||
```swift
|
```swift
|
||||||
ScrollView {
|
// Instead of trying to break this:
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
label.latex = "x = \\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}"
|
||||||
ForEach(steps) { step in
|
// Consider it as a single display equation without width constraint
|
||||||
MathView(
|
label.preferredMaxLayoutWidth = 0 // No breaking
|
||||||
equation: step.description,
|
|
||||||
fontSize: 17,
|
|
||||||
labelMode: .text
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
// Each MathView will automatically wrap based on available width
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Technical Details
|
||||||
|
|
||||||
|
- **Line spacing**: New lines are positioned at `fontSize × 1.5` below the previous line
|
||||||
|
- **Breaking algorithm**: Greedy - breaks immediately when projected width exceeds constraint
|
||||||
|
- **Width calculation**: Includes inter-element spacing according to TeX spacing rules
|
||||||
|
- **Number protection**: Numbers in patterns like "3.14", "1,000", etc. are kept intact
|
||||||
|
- **Supports locales**: English, French, Swiss number formats
|
||||||
|
|
||||||
### Included Features
|
### Included Features
|
||||||
This is a list of formula types that the library currently supports:
|
This is a list of formula types that the library currently supports:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user