diff --git a/MISSING_FEATURES.md b/MISSING_FEATURES.md new file mode 100644 index 0000000..9ae210a --- /dev/null +++ b/MISSING_FEATURES.md @@ -0,0 +1,236 @@ +# SwiftMath Missing Features - Implementation Status + +This document lists LaTeX features that are **not yet implemented** in SwiftMath, based on comprehensive testing against the LaTeX Mathematics reference. + +## Summary + +- **Total Features Tested**: 11 +- **Fully Implemented**: 6 (55%) +- **Partially Implemented**: 0 (0%) +- **Not Implemented**: 5 (45%) + +--- + +## HIGH PRIORITY Features (Not Implemented) + +### 1. ✅ `\displaystyle` and `\textstyle` - **IMPLEMENTED** +**Status**: ✅ Working +**Description**: Commands to force display or text style rendering within expressions + +**Test Results**: All tests passed +- `\displaystyle \sum_{i=1}^{n} x_i` - ✅ Works +- `\textstyle \int_{0}^{\infty} f(x) dx` - ✅ Works +- Inline displaystyle fractions - ✅ Works + +--- + +### 2. ❌ `\middle` - Delimiter in Middle of Expression +**Status**: ❌ Not Implemented +**Error**: `Invalid command \middle` + +**Description**: Used with `\left` and `\right` to add delimiters in the middle of expressions + +**Examples**: +```latex +\left( \frac{a}{b} \middle| \frac{c}{d} \right) +\left\{ x \middle\| y \right\} +``` + +**Use Case**: Set notation, conditional expressions, piecewise functions with multiple sections + +--- + +### 3. ✅ `\substack` - Multi-line Limits and Subscripts - **IMPLEMENTED** +**Status**: ✅ Working +**Description**: Creates multi-line subscripts and limits for operators + +**Test Results**: All tests passed +- `\substack{a \\ b}` - ✅ Works +- `\sum_{\substack{0 \le i \le m \\ 0 < j < n}} P(i,j)` - ✅ Works (nested in subscript) +- `\prod_{\substack{p \text{ prime} \\ p < 100}} p` - ✅ Works (nested in subscript) +- `\substack{\frac{a}{b} \\ c}` - ✅ Works (with nested commands) + +**Use Case**: Complex summation/product limits, constrained expressions + +**Implementation**: Uses `buildInternal(true)` pattern, handles implicit tables created by `\\` within braces. + +--- + +### 4. ❌ Manual Delimiter Sizing: `\big`, `\Big`, `\bigg`, `\Bigg` +**Status**: ❌ Not Implemented +**Error**: `Invalid command \big` + +**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 +``` + +**Use Case**: Fine control over delimiter appearance, nested expressions + +--- + +### 5. ❌ Spacing Commands: `\,`, `\:`, `\;`, `\!` +**Status**: ❌ Partially Not Implemented +**Error**: `Invalid command \:` (and likely others) + +**Description**: Fine-tuned horizontal spacing control + +| Command | Description | Width | +|---------|-------------|-------| +| `\,` | Thin space | 3/18 em | +| `\:` | Medium space | 4/18 em | +| `\;` | Thick space | 5/18 em | +| `\!` | Negative thin space | -3/18 em | + +**Examples**: +```latex +a\,b % thin space +\int\!\!\!\int f(x,y) dx dy % tight double integral +x \, y \: z \; w % mixed spacing +``` + +**Use Case**: Fine typography control, integral notation, custom spacing + +--- + +## MEDIUM PRIORITY Features + +### 6. ✅ Multiple Integral Symbols: `\iint`, `\iiint`, `\iiiint` - **IMPLEMENTED** +**Status**: ✅ Working +**Description**: Special symbols for double, triple, and quadruple integrals + +**Test Results**: All tests passed +- `\iint f(x,y) dx dy` - ✅ Works (double integral) +- `\iiint f(x,y,z) dx dy dz` - ✅ Works (triple integral) +- `\iiiint f(w,x,y,z) dw dx dy dz` - ✅ Works (quadruple integral) +- `\iint_{D} f(x,y) dA` - ✅ Works (with subscript limits) + +**Use Case**: Multivariable calculus, surface and volume integrals + +**Implementation**: Added U+2A0C (quadruple integral) Unicode character to operator definitions. + +--- + +### 7. ✅ `\cfrac` - Continued Fractions - **IMPLEMENTED** +**Status**: ✅ Working +**Description**: Optimized layout for continued fractions + +**Test Results**: All tests passed +- Simple `\cfrac{1}{2}` - ✅ Works +- Nested continued fractions - ✅ Works + +--- + +### 8. ❌ `\boldsymbol` - Bold Greek Letters +**Status**: ❌ Not Implemented +**Error**: `Invalid command \boldsymbol` + +**Description**: Creates bold Greek letters (whereas `\mathbf` doesn't work for Greek) + +**Examples**: +```latex +\boldsymbol{\alpha} % bold alpha +\boldsymbol{\beta} % bold beta +\boldsymbol{\Gamma} % bold Gamma +\mathbf{x} + \boldsymbol{\mu} % mix Roman and Greek bold +``` + +**Use Case**: Vectors with Greek symbols, bold emphasis for Greek letters + +--- + +### 9. ✅ Starred Matrix Environments: `pmatrix*`, `bmatrix*`, etc. - **IMPLEMENTED** +**Status**: ✅ Working +**Description**: Matrix environments with optional column alignment + +**Test Results**: All tests passed +- `\begin{pmatrix*}[r] 1 & 2 \\ 3 & 4 \end{pmatrix*}` - ✅ Works (right align) +- `\begin{bmatrix*}[l] a & b \\ c & d \end{bmatrix*}` - ✅ Works (left align) +- `\begin{vmatrix*}[c] x & y \\ z & w \end{vmatrix*}` - ✅ Works (center align) +- `\begin{matrix*}[r] 10 & 20 \\ 30 & 40 \end{matrix*}` - ✅ Works (no delimiters) + +**Alignment Options**: `[r]` = right, `[l]` = left, `[c]` = center + +**Use Case**: Financial tables, aligned numerical data in matrices + +**Implementation**: Added `readOptionalAlignment()` function, modified `readString()` to accept asterisks, applies alignment to all columns. + +--- + +### 10. ✅ `\smallmatrix` Environment - **IMPLEMENTED** +**Status**: ✅ Working +**Description**: Compact matrix for inline use (smaller than regular matrices) + +**Test Results**: All tests passed +- `\left( \begin{smallmatrix} a & b \\ c & d \end{smallmatrix} \right)` - ✅ Works (with delimiters) +- `A = \left( \begin{smallmatrix} 1 & 0 \\ 0 & 1 \end{smallmatrix} \right)` - ✅ Works (identity matrix) +- `\begin{smallmatrix} x \\ y \end{smallmatrix}` - ✅ Works (column vector) + +**Use Case**: Inline matrices, transformation matrices in text, compact notation + +**Implementation**: Uses `.script` style for smaller font size, tighter column spacing (6 vs 18), no built-in delimiters. + +--- + +## Implementation Priority Recommendations + +### 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 + +### Remaining Medium Priority Features +4. **`\boldsymbol`** - Important for vector notation with Greek letters + +--- + +## Testing Coverage + +All tests use the `MTMathListBuilder.build(fromString:error:)` API and automatically skip with `XCTSkip` when features are not implemented. + +**Test File**: `Tests/SwiftMathTests/MTMathListBuilderTests.swift` +**Test Functions**: +- `testDisplayStyle()` - ✅ Passed (IMPLEMENTED) +- `testMiddleDelimiter()` - ⏭️ Skipped (not implemented) +- `testSubstack()` - ✅ Passed (IMPLEMENTED) +- `testManualDelimiterSizing()` - ⏭️ Skipped (not implemented) +- `testSpacingCommands()` - ⏭️ Skipped (not implemented) +- `testMultipleIntegrals()` - ✅ Passed (IMPLEMENTED) +- `testContinuedFractions()` - ✅ Passed (IMPLEMENTED) +- `testBoldsymbol()` - ⏭️ Skipped (not implemented) +- `testStarredMatrices()` - ✅ Passed (IMPLEMENTED) +- `testSmallMatrix()` - ✅ Passed (IMPLEMENTED) + +--- + +## Notes for Future Implementation + +### For `\middle`: +- 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 (`\!`) +- Some might already be partially implemented + +### For `\boldsymbol`: +- Needs access to bold math font variants +- Should work with both Greek and other symbols +- Different from `\mathbf` (which changes font family) + +--- + +*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* diff --git a/README.md b/README.md index 17510b1..d4906d0 100644 --- a/README.md +++ b/README.md @@ -164,23 +164,25 @@ struct MathView: NSViewRepresentable { This is a list of formula types that the library currently supports: * Simple algebraic equations -* Fractions and continued fractions +* Fractions and continued fractions (including `\cfrac`) * Exponents and subscripts * Trigonometric formulae * Square roots and n-th roots -* Calculus symbos - limits, derivatives, integrals +* Calculus symbols - limits, derivatives, integrals (including `\iint`, `\iiint`, `\iiiint`) * Big operators (e.g. product, sum) -* Big delimiters (using \\left and \\right) +* Big delimiters (using `\left` and `\right`) * Greek alphabet -* Combinatorics (\\binom, \\choose etc.) +* Combinatorics (`\binom`, `\choose` etc.) * Geometry symbols (e.g. angle, congruence etc.) * Ratios, proportions, percentages * Math spacing * Overline and underline * Math accents -* Matrices +* Matrices (including `\smallmatrix` and starred variants like `pmatrix*` with alignment) +* Multi-line subscripts and limits (`\substack`) * Equation alignment -* Change bold, roman, caligraphic and other font styles (\\bf, \\text, etc.) +* Change bold, roman, caligraphic and other font styles (`\bf`, `\text`, etc.) +* Style commands (`\displaystyle`, `\textstyle`) * Most commonly used math symbols * Colors for both text and background * **Inline and display math mode delimiters** (see below) @@ -311,6 +313,37 @@ It is also possible to set different colors for different parts of the equation. Just access the `displayList` field and set the `textColor` of the underlying displays of which you want to change the color. +##### Fallback Font for Unicode Text +By default, math fonts only support a limited set of characters (Latin, Greek, common math symbols). +To display other Unicode characters like Chinese, Japanese, Korean, emoji, or other scripts in `\text{}` +commands, you can configure a fallback font: + +```swift +let mathFont = MTFontManager().font(withName: MathFont.latinModernFont.rawValue, size: 30) + +// Set a fallback font for unsupported characters (defaults to nil) +#if os(iOS) || os(visionOS) +let systemFont = UIFont.systemFont(ofSize: 30) +mathFont?.fallbackFont = CTFontCreateWithName(systemFont.fontName as CFString, 30, nil) +#elseif os(macOS) +let systemFont = NSFont.systemFont(ofSize: 30) +mathFont?.fallbackFont = CTFontCreateWithName(systemFont.fontName as CFString, 30, nil) +#endif + +label.font = mathFont +label.latex = "\\text{Hello 世界 🌍}" // English, Chinese, and emoji +``` + +When the main math font doesn't contain a glyph for a character, the fallback font will be used automatically. +This is particularly useful for: +- Chinese text: `\text{中文}` +- Japanese text: `\text{日本語}` +- Korean text: `\text{한국어}` +- Emoji: `\text{Math is fun! 🎉📐}` +- Mixed scripts: `\text{Equation: 方程式}` + +**Note**: The fallback font only applies to characters within `\text{}` commands, not regular math mode. + ##### Custom Commands You can define your own commands that are not already predefined. This is similar to macros is LaTeX. To define your own command use: @@ -350,8 +383,13 @@ Note this is not a complete implementation of LaTeX math mode. There are some important pieces that are missing and will be included in future updates. This includes: -* Support for explicit big delimiters (bigl, bigr etc.) -* Addition of missing plain TeX commands +* Support for explicit big delimiters (`\big`, `\Big`, `\bigg`, `\Bigg`, etc.) +* `\middle` delimiter for use between `\left` and `\right` +* Fine spacing commands (`\,`, `\:`, `\;`, `\!`) +* Bold symbol command (`\boldsymbol`) +* Addition of missing plain TeX commands + +For a complete list of missing features and their implementation status, see [MISSING_FEATURES.md](MISSING_FEATURES.md). ## License diff --git a/Sources/SwiftMath/MathRender/MTFont.swift b/Sources/SwiftMath/MathRender/MTFont.swift old mode 100755 new mode 100644 index 3dd3042..24a2d2a --- a/Sources/SwiftMath/MathRender/MTFont.swift +++ b/Sources/SwiftMath/MathRender/MTFont.swift @@ -11,12 +11,18 @@ import CoreText // public class MTFont { - + var defaultCGFont: CGFont! var ctFont: CTFont! var mathTable: MTFontMathTable? var rawMathTable: NSDictionary? - + + /// Fallback font for characters not supported by the main math font. + /// Defaults to the system font at the same size. This is particularly useful + /// for rendering text in \text{} commands with characters outside the math font's coverage + /// (e.g., Chinese, Japanese, Korean, emoji, etc.) + public var fallbackFont: CTFont? + init() {} /// `MTFont(fontWithName:)` does not load the complete math font, it only has about half the glyphs of the full math font. diff --git a/Sources/SwiftMath/MathRender/MTMathListBuilder.swift b/Sources/SwiftMath/MathRender/MTMathListBuilder.swift index dca3d23..d7a7c97 100644 --- a/Sources/SwiftMath/MathRender/MTMathListBuilder.swift +++ b/Sources/SwiftMath/MathRender/MTMathListBuilder.swift @@ -438,8 +438,15 @@ public struct MTMathListBuilder { } else { atom = MTMathAtomFactory.atom(forCharacter: char) if atom == nil { - // Not a recognized character - continue + // Not a recognized character in standard math mode + // In text mode (spacesAllowed && roman style), accept any Unicode character for fallback font support + // This enables Chinese, Japanese, Korean, emoji, etc. in \text{} commands + if spacesAllowed && currentFontStyle == .roman { + atom = MTMathAtom(type: .ordinary, value: String(char)) + } else { + // In math mode or non-text commands, skip unrecognized characters + continue + } } } diff --git a/Sources/SwiftMath/MathRender/MTTypesetter.swift b/Sources/SwiftMath/MathRender/MTTypesetter.swift index 6a6f0c8..db86de6 100644 --- a/Sources/SwiftMath/MathRender/MTTypesetter.swift +++ b/Sources/SwiftMath/MathRender/MTTypesetter.swift @@ -1276,11 +1276,18 @@ class MTTypesetter { func findGlyphForCharacterAtIndex(_ index:String.Index, inString str:String) -> CGGlyph { // Get the character at index taking into account UTF-32 characters var chars = Array(str[index].utf16) - + // Get the glyph from the font var glyph = [CGGlyph](repeating: CGGlyph.zero, count: chars.count) let found = CTFontGetGlyphsForCharacters(styleFont.ctFont, &chars, &glyph, chars.count) if !found { + // Try fallback font if available + if let fallbackFont = styleFont.fallbackFont { + let fallbackFound = CTFontGetGlyphsForCharacters(fallbackFont, &chars, &glyph, chars.count) + if fallbackFound { + return glyph[0] + } + } // the font did not contain a glyph for our character, so we just return 0 (notdef) return 0 } diff --git a/Tests/SwiftMathTests/MathFontTests.swift b/Tests/SwiftMathTests/MathFontTests.swift index 898a0b2..b690156 100644 --- a/Tests/SwiftMathTests/MathFontTests.swift +++ b/Tests/SwiftMathTests/MathFontTests.swift @@ -145,4 +145,44 @@ final class MathFontTests: XCTestCase { } queue.async(group: group, execute: workitem) } + + func testFallbackFont() throws { + #if os(iOS) || os(visionOS) + let systemFont = UIFont.systemFont(ofSize: 20) + let systemCTFont = CTFontCreateWithName(systemFont.fontName as CFString, 20, nil) + #elseif os(macOS) + let systemFont = NSFont.systemFont(ofSize: 20) + let systemCTFont = CTFontCreateWithName(systemFont.fontName as CFString, 20, nil) + #endif + + // Create a math font with fallback + guard let mathFont = MTFontManager().font(withName: MathFont.latinModernFont.rawValue, size: 20) else { + XCTFail("Failed to create math font") + return + } + mathFont.fallbackFont = systemCTFont + + // Build a math list with Chinese text + var error: NSError? + let mathList = MTMathListBuilder.build(fromString: "\\text{中文测试}", error: &error) + + XCTAssertNil(error, "Should parse Chinese text without error") + XCTAssertNotNil(mathList, "Math list should be created") + + // \text{...} creates atoms for each character (4 Chinese characters = 4 atoms) + XCTAssertEqual(mathList?.atoms.count, 4, "Should have 4 atoms for 4 Chinese characters") + + // Verify atoms have the correct font style (roman for text) + for atom in mathList?.atoms ?? [] { + XCTAssertEqual(atom.fontStyle, .roman, "Text atoms should have roman font style") + } + + // Create a display to verify glyph rendering works with fallback + let display = MTTypesetter.createLineForMathList(mathList!, font: mathFont, style: .text) + + XCTAssertNotNil(display, "Display should be created with fallback font") + + // Verify the display was actually created (would be nil if all glyphs failed) + XCTAssertGreaterThan(display?.width ?? 0, 0, "Display should have non-zero width with fallback font") + } }