add documentation

This commit is contained in:
Nicolas Guillot
2025-11-13 15:08:55 +01:00
parent 9f6f5a2934
commit cd9c3f7a37
2 changed files with 552 additions and 39 deletions

218
README.md
View File

@@ -193,7 +193,7 @@ struct MathView: NSViewRepresentable {
### 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
@@ -201,18 +201,17 @@ For direct `MTMathUILabel` usage, set the `preferredMaxLayoutWidth` property:
```swift
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.labelMode = .text
// 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:
```swift
let constrainedSize = label.sizeThatFits(CGSize(width: 300, height: .greatestFiniteMagnitude))
let constrainedSize = label.sizeThatFits(CGSize(width: 235, height: .greatestFiniteMagnitude))
```
#### Using Line Wrapping with SwiftUI
@@ -222,64 +221,205 @@ The `MathView` examples above include `sizeThatFits()` which automatically enabl
```swift
VStack(alignment: .leading, spacing: 8) {
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,
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
- **Breaks at**: Word boundaries (spaces)
- **Preserves**: Complex math layout (fractions, superscripts, matrices remain on single lines)
- **Respects**: Unicode text including CJK characters with proper word boundaries
SwiftMath implements **two complementary line breaking mechanisms**:
##### 1. Interatom Line Breaking (Primary)
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
**Simple text wrapping:**
**Excellent use case (discriminant formula):**
```swift
// Long text will wrap to multiple lines
label.latex = "\\(\\text{The quadratic formula is used to solve equations of the form } ax^2 + bx + c = 0\\)"
label.preferredMaxLayoutWidth = 250
label.latex = "\\text{Calculer le discriminant }\\Delta=b^{2}-4ac\\text{ avec }a=1\\text{, }b=-1\\text{, }c=-5"
label.preferredMaxLayoutWidth = 235
// ✅ Breaks naturally at good points between atoms
```
**Simple equation with operators:**
**Good use case (simple arithmetic):**
```swift
// Long equations can break between operators if too long
label.latex = "\\(5 + 10 + 15 + 20 + 25 + 30\\)"
label.latex = "5+10+15+20+25+30+35+40+45+50"
label.preferredMaxLayoutWidth = 150
// Will wrap: "5 + 10 + 15 + 20 +"
// "25 + 30"
// ✅ Breaks between operators cleanly
```
**Mixed text and math:**
**Problematic use case (many fractions):**
```swift
// Text wraps but math expressions stay intact
label.latex = "\\(\\text{Result: } 5 \\times 1000 = 5000 \\text{ meters}\\)"
label.latex = "\\frac{1}{2}+\\frac{3}{4}+\\frac{5}{6}+\\frac{7}{8}"
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
ScrollView {
VStack(alignment: .leading, spacing: 12) {
ForEach(steps) { step in
MathView(
equation: step.description,
fontSize: 17,
labelMode: .text
)
}
}
.padding()
}
// Each MathView will automatically wrap based on available width
// Instead of trying to break this:
label.latex = "x = \\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}"
// Consider it as a single display equation without width constraint
label.preferredMaxLayoutWidth = 0 // No breaking
```
#### 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
This is a list of formula types that the library currently supports: