add a fallback font system to render CJK text in the \text command
This commit is contained in:
236
MISSING_FEATURES.md
Normal file
236
MISSING_FEATURES.md
Normal file
@@ -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*
|
||||
54
README.md
54
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
|
||||
|
||||
|
||||
10
Sources/SwiftMath/MathRender/MTFont.swift
Executable file → Normal file
10
Sources/SwiftMath/MathRender/MTFont.swift
Executable file → Normal file
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user