Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 21 additions & 23 deletions MISSING_FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ This document lists LaTeX features that are **not yet implemented** in SwiftMath
## Summary

- **Total Features Tested**: 12
- **Fully Implemented**: 7 (58%)
- **Fully Implemented**: 8 (67%)
- **Partially Implemented**: 0 (0%)
- **Not Implemented**: 5 (42%)
- **Not Implemented**: 4 (33%)

---

Expand Down Expand Up @@ -56,22 +56,26 @@ This document lists LaTeX features that are **not yet implemented** in SwiftMath

---

### 4. ❌ Manual Delimiter Sizing: `\big`, `\Big`, `\bigg`, `\Bigg`
**Status**: ❌ Not Implemented
**Error**: `Invalid command \big`

### 4. ✅ Manual Delimiter Sizing: `\big`, `\Big`, `\bigg`, `\Bigg` - **IMPLEMENTED**
**Status**: ✅ Working
**Description**: Manually control delimiter sizes (4 levels beyond normal)

**Examples**:
```latex
\big( x \big) % slightly larger
\Big[ y \Big] % larger
\bigg\{ z \bigg\} % even larger
\Bigg| w \Bigg| % largest
```
**Test Results**: All tests passed
- `\big( x \big)` - ✅ Works (1.2x font size)
- `\Big[ y \Big]` - ✅ Works (1.8x font size)
- `\bigg\{ z \bigg\}` - ✅ Works (2.4x font size)
- `\Bigg| w \Bigg|` - ✅ Works (3.0x font size)

**Supported Commands**:
- `\big`, `\Big`, `\bigg`, `\Bigg` - basic sizing
- `\bigl`, `\Bigl`, `\biggl`, `\Biggl` - left delimiter variants
- `\bigr`, `\Bigr`, `\biggr`, `\Biggr` - right delimiter variants
- `\bigm`, `\Bigm`, `\biggm`, `\Biggm` - middle delimiter variants

**Use Case**: Fine control over delimiter appearance, nested expressions

**Implementation**: Added `delimiterHeight` property to `MTInner`, stores size multiplier (1.2, 1.8, 2.4, 3.0), applied in `MTTypesetter.makeLeftRight()`.

---

### 5. ❌ Spacing Commands: `\,`, `\:`, `\;`, `\!`
Expand Down Expand Up @@ -200,11 +204,10 @@ x \, y \: z \; w % mixed spacing

### Remaining High Priority Features
1. **Spacing commands** (`\,`, `\:`, `\;`, `\!`) - Used in almost all advanced math
2. **Manual delimiter sizing** (`\big`, etc.) - Common in published mathematics
3. **`\middle`** - Useful for conditional notation
2. **`\middle`** - Useful for conditional notation

### Remaining Medium Priority Features
4. **`\boldsymbol`** - Important for vector notation with Greek letters
3. **`\boldsymbol`** - Important for vector notation with Greek letters

---

Expand All @@ -217,7 +220,7 @@ All tests use the `MTMathListBuilder.build(fromString:error:)` API and automatic
- `testDisplayStyle()` - ✅ Passed (IMPLEMENTED)
- `testMiddleDelimiter()` - ⏭️ Skipped (not implemented)
- `testSubstack()` - ✅ Passed (IMPLEMENTED)
- `testManualDelimiterSizing()` - ⏭️ Skipped (not implemented)
- `testManualDelimiterSizing()` - ✅ Passed (IMPLEMENTED)
- `testSpacingCommands()` - ⏭️ Skipped (not implemented)
- `testMultipleIntegrals()` - ✅ Passed (IMPLEMENTED)
- `testContinuedFractions()` - ✅ Passed (IMPLEMENTED)
Expand All @@ -233,11 +236,6 @@ All tests use the `MTMathListBuilder.build(fromString:error:)` API and automatic
- Needs integration with existing `\left...\right` delimiter pairing system
- Should support all delimiter types that work with `\left` and `\right`

### For Manual Sizing (`\big`, etc.):
- Needs 4 size levels beyond normal
- Each size approximately 1.2x the previous
- Should work with all delimiter types

### For Spacing Commands:
- Need to insert proper `MTMathSpace` atoms
- Different space types: positive (`\,`, `\:`, `\;`) and negative (`\!`)
Expand All @@ -252,4 +250,4 @@ All tests use the `MTMathListBuilder.build(fromString:error:)` API and automatic

*Generated: 2025-10-01*
*SwiftMath Version: Based on iosMath v0.9.5*
*Last Updated: 2025-10-01 - Implemented 4 major features: \substack, \smallmatrix, starred matrices, \iiiint*
*Last Updated: 2026-01-10 - Implemented manual delimiter sizing (\big, \Big, \bigg, \Bigg and variants)*
88 changes: 87 additions & 1 deletion Sources/SwiftMath/MathRender/MTMathAtomFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public class MTMathAtomFactory {
"omicron" : MTMathAtom(type: .variable, value: "\u{03BF}"),
"pi" : MTMathAtom(type: .variable, value: "\u{03C0}"),
"rho" : MTMathAtom(type: .variable, value: "\u{03C1}"),
"varsigma" : MTMathAtom(type: .variable, value: "\u{03C1}"),
"varsigma" : MTMathAtom(type: .variable, value: "\u{03C2}"),
"sigma" : MTMathAtom(type: .variable, value: "\u{03C3}"),
"tau" : MTMathAtom(type: .variable, value: "\u{03C4}"),
"upsilon" : MTMathAtom(type: .variable, value: "\u{03C5}"),
Expand All @@ -177,6 +177,8 @@ public class MTMathAtomFactory {
"phi" : MTMathAtom(type: .ordinary, value: "\u{0001D719}"),
"varrho" : MTMathAtom(type: .ordinary, value: "\u{0001D71A}"),
"varpi" : MTMathAtom(type: .ordinary, value: "\u{0001D71B}"),
"varkappa" : MTMathAtom(type: .ordinary, value: "\u{03F0}"),
// Note: digamma (U+03DD) and Digamma (U+03DC) are not supported by Latin Modern Math font

// Capital greek characters
"Gamma" : MTMathAtom(type: .variable, value: "\u{0393}"),
Expand Down Expand Up @@ -227,11 +229,16 @@ public class MTMathAtomFactory {
"Longleftarrow" : MTMathAtom(type: .relation, value: "\u{27F8}"),
"Longrightarrow" : MTMathAtom(type: .relation, value: "\u{27F9}"),
"Longleftrightarrow" : MTMathAtom(type: .relation, value: "\u{27FA}"),
"longmapsto" : MTMathAtom(type: .relation, value: "\u{27FC}"),
"hookrightarrow" : MTMathAtom(type: .relation, value: "\u{21AA}"),
"hookleftarrow" : MTMathAtom(type: .relation, value: "\u{21A9}"),


// Relations
"leq" : MTMathAtom(type: .relation, value: UnicodeSymbol.lessEqual),
"geq" : MTMathAtom(type: .relation, value: UnicodeSymbol.greaterEqual),
"leqslant" : MTMathAtom(type: .relation, value: "\u{2A7D}"),
"geqslant" : MTMathAtom(type: .relation, value: "\u{2A7E}"),
"neq" : MTMathAtom(type: .relation, value: UnicodeSymbol.notEqual),
"in" : MTMathAtom(type: .relation, value: "\u{2208}"),
"notin" : MTMathAtom(type: .relation, value: "\u{2209}"),
Expand All @@ -250,6 +257,8 @@ public class MTMathAtomFactory {
"ll" : MTMathAtom(type: .relation, value: "\u{226A}"),
"prec" : MTMathAtom(type: .relation, value: "\u{227A}"),
"succ" : MTMathAtom(type: .relation, value: "\u{227B}"),
"preceq" : MTMathAtom(type: .relation, value: "\u{2AAF}"),
"succeq" : MTMathAtom(type: .relation, value: "\u{2AB0}"),
"subset" : MTMathAtom(type: .relation, value: "\u{2282}"),
"supset" : MTMathAtom(type: .relation, value: "\u{2283}"),
"subseteq" : MTMathAtom(type: .relation, value: "\u{2286}"),
Expand All @@ -259,9 +268,79 @@ public class MTMathAtomFactory {
"sqsubseteq" : MTMathAtom(type: .relation, value: "\u{2291}"),
"sqsupseteq" : MTMathAtom(type: .relation, value: "\u{2292}"),
"models" : MTMathAtom(type: .relation, value: "\u{22A7}"),
"vdash" : MTMathAtom(type: .relation, value: "\u{22A2}"),
"dashv" : MTMathAtom(type: .relation, value: "\u{22A3}"),
"bowtie" : MTMathAtom(type: .relation, value: "\u{22C8}"),
"perp" : MTMathAtom(type: .relation, value: "\u{27C2}"),
"implies" : MTMathAtom(type: .relation, value: "\u{27F9}"),

// Negated relations (amssymb)
// Inequality negations
"nless" : MTMathAtom(type: .relation, value: "\u{226E}"),
"ngtr" : MTMathAtom(type: .relation, value: "\u{226F}"),
"nleq" : MTMathAtom(type: .relation, value: "\u{2270}"),
"ngeq" : MTMathAtom(type: .relation, value: "\u{2271}"),
"nleqslant" : MTMathAtom(type: .relation, value: "\u{2A87}"),
"ngeqslant" : MTMathAtom(type: .relation, value: "\u{2A88}"),
"lneq" : MTMathAtom(type: .relation, value: "\u{2A87}"),
"gneq" : MTMathAtom(type: .relation, value: "\u{2A88}"),
"lneqq" : MTMathAtom(type: .relation, value: "\u{2268}"),
"gneqq" : MTMathAtom(type: .relation, value: "\u{2269}"),
"lnsim" : MTMathAtom(type: .relation, value: "\u{22E6}"),
"gnsim" : MTMathAtom(type: .relation, value: "\u{22E7}"),
"lnapprox" : MTMathAtom(type: .relation, value: "\u{2A89}"),
"gnapprox" : MTMathAtom(type: .relation, value: "\u{2A8A}"),

// Ordering negations
"nprec" : MTMathAtom(type: .relation, value: "\u{2280}"),
"nsucc" : MTMathAtom(type: .relation, value: "\u{2281}"),
"npreceq" : MTMathAtom(type: .relation, value: "\u{22E0}"),
"nsucceq" : MTMathAtom(type: .relation, value: "\u{22E1}"),
"precneqq" : MTMathAtom(type: .relation, value: "\u{2AB5}"),
"succneqq" : MTMathAtom(type: .relation, value: "\u{2AB6}"),
"precnsim" : MTMathAtom(type: .relation, value: "\u{22E8}"),
"succnsim" : MTMathAtom(type: .relation, value: "\u{22E9}"),
"precnapprox" : MTMathAtom(type: .relation, value: "\u{2AB9}"),
"succnapprox" : MTMathAtom(type: .relation, value: "\u{2ABA}"),

// Similarity/congruence negations
"nsim" : MTMathAtom(type: .relation, value: "\u{2241}"),
"ncong" : MTMathAtom(type: .relation, value: "\u{2247}"),
"nmid" : MTMathAtom(type: .relation, value: "\u{2224}"),
"nshortmid" : MTMathAtom(type: .relation, value: "\u{2224}"),
"nparallel" : MTMathAtom(type: .relation, value: "\u{2226}"),
"nshortparallel" : MTMathAtom(type: .relation, value: "\u{2226}"),

// Set relation negations
"nsubseteq" : MTMathAtom(type: .relation, value: "\u{2288}"),
"nsupseteq" : MTMathAtom(type: .relation, value: "\u{2289}"),
"subsetneq" : MTMathAtom(type: .relation, value: "\u{228A}"),
"supsetneq" : MTMathAtom(type: .relation, value: "\u{228B}"),
"subsetneqq" : MTMathAtom(type: .relation, value: "\u{2ACB}"),
"supsetneqq" : MTMathAtom(type: .relation, value: "\u{2ACC}"),
"varsubsetneq" : MTMathAtom(type: .relation, value: "\u{228A}"),
"varsupsetneq" : MTMathAtom(type: .relation, value: "\u{228B}"),
"varsubsetneqq" : MTMathAtom(type: .relation, value: "\u{2ACB}"),
"varsupsetneqq" : MTMathAtom(type: .relation, value: "\u{2ACC}"),
"notni" : MTMathAtom(type: .relation, value: "\u{220C}"),
"nni" : MTMathAtom(type: .relation, value: "\u{220C}"),

// Triangle negations
"ntriangleleft" : MTMathAtom(type: .relation, value: "\u{22EA}"),
"ntriangleright" : MTMathAtom(type: .relation, value: "\u{22EB}"),
"ntrianglelefteq" : MTMathAtom(type: .relation, value: "\u{22EC}"),
"ntrianglerighteq" : MTMathAtom(type: .relation, value: "\u{22ED}"),

// Turnstile negations
"nvdash" : MTMathAtom(type: .relation, value: "\u{22AC}"),
"nvDash" : MTMathAtom(type: .relation, value: "\u{22AD}"),
"nVdash" : MTMathAtom(type: .relation, value: "\u{22AE}"),
"nVDash" : MTMathAtom(type: .relation, value: "\u{22AF}"),

// Square subset negations
"nsqsubseteq" : MTMathAtom(type: .relation, value: "\u{22E2}"),
"nsqsupseteq" : MTMathAtom(type: .relation, value: "\u{22E3}"),

// operators
"times" : MTMathAtomFactory.times(),
"div" : MTMathAtomFactory.divide(),
Expand All @@ -288,6 +367,7 @@ public class MTMathAtomFactory {
"odot" : MTMathAtom(type: .binaryOperator, value: "\u{2299}"),
"star" : MTMathAtom(type: .binaryOperator, value: "\u{22C6}"),
"cdot" : MTMathAtom(type: .binaryOperator, value: "\u{22C5}"),
"diamond" : MTMathAtom(type: .binaryOperator, value: "\u{22C4}"),
"amalg" : MTMathAtom(type: .binaryOperator, value: "\u{2A3F}"),

// No limit operators
Expand Down Expand Up @@ -388,19 +468,25 @@ public class MTMathAtomFactory {
"Re" : MTMathAtom(type: .ordinary, value: "\u{211C}"),
"mho" : MTMathAtom(type: .ordinary, value: "\u{2127}"),
"aleph" : MTMathAtom(type: .ordinary, value: "\u{2135}"),
"beth" : MTMathAtom(type: .ordinary, value: "\u{2136}"),
"gimel" : MTMathAtom(type: .ordinary, value: "\u{2137}"),
"daleth" : MTMathAtom(type: .ordinary, value: "\u{2138}"),
"forall" : MTMathAtom(type: .ordinary, value: "\u{2200}"),
"exists" : MTMathAtom(type: .ordinary, value: "\u{2203}"),
"nexists" : MTMathAtom(type: .ordinary, value: "\u{2204}"),
"emptyset" : MTMathAtom(type: .ordinary, value: "\u{2205}"),
"varnothing" : MTMathAtom(type: .ordinary, value: "\u{2205}"),
"nabla" : MTMathAtom(type: .ordinary, value: "\u{2207}"),
"infty" : MTMathAtom(type: .ordinary, value: "\u{221E}"),
"angle" : MTMathAtom(type: .ordinary, value: "\u{2220}"),
"measuredangle" : MTMathAtom(type: .ordinary, value: "\u{2221}"),
"top" : MTMathAtom(type: .ordinary, value: "\u{22A4}"),
"bot" : MTMathAtom(type: .ordinary, value: "\u{22A5}"),
"vdots" : MTMathAtom(type: .ordinary, value: "\u{22EE}"),
"cdots" : MTMathAtom(type: .ordinary, value: "\u{22EF}"),
"ddots" : MTMathAtom(type: .ordinary, value: "\u{22F1}"),
"triangle" : MTMathAtom(type: .ordinary, value: "\u{25B3}"),
"Box" : MTMathAtom(type: .ordinary, value: "\u{25A1}"),
"imath" : MTMathAtom(type: .ordinary, value: "\u{0001D6A4}"),
"jmath" : MTMathAtom(type: .ordinary, value: "\u{0001D6A5}"),
"upquote" : MTMathAtom(type: .ordinary, value: "\u{0027}"),
Expand Down
6 changes: 5 additions & 1 deletion Sources/SwiftMath/MathRender/MTMathList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -476,13 +476,17 @@ public class MTInner: MTMathAtom {
}
}
}

/// Optional explicit delimiter height (in points). When set, this overrides the automatic
/// delimiter sizing based on inner content. Used by \big, \Big, \bigg, \Bigg commands.
public var delimiterHeight: CGFloat?

init(_ inner:MTInner?) {
super.init(inner)
self.type = .inner
self.innerList = MTMathList(inner?.innerList)
self.leftBoundary = MTMathAtom(inner?.leftBoundary)
self.rightBoundary = MTMathAtom(inner?.rightBoundary)
self.delimiterHeight = inner?.delimiterHeight
}

override init() {
Expand Down
50 changes: 49 additions & 1 deletion Sources/SwiftMath/MathRender/MTMathListBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,34 @@ public struct MTMathListBuilder {
"supseteq": "\u{2289}", // ⊉ Not superset or equal
"=": "\u{2260}", // ≠ Not equal (alternative to \neq)
]


/// Delimiter sizing commands with their size multipliers (relative to font size).
/// Values based on standard TeX: at 10pt, \big=8.5pt, \Big=11.5pt, \bigg=14.5pt, \Bigg=17.5pt
/// These translate to approximately 0.85x, 1.15x, 1.45x, 1.75x of font size.
/// We use slightly larger values to ensure visible size differences.
public static let delimiterSizeCommands: [String: CGFloat] = [
// Basic sizing commands
"big": 1.0,
"Big": 1.4,
"bigg": 1.8,
"Bigg": 2.2,
// Left variants (same sizes, just semantic distinction in LaTeX)
"bigl": 1.0,
"Bigl": 1.4,
"biggl": 1.8,
"Biggl": 2.2,
// Right variants
"bigr": 1.0,
"Bigr": 1.4,
"biggr": 1.8,
"Biggr": 2.2,
// Middle variants (used between delimiters)
"bigm": 1.0,
"Bigm": 1.4,
"biggm": 1.8,
"Biggm": 2.2,
]

init(string: String) {
self.error = nil
self.string = string
Expand Down Expand Up @@ -939,6 +966,27 @@ public struct MTMathListBuilder {
self.setError(.invalidCommand, message: errorMessage)
return nil
}
} else if let sizeMultiplier = Self.delimiterSizeCommands[command] {
// Handle \big, \Big, \bigg, \Bigg and their left/right variants
let delim = self.readDelimiter()
if delim == nil {
let errorMessage = "Missing delimiter for \\\(command)"
self.setError(.missingDelimiter, message: errorMessage)
return nil
}
let boundary = MTMathAtomFactory.boundary(forDelimiter: delim!)
if boundary == nil {
let errorMessage = "Invalid delimiter for \\\(command): \(delim!)"
self.setError(.invalidDelimiter, message: errorMessage)
return nil
}

// Create an MTInner with explicit delimiter height
let inner = MTInner()
inner.leftBoundary = boundary
inner.innerList = MTMathList() // Empty inner list
inner.delimiterHeight = sizeMultiplier
return inner
} else {
let errorMessage = "Invalid command \\\(command)"
self.setError(.invalidCommand, message:errorMessage)
Expand Down
45 changes: 30 additions & 15 deletions Sources/SwiftMath/MathRender/MTTypesetter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2322,16 +2322,26 @@ class MTTypesetter {
func makeLeftRight(_ inner: MTInner?, maxWidth: CGFloat = 0) -> MTDisplay? {
assert(inner!.leftBoundary != nil || inner!.rightBoundary != nil, "Inner should have a boundary to call this function");

let innerListDisplay = MTTypesetter.createLineForMathList(inner!.innerList, font:font, style:style, cramped:cramped, spaced:true, maxWidth:maxWidth)
let axisHeight = styleFont.mathTable!.axisHeight
// delta is the max distance from the axis
let delta = max(innerListDisplay!.ascent - axisHeight, innerListDisplay!.descent + axisHeight);
let d1 = (delta / 500) * MTTypesetter.kDelimiterFactor; // This represents atleast 90% of the formula
let d2 = 2 * delta - MTTypesetter.kDelimiterShortfallPoints; // This represents a shortfall of 5pt
// The size of the delimiter glyph should cover at least 90% of the formula or
// be at most 5pt short.
let glyphHeight = max(d1, d2);

let glyphHeight: CGFloat

// Check if we have an explicit delimiter height (from \big, \Big, etc.)
if let delimiterMultiplier = inner!.delimiterHeight {
// delimiterHeight is a multiplier (e.g., 1.2, 1.8, 2.4, 3.0)
// Multiply by font size to get actual height
glyphHeight = styleFont.fontSize * delimiterMultiplier
} else {
// Calculate height based on inner content (for \left...\right)
let innerListDisplay = MTTypesetter.createLineForMathList(inner!.innerList, font:font, style:style, cramped:cramped, spaced:true, maxWidth:maxWidth)
let axisHeight = styleFont.mathTable!.axisHeight
// delta is the max distance from the axis
let delta = max(innerListDisplay!.ascent - axisHeight, innerListDisplay!.descent + axisHeight);
let d1 = (delta / 500) * MTTypesetter.kDelimiterFactor; // This represents atleast 90% of the formula
let d2 = 2 * delta - MTTypesetter.kDelimiterShortfallPoints; // This represents a shortfall of 5pt
// The size of the delimiter glyph should cover at least 90% of the formula or
// be at most 5pt short.
glyphHeight = max(d1, d2);
}

var innerElements = [MTDisplay]()
var position = CGPoint.zero
if inner!.leftBoundary != nil && !inner!.leftBoundary!.nucleus.isEmpty {
Expand All @@ -2340,11 +2350,16 @@ class MTTypesetter {
position.x += leftGlyph!.width
innerElements.append(leftGlyph!)
}

innerListDisplay!.position = position;
position.x += innerListDisplay!.width;
innerElements.append(innerListDisplay!)


// Only include inner content if not using explicit delimiter height
// (explicit height commands like \big produce standalone delimiters)
if inner!.delimiterHeight == nil {
let innerListDisplay = MTTypesetter.createLineForMathList(inner!.innerList, font:font, style:style, cramped:cramped, spaced:true, maxWidth:maxWidth)
innerListDisplay!.position = position;
position.x += innerListDisplay!.width;
innerElements.append(innerListDisplay!)
}

if inner!.rightBoundary != nil && !inner!.rightBoundary!.nucleus.isEmpty {
let rightGlyph = self.findGlyphForBoundary(inner!.rightBoundary!.nucleus, withHeight:glyphHeight)
rightGlyph!.position = position;
Expand Down
Loading