Clean slate

This commit is contained in:
Guille Gonzalez
2025-12-31 10:06:55 +01:00
parent d056918e1e
commit f805b3adf5
86 changed files with 775 additions and 703 deletions

View File

@@ -1,47 +0,0 @@
//
// ConcurrencyThreadsafeTests.swift
//
//
// Created by Peter Tang on 26/9/2023.
//
import XCTest
@testable import SwiftUIMath
final class ConcurrencyThreadsafeTests: XCTestCase {
private let executionQueue = DispatchQueue(label: "com.swiftmath.concurrencytests", attributes: .concurrent)
private let executionGroup = DispatchGroup()
let totalCases = 20
var testCount = 0
func testSwiftMathConcurrentScript() throws {
for caseNumber in 0 ..< totalCases {
helperConcurrency(caseNumber, in: executionGroup, on: executionQueue) {
let result1 = getInterElementSpaces()
let result2 = MTMathAtomFactory.delimValueToName
let result3 = MTMathAtomFactory.accentValueToName
let result4 = MTMathAtomFactory.textToLatexSymbolName
XCTAssertNotNil(result1)
XCTAssertNotNil(result2)
XCTAssertNotNil(result3)
XCTAssertNotNil(result4)
}
}
// executionGroup.notify(queue: .main) { [weak self] in
// // print("All test cases completed: \(self?.testCount ?? 0)")
// }
executionGroup.wait()
}
func helperConcurrency(_ count: Int, in group: DispatchGroup, on queue: DispatchQueue, _ testClosure: @escaping () -> (Void)) {
let workitem = DispatchWorkItem {
testClosure()
}
workitem.notify(queue: .main) { [weak self] in
self?.testCount += 1
}
queue.async(group: group, execute: workitem)
}
}

View File

@@ -1,48 +0,0 @@
//
// MTFontMathTableV2Tests.swift
//
//
// Created by Peter Tang on 15/9/2023.
//
import XCTest
@testable import SwiftUIMath
final class MTFontMathTableV2Tests: XCTestCase {
func testMTFontV2Script() throws {
let size = CGFloat(Int.random(in: 20 ... 40))
MathFont.allCases.forEach {
let mTable = $0.mtfont(size: size).mathTable
XCTAssertNotNil(mTable)
}
}
private let executionQueue = DispatchQueue(label: "com.swiftmath.mathbundle", attributes: .concurrent)
private let executionGroup = DispatchGroup()
let totalCases = 1000
var testCount = 0
func testConcurrentThreadsafeScript() throws {
testCount = 0
var mathFont: MathFont { .allCases.randomElement()! }
var size: CGFloat { CGFloat.random(in: 20 ... 40) }
let mtfonts = Array( 0 ..< 10 ).map { _ in mathFont.mtfont(size: size) }
for caseNumber in 0 ..< totalCases {
helperConcurrentMTFontMathTableV2(caseNumber, mtfont: mtfonts.randomElement()!, in: executionGroup, on: executionQueue)
}
executionGroup.notify(queue: .main) { [weak self] in
guard let self = self else { return }
XCTAssertEqual(self.testCount, totalCases)
}
executionGroup.wait()
}
func helperConcurrentMTFontMathTableV2(_ count: Int, mtfont: MTFontV2, in group: DispatchGroup, on queue: DispatchQueue) {
let workitem = DispatchWorkItem {
let mTable = mtfont.mathTable
XCTAssertNotNil(mTable)
}
workitem.notify(queue: .main) { [weak self] in
self?.testCount += 1
}
queue.async(group: group, execute: workitem)
}
}

View File

@@ -1,86 +0,0 @@
//
// MTFontV2Tests.swift
//
//
// Created by Peter Tang on 15/9/2023.
//
import XCTest
@testable import SwiftUIMath
final class MTFontV2Tests: XCTestCase {
func testMTFontV2Script() throws {
let size = CGFloat(Int.random(in: 20 ... 40))
MathFont.allCases.forEach {
let mtfont = $0.mtfont(size: size)
let mTable = mtfont.mathTable?._mathTable
XCTAssertNotNil(mtfont)
XCTAssertNotNil(mTable)
}
}
private let executionQueue = DispatchQueue(label: "com.swiftmath.mathbundle", attributes: .concurrent)
private let executionGroup = DispatchGroup()
let totalCases = 1000
var testCount = 0
func testConcurrentThreadsafeScript() throws {
testCount = 0
var mathFont: MathFont { .allCases.randomElement()! }
for caseNumber in 0 ..< totalCases {
helperConcurrentMTFontV2(caseNumber, mathFont: mathFont, in: executionGroup, on: executionQueue)
}
executionGroup.notify(queue: .main) { [weak self] in
guard let self = self else { return }
XCTAssertEqual(self.testCount, totalCases)
}
executionGroup.wait()
}
func helperConcurrentMTFontV2(_ count: Int, mathFont: MathFont, in group: DispatchGroup, on queue: DispatchQueue) {
let size = CGFloat.random(in: 20 ... 40)
let workitem = DispatchWorkItem {
let fontV2 = mathFont.mtfont(size: size)
XCTAssertNotNil(fontV2)
let (cgfont, ctfont) = (fontV2.defaultCGFont, fontV2.ctFont)
XCTAssertNotNil(cgfont)
XCTAssertNotNil(ctfont)
}
workitem.notify(queue: .main) { [weak self] in
// print("\(Thread.isMainThread ? "main" : "global") completed .....")
let fontV2 = mathFont.mtfont(size: size)
XCTAssertNotNil(fontV2)
let (cgfont, ctfont) = (fontV2.defaultCGFont, fontV2.ctFont)
XCTAssertNotNil(cgfont)
XCTAssertNotNil(ctfont)
let mTable = mathFont.rawMathTable()
XCTAssertNotNil(mTable)
self?.testCount += 1
}
queue.async(group: group, execute: workitem)
}
func testConcurrentThreadsafeMathTableLockScript() throws {
testCount = 0
var mathFont: MathFont { .allCases.randomElement()! }
var size: CGFloat { CGFloat.random(in: 20 ... 40) }
let mtfonts = Array( 0 ..< 5 ).map { _ in mathFont.mtfont(size: size) }
for caseNumber in 0 ..< totalCases {
helperConcurrentMTFontV2MathTableLock(caseNumber, mtfont: mtfonts.randomElement()!, in: executionGroup, on: executionQueue)
}
executionGroup.notify(queue: .main) { [weak self] in
guard let self = self else { return }
XCTAssertEqual(self.testCount, totalCases)
}
executionGroup.wait()
}
func helperConcurrentMTFontV2MathTableLock(_ count: Int, mtfont: MTFontV2, in group: DispatchGroup, on queue: DispatchQueue) {
let workitem = DispatchWorkItem {
let mathTable = mtfont.mathTable as? MTFontMathTableV2
// each mathTable is initialized once per mtfont with a NSLock.
// this is even when mathTable is accessed via different threads.
XCTAssertNotNil(mathTable)
}
workitem.notify(queue: .main) { [weak self] in
// print("\(Thread.isMainThread ? "main" : "global") completed .....")
self?.testCount += 1
}
queue.async(group: group, execute: workitem)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,624 +0,0 @@
import XCTest
@testable import SwiftUIMath
//
// MathRenderSwiftTests.swift
// MathRenderSwiftTests
//
// Created by Mike Griebling on 2023-01-02.
//
final class MTMathListTests: XCTestCase {
func testSubScript() throws {
let str = "-52x^{13+y}_{15-} + (-12.3 *)\\frac{-12}{15.2}"
let list = MTMathListBuilder.build(fromString: str)!
let finalized = list.finalized
try self.checkListContents(finalized)
// refinalizing a finalized list should not cause any more changes
try self.checkListContents(finalized.finalized)
}
func checkListContents(_ finalized:MTMathList) throws {
// check
XCTAssertEqual((finalized.atoms.count), 10, "Num atoms");
var atom = finalized.atoms[0];
XCTAssertEqual(atom.type, .unaryOperator, "Atom 0");
XCTAssertEqual(atom.nucleus, "", "Atom 0 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(0, 1)), "Range");
atom = finalized.atoms[1];
XCTAssertEqual(atom.type, .number, "Atom 1");
XCTAssertEqual(atom.nucleus, "52", "Atom 1 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(1, 2)), "Range");
atom = finalized.atoms[2];
XCTAssertEqual(atom.type, .variable, "Atom 2");
XCTAssertEqual(atom.nucleus, "x", "Atom 2 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(3, 1)), "Range");
let superScr = atom.superScript!
XCTAssertEqual((superScr.atoms.count), 3, "Super script");
atom = superScr.atoms[0];
XCTAssertEqual(atom.type, .number, "Super Atom 0");
XCTAssertEqual(atom.nucleus, "13", "Super Atom 0 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(0, 2)), "Range");
atom = superScr.atoms[1];
XCTAssertEqual(atom.type, .binaryOperator, "Super Atom 1");
XCTAssertEqual(atom.nucleus, "+", "Super Atom 1 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(2, 1)), "Range");
atom = superScr.atoms[2];
XCTAssertEqual(atom.type, .variable, "Super Atom 2");
XCTAssertEqual(atom.nucleus, "y", "Super Atom 2 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(3, 1)), "Range");
atom = finalized.atoms[2];
let subScr = atom.subScript!
XCTAssertEqual((subScr.atoms.count), 2, "Sub script");
atom = subScr.atoms[0];
XCTAssertEqual(atom.type, .number, "Sub Atom 0");
XCTAssertEqual(atom.nucleus, "15", "Sub Atom 0 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(0, 2)), "Range");
atom = subScr.atoms[1];
XCTAssertEqual(atom.type, .unaryOperator, "Sub Atom 1");
XCTAssertEqual(atom.nucleus, "", "Sub Atom 1 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(2, 1)), "Range");
atom = finalized.atoms[3];
XCTAssertEqual(atom.type, .binaryOperator, "Atom 3");
XCTAssertEqual(atom.nucleus, "+", "Atom 3 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(4, 1)), "Range");
atom = finalized.atoms[4];
XCTAssertEqual(atom.type, .open, "Atom 4");
XCTAssertEqual(atom.nucleus, "(", "Atom 4 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(5, 1)), "Range");
atom = finalized.atoms[5];
XCTAssertEqual(atom.type, .unaryOperator, "Atom 5");
XCTAssertEqual(atom.nucleus, "", "Atom 5 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(6, 1)), "Range");
atom = finalized.atoms[6];
XCTAssertEqual(atom.type, .number, "Atom 6");
XCTAssertEqual(atom.nucleus, "12.3", "Atom 6 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(7, 4)), "Range");
atom = finalized.atoms[7];
XCTAssertEqual(atom.type, .unaryOperator, "Atom 7");
XCTAssertEqual(atom.nucleus, "*", "Atom 7 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(11, 1)), "Range");
atom = finalized.atoms[8];
XCTAssertEqual(atom.type, .close, "Atom 8");
XCTAssertEqual(atom.nucleus, ")", "Atom 8 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(12, 1)), "Range");
let frac = finalized.atoms[9] as! MTFraction
XCTAssertEqual(frac.type, .fraction, "Atom 9");
XCTAssertEqual(frac.nucleus, "", "Atom 9 value");
XCTAssertTrue(NSEqualRanges(frac.indexRange, NSMakeRange(13, 1)), "Range");
let numer = frac.numerator!
XCTAssertNotNil(numer, "Numerator");
XCTAssertEqual((numer.atoms.count), 2, "Numer script");
atom = numer.atoms[0];
XCTAssertEqual(atom.type, .unaryOperator, "Numer Atom 0");
XCTAssertEqual(atom.nucleus, "", "Numer Atom 0 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(0, 1)), "Range");
atom = numer.atoms[1];
XCTAssertEqual(atom.type, .number, "Numer Atom 1");
XCTAssertEqual(atom.nucleus, "12", "Numer Atom 1 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(1, 2)), "Range");
let denom = frac.denominator!
XCTAssertNotNil(denom, "Denominator");
XCTAssertEqual((denom.atoms.count), 1, "Denom script");
atom = denom.atoms[0];
XCTAssertEqual(atom.type, .number, "Denom Atom 0");
XCTAssertEqual(atom.nucleus, "15.2", "Denom Atom 0 value");
XCTAssertTrue(NSEqualRanges(atom.indexRange, NSMakeRange(0, 4)), "Range");
}
func testAdd() throws {
let list = MTMathList()
XCTAssertEqual(list.atoms.count, 0);
let atom = MTMathAtomFactory.placeholder()
list.add(atom)
XCTAssertEqual(list.atoms.count, 1);
XCTAssertEqual(list.atoms[0], atom);
let atom2 = MTMathAtomFactory.placeholder()
list.add(atom2);
XCTAssertEqual(list.atoms.count, 2);
XCTAssertEqual(list.atoms[0], atom);
XCTAssertEqual(list.atoms[1], atom2);
}
private var options : XCTExpectedFailure.Options {
let op = XCTExpectedFailure.Options()
op.isStrict = true
return op
}
func testAddErrors() throws {
let list = MTMathList()
var atom : MTMathAtom? = nil
list.add(atom)
atom = MTMathAtom(type: .boundary, value: "")
XCTExpectFailure("Test adding an illegal atom", options:options) {
XCTAssertThrowsError(list.add(atom))
}
}
func testInsert() throws {
let list = MTMathList()
XCTAssertEqual(list.atoms.count, 0);
let atom = MTMathAtomFactory.placeholder()
list.insert(atom, at: 0)
XCTAssertEqual(list.atoms.count, 1);
XCTAssertEqual(list.atoms[0], atom);
let atom2 = MTMathAtomFactory.placeholder()
list.insert(atom2, at: 0)
XCTAssertEqual(list.atoms.count, 2);
XCTAssertEqual(list.atoms[0], atom2);
XCTAssertEqual(list.atoms[1], atom);
let atom3 = MTMathAtomFactory.placeholder()
list.insert(atom3, at: 2)
XCTAssertEqual(list.atoms.count, 3);
XCTAssertEqual(list.atoms[0], atom2);
XCTAssertEqual(list.atoms[1], atom);
XCTAssertEqual(list.atoms[2], atom3);
}
func testInsertErrors() throws {
let list = MTMathList()
var atom : MTMathAtom? = nil
list.insert(atom, at: 0)
atom = MTMathAtom(type: .boundary, value:"")
XCTExpectFailure("Test adding an illegal atom", options:options) {
XCTAssertThrowsError(list.insert(atom, at:0))
}
atom = MTMathAtomFactory.placeholder()
list.insert(atom, at:1)
}
func testAppend() throws {
let list1 = MTMathList()
let atom = MTMathAtomFactory.placeholder()
let atom2 = MTMathAtomFactory.placeholder()
let atom3 = MTMathAtomFactory.placeholder()
list1.add(atom)
list1.add(atom2)
list1.add(atom3)
let list2 = MTMathList()
let atom5 = MTMathAtomFactory.times()
let atom6 = MTMathAtomFactory.divide()
list2.add(atom5)
list2.add(atom6)
XCTAssertEqual(list1.atoms.count, 3);
XCTAssertEqual(list2.atoms.count, 2);
list1.append(list2)
XCTAssertEqual(list1.atoms.count, 5);
XCTAssertEqual(list1.atoms[3], atom5);
XCTAssertEqual(list1.atoms[4], atom6);
}
func testRemoveLast() throws {
let list = MTMathList()
let atom = MTMathAtomFactory.placeholder()
list.add(atom)
XCTAssertEqual(list.atoms.count, 1);
list.removeLastAtom()
XCTAssertEqual(list.atoms.count, 0);
// Removing from empty list.
list.removeLastAtom()
XCTAssertEqual(list.atoms.count, 0);
let atom2 = MTMathAtomFactory.placeholder()
list.add(atom)
list.add(atom2);
XCTAssertEqual(list.atoms.count, 2);
list.removeLastAtom()
XCTAssertEqual(list.atoms.count, 1);
XCTAssertEqual(list.atoms[0], atom);
}
func testRemoveAtomAtIndex() throws {
let list = MTMathList()
let atom = MTMathAtomFactory.placeholder()
let atom2 = MTMathAtomFactory.placeholder()
list.add(atom)
list.add(atom2);
XCTAssertEqual(list.atoms.count, 2);
list.removeAtom(at:0)
XCTAssertEqual(list.atoms.count, 1);
XCTAssertEqual(list.atoms[0], atom2);
// Index out of range
XCTExpectFailure("Test removing an out-of-index cell", options: options) {
XCTAssertThrowsError(list.removeAtom(at:2))
}
}
func testRemoveAtomsInRange() throws {
let list = MTMathList()
let atom = MTMathAtomFactory.placeholder()
let atom2 = MTMathAtomFactory.placeholder()
let atom3 = MTMathAtomFactory.placeholder()
list.add(atom)
list.add(atom2);
list.add(atom3)
XCTAssertEqual(list.atoms.count, 3)
list.removeAtoms(in: 1...2)
XCTAssertEqual(list.atoms.count, 1);
XCTAssertEqual(list.atoms[0], atom);
// Index out of range
XCTExpectFailure("Test removing an out-of-bounds range", options: options) {
XCTAssertThrowsError(list.removeAtoms(in: 1...3))
}
}
// func MTAssertEqual(test, expression1, expression2, ...) \
// _XCTPrimitiveAssertEqual(test, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)
//
// func MTAssertNotEqual(test, expression1, expression2, ...) \
// _XCTPrimitiveAssertNotEqual(test, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)
func checkAtomCopy(_ copy:MTMathAtom?, original:MTMathAtom?, forTest test:String) throws {
guard let copy = copy, let original = original else { return }
XCTAssertEqual(copy.type, original.type, test)
XCTAssertEqual(copy.nucleus, original.nucleus, test)
// Should be different objects with the same content
XCTAssertNotEqual(copy, original, test)
}
func checkListCopy(_ copy:MTMathList?, original:MTMathList?, forTest test:String) throws {
guard let copy = copy, let original = original else { return }
XCTAssertEqual(copy.atoms.count, original.atoms.count, test)
for (i, copyAtom) in copy.atoms.enumerated() {
let origAtom = original.atoms[i];
try self.checkAtomCopy(copyAtom, original:origAtom, forTest:test)
}
}
func testCopy() throws {
let list = MTMathList()
let atom = MTMathAtomFactory.placeholder()
let atom2 = MTMathAtomFactory.times()
let atom3 = MTMathAtomFactory.divide()
list.add(atom)
list.add(atom2);
list.add(atom3)
let list2 = MTMathList(list)
try checkListCopy(list2, original:list, forTest:self.description)
}
func testAtomInit() throws {
var atom = MTMathAtom(type: .open, value: "(")
XCTAssertEqual(atom.nucleus, "(")
XCTAssertEqual(atom.type, .open)
atom = MTMathAtom(type: .radical, value:"(")
XCTAssertEqual(atom.nucleus, "");
XCTAssertEqual(atom.type, .radical);
}
func testAtomScripts() throws {
var atom = MTMathAtom(type: .open, value:"(")
XCTAssertTrue(atom.isScriptAllowed())
atom.subScript = MTMathList()
XCTAssertNotNil(atom.subScript);
atom.superScript = MTMathList()
XCTAssertNotNil(atom.superScript);
atom = MTMathAtom(type: .boundary, value:"(")
XCTAssertFalse(atom.isScriptAllowed());
// Can set to nil
atom.subScript = nil;
XCTAssertNil(atom.subScript);
atom.superScript = nil;
XCTAssertNil(atom.superScript);
// Can't set to value
let list = MTMathList()
XCTExpectFailure("No sub/super-script on boundary atoms", options: options) {
XCTAssertThrowsError(atom.subScript = list)
XCTAssertThrowsError(atom.superScript = list)
}
}
func testAtomCopy() throws {
let list = MTMathList()
let atom1 = MTMathAtomFactory.placeholder()
let atom2 = MTMathAtomFactory.times()
let atom3 = MTMathAtomFactory.divide()
list.add(atom1)
list.add(atom2);
list.add(atom3)
let list2 = MTMathList()
list2.add(atom3)
list2.add(atom2)
let atom = MTMathAtom(type: .open, value:"(")
atom.subScript = list;
atom.superScript = list2;
let copy : MTMathAtom = atom.copy()
try checkAtomCopy(copy, original:atom, forTest:self.description)
try checkListCopy(copy.superScript, original:atom.superScript, forTest:self.description)
try checkListCopy(copy.subScript, original:atom.subScript, forTest:self.description)
}
func testCopyFraction() throws {
let list = MTMathList()
let atom = MTMathAtomFactory.placeholder()
let atom2 = MTMathAtomFactory.times()
let atom3 = MTMathAtomFactory.divide()
list.add(atom)
list.add(atom2);
list.add(atom3)
let list2 = MTMathList()
list2.add(atom3)
list2.add(atom2)
let frac = MTFraction(hasRule: false)
XCTAssertEqual(frac.type, .fraction);
frac.numerator = list;
frac.denominator = list2;
frac.leftDelimiter = "a";
frac.rightDelimiter = "b";
let copy = MTFraction(frac)
try checkAtomCopy(copy, original:frac, forTest:self.description)
try checkListCopy(copy.numerator, original:frac.numerator, forTest:self.description)
try checkListCopy(copy.denominator, original:frac.denominator, forTest:self.description)
XCTAssertFalse(copy.hasRule)
XCTAssertEqual(copy.leftDelimiter, "a");
XCTAssertEqual(copy.rightDelimiter, "b");
}
func testCopyRadical() throws {
let list = MTMathList()
let atom = MTMathAtomFactory.placeholder()
let atom2 = MTMathAtomFactory.times()
let atom3 = MTMathAtomFactory.divide()
list.add(atom)
list.add(atom2);
list.add(atom3)
let list2 = MTMathList()
list2.add(atom3)
list2.add(atom2)
let rad = MTRadical()
XCTAssertEqual(rad.type, .radical)
rad.radicand = list;
rad.degree = list2;
let copy = MTRadical(rad)
try checkAtomCopy(copy, original:rad, forTest:self.description)
try checkListCopy(copy.radicand, original:rad.radicand ,forTest:self.description)
try checkListCopy(copy.degree, original:rad.degree, forTest:self.description)
}
func testCopyLargeOperator() throws {
let lg = MTLargeOperator(value: "lim", limits:true)
XCTAssertEqual(lg.type, .largeOperator);
XCTAssertTrue(lg.limits);
let copy = MTLargeOperator(lg)
try checkAtomCopy(copy, original:lg, forTest:self.description)
XCTAssertEqual(copy.limits, lg.limits);
}
func testCopyInner() throws {
let list = MTMathList()
let atom = MTMathAtomFactory.placeholder()
let atom2 = MTMathAtomFactory.times()
let atom3 = MTMathAtomFactory.divide()
list.add(atom)
list.add(atom2);
list.add(atom3)
let inner = MTInner()
inner.innerList = list;
inner.leftBoundary = MTMathAtom(type: .boundary, value: "(")
inner.rightBoundary = MTMathAtom(type: .boundary, value:")")
XCTAssertEqual(inner.type, .inner);
let copy = MTInner(inner)
try checkAtomCopy(copy, original:inner, forTest:self.description)
try checkListCopy(copy.innerList, original:inner.innerList, forTest:self.description)
try checkAtomCopy(copy.leftBoundary!, original:inner.leftBoundary, forTest:self.description)
try checkAtomCopy(copy.rightBoundary, original:inner.rightBoundary, forTest:self.description)
}
func testSetInnerBoundary() throws {
let inner = MTInner()
// Can set non-nil
inner.leftBoundary = MTMathAtom(type: .boundary, value:"(")
inner.rightBoundary = MTMathAtom(type: .boundary, value:")")
XCTAssertNotNil(inner.leftBoundary);
XCTAssertNotNil(inner.rightBoundary);
// Can set nil
inner.leftBoundary = nil;
inner.rightBoundary = nil;
XCTAssertNil(inner.leftBoundary);
XCTAssertNil(inner.rightBoundary);
// Can't set non boundary
let atom = MTMathAtomFactory.placeholder()
XCTExpectFailure("Setting illegal boundary atoms", options: options) {
XCTAssertThrowsError(inner.leftBoundary = atom);
XCTAssertThrowsError(inner.rightBoundary = atom);
}
}
func testCopyOverline() throws {
let list = MTMathList()
let atom = MTMathAtomFactory.placeholder()
let atom2 = MTMathAtomFactory.times()
let atom3 = MTMathAtomFactory.divide()
list.add(atom)
list.add(atom2);
list.add(atom3)
let over = MTOverLine()
XCTAssertEqual(over.type, .overline);
over.innerList = list;
let copy = MTOverLine(over)
try checkAtomCopy(copy, original:over, forTest:self.description)
try checkListCopy(copy.innerList, original:over.innerList, forTest:self.description)
}
func testCopyUnderline() throws {
let list = MTMathList()
let atom = MTMathAtomFactory.placeholder()
let atom2 = MTMathAtomFactory.times()
let atom3 = MTMathAtomFactory.divide()
list.add(atom)
list.add(atom2);
list.add(atom3)
let under = MTUnderLine()
XCTAssertEqual(under.type, .underline);
under.innerList = list;
let copy = MTUnderLine(under)
try checkAtomCopy(copy, original:under, forTest:self.description)
try checkListCopy(copy.innerList, original:under.innerList, forTest:self.description)
}
func testCopyAcccent() throws {
let list = MTMathList()
let atom = MTMathAtomFactory.placeholder()
let atom2 = MTMathAtomFactory.times()
let atom3 = MTMathAtomFactory.divide()
list.add(atom)
list.add(atom2);
list.add(atom3)
let accent = MTAccent(value: "^")
XCTAssertEqual(accent.type, .accent);
accent.innerList = list;
let copy = MTAccent(accent)
try checkAtomCopy(copy, original:accent, forTest:self.description)
try checkListCopy(copy.innerList ,original:accent.innerList, forTest:self.description)
}
func testCopySpace() throws {
let space = MTMathSpace(space: 3)
XCTAssertEqual(space.type, .space);
let copy = MTMathSpace(space)
try checkAtomCopy(copy, original:space, forTest:self.description)
XCTAssertEqual(space.space, copy.space);
}
func testCopyStyle() throws {
let style = MTMathStyle(style: .script)
XCTAssertEqual(style.type, .style);
let copy = MTMathStyle(style)
try checkAtomCopy(copy, original:style, forTest:self.description)
XCTAssertEqual(style.style, copy.style);
}
func testCreateMathTable() throws {
let table = MTMathTable()
XCTAssertEqual(table.type, .table);
let list = MTMathList()
let atom = MTMathAtomFactory.placeholder()
let atom2 = MTMathAtomFactory.times()
let atom3 = MTMathAtomFactory.divide()
list.add(atom)
list.add(atom2);
list.add(atom3)
let list2 = MTMathList()
list2.add(atom3)
list2.add(atom2)
table.set(cell: list, forRow:3, column:2)
table.set(cell: list2, forRow:1, column:0)
table.set(alignment: .left, forColumn: 2)
table.set(alignment: .right, forColumn:1)
// Verify that everything is created correctly
XCTAssertEqual(table.cells.count, 4); // 4 rows
XCTAssertNotNil(table.cells[0]);
XCTAssertEqual(table.cells[0].count, 0); // 0 elements in row 0
XCTAssertEqual(table.cells[1].count, 1); // 1 element in row 1
XCTAssertNotNil(table.cells[2]);
XCTAssertEqual(table.cells[2].count, 0);
XCTAssertEqual(table.cells[3].count, 3);
// Verify the elements in the rows
XCTAssertEqual(table.cells[1][0].atoms.count, 2);
XCTAssertEqual(table.cells[1][0], list2);
XCTAssertNotNil(table.cells[3][0]);
XCTAssertEqual(table.cells[3][0].atoms.count, 0);
XCTAssertNotNil(table.cells[3][0]);
XCTAssertEqual(table.cells[3][0].atoms.count, 0);
XCTAssertNotNil(table.cells[3][1]);
XCTAssertEqual(table.cells[3][1].atoms.count, 0);
XCTAssertEqual(table.cells[3][2], list);
XCTAssertEqual(table.numRows, 4);
XCTAssertEqual(table.numColumns, 3);
// Verify the alignments
XCTAssertEqual(table.alignments.count, 3);
XCTAssertEqual(table.alignments[0], .center);
XCTAssertEqual(table.alignments[1], .right);
XCTAssertEqual(table.alignments[2], .left);
}
func testCopyMathTable() throws {
let table = MTMathTable()
XCTAssertEqual(table.type, .table);
let list = MTMathList()
let atom = MTMathAtomFactory.placeholder()
let atom2 = MTMathAtomFactory.times()
let atom3 = MTMathAtomFactory.divide()
list.add(atom)
list.add(atom2);
list.add(atom3)
let list2 = MTMathList()
list2.add(atom3)
list2.add(atom2)
table.set(cell:list, forRow:0, column:1)
table.set(cell:list2, forRow:0, column:2)
table.set(alignment: .left, forColumn:2)
table.set(alignment: .right, forColumn:1)
table.interRowAdditionalSpacing = 3;
table.interColumnSpacing = 10;
let copy = MTMathTable(table)
try checkAtomCopy(copy, original:table, forTest:self.description)
XCTAssertEqual(copy.interColumnSpacing, table.interColumnSpacing);
XCTAssertEqual(copy.interRowAdditionalSpacing, table.interRowAdditionalSpacing);
XCTAssertEqual(copy.alignments, table.alignments)
XCTAssertNotEqual(copy.cells, table.cells);
XCTAssertNotEqual(copy.cells[0], table.cells[0] );
XCTAssertEqual(copy.cells[0].count, table.cells[0].count);
XCTAssertEqual(copy.cells[0][0].atoms.count, 0);
XCTAssertNotEqual(copy.cells[0][0], table.cells[0][0]);
try checkListCopy(copy.cells[0][1], original:list, forTest:self.description)
try checkListCopy(copy.cells[0][2], original:list2, forTest:self.description)
}
}

View File

@@ -1,737 +0,0 @@
//
// MTMathUILabelLineWrappingTests.swift
// SwiftMathTests
//
// Tests for line wrapping functionality in MTMathUILabel
//
import XCTest
@testable import SwiftUIMath
class MTMathUILabelLineWrappingTests: XCTestCase {
func testBasicIntrinsicContentSize() {
let label = MTMathUILabel()
label.latex = "\\(x + y\\)"
label.font = MTFontManager.fontManager.defaultFont
// Debug: check if parsing worked
XCTAssertNotNil(label.mathList, "Math list should not be nil")
XCTAssertNil(label.error, "Should have no parsing error, got: \(String(describing: label.error))")
XCTAssertNotNil(label.font, "Font should not be nil")
let size = label.intrinsicContentSize
XCTAssertGreaterThan(size.width, 0, "Width should be greater than 0, got \(size.width)")
XCTAssertGreaterThan(size.height, 0, "Height should be greater than 0, got \(size.height)")
}
func testTextModeIntrinsicContentSize() {
let label = MTMathUILabel()
label.latex = "\\(\\text{Hello World}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
let size = label.intrinsicContentSize
XCTAssertGreaterThan(size.width, 0, "Width should be greater than 0, got \(size.width)")
XCTAssertGreaterThan(size.height, 0, "Height should be greater than 0, got \(size.height)")
}
func testLongTextIntrinsicContentSize() {
let label = MTMathUILabel()
label.latex = "\\(\\text{Rappelons la conversion : 1 km équivaut à 1000 m.}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
let size = label.intrinsicContentSize
XCTAssertGreaterThan(size.width, 0, "Width should be greater than 0, got \(size.width)")
XCTAssertGreaterThan(size.height, 0, "Height should be greater than 0, got \(size.height)")
}
func testSizeThatFitsWithoutConstraint() {
let label = MTMathUILabel()
label.latex = "\\(\\text{Hello World}\\)"
label.font = MTFontManager.fontManager.defaultFont
let size = label.sizeThatFits(CGSize.zero)
XCTAssertGreaterThan(size.width, 0, "Width should be greater than 0, got \(size.width)")
XCTAssertGreaterThan(size.height, 0, "Height should be greater than 0, got \(size.height)")
}
func testSizeThatFitsWithWidthConstraint() {
let label = MTMathUILabel()
label.latex = "\\(\\text{Rappelons la conversion : 1 km équivaut à 1000 m.}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
// Get unconstrained size first
let unconstrainedSize = label.sizeThatFits(CGSize.zero)
XCTAssertGreaterThan(unconstrainedSize.width, 0, "Unconstrained width should be > 0")
// Test with width constraint (use 300 since longest word might be ~237pt)
let constrainedSize = label.sizeThatFits(CGSize(width: 300, height: CGFloat.greatestFiniteMagnitude))
XCTAssertGreaterThan(constrainedSize.width, 0, "Constrained width should be greater than 0, got \(constrainedSize.width)")
XCTAssertLessThan(constrainedSize.width, unconstrainedSize.width, "Constrained width (\(constrainedSize.width)) should be less than unconstrained (\(unconstrainedSize.width))")
XCTAssertGreaterThan(constrainedSize.height, 0, "Constrained height should be greater than 0, got \(constrainedSize.height)")
// When constrained, height should increase when text wraps
XCTAssertGreaterThan(constrainedSize.height, unconstrainedSize.height,
"Constrained height (\(constrainedSize.height)) should be > unconstrained (\(unconstrainedSize.height)) when text wraps")
}
func testPreferredMaxLayoutWidth() {
let label = MTMathUILabel()
label.latex = "\\(\\text{Rappelons la conversion : 1 km équivaut à 1000 m.}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
// Get unconstrained size
let unconstrainedSize = label.intrinsicContentSize
// Now set preferred max width (use 300 since longest word might be ~237pt)
label.preferredMaxLayoutWidth = 300
let constrainedSize = label.intrinsicContentSize
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be greater than 0, got \(constrainedSize.width)")
XCTAssertLessThan(constrainedSize.width, unconstrainedSize.width, "Constrained width (\(constrainedSize.width)) should be < unconstrained (\(unconstrainedSize.width))")
XCTAssertGreaterThan(constrainedSize.height, unconstrainedSize.height, "Constrained height (\(constrainedSize.height)) should be > unconstrained (\(unconstrainedSize.height)) due to wrapping")
}
func testWordBoundaryBreaking() {
let label = MTMathUILabel()
label.latex = "\\(\\text{Word1 Word2 Word3 Word4 Word5}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
label.preferredMaxLayoutWidth = 150
let size = label.intrinsicContentSize
XCTAssertGreaterThan(size.width, 0, "Width should be greater than 0, got \(size.width)")
XCTAssertGreaterThan(size.height, 0, "Height should be greater than 0, got \(size.height)")
// Verify it actually uses the layout
label.frame = CGRect(origin: .zero, size: size)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
}
func testEmptyLatex() {
let label = MTMathUILabel()
label.latex = ""
label.font = MTFontManager.fontManager.defaultFont
let size = label.intrinsicContentSize
// Empty latex should still return a valid size (might be zero or minimal)
XCTAssertGreaterThanOrEqual(size.width, 0, "Width should be >= 0 for empty latex, got \(size.width)")
XCTAssertGreaterThanOrEqual(size.height, 0, "Height should be >= 0 for empty latex, got \(size.height)")
}
func testMathAndTextMixed() {
let label = MTMathUILabel()
label.latex = "\\(\\text{Result: } x^2 + y^2 = z^2\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
let size = label.intrinsicContentSize
XCTAssertGreaterThan(size.width, 0, "Width should be greater than 0, got \(size.width)")
XCTAssertGreaterThan(size.height, 0, "Height should be greater than 0, got \(size.height)")
}
func testDebugSizeThatFitsWithConstraint() {
let label = MTMathUILabel()
label.latex = "\\(\\text{Word1 Word2 Word3 Word4 Word5}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
let unconstr = label.sizeThatFits(CGSize.zero)
let constr = label.sizeThatFits(CGSize(width: 150, height: 999))
XCTAssertLessThan(constr.width, unconstr.width, "Constrained (\(constr.width)) should be < unconstrained (\(unconstr.width))")
XCTAssertGreaterThan(constr.height, unconstr.height, "Constrained height (\(constr.height)) should be > unconstrained (\(unconstr.height))")
}
func testAccentedCharactersWithLineWrapping() {
let label = MTMathUILabel()
// French text with accented characters: è, é, à
label.latex = "\\(\\text{Rappelons la relation entre kilomètres et mètres.}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
// Get unconstrained size
let unconstrainedSize = label.intrinsicContentSize
// Set a width constraint that should cause wrapping
label.preferredMaxLayoutWidth = 250
let constrainedSize = label.intrinsicContentSize
// Verify wrapping occurred
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be > 0")
XCTAssertLessThan(constrainedSize.width, unconstrainedSize.width, "Constrained width should be < unconstrained")
XCTAssertGreaterThan(constrainedSize.height, unconstrainedSize.height, "Height should increase when wrapped")
// Verify the label can render without errors
label.frame = CGRect(origin: .zero, size: constrainedSize)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
}
func testUnicodeWordBreaking_EquivautCase() {
// Specific test for the reported issue: "équivaut" should not break at "é"
let label = MTMathUILabel()
label.latex = "\\(\\text{Rappelons la conversion : 1 km équivaut à 1000 m.}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
// Set the exact width constraint from the bug report
label.preferredMaxLayoutWidth = 235
let constrainedSize = label.intrinsicContentSize
// Verify the label can render without errors
label.frame = CGRect(origin: .zero, size: constrainedSize)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
// Verify that the text wrapped (multiple lines)
XCTAssertGreaterThan(constrainedSize.height, 20, "Should have wrapped to multiple lines")
// The critical check: ensure "équivaut" is not broken in the middle
// We can't easily check the exact line breaks, but we can verify:
// 1. The rendering succeeded without crashes
// 2. The display has reasonable dimensions
XCTAssertGreaterThan(constrainedSize.width, 100, "Width should be reasonable")
XCTAssertLessThan(constrainedSize.width, 250, "Width should respect constraint")
}
func testMixedTextMathNoTruncation() {
// Test for truncation bug: content should wrap, not be lost
// Input: \(\text{Calculer le discriminant }\Delta=b^{2}-4ac\text{ avec }a=1\text{, }b=-1\text{, }c=-5\)
let label = MTMathUILabel()
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
// Set width constraint that should cause wrapping
label.preferredMaxLayoutWidth = 235
let constrainedSize = label.intrinsicContentSize
// Verify the label can render without errors
label.frame = CGRect(origin: .zero, size: constrainedSize)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
// Verify content is not truncated - should wrap to multiple lines
XCTAssertGreaterThan(constrainedSize.height, 30, "Should wrap to multiple lines (not truncate)")
// Check that we have multiple display elements (wrapped content)
if let displayList = label.displayList {
XCTAssertGreaterThan(displayList.subDisplays.count, 1, "Should have multiple display elements from wrapping")
}
}
func testNumberProtection_FrenchDecimal() {
let label = MTMathUILabel()
// French decimal number should NOT be broken
label.latex = "\\(\\text{La valeur de pi est approximativement 3,14 dans ce calcul simple.}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
// Constrain to force wrapping, but 3,14 should stay together
label.preferredMaxLayoutWidth = 200
let size = label.intrinsicContentSize
// Verify it renders without error
label.frame = CGRect(origin: .zero, size: size)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
}
func testNumberProtection_ThousandsSeparator() {
let label = MTMathUILabel()
// Number with comma separator should stay together
label.latex = "\\(\\text{The population is approximately 1,000,000 people in this city.}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
label.preferredMaxLayoutWidth = 200
let size = label.intrinsicContentSize
label.frame = CGRect(origin: .zero, size: size)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
}
func testNumberProtection_MixedWithText() {
let label = MTMathUILabel()
// Mixed numbers and text - numbers should be protected
label.latex = "\\(\\text{Results: 3.14, 2.71, and 1.41 are important constants.}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
label.preferredMaxLayoutWidth = 180
let size = label.intrinsicContentSize
label.frame = CGRect(origin: .zero, size: size)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
}
// MARK: - International Text Tests
func testChineseTextWrapping() {
let label = MTMathUILabel()
// Chinese text: "Mathematical equations are an important tool for describing natural phenomena"
label.latex = "\\(\\text{数学方程式は自然現象を記述するための重要なツールです。}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
// Get unconstrained size
let unconstrainedSize = label.intrinsicContentSize
// Set constraint to force wrapping
label.preferredMaxLayoutWidth = 200
let constrainedSize = label.intrinsicContentSize
// Chinese should wrap (can break between characters)
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be > 0")
XCTAssertLessThanOrEqual(constrainedSize.width, 200, "Width should not exceed constraint")
XCTAssertGreaterThan(constrainedSize.height, unconstrainedSize.height, "Height should increase when wrapped")
label.frame = CGRect(origin: .zero, size: constrainedSize)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
}
func testJapaneseTextWrapping() {
let label = MTMathUILabel()
// Japanese text (Hiragana + Kanji): "This is a mathematics explanation"
label.latex = "\\(\\text{これは数学の説明です。計算式を使います。}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
let unconstrainedSize = label.intrinsicContentSize
label.preferredMaxLayoutWidth = 180
let constrainedSize = label.intrinsicContentSize
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be > 0")
XCTAssertLessThanOrEqual(constrainedSize.width, 180, "Width should not exceed constraint")
XCTAssertGreaterThan(constrainedSize.height, unconstrainedSize.height, "Height should increase when wrapped")
label.frame = CGRect(origin: .zero, size: constrainedSize)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
}
func testKoreanTextWrapping() {
let label = MTMathUILabel()
// Korean text: "Mathematics is a very important subject"
label.latex = "\\(\\text{수학은 매우 중요한 과목입니다. 방정식을 배웁니다.}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
label.preferredMaxLayoutWidth = 200
let constrainedSize = label.intrinsicContentSize
// Korean uses spaces, should wrap at word boundaries
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be > 0")
XCTAssertLessThanOrEqual(constrainedSize.width, 200, "Width should not exceed constraint")
label.frame = CGRect(origin: .zero, size: constrainedSize)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
}
func testMixedLatinCJKWrapping() {
let label = MTMathUILabel()
// Mixed English and Chinese
label.latex = "\\(\\text{The equation is 方程式: } x^2 + y^2 = r^2 \\text{ です。}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
label.preferredMaxLayoutWidth = 250
let constrainedSize = label.intrinsicContentSize
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be > 0")
XCTAssertLessThanOrEqual(constrainedSize.width, 250, "Width should not exceed constraint")
label.frame = CGRect(origin: .zero, size: constrainedSize)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
}
func testEmojiGraphemeClusters() {
let label = MTMathUILabel()
// Emoji and complex grapheme clusters should not be broken
label.latex = "\\(\\text{Math is fun! 🎉📐📊 The formula is } E = mc^2 \\text{ 🚀✨}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
label.preferredMaxLayoutWidth = 200
let size = label.intrinsicContentSize
// Should wrap but not break emoji
XCTAssertGreaterThan(size.width, 0, "Width should be > 0")
XCTAssertLessThanOrEqual(size.width, 200, "Width should not exceed constraint")
label.frame = CGRect(origin: .zero, size: size)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
}
func testLongEnglishMultiSentence() {
let label = MTMathUILabel()
// Standard English multi-sentence paragraph
label.latex = "\\(\\text{Mathematics is the study of numbers, shapes, and patterns. It is used in science, engineering, and everyday life. Equations help us solve problems.}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
let unconstrainedSize = label.intrinsicContentSize
label.preferredMaxLayoutWidth = 300
let constrainedSize = label.intrinsicContentSize
// Should wrap at word boundaries (spaces)
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be > 0")
XCTAssertLessThanOrEqual(constrainedSize.width, 300, "Width should not exceed constraint")
XCTAssertGreaterThan(constrainedSize.height, unconstrainedSize.height, "Height should increase when wrapped")
label.frame = CGRect(origin: .zero, size: constrainedSize)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
}
func testSpanishAccentedText() {
let label = MTMathUILabel()
// Spanish with various accents
label.latex = "\\(\\text{La ecuación es muy útil para cálculos científicos y matemáticos.}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
let unconstrainedSize = label.intrinsicContentSize
label.preferredMaxLayoutWidth = 220
let constrainedSize = label.intrinsicContentSize
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be > 0")
XCTAssertLessThanOrEqual(constrainedSize.width, 220, "Width should not exceed constraint")
XCTAssertGreaterThan(constrainedSize.height, unconstrainedSize.height, "Height should increase when wrapped")
label.frame = CGRect(origin: .zero, size: constrainedSize)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
}
func testGermanUmlautsWrapping() {
let label = MTMathUILabel()
// German with umlauts
label.latex = "\\(\\text{Mathematische Gleichungen können für Berechnungen verwendet werden.}\\)"
label.font = MTFontManager.fontManager.defaultFont
label.labelMode = .text
let unconstrainedSize = label.intrinsicContentSize
label.preferredMaxLayoutWidth = 250
let constrainedSize = label.intrinsicContentSize
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be > 0")
XCTAssertLessThanOrEqual(constrainedSize.width, 250, "Width should not exceed constraint")
XCTAssertGreaterThan(constrainedSize.height, unconstrainedSize.height, "Height should increase when wrapped")
label.frame = CGRect(origin: .zero, size: constrainedSize)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
}
// MARK: - Tests for Complex Math Expressions with Line Breaking
func testComplexExpressionWithRadicalWrapping() {
// This is the reported issue: y=x^{2}+3x+4x+9x+8x+8+\sqrt{\dfrac{3x^{2}+5x}{\cos x}}
// The sqrt part is displayed on the second line and overlaps the first line
let label = MTMathUILabel()
label.latex = "y=x^{2}+3x+4x+9x+8x+8+\\sqrt{\\dfrac{3x^{2}+5x}{\\cos x}}"
label.font = MTFontManager.fontManager.defaultFont
// Get unconstrained size first
let unconstrainedSize = label.intrinsicContentSize
XCTAssertGreaterThan(unconstrainedSize.width, 0, "Unconstrained width should be > 0")
XCTAssertGreaterThan(unconstrainedSize.height, 0, "Unconstrained height should be > 0")
// Now constrain the width to force wrapping
label.preferredMaxLayoutWidth = 200
let constrainedSize = label.intrinsicContentSize
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be > 0")
XCTAssertLessThanOrEqual(constrainedSize.width, 200, "Width should not exceed constraint")
XCTAssertGreaterThan(constrainedSize.height, unconstrainedSize.height, "Height should increase when wrapped")
// Layout and check for overlapping
label.frame = CGRect(origin: .zero, size: constrainedSize)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
// Check that displays don't overlap by examining positions
// Group displays by line (similar y positions) and check for overlap between lines
if let displayList = label.displayList {
// Group displays by line based on their y position
var lineGroups: [[MTDisplay]] = []
var currentLineDisplays: [MTDisplay] = []
var currentLineY: CGFloat? = nil
let yTolerance: CGFloat = 15.0 // Displays within 15 units are considered on same line (accounts for superscripts/subscripts)
for display in displayList.subDisplays {
if let lineY = currentLineY {
if abs(display.position.y - lineY) < yTolerance {
// Same line
currentLineDisplays.append(display)
} else {
// New line
lineGroups.append(currentLineDisplays)
currentLineDisplays = [display]
currentLineY = display.position.y
}
} else {
// First display
currentLineDisplays = [display]
currentLineY = display.position.y
}
}
if !currentLineDisplays.isEmpty {
lineGroups.append(currentLineDisplays)
}
// Check for overlap between consecutive lines
for i in 1..<lineGroups.count {
let previousLine = lineGroups[i-1]
let currentLine = lineGroups[i]
// Find the minimum bottom edge of previous line (Y-up: bottom = pos - desc, smaller Y)
let previousLineMinBottom = previousLine.map { $0.position.y - $0.descent }.min() ?? 0
// Find the maximum top edge of current line (Y-up: top = pos + asc, larger Y)
let currentLineMaxTop = currentLine.map { $0.position.y + $0.ascent }.max() ?? 0
// Check for overlap: if current line's top > previous line's bottom, they overlap
// (In Y-up coordinate system: positive Y is upward, negative Y is downward)
// Allow 0.5 points tolerance for floating-point precision and small adjustments
XCTAssertLessThanOrEqual(currentLineMaxTop, previousLineMinBottom + 0.5,
"Line \(i) (top at \(currentLineMaxTop)) overlaps with line \(i-1) (bottom at \(previousLineMinBottom))")
}
}
}
func testRadicalWithFractionInsideWrapping() {
// Simplified version: just a radical with a fraction inside
let label = MTMathUILabel()
label.latex = "x+y+z+\\sqrt{\\dfrac{a}{b}}"
label.font = MTFontManager.fontManager.defaultFont
let unconstrainedSize = label.intrinsicContentSize
label.preferredMaxLayoutWidth = 100
let constrainedSize = label.intrinsicContentSize
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be > 0")
XCTAssertGreaterThan(constrainedSize.height, unconstrainedSize.height, "Height should increase when wrapped")
label.frame = CGRect(origin: .zero, size: constrainedSize)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
}
func testTallElementsOnSecondLine() {
// Test case with tall fractions and radicals breaking to second line
let label = MTMathUILabel()
label.latex = "a+b+c+\\dfrac{x^2+y^2}{z^2}+\\sqrt{\\dfrac{p}{q}}"
label.font = MTFontManager.fontManager.defaultFont
let unconstrainedSize = label.intrinsicContentSize
label.preferredMaxLayoutWidth = 150
let constrainedSize = label.intrinsicContentSize
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be > 0")
XCTAssertGreaterThan(constrainedSize.height, unconstrainedSize.height, "Height should increase when wrapped")
label.frame = CGRect(origin: .zero, size: constrainedSize)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
// Verify no overlapping displays between lines
if let displayList = label.displayList {
// Group displays by line
var lineGroups: [[MTDisplay]] = []
var currentLineDisplays: [MTDisplay] = []
var currentLineY: CGFloat? = nil
let yTolerance: CGFloat = 15.0
for display in displayList.subDisplays {
if let lineY = currentLineY {
if abs(display.position.y - lineY) < yTolerance {
currentLineDisplays.append(display)
} else {
lineGroups.append(currentLineDisplays)
currentLineDisplays = [display]
currentLineY = display.position.y
}
} else {
currentLineDisplays = [display]
currentLineY = display.position.y
}
}
if !currentLineDisplays.isEmpty {
lineGroups.append(currentLineDisplays)
}
// Check for overlap between consecutive lines
for i in 1..<lineGroups.count {
let previousLine = lineGroups[i-1]
let currentLine = lineGroups[i]
let previousLineMinBottom = previousLine.map { $0.position.y - $0.descent }.min() ?? 0
let currentLineMaxTop = currentLine.map { $0.position.y + $0.ascent }.max() ?? 0
// Allow 0.5 points tolerance for floating-point precision
XCTAssertLessThanOrEqual(currentLineMaxTop, previousLineMinBottom + 0.5,
"Line \(i) overlaps with line \(i-1)")
}
}
}
func testMultipleLinesWithVaryingHeights() {
// Test expression that should wrap to multiple lines with different heights
let label = MTMathUILabel()
label.latex = "x+y+z+a+b+c+\\sqrt{d}+e+f+g+h+\\dfrac{i}{j}+k"
label.font = MTFontManager.fontManager.defaultFont
let unconstrainedSize = label.intrinsicContentSize
label.preferredMaxLayoutWidth = 120
let constrainedSize = label.intrinsicContentSize
XCTAssertGreaterThan(constrainedSize.width, 0, "Width should be > 0")
XCTAssertGreaterThan(constrainedSize.height, unconstrainedSize.height, "Height should increase when wrapped")
label.frame = CGRect(origin: .zero, size: constrainedSize)
#if os(macOS)
label.layout()
#else
label.layoutSubviews()
#endif
XCTAssertNotNil(label.displayList, "Display list should be created")
XCTAssertNil(label.error, "Should have no rendering error")
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,187 +0,0 @@
import XCTest
@testable import SwiftUIMath
//
// MathFontTests.swift
//
//
// Created by Peter Tang on 12/9/2023.
//
final class MathFontTests: XCTestCase {
func testMathFontScript() throws {
let size = Int.random(in: 20 ... 40)
MathFont.allCases.forEach {
// print("\(#function) cgfont \($0.cgFont())")
// print("\(#function) ctfont \($0.ctFont(withSize: CGFloat(size)))")
XCTAssertNotNil($0.cgFont())
XCTAssertNotNil($0.ctFont(withSize: CGFloat(size)))
XCTAssertEqual($0.ctFont(withSize: CGFloat(size)).fontSize, CGFloat(size), "ctFont fontSize != size.")
XCTAssertEqual($0.cgFont().postScriptName as? String, $0.postScriptName, "cgFont.postScriptName != postScriptName")
// XCTAssertEqual($0.uiFont(withSize: CGFloat(size))?.familyName, $0.fontFamilyName, "uifont familyName != familyName.")
XCTAssertEqual(CTFontCopyFamilyName($0.ctFont(withSize: CGFloat(size))) as String, $0.fontFamilyName, "ctfont.family != familyName")
}
#if os(iOS) || os(visionOS)
// for family in UIFont.familyNames.sorted() {
// let names = UIFont.fontNames(forFamilyName: family)
// print("Family: \(family) Font names: \(names)")
// }
fontNames.forEach { name in
XCTAssertNotNil(UIFont(name: name, size: CGFloat(size)))
}
fontFamilyNames.forEach { name in
XCTAssertNotNil(UIFont.fontNames(forFamilyName: name))
}
#endif
#if os(macOS)
fontNames.forEach { name in
let font = NSFont(name: name, size: CGFloat(size))
XCTAssertNotNil(font)
}
#endif
}
func testOnDemandMathFontScript() throws {
let size = Int.random(in: 20 ... 40)
let mathFont = MathFont.allCases.randomElement()!
XCTAssertNotNil(mathFont.cgFont())
XCTAssertNotNil(mathFont.ctFont(withSize: CGFloat(size)))
XCTAssertEqual(mathFont.ctFont(withSize: CGFloat(size)).fontSize, CGFloat(size), "ctFont fontSize test")
}
var fontNames: [String] {
MathFont.allCases.map { $0.postScriptName }
}
var fontFamilyNames: [String] {
MathFont.allCases.map { $0.fontFamilyName }
}
private let executionQueue = DispatchQueue(label: "com.swiftmath.mathbundle", attributes: .concurrent)
private let executionGroup = DispatchGroup()
let totalCases = 5000
var testCount = 0
func testConcurrentThreadsafeScript() throws {
var mathFont: MathFont { .allCases.randomElement()! }
for caseNumber in 0 ..< totalCases {
switch caseNumber % 3 {
case 0:
helperConcurrentCGFont(caseNumber, mathFont: mathFont, in: executionGroup, on: executionQueue)
case 1:
helperConcurrentCTFont(caseNumber, mathFont: mathFont, in: executionGroup, on: executionQueue)
case 2:
helperConcurrentMathTable(caseNumber, mathFont: mathFont, in: executionGroup, on: executionQueue)
default:
continue
}
}
executionGroup.notify(queue: .main) { [weak self] in
guard let self = self else { return }
XCTAssertEqual(self.testCount, totalCases)
}
executionGroup.wait()
}
// func helperConcurrentOnDemandRegistration(_ count: Int, mathFont: MathFont, in group: DispatchGroup, on queue: DispatchQueue) {
// let workitem = DispatchWorkItem {
// BundleManager.manager.onDemandRegistration(mathFont: mathFont)
// }
// workitem.notify(queue: .main) { [weak self] in
// self?.testCount += 1
// }
// queue.async(group: group, execute: workitem)
// }
// func helperConcurrentBundleRegistration(mathFont: MathFont, in group: DispatchGroup, on queue: DispatchQueue) {
// let workitem = DispatchWorkItem {
// // BundleManager.manager.onDemandRegistration(mathFont: mathFont)
// try? BundleManager.manager.registerCGFont(mathFont: mathFont)
// try? BundleManager.manager.registerMathTable(mathFont: mathFont)
// let font = BundleManager.manager.cgFonts[mathFont]
// XCTAssertNotNil(font, "font != nil")
// }
// workitem.notify(queue: .main) { [weak self] in
// // print("\(Thread.isMainThread ? "main" : "global") completed .....")
// let font = mathFont.cgFont()
// XCTAssertNotNil(font, "font != nil")
// self?.testCount += 1
// }
// queue.async(group: group, execute: workitem)
// }
func helperConcurrentCGFont(_ count: Int, mathFont: MathFont, in group: DispatchGroup, on queue: DispatchQueue) {
let workitem = DispatchWorkItem {
let font = mathFont.cgFont()
XCTAssertNotNil(font, "font != nil")
}
workitem.notify(queue: .main) { [weak self] in
// print("\(Thread.isMainThread ? "main" : "global") completed .....")
let font = mathFont.cgFont()
XCTAssertNotNil(font, "font != nil")
self?.testCount += 1
}
queue.async(group: group, execute: workitem)
}
func helperConcurrentCTFont(_ count: Int, mathFont: MathFont, in group: DispatchGroup, on queue: DispatchQueue) {
let size = CGFloat.random(in: 20 ... 40)
let workitem = DispatchWorkItem {
let font = mathFont.ctFont(withSize: size)
XCTAssertNotNil(font, "font != nil")
}
workitem.notify(queue: .main) { [weak self] in
// print("\(Thread.isMainThread ? "main" : "global") completed .....")
let font = mathFont.ctFont(withSize: size)
XCTAssertNotNil(font, "font != nil")
self?.testCount += 1
}
queue.async(group: group, execute: workitem)
}
func helperConcurrentMathTable(_ count: Int, mathFont: MathFont, in group: DispatchGroup, on queue: DispatchQueue) {
let workitem = DispatchWorkItem {
let mathtable = mathFont.rawMathTable()
XCTAssertNotNil(mathtable, "mathTable != nil")
}
workitem.notify(queue: .main) { [weak self] in
// print("\(Thread.isMainThread ? "main" : "global") completed .....")
let mathtable = mathFont.rawMathTable()
XCTAssertNotNil(mathtable, "mathTable != nil")
self?.testCount += 1
}
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")
}
}

View File

@@ -1,203 +0,0 @@
//
// MathImageTests.swift
//
//
// Created by Peter Tang on 18/9/2023.
//
import XCTest
@testable import SwiftUIMath
final class MathImageTests: XCTestCase {
func safeImage(fileName: String, pngData: Data) {
let imageFileURL = URL(fileURLWithPath: NSTemporaryDirectory().appending("image-\(fileName).png"))
try? pngData.write(to: imageFileURL, options: [.atomicWrite])
//print("\(#function) \(imageFileURL.path)")
}
func testMathImageScript() throws {
let latex = Latex.samples.randomElement()!
let mathfont = MathFont.allCases.randomElement()!
let fontsize = CGFloat.random(in: 24 ... 36)
let result = SwiftMathImageResult.useMathImage(latex: latex, font: mathfont, fontSize: fontsize)
XCTAssertNil(result.error)
XCTAssertNotNil(result.image)
XCTAssertNotNil(result.layoutInfo)
if result.error == nil, let image = result.image, let imageData = image.pngData() {
safeImage(fileName: "test", pngData: imageData)
}
}
func testSequentialMultipleImageScript() throws {
var latex: String { Latex.samples.randomElement()! }
var mathfont: MathFont { MathFont.allCases.randomElement()! }
var fontsize: CGFloat { CGFloat.random(in: 20 ... 40) }
for caseNumber in 0 ..< 20 {
let result: SwiftMathImageResult
switch caseNumber % 2 {
case 0:
result = SwiftMathImageResult.useMathImage(latex: latex, font: mathfont, fontSize: fontsize)
XCTAssertNil(result.error)
XCTAssertNotNil(result.image)
XCTAssertNotNil(result.layoutInfo)
if result.error == nil, let image = result.image, let imageData = image.pngData() {
safeImage(fileName: "\(caseNumber)", pngData: imageData)
}
default:
result = SwiftMathImageResult.useMTMathImage(latex: latex, font: mathfont, fontSize: fontsize)
XCTAssertNil(result.error)
XCTAssertNotNil(result.image)
if result.error == nil, let image = result.image, let imageData = image.pngData() {
safeImage(fileName: "\(caseNumber)", pngData: imageData)
}
}
}
}
private let executionQueue = DispatchQueue(label: "com.swiftmath.mathbundle", attributes: .concurrent)
private let executionGroup = DispatchGroup()
let totalCases = 20
var testCount = 0
func testConcurrentMathImageScript() throws {
var latex: String { Latex.samples.randomElement()! }
var mathfont: MathFont { MathFont.allCases.randomElement()! }
var size: CGFloat { CGFloat.random(in: 20 ... 40) }
for caseNumber in 0 ..< totalCases {
switch caseNumber % 2 {
case 0:
helperConcurrentMathImage(caseNumber, latex: latex, mathfont: mathfont, fontsize: size, in: executionGroup, on: executionQueue)
default:
helperConcurrentMTMathImage(caseNumber, latex: latex, mathfont: mathfont, fontsize: size, in: executionGroup, on: executionQueue)
}
}
executionGroup.notify(queue: .main) { [weak self] in
XCTAssertEqual(self?.testCount,self?.totalCases)
}
executionGroup.wait()
}
func helperConcurrentMathImage(_ count: Int, latex: String, mathfont: MathFont, fontsize: CGFloat, in group: DispatchGroup, on queue: DispatchQueue) {
let workitem = DispatchWorkItem { [weak self] in
let result = SwiftMathImageResult.useMathImage(latex: latex, font: mathfont, fontSize: fontsize)
XCTAssertNil(result.error)
XCTAssertNotNil(result.image)
XCTAssertNotNil(result.layoutInfo)
if result.error == nil, let image = result.image, let imageData = image.pngData() {
self?.safeImage(fileName: "\(count)", pngData: imageData)
}
}
workitem.notify(queue: .main) { [weak self] in
self?.testCount += 1
}
queue.async(group: group, execute: workitem)
}
func helperConcurrentMTMathImage(_ count: Int, latex: String, mathfont: MathFont, fontsize: CGFloat, in group: DispatchGroup, on queue: DispatchQueue) {
let workitem = DispatchWorkItem { [weak self] in
let result = SwiftMathImageResult.useMTMathImage(latex: latex, font: mathfont, fontSize: fontsize)
XCTAssertNil(result.error)
XCTAssertNotNil(result.image)
if result.error == nil, let image = result.image, let imageData = image.pngData() {
self?.safeImage(fileName: "\(count)", pngData: imageData)
}
}
workitem.notify(queue: .main) { [weak self] in
self?.testCount += 1
}
queue.async(group: group, execute: workitem)
}
}
public struct SwiftMathImageResult {
let error: NSError?
let image: MTImage?
let layoutInfo: MathImage.LayoutInfo?
}
extension SwiftMathImageResult {
public static func useMTMathImage(latex: String, font: MathFont, fontSize: CGFloat, textColor: MTColor = MTColor.black) -> SwiftMathImageResult {
let alignment = MTTextAlignment.left
let formatter = MTMathImage(latex: latex, fontSize: fontSize - 1.0,
textColor: textColor,
labelMode: .text, textAlignment: alignment)
formatter.font = font.mtfont(size: fontSize)
let (error, image) = formatter.asImage()
return SwiftMathImageResult(error: error, image: image, layoutInfo: nil)
}
public static func useMathImage(latex: String, font: MathFont, fontSize: CGFloat, textColor: MTColor = MTColor.black) -> SwiftMathImageResult {
let alignment = MTTextAlignment.left
var formatter = MathImage(latex: latex, fontSize: fontSize - 1.0,
textColor: textColor,
labelMode: .text, textAlignment: alignment)
formatter.font = font
let (error, image, layoutInfo) = formatter.asImage()
return SwiftMathImageResult(error: error, image: image, layoutInfo: layoutInfo)
}
}
#if os(macOS)
extension NSBitmapImageRep {
var png: Data? { representation(using: .png, properties: [:]) }
}
extension Data {
var bitmap: NSBitmapImageRep? { NSBitmapImageRep(data: self) }
}
extension NSImage {
func pngData() -> Data? {
tiffRepresentation?.bitmap?.png
}
}
#endif
enum Latex {
static let samples: [String] = [
#"(a_1 + a_2)^2 = a_1^2 + 2a_1a_2 + a_2^2"#,
#"x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}"#,
#"\sigma = \sqrt{\frac{1}{N}\sum_{i=1}^N (x_i - \mu)^2}"#,
#"\neg(P\land Q) \iff (\neg P)\lor(\neg Q)"#,
#"\cos(\theta + \varphi) = \cos(\theta)\cos(\varphi) - \sin(\theta)\sin(\varphi)"#,
#"\lim_{x\to\infty}\left(1 + \frac{k}{x}\right)^x = e^k"#,
#"f(x) = \int\limits_{-\infty}^\infty\hat f(\xi)\,e^{2 \pi i \xi x}\,\mathrm{d}\xi"#,
#"{n \brace k} = \frac{1}{k!}\sum_{j=0}^k (-1)^{k-j}\binom{k}{j}(k-j)^n"#,
#"\int_{-\infty}^{\infty} \! e^{-x^2} dx = \sqrt{\pi}"#,
#"\frac{1}{n}\sum_{i=1}^{n}x_i \geq \sqrt[n]{\prod_{i=1}^{n}x_i}"#,
#"\left(\sum_{k=1}^n a_k b_k \right)^2 \le \left(\sum_{k=1}^n a_k^2\right)\left(\sum_{k=1}^n b_k^2\right)"#,
#"\left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)"#,
#"i\hbar\frac{\partial}{\partial t}\mathbf\Psi(\mathbf{x},t) = -\frac{\hbar}{2m}\nabla^2\mathbf\Psi(\mathbf{x},t) + V(\mathbf{x})\mathbf\Psi(\mathbf{x},t)"#,
#"""
\begin{gather}
\dot{x} = \sigma(y-x) \\
\dot{y} = \rho x - y - xz \\
\dot{z} = -\beta z + xy"
\end{gather}
"""#,
#"""
\vec \bf V_1 \times \vec \bf V_2 = \begin{vmatrix}
\hat \imath &\hat \jmath &\hat k \\
\frac{\partial X}{\partial u} & \frac{\partial Y}{\partial u} & 0 \\
\frac{\partial X}{\partial v} & \frac{\partial Y}{\partial v} & 0
\end{vmatrix}
"""#,
#"""
\begin{eqalign}
\nabla \cdot \vec{\bf E} & = \frac {\rho} {\varepsilon_0} \\
\nabla \cdot \vec{\bf B} & = 0 \\
\nabla \times \vec{\bf E} &= - \frac{\partial\vec{\bf B}}{\partial t} \\
\nabla \times \vec{\bf B} & = \mu_0\vec{\bf J} + \mu_0\varepsilon_0 \frac{\partial\vec{\bf E}}{\partial t}
\end{eqalign}
"""#,
#"\log_b(x) = \frac{\log_a(x)}{\log_a(b)}"#,
#"""
\begin{pmatrix}
a & b\\ c & d
\end{pmatrix}
\begin{pmatrix}
\alpha & \beta \\ \gamma & \delta
\end{pmatrix} =
\begin{pmatrix}
a\alpha + b\gamma & a\beta + b \delta \\
c\alpha + d\gamma & c\beta + d \delta
\end{pmatrix}
"""#,
#"""
\frak Q(\lambda,\hat{\lambda}) =
-\frac{1}{2} \mathbb P(O \mid \lambda ) \sum_s \sum_m \sum_t \gamma_m^{(s)} (t) +\\
\quad \left( \log(2 \pi ) + \log \left| \cal C_m^{(s)} \right| +
\left( o_t - \hat{\mu}_m^{(s)} \right) ^T \cal C_m^{(s)-1} \right)
"""#
]
}

View File

@@ -0,0 +1,6 @@
//
// Untitled.swift
// swiftui-math
//
// Created by Guille Gonzalez on 31/12/25.
//