Merge pull request #54 from nguillot/dfrac_support

Add support for dfrac and tfrac LaTeX commands
This commit is contained in:
mgriebling
2025-11-04 09:54:02 -05:00
committed by GitHub
4 changed files with 191 additions and 4 deletions

View File

@@ -4,10 +4,10 @@ This document lists LaTeX features that are **not yet implemented** in SwiftMath
## Summary ## Summary
- **Total Features Tested**: 11 - **Total Features Tested**: 12
- **Fully Implemented**: 6 (55%) - **Fully Implemented**: 7 (58%)
- **Partially Implemented**: 0 (0%) - **Partially Implemented**: 0 (0%)
- **Not Implemented**: 5 (45%) - **Not Implemented**: 5 (42%)
--- ---
@@ -126,6 +126,25 @@ x \, y \: z \; w % mixed spacing
--- ---
### 7b. ✅ `\dfrac` and `\tfrac` - Display/Text Style Fractions - **IMPLEMENTED**
**Status**: ✅ Working
**Description**: Fractions with forced display or text style
**Test Results**: All tests passed
- `\dfrac{1}{2}` - ✅ Works (display-style fraction)
- `\tfrac{a}{b}` - ✅ Works (text-style fraction)
- `y'=-\dfrac{2}{x^{3}}` - ✅ Works (complex expression)
- Nested `\dfrac` and `\tfrac` - ✅ Works
**Use Case**:
- `\dfrac` forces display style (larger, more readable fractions)
- `\tfrac` forces text style (smaller, inline fractions)
- Useful when you want consistent fraction appearance regardless of context
**Implementation**: Prepends style atoms to numerator and denominator to force rendering style.
---
### 8. ❌ `\boldsymbol` - Bold Greek Letters ### 8. ❌ `\boldsymbol` - Bold Greek Letters
**Status**: ❌ Not Implemented **Status**: ❌ Not Implemented
**Error**: `Invalid command \boldsymbol` **Error**: `Invalid command \boldsymbol`

View File

@@ -284,7 +284,7 @@ ScrollView {
This is a list of formula types that the library currently supports: This is a list of formula types that the library currently supports:
* Simple algebraic equations * Simple algebraic equations
* Fractions and continued fractions (including `\cfrac`) * Fractions and continued fractions (including `\frac`, `\dfrac`, `\tfrac`, `\cfrac`)
* Exponents and subscripts * Exponents and subscripts
* Trigonometric formulae * Trigonometric formulae
* Square roots and n-th roots * Square roots and n-th roots

View File

@@ -702,6 +702,34 @@ public struct MTMathListBuilder {
frac.numerator = self.buildInternal(true) frac.numerator = self.buildInternal(true)
frac.denominator = self.buildInternal(true) frac.denominator = self.buildInternal(true)
return frac; return frac;
} else if command == "dfrac" {
// Display-style fraction command has 2 arguments
let frac = MTFraction()
let numerator = self.buildInternal(true)
let denominator = self.buildInternal(true)
// Prepend \displaystyle to force display mode rendering
let displayStyle = MTMathStyle(style: .display)
numerator?.insert(displayStyle, at: 0)
denominator?.insert(displayStyle, at: 0)
frac.numerator = numerator
frac.denominator = denominator
return frac;
} else if command == "tfrac" {
// Text-style fraction command has 2 arguments
let frac = MTFraction()
let numerator = self.buildInternal(true)
let denominator = self.buildInternal(true)
// Prepend \textstyle to force text mode rendering
let textStyle = MTMathStyle(style: .text)
numerator?.insert(textStyle, at: 0)
denominator?.insert(textStyle, at: 0)
frac.numerator = numerator
frac.denominator = denominator
return frac;
} else if command == "binom" { } else if command == "binom" {
// A binom command has 2 arguments // A binom command has 2 arguments
let frac = MTFraction(hasRule: false) let frac = MTFraction(hasRule: false)
@@ -1048,6 +1076,34 @@ public struct MTMathListBuilder {
frac.numerator = self.buildInternal(true) frac.numerator = self.buildInternal(true)
frac.denominator = self.buildInternal(true) frac.denominator = self.buildInternal(true)
return frac return frac
} else if command == "dfrac" {
// Display-style fraction command has 2 arguments
let frac = MTFraction()
let numerator = self.buildInternal(true)
let denominator = self.buildInternal(true)
// Prepend \displaystyle to force display mode rendering
let displayStyle = MTMathStyle(style: .display)
numerator?.insert(displayStyle, at: 0)
denominator?.insert(displayStyle, at: 0)
frac.numerator = numerator
frac.denominator = denominator
return frac
} else if command == "tfrac" {
// Text-style fraction command has 2 arguments
let frac = MTFraction()
let numerator = self.buildInternal(true)
let denominator = self.buildInternal(true)
// Prepend \textstyle to force text mode rendering
let textStyle = MTMathStyle(style: .text)
numerator?.insert(textStyle, at: 0)
denominator?.insert(textStyle, at: 0)
frac.numerator = numerator
frac.denominator = denominator
return frac
} else if command == "binom" { } else if command == "binom" {
let frac = MTFraction(hasRule: false) let frac = MTFraction(hasRule: false)
frac.numerator = self.buildInternal(true) frac.numerator = self.buildInternal(true)

View File

@@ -2330,6 +2330,118 @@ final class MTMathListBuilderTests: XCTestCase {
} }
} }
func testDisplayStyleFraction() throws {
// Test \dfrac - display-style fraction
let str = "\\dfrac{1}{2}"
var error: NSError? = nil
let list = MTMathListBuilder.build(fromString: str, error: &error)
let desc = "Error for string: \(str)"
XCTAssertNil(error, desc)
let unwrappedList = try XCTUnwrap(list, desc)
XCTAssertEqual(unwrappedList.atoms.count, 1, desc)
let frac = try XCTUnwrap(unwrappedList.atoms[0] as? MTFraction, desc)
XCTAssertEqual(frac.type, .fraction, desc)
XCTAssertTrue(frac.hasRule, desc)
// Check numerator
let numerator = try XCTUnwrap(frac.numerator, desc)
XCTAssertTrue(numerator.atoms.count >= 1, "Numerator should have at least style atom")
// First atom should be displaystyle
if numerator.atoms.count > 1 {
let styleAtom = numerator.atoms[0] as? MTMathStyle
XCTAssertNotNil(styleAtom, "First atom should be style atom")
XCTAssertEqual(styleAtom?.style, .display, "Should be display style")
}
// Check denominator
let denominator = try XCTUnwrap(frac.denominator, desc)
XCTAssertTrue(denominator.atoms.count >= 1, "Denominator should have at least style atom")
if denominator.atoms.count > 1 {
let styleAtom = denominator.atoms[0] as? MTMathStyle
XCTAssertNotNil(styleAtom, "First atom should be style atom")
XCTAssertEqual(styleAtom?.style, .display, "Should be display style")
}
}
func testTextStyleFraction() throws {
// Test \tfrac - text-style fraction
let str = "\\tfrac{a}{b}"
var error: NSError? = nil
let list = MTMathListBuilder.build(fromString: str, error: &error)
let desc = "Error for string: \(str)"
XCTAssertNil(error, desc)
let unwrappedList = try XCTUnwrap(list, desc)
XCTAssertEqual(unwrappedList.atoms.count, 1, desc)
let frac = try XCTUnwrap(unwrappedList.atoms[0] as? MTFraction, desc)
XCTAssertEqual(frac.type, .fraction, desc)
XCTAssertTrue(frac.hasRule, desc)
// Check numerator
let numerator = try XCTUnwrap(frac.numerator, desc)
XCTAssertTrue(numerator.atoms.count >= 1, "Numerator should have at least style atom")
if numerator.atoms.count > 1 {
let styleAtom = numerator.atoms[0] as? MTMathStyle
XCTAssertNotNil(styleAtom, "First atom should be style atom")
XCTAssertEqual(styleAtom?.style, .text, "Should be text style")
}
// Check denominator
let denominator = try XCTUnwrap(frac.denominator, desc)
XCTAssertTrue(denominator.atoms.count >= 1, "Denominator should have at least style atom")
if denominator.atoms.count > 1 {
let styleAtom = denominator.atoms[0] as? MTMathStyle
XCTAssertNotNil(styleAtom, "First atom should be style atom")
XCTAssertEqual(styleAtom?.style, .text, "Should be text style")
}
}
func testDisplayAndTextStyleFractions() throws {
// Test the original LaTeX from the user's issue
let str = "y'=-\\dfrac{2}{x^{3}}"
var error: NSError? = nil
let list = MTMathListBuilder.build(fromString: str, error: &error)
let desc = "Error for string: \(str)"
XCTAssertNil(error, desc)
let unwrappedList = try XCTUnwrap(list, desc)
XCTAssertTrue(unwrappedList.atoms.count >= 4, "Should have y, ', =, -, and fraction")
// Find the fraction atom
var foundFraction = false
for atom in unwrappedList.atoms {
if atom.type == .fraction {
foundFraction = true
let frac = atom as! MTFraction
// Check that numerator has displaystyle
if let numerator = frac.numerator, numerator.atoms.count > 0 {
let firstAtom = numerator.atoms[0]
if let styleAtom = firstAtom as? MTMathStyle {
XCTAssertEqual(styleAtom.style, .display, "Should force display style")
}
}
break
}
}
XCTAssertTrue(foundFraction, "Should find fraction in the expression")
// Test nested dfrac and tfrac
let nestedStr = "\\dfrac{\\tfrac{a}{b}}{c}"
error = nil
let nestedList = MTMathListBuilder.build(fromString: nestedStr, error: &error)
XCTAssertNil(error, "Should parse nested dfrac/tfrac")
XCTAssertNotNil(nestedList, "Should parse nested dfrac/tfrac")
}
func testBoldsymbol() throws { func testBoldsymbol() throws {
// Test \boldsymbol for bold Greek letters // Test \boldsymbol for bold Greek letters
let testCases = [ let testCases = [