import XCTest @testable import SwiftMath // // MathTypesetterTests.swift // MathTypesetterTests // // Created by Mike Griebling on 2023-01-02. // extension CGPoint { func isEqual(to p:CGPoint, accuracy:CGFloat) -> Bool { abs(self.x - p.x) < accuracy && abs(self.y - p.y) < accuracy } } final class MTTypesetterTests: XCTestCase { var font:MTFont! override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. try super.setUpWithError() self.font = MTFontManager.fontManager.defaultFont } override func tearDownWithError() throws { // Put teardown code here. This method is called after the invocation of each test method in the class. try super.tearDownWithError() } func testSimpleVariable() throws { let mathList = MTMathList() mathList.add(MTMathAtomFactory.atom(forCharacter: "x")) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))); XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTCTLineDisplay) let line = sub0 as! MTCTLineDisplay XCTAssertEqual(line.atoms.count, 1); // The x is italicized XCTAssertEqual(line.attributedString?.string, "π‘₯"); XCTAssertTrue(CGPointEqualToPoint(line.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line.range, NSMakeRange(0, 1))); XCTAssertFalse(line.hasScript); // dimensions XCTAssertEqual(display.ascent, line.ascent); XCTAssertEqual(display.descent, line.descent); XCTAssertEqual(display.width, line.width); XCTAssertEqual(display.ascent, 8.834, accuracy: 0.01) XCTAssertEqual(display.descent, 0.22, accuracy: 0.01) XCTAssertEqual(display.width, 11.44, accuracy: 0.01) } func testMultipleVariables() throws { let mathList = MTMathAtomFactory.mathListForCharacters("xyzw") let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 4)), "Got \(display.range) instead") XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTCTLineDisplay); let line = sub0 as! MTCTLineDisplay XCTAssertEqual(line.atoms.count, 4); XCTAssertEqual(line.attributedString?.string, "π‘₯𝑦𝑧𝑀"); XCTAssertTrue(CGPointEqualToPoint(line.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line.range, NSMakeRange(0, 4))); XCTAssertFalse(line.hasScript); // dimensions XCTAssertEqual(display.ascent, line.ascent); XCTAssertEqual(display.descent, line.descent); XCTAssertEqual(display.width, line.width); XCTAssertEqual(display.ascent, 8.834, accuracy: 0.01) XCTAssertEqual(display.descent, 4.10, accuracy: 0.01) XCTAssertEqual(display.width, 44.86, accuracy: 0.01) } func testVariablesAndNumbers() throws { let mathList = MTMathAtomFactory.mathListForCharacters("xy2w") let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular) XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)) XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 4)), "Got \(display.range) instead") XCTAssertFalse(display.hasScript) XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTCTLineDisplay); let line = sub0 as! MTCTLineDisplay XCTAssertEqual(line.atoms.count, 4); XCTAssertEqual(line.attributedString?.string, "π‘₯𝑦2𝑀"); XCTAssertTrue(CGPointEqualToPoint(line.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line.range, NSMakeRange(0, 4))); XCTAssertFalse(line.hasScript); // dimensions XCTAssertEqual(display.ascent, line.ascent); XCTAssertEqual(display.descent, line.descent); XCTAssertEqual(display.width, line.width); XCTAssertEqual(display.ascent, 13.32, accuracy: 0.01) XCTAssertEqual(display.descent, 4.10, accuracy: 0.01) XCTAssertEqual(display.width, 45.56, accuracy: 0.01) } func testEquationWithOperatorsAndRelations() throws { let mathList = MTMathAtomFactory.mathListForCharacters("2x+3=y") let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 6)), "Got \(display.range) instead") XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTCTLineDisplay); let line = sub0 as! MTCTLineDisplay XCTAssertEqual(line.atoms.count, 6); XCTAssertEqual(line.attributedString?.string, "2π‘₯+3=𝑦"); XCTAssertTrue(CGPointEqualToPoint(line.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line.range, NSMakeRange(0, 6))); XCTAssertFalse(line.hasScript); // dimensions XCTAssertEqual(display.ascent, line.ascent); XCTAssertEqual(display.descent, line.descent); XCTAssertEqual(display.width, line.width); XCTAssertEqual(display.ascent, 13.32, accuracy: 0.01) XCTAssertEqual(display.descent, 4.10, accuracy: 0.01) XCTAssertEqual(display.width, 92.36, accuracy: 0.01) } // #define XCTAssertTrue(CGPointEqualToPoint(p1, p2, accuracy, ...) \ // XCTAssertEqual(p1.x, p2.x, accuracy, __VA_ARGS__); \ // XCTAssertEqual(p1.y, p2.y, accuracy, __VA_ARGS__) // // // #define XCTAssertTrue(NSEqualRanges(r1, r2, ...) \ // XCTAssertEqual(r1.location, r2.location, __VA_ARGS__); \ // XCTAssertEqual(r1.length, r2.length, __VA_ARGS__) func testSuperscript() throws { let mathList = MTMathList() let x = MTMathAtomFactory.atom(forCharacter: "x") let supersc = MTMathList() supersc.add(MTMathAtomFactory.atom(forCharacter: "2")) x?.superScript = supersc; mathList.add(x) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))); XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 2); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTCTLineDisplay); let line = sub0 as! MTCTLineDisplay XCTAssertEqual(line.atoms.count, 1); // The x is italicized XCTAssertEqual(line.attributedString?.string, "π‘₯"); XCTAssertTrue(CGPointEqualToPoint(line.position, CGPointZero)); XCTAssertTrue(line.hasScript); let sub1 = display.subDisplays[1]; XCTAssertTrue(sub1 is MTMathListDisplay) let display2 = sub1 as! MTMathListDisplay XCTAssertEqual(display2.type, .superscript) XCTAssertTrue(CGPointEqualToPoint(display2.position, CGPointMake(11.44, 7.26))) XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 1))); XCTAssertFalse(display2.hasScript); XCTAssertEqual(display2.index, 0); XCTAssertEqual(display2.subDisplays.count, 1); let sub1sub0 = display2.subDisplays[0]; XCTAssertTrue(sub1sub0 is MTCTLineDisplay); let line2 = sub1sub0 as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "2"); XCTAssertTrue(CGPointEqualToPoint(line2.position, CGPointZero)); XCTAssertFalse(line2.hasScript); // dimensions XCTAssertEqual(display.ascent, 16.584, accuracy: 0.01) XCTAssertEqual(display.descent, 0.22, accuracy: 0.01) XCTAssertEqual(display.width, 18.44, accuracy: 0.01) } func testSubscript() throws { let mathList = MTMathList() let x = MTMathAtomFactory.atom(forCharacter: "x") let subsc = MTMathList() subsc.add(MTMathAtomFactory.atom(forCharacter: "1")) x?.subScript = subsc mathList.add(x) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))); XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 2); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTCTLineDisplay); let line = sub0 as! MTCTLineDisplay XCTAssertEqual(line.atoms.count, 1); // The x is italicized XCTAssertEqual(line.attributedString?.string, "π‘₯"); XCTAssertTrue(CGPointEqualToPoint(line.position, CGPointZero)); XCTAssertTrue(line.hasScript); let sub1 = display.subDisplays[1]; XCTAssertTrue(sub1 is MTMathListDisplay); let display2 = sub1 as! MTMathListDisplay XCTAssertEqual(display2.type, .ssubscript); XCTAssertTrue(CGPointEqualToPoint(display2.position, CGPointMake(11.44, -4.94))) XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 1))) XCTAssertFalse(display2.hasScript); XCTAssertEqual(display2.index, 0); XCTAssertEqual(display2.subDisplays.count, 1); let sub1sub0 = display2.subDisplays[0]; XCTAssertTrue(sub1sub0 is MTCTLineDisplay); let line2 = sub1sub0 as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "1"); XCTAssertTrue(CGPointEqualToPoint(line2.position, CGPointZero)); XCTAssertFalse(line2.hasScript); // dimensions XCTAssertEqual(display.ascent, 8.834, accuracy: 0.01) XCTAssertEqual(display.descent, 4.940, accuracy: 0.01) XCTAssertEqual(display.width, 18.44, accuracy: 0.01) } func testSupersubscript() throws { let mathList = MTMathList() let x = MTMathAtomFactory.atom(forCharacter: "x") let supersc = MTMathList() supersc.add(MTMathAtomFactory.atom(forCharacter: "2")) let subsc = MTMathList() subsc.add(MTMathAtomFactory.atom(forCharacter: "1")) x?.subScript = subsc; x?.superScript = supersc; mathList.add(x) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))); XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 3); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTCTLineDisplay); let line = sub0 as! MTCTLineDisplay XCTAssertEqual(line.atoms.count, 1); // The x is italicized XCTAssertEqual(line.attributedString?.string, "π‘₯"); XCTAssertTrue(CGPointEqualToPoint(line.position, CGPointZero)); XCTAssertTrue(line.hasScript); let sub1 = display.subDisplays[1]; XCTAssertTrue(sub1 is MTMathListDisplay); let display2 = sub1 as! MTMathListDisplay XCTAssertEqual(display2.type, .superscript); XCTAssertTrue(CGPointEqualToPoint(display2.position, CGPointMake(11.44, 7.26))) XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 1))); XCTAssertFalse(display2.hasScript); XCTAssertEqual(display2.index, 0); XCTAssertEqual(display2.subDisplays.count, 1); let sub1sub0 = display2.subDisplays[0]; XCTAssertTrue(sub1sub0 is MTCTLineDisplay); let line2 = sub1sub0 as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "2"); XCTAssertTrue(CGPointEqualToPoint(line2.position, CGPointZero)); XCTAssertFalse(line2.hasScript); let sub2 = display.subDisplays[2]; XCTAssertTrue(sub2 is MTMathListDisplay); let display3 = sub2 as! MTMathListDisplay XCTAssertEqual(display3.type, .ssubscript); // Positioned differently when both subscript and superscript present. XCTAssertTrue(CGPointEqualToPoint(display3.position, CGPointMake(11.44, -5.264))) XCTAssertTrue(NSEqualRanges(display3.range, NSMakeRange(0, 1))) XCTAssertFalse(display3.hasScript); XCTAssertEqual(display3.index, 0); XCTAssertEqual(display3.subDisplays.count, 1); let sub2sub0 = display3.subDisplays[0]; XCTAssertTrue(sub2sub0 is MTCTLineDisplay) let line3 = sub2sub0 as! MTCTLineDisplay XCTAssertEqual(line3.atoms.count, 1); XCTAssertEqual(line3.attributedString?.string, "1"); XCTAssertTrue(CGPointEqualToPoint(line3.position, CGPointZero)); XCTAssertFalse(line3.hasScript); // dimensions XCTAssertEqual(display.ascent, 16.584, accuracy: 0.01) XCTAssertEqual(display.descent, 5.264, accuracy: 0.01) XCTAssertEqual(display.width, 18.44, accuracy: 0.01) } func testRadical() throws { let mathList = MTMathList() let rad = MTRadical() let radicand = MTMathList() radicand.add(MTMathAtomFactory.atom(forCharacter: "1")) rad.radicand = radicand; mathList.add(rad) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))); XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTRadicalDisplay); let radical = sub0 as! MTRadicalDisplay XCTAssertTrue(NSEqualRanges(radical.range, NSMakeRange(0, 1))); XCTAssertFalse(radical.hasScript); XCTAssertTrue(CGPointEqualToPoint(radical.position, CGPointZero)); XCTAssertNotNil(radical.radicand); XCTAssertNil(radical.degree); let display2 = radical.radicand! XCTAssertEqual(display2.type, .regular) XCTAssertTrue(CGPointMake(16.66, 0).isEqual(to: display2.position, accuracy: 0.01)) XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 1))); XCTAssertFalse(display2.hasScript); XCTAssertEqual(display2.index, NSNotFound); XCTAssertEqual(display2.subDisplays.count, 1); let subrad = display2.subDisplays[0]; XCTAssertTrue(subrad is MTCTLineDisplay); let line2 = subrad as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "1"); XCTAssertTrue(CGPointEqualToPoint(line2.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(0, 1))); XCTAssertFalse(line2.hasScript); // dimensions XCTAssertEqual(display.ascent, 19.34, accuracy: 0.01) XCTAssertEqual(display.descent, 1.46, accuracy: 0.01) XCTAssertEqual(display.width, 26.66, accuracy: 0.01) } func testRadicalWithDegree() throws { let mathList = MTMathList() let rad = MTRadical() let radicand = MTMathList() radicand.add(MTMathAtomFactory.atom(forCharacter: "1")) let degree = MTMathList() degree.add(MTMathAtomFactory.atom(forCharacter: "3")) rad.radicand = radicand; rad.degree = degree; mathList.add(rad) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))); XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTRadicalDisplay); let radical = sub0 as! MTRadicalDisplay XCTAssertTrue(NSEqualRanges(radical.range, NSMakeRange(0, 1))); XCTAssertFalse(radical.hasScript); XCTAssertTrue(CGPointEqualToPoint(radical.position, CGPointZero)); XCTAssertNotNil(radical.radicand); XCTAssertNotNil(radical.degree); let display2 = radical.radicand! XCTAssertEqual(display2.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display2.position, CGPointMake(16.66, 0))) XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 1))); XCTAssertFalse(display2.hasScript); XCTAssertEqual(display2.index, NSNotFound); XCTAssertEqual(display2.subDisplays.count, 1); let subrad = display2.subDisplays[0]; XCTAssertTrue(subrad is MTCTLineDisplay); let line2 = subrad as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "1"); XCTAssertTrue(CGPointEqualToPoint(line2.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(0, 1))); XCTAssertFalse(line2.hasScript); let display3 = radical.degree! XCTAssertEqual(display3.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display3.position, CGPointMake(6.12, 10.728))) XCTAssertTrue(NSEqualRanges(display3.range, NSMakeRange(0, 1))); XCTAssertFalse(display3.hasScript); XCTAssertEqual(display3.index, NSNotFound); XCTAssertEqual(display3.subDisplays.count, 1); let subdeg = display3.subDisplays[0]; XCTAssertTrue(subdeg is MTCTLineDisplay); let line3 = subdeg as! MTCTLineDisplay XCTAssertEqual(line3.atoms.count, 1); XCTAssertEqual(line3.attributedString?.string, "3"); XCTAssertTrue(CGPointEqualToPoint(line3.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line3.range, NSMakeRange(0, 1))); XCTAssertFalse(line3.hasScript); // dimensions XCTAssertEqual(display.ascent, 19.34, accuracy: 0.01) XCTAssertEqual(display.descent, 1.46, accuracy: 0.01) XCTAssertEqual(display.width, 26.66, accuracy: 0.01) } func testFraction() throws { let mathList = MTMathList() let frac = MTFraction(hasRule: true) let num = MTMathList() num.add(MTMathAtomFactory.atom(forCharacter: "1")) let denom = MTMathList() denom.add(MTMathAtomFactory.atom(forCharacter: "3")) frac.numerator = num; frac.denominator = denom; mathList.add(frac) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))); XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTFractionDisplay) let fraction = sub0 as! MTFractionDisplay XCTAssertTrue(NSEqualRanges(fraction.range, NSMakeRange(0, 1))); XCTAssertFalse(fraction.hasScript); XCTAssertTrue(CGPointEqualToPoint(fraction.position, CGPointZero)); XCTAssertNotNil(fraction.numerator); XCTAssertNotNil(fraction.denominator); let display2 = fraction.numerator! XCTAssertEqual(display2.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display2.position, CGPointMake(0, 13.54))) XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 1))); XCTAssertFalse(display2.hasScript); XCTAssertEqual(display2.index, NSNotFound); XCTAssertEqual(display2.subDisplays.count, 1); let subnum = display2.subDisplays[0]; XCTAssertTrue(subnum is MTCTLineDisplay) let line2 = subnum as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "1"); XCTAssertTrue(CGPointEqualToPoint(line2.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(0, 1))); XCTAssertFalse(line2.hasScript); let display3 = fraction.denominator! XCTAssertEqual(display3.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display3.position, CGPointMake(0, -13.72))) XCTAssertTrue(NSEqualRanges(display3.range, NSMakeRange(0, 1))); XCTAssertFalse(display3.hasScript); XCTAssertEqual(display3.index, NSNotFound); XCTAssertEqual(display3.subDisplays.count, 1); let subdenom = display3.subDisplays[0]; XCTAssertTrue(subdenom is MTCTLineDisplay); let line3 = subdenom as! MTCTLineDisplay XCTAssertEqual(line3.atoms.count, 1); XCTAssertEqual(line3.attributedString?.string, "3"); XCTAssertTrue(CGPointEqualToPoint(line3.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line3.range, NSMakeRange(0, 1))); XCTAssertFalse(line3.hasScript); // dimensions XCTAssertEqual(display.ascent, 26.86, accuracy: 0.01) XCTAssertEqual(display.descent, 14.16, accuracy: 0.01) XCTAssertEqual(display.width, 10, accuracy: 0.01) } func testAtop() throws { let mathList = MTMathList() let frac = MTFraction(hasRule: false) let num = MTMathList() num.add(MTMathAtomFactory.atom(forCharacter: "1")) let denom = MTMathList() denom.add(MTMathAtomFactory.atom(forCharacter: "3")) frac.numerator = num; frac.denominator = denom; mathList.add(frac) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))); XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTFractionDisplay) let fraction = sub0 as! MTFractionDisplay XCTAssertTrue(NSEqualRanges(fraction.range, NSMakeRange(0, 1))); XCTAssertFalse(fraction.hasScript); XCTAssertTrue(CGPointEqualToPoint(fraction.position, CGPointZero)); XCTAssertNotNil(fraction.numerator); XCTAssertNotNil(fraction.denominator); let display2 = fraction.numerator! XCTAssertEqual(display2.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display2.position, CGPointMake(0, 13.54))) XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 1))); XCTAssertFalse(display2.hasScript); XCTAssertEqual(display2.index, NSNotFound); XCTAssertEqual(display2.subDisplays.count, 1); let subnum = display2.subDisplays[0]; XCTAssertTrue(subnum is MTCTLineDisplay); let line2 = subnum as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "1"); XCTAssertTrue(CGPointEqualToPoint(line2.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(0, 1))); XCTAssertFalse(line2.hasScript); let display3 = fraction.denominator! XCTAssertEqual(display3.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display3.position, CGPointMake(0, -13.72))) XCTAssertTrue(NSEqualRanges(display3.range, NSMakeRange(0, 1))); XCTAssertFalse(display3.hasScript); XCTAssertEqual(display3.index, NSNotFound); XCTAssertEqual(display3.subDisplays.count, 1); let subdenom = display3.subDisplays[0]; XCTAssertTrue(subdenom is MTCTLineDisplay); let line3 = subdenom as! MTCTLineDisplay XCTAssertEqual(line3.atoms.count, 1); XCTAssertEqual(line3.attributedString?.string, "3"); XCTAssertTrue(CGPointEqualToPoint(line3.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line3.range, NSMakeRange(0, 1))); XCTAssertFalse(line3.hasScript); // dimensions XCTAssertEqual(display.ascent, 26.86, accuracy: 0.01) XCTAssertEqual(display.descent, 14.16, accuracy: 0.01) XCTAssertEqual(display.width, 10, accuracy: 0.01) } func testBinomial() throws { let mathList = MTMathList() let frac = MTFraction(hasRule: false) let num = MTMathList() num.add(MTMathAtomFactory.atom(forCharacter: "1")) let denom = MTMathList() denom.add(MTMathAtomFactory.atom(forCharacter: "3")) frac.numerator = num; frac.denominator = denom; frac.leftDelimiter = "("; frac.rightDelimiter = ")"; mathList.add(frac) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))); XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTMathListDisplay); let display0 = sub0 as! MTMathListDisplay XCTAssertNotNil(display0); XCTAssertEqual(display0.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display0.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display0.range, NSMakeRange(0, 1))); XCTAssertFalse(display0.hasScript); XCTAssertEqual(display0.index, NSNotFound); XCTAssertEqual(display0.subDisplays.count, 3); let subLeft = display0.subDisplays[0]; XCTAssertTrue(subLeft is MTGlyphDisplay); let glyph = subLeft; XCTAssertTrue(CGPointEqualToPoint(glyph.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(glyph.range, NSMakeRange(NSNotFound, 0))); XCTAssertFalse(glyph.hasScript); let subFrac = display0.subDisplays[1]; XCTAssertTrue(subFrac is MTFractionDisplay) let fraction = subFrac as! MTFractionDisplay XCTAssertTrue(NSEqualRanges(fraction.range, NSMakeRange(0, 1))); XCTAssertFalse(fraction.hasScript); XCTAssertTrue(CGPointEqualToPoint(fraction.position, CGPointMake(14.72, 0))) XCTAssertNotNil(fraction.numerator); XCTAssertNotNil(fraction.denominator); let display2 = fraction.numerator! XCTAssertEqual(display2.type, .regular) XCTAssertTrue(CGPointMake(14.72, 13.54).isEqual(to: display2.position, accuracy: 0.01)) XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 1))); XCTAssertFalse(display2.hasScript); XCTAssertEqual(display2.index, NSNotFound); XCTAssertEqual(display2.subDisplays.count, 1); let subnum = display2.subDisplays[0]; XCTAssertTrue(subnum is MTCTLineDisplay); let line2 = subnum as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "1"); XCTAssertTrue(CGPointEqualToPoint(line2.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(0, 1))); XCTAssertFalse(line2.hasScript); let display3 = fraction.denominator! XCTAssertEqual(display3.type, .regular) XCTAssertTrue(CGPointMake(14.72, -13.72).isEqual(to: display3.position, accuracy: 0.01)) XCTAssertTrue(NSEqualRanges(display3.range, NSMakeRange(0, 1))); XCTAssertFalse(display3.hasScript); XCTAssertEqual(display3.index, NSNotFound); XCTAssertEqual(display3.subDisplays.count, 1); let subdenom = display3.subDisplays[0]; XCTAssertTrue(subdenom is MTCTLineDisplay); let line3 = subdenom as! MTCTLineDisplay XCTAssertEqual(line3.atoms.count, 1); XCTAssertEqual(line3.attributedString?.string, "3"); XCTAssertTrue(CGPointEqualToPoint(line3.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line3.range, NSMakeRange(0, 1))); XCTAssertFalse(line3.hasScript); let subRight = display0.subDisplays[2]; XCTAssertTrue(subRight is MTGlyphDisplay); let glyph2 = subRight as! MTGlyphDisplay XCTAssertTrue(CGPointEqualToPoint(glyph2.position, CGPointMake(24.72, 0))) XCTAssertTrue(NSEqualRanges(glyph2.range, NSMakeRange(NSNotFound, 0)), "Got \(glyph2.range) instead") XCTAssertFalse(glyph2.hasScript); // dimensions XCTAssertEqual(display.ascent, 28.92, accuracy: 0.001); XCTAssertEqual(display.descent, 18.92, accuracy: 0.001); XCTAssertEqual(display.width, 39.44, accuracy: 0.001); } func testLargeOpNoLimitsText() throws { let mathList = MTMathList() mathList.add(MTMathAtomFactory.atom(forLatexSymbol: "sin")) mathList.add(MTMathAtomFactory.atom(forCharacter: "x")) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 2)), "Got \(display.range) instead") XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 2); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTCTLineDisplay); let line = sub0 as! MTCTLineDisplay XCTAssertEqual(line.atoms.count, 1); XCTAssertEqual(line.attributedString?.string, "sin"); XCTAssertTrue(CGPointEqualToPoint(line.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line.range, NSMakeRange(0, 1))); XCTAssertFalse(line.hasScript); let sub1 = display.subDisplays[1]; XCTAssertTrue(sub1 is MTCTLineDisplay); let line2 = sub1 as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "π‘₯"); XCTAssertTrue(CGPointMake(27.893, 0).isEqual(to: line2.position, accuracy: 0.01)) XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(1, 1)), "Got \(line2.range) instead") XCTAssertFalse(line2.hasScript); XCTAssertEqual(display.ascent, 13.14, accuracy: 0.01) XCTAssertEqual(display.descent, 0.22, accuracy: 0.01) XCTAssertEqual(display.width, 39.33, accuracy: 0.01) } func testLargeOpNoLimitsSymbol() throws { let mathList = MTMathList() // Integral mathList.add(MTMathAtomFactory.atom(forLatexSymbol:"int")) mathList.add(MTMathAtomFactory.atom(forCharacter: "x")) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 2)), "Got \(display.range) instead") XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 2); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTGlyphDisplay); let glyph = sub0; XCTAssertTrue(CGPointEqualToPoint(glyph.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(glyph.range, NSMakeRange(0, 1))); XCTAssertFalse(glyph.hasScript); let sub1 = display.subDisplays[1]; XCTAssertTrue(sub1 is MTCTLineDisplay); let line2 = sub1 as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "π‘₯"); XCTAssertTrue(CGPointMake(23.313, 0).isEqual(to: line2.position, accuracy: 0.01)) XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(1, 1)), "Got \(line2.range) instead") XCTAssertFalse(line2.hasScript); XCTAssertEqual(display.ascent, 27.22, accuracy: 0.01) XCTAssertEqual(display.descent, 17.22, accuracy: 0.01) XCTAssertEqual(display.width, 34.753, accuracy: 0.01) } func testLargeOpNoLimitsSymbolWithScripts() throws { let mathList = MTMathList() // Integral let op = MTMathAtomFactory.atom(forLatexSymbol:"int")! op.superScript = MTMathList() op.superScript?.add(MTMathAtomFactory.atom(forCharacter: "1")) op.subScript = MTMathList() op.subScript?.add(MTMathAtomFactory.atom(forCharacter: "0")) mathList.add(op) mathList.add(MTMathAtomFactory.atom(forCharacter: "x")) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 2)), "Got \(display.range) instead") XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 4); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTMathListDisplay); let display0 = sub0 as! MTMathListDisplay XCTAssertEqual(display0.type, .superscript); XCTAssertTrue(CGPointEqualToPoint(display0.position, CGPointMake(19.98, 23.72))) XCTAssertTrue(NSEqualRanges(display0.range, NSMakeRange(0, 1))) XCTAssertFalse(display0.hasScript); XCTAssertEqual(display0.index, 0); XCTAssertEqual(display0.subDisplays.count, 1); let sub0sub0 = display0.subDisplays[0]; XCTAssertTrue(sub0sub0 is MTCTLineDisplay); let line1 = sub0sub0 as! MTCTLineDisplay XCTAssertEqual(line1.atoms.count, 1); XCTAssertEqual(line1.attributedString?.string, "1"); XCTAssertTrue(CGPointEqualToPoint(line1.position, CGPointZero)); XCTAssertFalse(line1.hasScript); let sub1 = display.subDisplays[1]; XCTAssertTrue(sub1 is MTMathListDisplay); let display1 = sub1 as! MTMathListDisplay XCTAssertEqual(display1.type, .ssubscript); // Due to italic correction, positioned before subscript. XCTAssertTrue(CGPointEqualToPoint(display1.position, CGPointMake(8.16, -20.02))) XCTAssertTrue(NSEqualRanges(display1.range, NSMakeRange(0, 1))) XCTAssertFalse(display1.hasScript); XCTAssertEqual(display1.index, 0); XCTAssertEqual(display1.subDisplays.count, 1); let sub1sub0 = display1.subDisplays[0]; XCTAssertTrue(sub1sub0 is MTCTLineDisplay); let line3 = sub1sub0 as! MTCTLineDisplay XCTAssertEqual(line3.atoms.count, 1); XCTAssertEqual(line3.attributedString?.string, "0"); XCTAssertTrue(CGPointEqualToPoint(line3.position, CGPointZero)); XCTAssertFalse(line3.hasScript); let sub2 = display.subDisplays[2]; XCTAssertTrue(sub2 is MTGlyphDisplay); let glyph = sub2; XCTAssertTrue(CGPointEqualToPoint(glyph.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(glyph.range, NSMakeRange(0, 1))); XCTAssertTrue(glyph.hasScript); // There are subscripts and superscripts let sub3 = display.subDisplays[3]; XCTAssertTrue(sub3 is MTCTLineDisplay); let line2 = sub3 as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "π‘₯"); XCTAssertTrue(CGPointMake(31.433, 0).isEqual(to: line2.position, accuracy: 0.01)) XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(1, 1)), "Got \(line2.range) instead") XCTAssertFalse(line1.hasScript); XCTAssertEqual(display.ascent, 33.044, accuracy: 0.001); XCTAssertEqual(display.descent, 20.328, accuracy: 0.001); XCTAssertEqual(display.width, 42.873, accuracy: 0.001); } func testLargeOpWithLimitsTextWithScripts() throws { let mathList = MTMathList() let op = MTMathAtomFactory.atom(forLatexSymbol:"lim")! op.subScript = MTMathList() op.subScript?.add(MTMathAtomFactory.atom(forLatexSymbol:"infty")) mathList.add(op) mathList.add(MTMathAtom(type: .variable, value:"x")) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 2)), "Got \(display.range) instead") XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 2); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTLargeOpLimitsDisplay) let largeOp = sub0 as! MTLargeOpLimitsDisplay XCTAssertTrue(CGPointEqualToPoint(largeOp.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(largeOp.range, NSMakeRange(0, 1))); XCTAssertFalse(largeOp.hasScript); XCTAssertNotNil(largeOp.lowerLimit); XCTAssertNil(largeOp.upperLimit); let display2 = largeOp.lowerLimit! XCTAssertEqual(display2.type, .regular) XCTAssertTrue(CGPointMake(6.89, -12.00).isEqual(to: display2.position, accuracy: 0.01)) XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 1))); XCTAssertFalse(display2.hasScript); XCTAssertEqual(display2.index, NSNotFound); XCTAssertEqual(display2.subDisplays.count, 1); let sub0sub0 = display2.subDisplays[0]; XCTAssertTrue(sub0sub0 is MTCTLineDisplay); let line1 = sub0sub0 as! MTCTLineDisplay XCTAssertEqual(line1.atoms.count, 1); XCTAssertEqual(line1.attributedString?.string, "∞"); XCTAssertTrue(CGPointEqualToPoint(line1.position, CGPointZero)); XCTAssertFalse(line1.hasScript); let sub3 = display.subDisplays[1]; XCTAssertTrue(sub3 is MTCTLineDisplay); let line2 = sub3 as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "π‘₯"); XCTAssertTrue(CGPointMake(31.1133, 0).isEqual(to: line2.position, accuracy: 0.01)) XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(1, 1)), "Got \(line2.range) instead") XCTAssertFalse(line1.hasScript); XCTAssertEqual(display.ascent, 13.88, accuracy: 0.01) XCTAssertEqual(display.descent, 12.154, accuracy: 0.01) XCTAssertEqual(display.width, 42.553, accuracy: 0.01) } func testLargeOpWithLimitsSymboltWithScripts() throws { let mathList = MTMathList() let op = MTMathAtomFactory.atom(forLatexSymbol:"sum")! op.superScript = MTMathList() op.superScript?.add(MTMathAtomFactory.atom(forLatexSymbol:"infty")) op.subScript = MTMathList() op.subScript?.add(MTMathAtomFactory.atom(forCharacter: "0")) mathList.add(op) mathList.add(MTMathAtom(type: .variable, value:"x")) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 2)), "Got \(display.range) instead") XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 2); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTLargeOpLimitsDisplay); let largeOp = sub0 as! MTLargeOpLimitsDisplay XCTAssertTrue(CGPointEqualToPoint(largeOp.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(largeOp.range, NSMakeRange(0, 1))); XCTAssertFalse(largeOp.hasScript); XCTAssertNotNil(largeOp.lowerLimit); XCTAssertNotNil(largeOp.upperLimit); let display2 = largeOp.lowerLimit! XCTAssertEqual(display2.type, .regular); XCTAssertTrue(CGPointMake(10.94, -21.664).isEqual(to: display2.position, accuracy: 0.01)) XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 1))) XCTAssertFalse(display2.hasScript); XCTAssertEqual(display2.index, NSNotFound); XCTAssertEqual(display2.subDisplays.count, 1); let sub0sub0 = display2.subDisplays[0]; XCTAssertTrue(sub0sub0 is MTCTLineDisplay); let line1 = sub0sub0 as! MTCTLineDisplay XCTAssertEqual(line1.atoms.count, 1); XCTAssertEqual(line1.attributedString?.string, "0"); XCTAssertTrue(CGPointEqualToPoint(line1.position, CGPointZero)); XCTAssertFalse(line1.hasScript); let displayU = largeOp.upperLimit! XCTAssertEqual(displayU.type, .regular); XCTAssertTrue(CGPointMake(7.44, 23.154).isEqual(to: displayU.position, accuracy: 0.01)) XCTAssertTrue(NSEqualRanges(displayU.range, NSMakeRange(0, 1))) XCTAssertFalse(displayU.hasScript); XCTAssertEqual(displayU.index, NSNotFound); XCTAssertEqual(displayU.subDisplays.count, 1); let sub0subU = displayU.subDisplays[0]; XCTAssertTrue(sub0subU is MTCTLineDisplay); let line3 = sub0subU as! MTCTLineDisplay XCTAssertEqual(line3.atoms.count, 1); XCTAssertEqual(line3.attributedString?.string, "∞"); XCTAssertTrue(CGPointEqualToPoint(line3.position, CGPointZero)); XCTAssertFalse(line3.hasScript); let sub3 = display.subDisplays[1]; XCTAssertTrue(sub3 is MTCTLineDisplay); let line2 = sub3 as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "π‘₯"); XCTAssertTrue(CGPointMake(32.2133, 0).isEqual(to: line2.position, accuracy: 0.01)) XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(1, 1)), "Got \(line2.range) instead") XCTAssertFalse(line2.hasScript); XCTAssertEqual(display.ascent, 29.342, accuracy: 0.001); XCTAssertEqual(display.descent, 21.972, accuracy: 0.001); XCTAssertEqual(display.width, 43.653, accuracy: 0.001); } func testInner() throws { let innerList = MTMathList() innerList.add(MTMathAtomFactory.atom(forCharacter: "x")) let inner = MTInner() inner.innerList = innerList; inner.leftBoundary = MTMathAtom(type: .boundary, value:"(") inner.rightBoundary = MTMathAtom(type: .boundary, value:")") let mathList = MTMathList() mathList.add(inner) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))); XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTMathListDisplay); let display2 = sub0 as! MTMathListDisplay XCTAssertEqual(display2.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display2.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 1))); XCTAssertFalse(display2.hasScript); XCTAssertEqual(display2.index, NSNotFound); XCTAssertEqual(display2.subDisplays.count, 3); let subLeft = display2.subDisplays[0]; XCTAssertTrue(subLeft is MTGlyphDisplay); let glyph = subLeft; XCTAssertTrue(CGPointEqualToPoint(glyph.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(glyph.range, NSMakeRange(NSNotFound, 0))); XCTAssertFalse(glyph.hasScript); let sub3 = display2.subDisplays[1]; XCTAssertTrue(sub3 is MTMathListDisplay); let display3 = sub3 as! MTMathListDisplay XCTAssertEqual(display3.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display3.position, CGPointMake(7.78, 0))) XCTAssertTrue(NSEqualRanges(display3.range, NSMakeRange(0, 1))); XCTAssertFalse(display3.hasScript); XCTAssertEqual(display3.index, NSNotFound); XCTAssertEqual(display3.subDisplays.count, 1); let subsub3 = display3.subDisplays[0]; XCTAssertTrue(subsub3 is MTCTLineDisplay); let line = subsub3 as! MTCTLineDisplay XCTAssertEqual(line.atoms.count, 1); // The x is italicized XCTAssertEqual(line.attributedString?.string, "π‘₯"); XCTAssertTrue(CGPointEqualToPoint(line.position, CGPointZero)); XCTAssertFalse(line.hasScript); let subRight = display2.subDisplays[2]; XCTAssertTrue(subRight is MTGlyphDisplay); let glyph2 = subRight as! MTGlyphDisplay XCTAssertTrue(CGPointEqualToPoint(glyph2.position, CGPointMake(19.22, 0))) XCTAssertTrue(NSEqualRanges(glyph2.range, NSMakeRange(NSNotFound, 0)), "Got \(glyph2.range) instead"); XCTAssertFalse(glyph2.hasScript); // dimensions XCTAssertEqual(display.ascent, display2.ascent); XCTAssertEqual(display.descent, display2.descent); XCTAssertEqual(display.width, display2.width); XCTAssertEqual(display.ascent, 14.96, accuracy: 0.001); XCTAssertEqual(display.descent, 4.96, accuracy: 0.001); XCTAssertEqual(display.width, 27, accuracy: 0.01) } func testOverline() throws { let mathList = MTMathList() let over = MTOverLine() let inner = MTMathList() inner.add(MTMathAtomFactory.atom(forCharacter: "1")) over.innerList = inner; mathList.add(over) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))); XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTLineDisplay); let overline = sub0 as! MTLineDisplay XCTAssertTrue(NSEqualRanges(overline.range, NSMakeRange(0, 1))); XCTAssertFalse(overline.hasScript); XCTAssertTrue(CGPointEqualToPoint(overline.position, CGPointZero)); XCTAssertNotNil(overline.inner); let display2 = overline.inner! XCTAssertEqual(display2.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display2.position, CGPointZero)) XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 1))); XCTAssertFalse(display2.hasScript); XCTAssertEqual(display2.index, NSNotFound); XCTAssertEqual(display2.subDisplays.count, 1); let subover = display2.subDisplays[0]; XCTAssertTrue(subover is MTCTLineDisplay); let line2 = subover as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "1"); XCTAssertTrue(CGPointEqualToPoint(line2.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(0, 1))); XCTAssertFalse(line2.hasScript); // dimensions XCTAssertEqual(display.ascent, 17.32, accuracy: 0.01) XCTAssertEqual(display.descent, 0.00, accuracy: 0.01) XCTAssertEqual(display.width, 10, accuracy: 0.01) } func testUnderline() throws { let mathList = MTMathList() let under = MTUnderLine() let inner = MTMathList() inner.add(MTMathAtomFactory.atom(forCharacter: "1")) under.innerList = inner; mathList.add(under) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))); XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTLineDisplay) let underline = sub0 as! MTLineDisplay XCTAssertTrue(NSEqualRanges(underline.range, NSMakeRange(0, 1))); XCTAssertFalse(underline.hasScript); XCTAssertTrue(CGPointEqualToPoint(underline.position, CGPointZero)); XCTAssertNotNil(underline.inner); let display2 = underline.inner! XCTAssertEqual(display2.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display2.position, CGPointZero)) XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 1))); XCTAssertFalse(display2.hasScript); XCTAssertEqual(display2.index, NSNotFound); XCTAssertEqual(display2.subDisplays.count, 1); let subover = display2.subDisplays[0]; XCTAssertTrue(subover is MTCTLineDisplay); let line2 = subover as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "1"); XCTAssertTrue(CGPointEqualToPoint(line2.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(0, 1))); XCTAssertFalse(line2.hasScript); // dimensions XCTAssertEqual(display.ascent, 13.32, accuracy: 0.01) XCTAssertEqual(display.descent, 4.00, accuracy: 0.01) XCTAssertEqual(display.width, 10, accuracy: 0.01) } func testSpacing() throws { let mathList = MTMathList() mathList.add(MTMathAtomFactory.atom(forCharacter: "x")) mathList.add(MTMathSpace(space: 9)) mathList.add(MTMathAtomFactory.atom(forCharacter: "y")) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 3)), "Got \(display.range) instead") XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 2); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTCTLineDisplay); let line = sub0 as! MTCTLineDisplay XCTAssertEqual(line.atoms.count, 1); // The x is italicized XCTAssertEqual(line.attributedString?.string, "π‘₯"); XCTAssertTrue(CGPointEqualToPoint(line.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line.range, NSMakeRange(0, 1))); XCTAssertFalse(line.hasScript); let sub1 = display.subDisplays[1]; XCTAssertTrue(sub1 is MTCTLineDisplay); let line2 = sub1 as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); // The y is italicized XCTAssertEqual(line2.attributedString?.string, "𝑦") XCTAssertTrue(CGPointMake(21.44, 0).isEqual(to: line2.position, accuracy: 0.01)) XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(2, 1)), "Got \(line2.range) instead") XCTAssertFalse(line2.hasScript); let noSpace = MTMathList() noSpace.add(MTMathAtomFactory.atom(forCharacter: "x")) noSpace.add(MTMathAtomFactory.atom(forCharacter: "y")) let noSpaceDisplay = MTTypesetter.createLineForMathList(noSpace, font:self.font, style:.display)! // dimensions XCTAssertEqual(display.ascent, noSpaceDisplay.ascent, accuracy: 0.01) XCTAssertEqual(display.descent, noSpaceDisplay.descent, accuracy: 0.01) XCTAssertEqual(display.width, noSpaceDisplay.width + 10, accuracy: 0.01) } // For issue: https://github.com/kostub/iosMath/issues/5 func testLargeRadicalDescent() throws { let list = MTMathListBuilder.build(fromString: "\\sqrt{\\frac{\\sqrt{\\frac{1}{2}} + 3}{\\sqrt{5}^x}}") let display = MTTypesetter.createLineForMathList(list, font:self.font, style:.display)! // dimensions XCTAssertEqual(display.ascent, 49.16, accuracy: 0.01) XCTAssertEqual(display.descent, 21.288, accuracy: 0.01) XCTAssertEqual(display.width, 82.569, accuracy: 0.01) } func testMathTable() throws { let c00 = MTMathAtomFactory.mathListForCharacters("1") let c01 = MTMathAtomFactory.mathListForCharacters("y+z") let c02 = MTMathAtomFactory.mathListForCharacters("y") let c11 = MTMathList() c11.add(MTMathAtomFactory.fraction(withNumeratorString: "1", denominatorString:"2x")) let c12 = MTMathAtomFactory.mathListForCharacters("x-y") let c20 = MTMathAtomFactory.mathListForCharacters("x+5") let c22 = MTMathAtomFactory.mathListForCharacters("12") let table = MTMathTable() table.set(cell: c00!, forRow:0, column:0) table.set(cell: c01!, forRow:0, column:1) table.set(cell: c02!, forRow:0, column:2) table.set(cell: c11, forRow:1, column:1) table.set(cell: c12!, forRow:1, column:2) table.set(cell: c20!, forRow:2, column:0) table.set(cell: c22!, forRow:2, column:2) // alignments table.set(alignment: .right, forColumn:0) table.set(alignment: .left, forColumn:2) table.interColumnSpacing = 18; // 1 quad let mathList = MTMathList() mathList.add(table) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))); XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTMathListDisplay); let display2 = sub0 as! MTMathListDisplay XCTAssertEqual(display2.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display2.position, CGPointZero)) XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 1))) XCTAssertFalse(display2.hasScript); XCTAssertEqual(display2.index, NSNotFound); XCTAssertEqual(display2.subDisplays.count, 3); let rowPos = [ 30.28, -2.68, -31.95 ] // alignment is right, center, left. let cellPos = [ [ 35.89, 65.89, 129.438 ], [ 45.89, 76.94, 129.438 ], [ 0, 87.66, 129.438] ] // check the 3 rows of the matrix for i in 0..<3 { let sub0i = display2.subDisplays[i]; XCTAssertTrue(sub0i is MTMathListDisplay); let row = sub0i as! MTMathListDisplay XCTAssertEqual(row.type, .regular) XCTAssertTrue(CGPointMake(0, rowPos[i]).isEqual(to: row.position, accuracy: 0.01)) XCTAssertTrue(NSEqualRanges(row.range, NSMakeRange(0, 3))); XCTAssertFalse(row.hasScript); XCTAssertEqual(row.index, NSNotFound); XCTAssertEqual(row.subDisplays.count, 3); for j in 0..<3 { let sub0ij = row.subDisplays[j]; XCTAssertTrue(sub0ij is MTMathListDisplay); let col = sub0ij as! MTMathListDisplay XCTAssertEqual(col.type, .regular); XCTAssertTrue(CGPointMake(cellPos[i][j], 0).isEqual(to: col.position, accuracy: 0.01)) XCTAssertFalse(col.hasScript) XCTAssertEqual(col.index, NSNotFound); } } } func testLatexSymbols() throws { // Test all latex symbols let allSymbols = MTMathAtomFactory.supportedLatexSymbolNames for symName in allSymbols { let list = MTMathList() let atom = MTMathAtomFactory.atom(forLatexSymbol:symName) XCTAssertNotNil(atom) if atom!.type >= .boundary { // Skip these types as they aren't symbols. continue; } list.add(atom) let display = MTTypesetter.createLineForMathList(list, font:self.font, style:.display)! XCTAssertNotNil(display, "Symbol \(symName)") XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))) XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1, "Symbol \(symName)"); let sub0 = display.subDisplays[0]; if atom!.type == .largeOperator && atom!.nucleus.count == 1 { // These large operators are rendered differently; XCTAssertTrue(sub0 is MTGlyphDisplay); let glyph = sub0 as! MTGlyphDisplay XCTAssertTrue(CGPointEqualToPoint(glyph.position, CGPointZero)) XCTAssertTrue(NSEqualRanges(glyph.range, NSMakeRange(0, 1))) XCTAssertFalse(glyph.hasScript); } else { XCTAssertTrue(sub0 is MTCTLineDisplay, "Symbol \(symName)"); let line = sub0 as! MTCTLineDisplay XCTAssertEqual(line.atoms.count, 1); if atom!.type != .variable { XCTAssertEqual(line.attributedString?.string, atom!.nucleus); } XCTAssertTrue(CGPointEqualToPoint(line.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line.range, NSMakeRange(0, 1))) XCTAssertFalse(line.hasScript); } // dimensions XCTAssertEqual(display.ascent, sub0.ascent); XCTAssertEqual(display.descent, sub0.descent); XCTAssertEqual(display.width, sub0.width); // All chars will occupy some space. if atom!.nucleus != " " { // all chars except space have height XCTAssertGreaterThan(display.ascent + display.descent, 0, "Symbol \(symName)") } // all chars have a width. XCTAssertGreaterThan(display.width, 0); } } func testAtomWithAllFontStyles(_ atom:MTMathAtom?) throws { guard let atom = atom else { return } let fontStyles = [ MTFontStyle.defaultStyle, .roman, .bold, .caligraphic, .typewriter, .italic, .sansSerif, .fraktur, .blackboard, .boldItalic, ] for fontStyle in fontStyles { let style = fontStyle let copy : MTMathAtom = atom.copy() copy.fontStyle = style let list = MTMathList(atom: copy) let display = MTTypesetter.createLineForMathList(list, font:self.font, style:.display)! XCTAssertNotNil(display, "Symbol \(atom.nucleus)") XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))) XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1, "Symbol \(atom.nucleus)") let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTCTLineDisplay, "Symbol \(atom.nucleus)") let line = sub0 as! MTCTLineDisplay XCTAssertEqual(line.atoms.count, 1); XCTAssertTrue(CGPointEqualToPoint(line.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line.range, NSMakeRange(0, 1))) XCTAssertFalse(line.hasScript); // dimensions XCTAssertEqual(display.ascent, sub0.ascent); XCTAssertEqual(display.descent, sub0.descent); XCTAssertEqual(display.width, sub0.width); // All chars will occupy some space. XCTAssertGreaterThan(display.ascent + display.descent, 0, "Symbol \(atom.nucleus)") // all chars have a width. XCTAssertGreaterThan(display.width, 0); } } func testVariables() throws { // Test all variables let allSymbols = MTMathAtomFactory.supportedLatexSymbolNames for symName in allSymbols { let atom = MTMathAtomFactory.atom(forLatexSymbol:symName)! XCTAssertNotNil(atom) if atom.type != .variable { // Skip these types as we are only interested in variables. continue; } try self.testAtomWithAllFontStyles(atom) } let alphaNum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789." let mathList = MTMathAtomFactory.mathListForCharacters(alphaNum) for atom in mathList!.atoms { try self.testAtomWithAllFontStyles(atom) } } func testStyleChanges() throws { let frac = MTMathAtomFactory.fraction(withNumeratorString: "1", denominatorString: "2") let list = MTMathList(atoms: [frac]) let style = MTMathStyle(style: .text) let textList = MTMathList(atoms: [style, frac]) // This should make the display same as text. let display = MTTypesetter.createLineForMathList(textList, font:self.font, style:.display)! let textDisplay = MTTypesetter.createLineForMathList(list, font:self.font, style:.text)! let originalDisplay = MTTypesetter.createLineForMathList(list, font:self.font, style:.display)! // Display should be the same as rendering the fraction in text style. XCTAssertEqual(display.ascent, textDisplay.ascent); XCTAssertEqual(display.descent, textDisplay.descent); XCTAssertEqual(display.width, textDisplay.width); // Original display should be larger than display since it is greater. XCTAssertGreaterThan(originalDisplay.ascent, display.ascent); XCTAssertGreaterThan(originalDisplay.descent, display.descent); XCTAssertGreaterThan(originalDisplay.width, display.width); } func testStyleMiddle() throws { let atom1 = MTMathAtomFactory.atom(forCharacter: "x")! let style1 = MTMathStyle(style: .script) as MTMathAtom let atom2 = MTMathAtomFactory.atom(forCharacter: "y")! let style2 = MTMathStyle(style: .scriptOfScript) as MTMathAtom let atom3 = MTMathAtomFactory.atom(forCharacter: "z")! let list = MTMathList(atoms: [atom1, style1, atom2, style2, atom3]) let display = MTTypesetter.createLineForMathList(list, font:self.font, style:.display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 5))) XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 3); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTCTLineDisplay); let line = sub0 as! MTCTLineDisplay XCTAssertEqual(line.atoms.count, 1); XCTAssertEqual(line.attributedString?.string, "π‘₯"); XCTAssertTrue(CGPointEqualToPoint(line.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line.range, NSMakeRange(0, 1))) XCTAssertFalse(line.hasScript); let sub1 = display.subDisplays[1]; XCTAssertTrue(sub1 is MTCTLineDisplay); let line1 = sub1 as! MTCTLineDisplay XCTAssertEqual(line1.atoms.count, 1); XCTAssertEqual(line1.attributedString?.string, "𝑦"); XCTAssertTrue(NSEqualRanges(line1.range, NSMakeRange(2, 1))) XCTAssertFalse(line1.hasScript); let sub2 = display.subDisplays[2]; XCTAssertTrue(sub2 is MTCTLineDisplay); let line2 = sub2 as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "𝑧"); XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(4, 1))) XCTAssertFalse(line2.hasScript); } func testAccent() throws { let mathList = MTMathList() let accent = MTMathAtomFactory.accent(withName: "hat") let inner = MTMathList() inner.add(MTMathAtomFactory.atom(forCharacter: "x")) accent?.innerList = inner; mathList.add(accent) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))); XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTAccentDisplay) let accentDisp = sub0 as! MTAccentDisplay XCTAssertTrue(NSEqualRanges(accentDisp.range, NSMakeRange(0, 1))); XCTAssertFalse(accentDisp.hasScript); XCTAssertTrue(CGPointEqualToPoint(accentDisp.position, CGPointZero)); XCTAssertNotNil(accentDisp.accentee); XCTAssertNotNil(accentDisp.accent); let display2 = accentDisp.accentee! XCTAssertEqual(display2.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display2.position, CGPointZero)) XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 1))); XCTAssertFalse(display2.hasScript); XCTAssertEqual(display2.index, NSNotFound); XCTAssertEqual(display2.subDisplays.count, 1); let subaccentee = display2.subDisplays[0]; XCTAssertTrue(subaccentee is MTCTLineDisplay); let line2 = subaccentee as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 1); XCTAssertEqual(line2.attributedString?.string, "π‘₯"); XCTAssertTrue(CGPointEqualToPoint(line2.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(0, 1))); XCTAssertFalse(line2.hasScript); let glyph = accentDisp.accent! XCTAssertTrue(CGPointEqualToPoint(glyph.position, CGPointMake(11.86, 0))) XCTAssertTrue(NSEqualRanges(glyph.range, NSMakeRange(0, 1))) XCTAssertFalse(glyph.hasScript); // dimensions XCTAssertEqual(display.ascent, 14.68, accuracy: 0.01) XCTAssertEqual(display.descent, 0.22, accuracy: 0.01) XCTAssertEqual(display.width, 11.44, accuracy: 0.01) } func testWideAccent() throws { let mathList = MTMathList() let accent = MTMathAtomFactory.accent(withName: "hat") accent?.innerList = MTMathAtomFactory.mathListForCharacters("xyzw") mathList.add(accent) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display)! XCTAssertNotNil(display); XCTAssertEqual(display.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(display.range, NSMakeRange(0, 1))); XCTAssertFalse(display.hasScript); XCTAssertEqual(display.index, NSNotFound); XCTAssertEqual(display.subDisplays.count, 1); let sub0 = display.subDisplays[0]; XCTAssertTrue(sub0 is MTAccentDisplay) let accentDisp = sub0 as! MTAccentDisplay XCTAssertTrue(NSEqualRanges(accentDisp.range, NSMakeRange(0, 1))); XCTAssertFalse(accentDisp.hasScript); XCTAssertTrue(CGPointEqualToPoint(accentDisp.position, CGPointZero)); XCTAssertNotNil(accentDisp.accentee); XCTAssertNotNil(accentDisp.accent); let display2 = accentDisp.accentee! XCTAssertEqual(display2.type, .regular); XCTAssertTrue(CGPointEqualToPoint(display2.position, CGPointZero)) XCTAssertTrue(NSEqualRanges(display2.range, NSMakeRange(0, 4))); XCTAssertFalse(display2.hasScript); XCTAssertEqual(display2.index, NSNotFound); XCTAssertEqual(display2.subDisplays.count, 1); let subaccentee = display2.subDisplays[0]; XCTAssertTrue(subaccentee is MTCTLineDisplay); let line2 = subaccentee as! MTCTLineDisplay XCTAssertEqual(line2.atoms.count, 4); XCTAssertEqual(line2.attributedString?.string, "π‘₯𝑦𝑧𝑀"); XCTAssertTrue(CGPointEqualToPoint(line2.position, CGPointZero)); XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(0, 4))); XCTAssertFalse(line2.hasScript); let glyph = accentDisp.accent! XCTAssertTrue(CGPointMake(3.47, 0).isEqual(to: glyph.position, accuracy: 0.01)) XCTAssertTrue(NSEqualRanges(glyph.range, NSMakeRange(0, 1))) XCTAssertFalse(glyph.hasScript); // dimensions XCTAssertEqual(display.ascent, 14.98, accuracy: 0.01) XCTAssertEqual(display.descent, 4.10, accuracy: 0.01) XCTAssertEqual(display.width, 44.86, accuracy: 0.01) } // MARK: - Interatom Line Breaking Tests func testInteratomLineBreaking_SimpleEquation() throws { // Simple equation that should break between atoms when width is constrained let latex = "a=1, b=2, c=3, d=4" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") // Create display with narrow width constraint (should force multiple lines) let maxWidth: CGFloat = 100 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should have multiple sub-displays (lines) XCTAssertGreaterThan(display!.subDisplays.count, 1, "Expected multiple lines with width constraint of \(maxWidth)") // Verify that each line respects the width constraint for (index, subDisplay) in display!.subDisplays.enumerated() { XCTAssertLessThanOrEqual(subDisplay.width, maxWidth * 1.1, "Line \(index) width \(subDisplay.width) exceeds maxWidth \(maxWidth)") } // Verify vertical positioning - lines should be below each other if display!.subDisplays.count > 1 { let firstLine = display!.subDisplays[0] let secondLine = display!.subDisplays[1] XCTAssertLessThan(secondLine.position.y, firstLine.position.y, "Second line should be positioned below first line") } } func testInteratomLineBreaking_TextAndMath() throws { // The user's specific example: text mixed with math let latex = "\\text{Calculer le discriminant }\\Delta=b^{2}-4ac\\text{ avec }a=1\\text{, }b=-1\\text{, }c=-5" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") // Create display with width constraint of 235 as specified by user let maxWidth: CGFloat = 235 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should have multiple lines XCTAssertGreaterThan(display!.subDisplays.count, 1, "Expected multiple lines with width \(maxWidth) for the given LaTeX") // Verify each line respects width constraint for (index, subDisplay) in display!.subDisplays.enumerated() { // Allow 10% tolerance for spacing and rounding XCTAssertLessThanOrEqual(subDisplay.width, maxWidth * 1.1, "Line \(index) width \(subDisplay.width) exceeds maxWidth \(maxWidth)") } // Verify vertical spacing between lines if display!.subDisplays.count >= 2 { let firstLine = display!.subDisplays[0] let secondLine = display!.subDisplays[1] let verticalSpacing = abs(firstLine.position.y - secondLine.position.y) XCTAssertGreaterThan(verticalSpacing, 0, "Lines should have vertical spacing") // Typical line height is around 1.5 * font size XCTAssertGreaterThan(verticalSpacing, self.font.fontSize * 0.5, "Vertical spacing seems too small") } } func testInteratomLineBreaking_BreaksAtAtomBoundaries() throws { // Test that breaking happens between atoms, not within them // Using mathematical atoms separated by operators let latex = "a+b+c+d+e+f+g+h+i+j+k+l+m+n+o+p" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") // Create display with narrow width that should force breaking let maxWidth: CGFloat = 120 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should have multiple lines XCTAssertGreaterThan(display!.subDisplays.count, 1, "Expected line breaking with narrow width") // Each line should respect the width constraint (with some tolerance) // since we break at atom boundaries, not mid-atom for (index, subDisplay) in display!.subDisplays.enumerated() { XCTAssertLessThanOrEqual(subDisplay.width, maxWidth * 1.2, "Line \(index) width \(subDisplay.width) exceeds maxWidth \(maxWidth) by too much") } } func testInteratomLineBreaking_WithSuperscripts() throws { // Test breaking with atoms that have superscripts let latex = "a^{2}+b^{2}+c^{2}+d^{2}+e^{2}" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") let maxWidth: CGFloat = 100 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should handle superscripts properly and create multiple lines if needed for (index, subDisplay) in display!.subDisplays.enumerated() { XCTAssertLessThanOrEqual(subDisplay.width, maxWidth * 1.1, "Line \(index) with superscripts exceeds width") } } func testInteratomLineBreaking_NoBreakingWhenNotNeeded() throws { // Test that short content doesn't break unnecessarily let latex = "a=b" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") let maxWidth: CGFloat = 200 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should stay on single line since content is short // Note: The number of subDisplays might be 1 or more depending on internal structure, // but the total width should be well under maxWidth XCTAssertLessThan(display!.width, maxWidth, "Short content should fit without breaking") } func testInteratomLineBreaking_BreaksAfterOperators() throws { // Test that breaking prefers to happen after operators (good break points) let latex = "a+b+c+d+e+f+g+h" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") let maxWidth: CGFloat = 80 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should break into multiple lines XCTAssertGreaterThan(display!.subDisplays.count, 1, "Expected multiple lines") // Verify width constraints for (index, subDisplay) in display!.subDisplays.enumerated() { XCTAssertLessThanOrEqual(subDisplay.width, maxWidth * 1.1, "Line \(index) exceeds width") } } // MARK: - Complex Display Line Breaking Tests (Fractions & Radicals) func testComplexDisplay_FractionStaysInlineWhenFits() throws { // Fraction that should stay inline with surrounding content let latex = "a+\\frac{1}{2}+b" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") // Wide enough to fit everything on one line let maxWidth: CGFloat = 200 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should fit on a single line (fraction stays inline) XCTAssertLessThanOrEqual(display!.subDisplays.count, 2, "Expected fraction to stay inline, not break to separate line") // Total width should be within constraint XCTAssertLessThan(display!.width, maxWidth, "Expression should fit within width constraint") } func testComplexDisplay_FractionBreaksWhenTooWide() throws { // Multiple fractions with narrow width should break let latex = "a+\\frac{1}{2}+b+\\frac{3}{4}+c" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") // Narrow width should force breaking let maxWidth: CGFloat = 80 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should have multiple lines XCTAssertGreaterThan(display!.subDisplays.count, 1, "Expected line breaking with narrow width") // Each line should respect width constraint (with tolerance) for (index, subDisplay) in display!.subDisplays.enumerated() { XCTAssertLessThanOrEqual(subDisplay.width, maxWidth * 1.2, "Line \(index) width \(subDisplay.width) exceeds maxWidth \(maxWidth) significantly") } } func testComplexDisplay_RadicalStaysInlineWhenFits() throws { // Radical that should stay inline with surrounding content let latex = "x+\\sqrt{2}+y" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") // Wide enough to fit everything on one line let maxWidth: CGFloat = 150 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should fit on a single line (radical stays inline) XCTAssertLessThanOrEqual(display!.subDisplays.count, 2, "Expected radical to stay inline, not break to separate line") // Total width should be within constraint XCTAssertLessThan(display!.width, maxWidth, "Expression should fit within width constraint") } func testComplexDisplay_RadicalBreaksWhenTooWide() throws { // Multiple radicals with narrow width should break let latex = "a+\\sqrt{2}+b+\\sqrt{3}+c+\\sqrt{5}" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") // Narrow width should force breaking let maxWidth: CGFloat = 100 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should have multiple lines XCTAssertGreaterThan(display!.subDisplays.count, 1, "Expected line breaking with narrow width") // Each line should respect width constraint (with tolerance) for (index, subDisplay) in display!.subDisplays.enumerated() { XCTAssertLessThanOrEqual(subDisplay.width, maxWidth * 1.2, "Line \(index) width \(subDisplay.width) exceeds maxWidth \(maxWidth) significantly") } } func testComplexDisplay_MixedFractionsAndRadicals() throws { // Mix of fractions and radicals let latex = "a+\\frac{1}{2}+\\sqrt{3}+b" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") // Medium width let maxWidth: CGFloat = 150 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should handle mixed complex displays for (index, subDisplay) in display!.subDisplays.enumerated() { XCTAssertLessThanOrEqual(subDisplay.width, maxWidth * 1.2, "Line \(index) width exceeds constraint") } } func testComplexDisplay_FractionWithComplexNumerator() throws { // Fraction with more complex content let latex = "\\frac{a+b}{c}+d" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") let maxWidth: CGFloat = 150 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should stay inline if it fits XCTAssertLessThan(display!.width, maxWidth * 1.5, "Complex fraction should handle width reasonably") } func testComplexDisplay_RadicalWithDegree() throws { // Cube root let latex = "\\sqrt[3]{8}+x" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") let maxWidth: CGFloat = 150 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should handle radicals with degrees XCTAssertLessThan(display!.width, maxWidth * 1.2, "Radical with degree should fit reasonably") } func testComplexDisplay_NoBreakingWithoutWidthConstraint() throws { // Without width constraint, should never break let latex = "a+\\frac{1}{2}+\\sqrt{3}+b+\\frac{4}{5}+c" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") // No width constraint (maxWidth = 0) let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display) XCTAssertNotNil(display) // Should not artificially break when no constraint // The display might have multiple subDisplays for internal structure, // but we verify that the total rendering doesn't have forced line breaks // by checking that all elements are at y=0 (no vertical offset) var allAtSameY = true let firstY = display!.subDisplays.first?.position.y ?? 0 for subDisplay in display!.subDisplays { if abs(subDisplay.position.y - firstY) > 0.1 { allAtSameY = false break } } XCTAssertTrue(allAtSameY, "Without width constraint, all elements should be at same Y position") } // MARK: - Additional Recommended Tests func testEdgeCase_VeryNarrowWidth() throws { // Test behavior with extremely narrow width constraint let latex = "a+b+c" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") // Very narrow width - each element might need its own line let maxWidth: CGFloat = 30 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should handle gracefully without crashing XCTAssertGreaterThan(display!.subDisplays.count, 0, "Should produce at least one display") // Each subdisplay should attempt to respect width (though may overflow for single atoms) for subDisplay in display!.subDisplays { // Allow overflow for unavoidable cases (single atom wider than constraint) XCTAssertLessThan(subDisplay.width, maxWidth * 3, "Width shouldn't be excessively larger than constraint") } } func testEdgeCase_VeryWideAtom() throws { // Test handling of atom that's wider than maxWidth constraint let latex = "\\text{ThisIsAnExtremelyLongWordThatCannotBreak}+b" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") let maxWidth: CGFloat = 100 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should not crash, even if single atom exceeds width XCTAssertGreaterThan(display!.subDisplays.count, 0, "Should produce display") // The wide atom should be placed, even if it exceeds maxWidth // (no way to break it further) XCTAssertNotNil(display, "Should handle oversized atoms gracefully") } func testMixedScriptsAndNonScripts() throws { // Test mixing atoms with scripts and without scripts let latex = "a+b^{2}+c+d^{3}+e" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") let maxWidth: CGFloat = 120 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should handle mixed content for (index, subDisplay) in display!.subDisplays.enumerated() { XCTAssertLessThanOrEqual(subDisplay.width, maxWidth * 1.3, "Line \(index) with mixed scripts should respect width reasonably") } } func testMultipleLineBreaks() throws { // Test expression that requires 4+ line breaks let latex = "a+b+c+d+e+f+g+h+i+j+k+l+m+n+o+p+q+r+s+t" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") // Very narrow to force many breaks let maxWidth: CGFloat = 60 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should create multiple lines XCTAssertGreaterThanOrEqual(display!.subDisplays.count, 4, "Should create at least 4 lines for long expression") // Verify vertical positioning - each line should be below the previous for i in 1..= 3 { let spacing1 = abs(display!.subDisplays[0].position.y - display!.subDisplays[1].position.y) let spacing2 = abs(display!.subDisplays[1].position.y - display!.subDisplays[2].position.y) XCTAssertEqual(spacing1, spacing2, accuracy: 1.0, "Line spacing should be consistent") } } func testUnicodeTextWrapping() throws { // Test wrapping with Unicode characters (including CJK) let latex = "\\text{Hello δΈ–η•Œ こんにけは μ•ˆλ…•ν•˜μ„Έμš” Ω…Ψ±Ψ­Ψ¨Ψ§}" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") let maxWidth: CGFloat = 150 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should handle Unicode text (may need fallback font) XCTAssertNotNil(display, "Should handle Unicode text") // Each line should attempt to respect width for subDisplay in display!.subDisplays { // More tolerance for Unicode as font metrics vary XCTAssertLessThanOrEqual(subDisplay.width, maxWidth * 1.5, "Unicode text line should respect width reasonably") } } func testNumberProtection() throws { // Test that numbers don't break in the middle let latex = "\\text{The value is 3.14159 or 2,718 or 1,000,000}" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") let maxWidth: CGFloat = 150 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Numbers should stay together (not split like "3.14" β†’ "3." on one line, "14" on next) // This is handled by the universal breaking mechanism with Core Text XCTAssertNotNil(display, "Should handle text with numbers") } // MARK: - Tests for Not-Yet-Optimized Cases (Document Current Behavior) func testCurrentBehavior_LargeOperators() throws { // Documents current behavior: large operators still force line breaks let latex = "\\sum_{i=1}^{n}x_{i}+\\int_{0}^{1}f(x)dx" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") let maxWidth: CGFloat = 300 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Current behavior: operators force breaks // This test documents current behavior for future improvement XCTAssertNotNil(display, "Large operators render (may force breaks)") } func testCurrentBehavior_NestedDelimiters() throws { // Documents current behavior: \left...\right still forces line breaks let latex = "a+\\left(b+c\\right)+d" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") let maxWidth: CGFloat = 200 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Current behavior: delimiters may force breaks // This test documents current behavior for future improvement XCTAssertNotNil(display, "Delimiters render (may force breaks)") } func testCurrentBehavior_ColoredExpressions() throws { // Documents current behavior: colored sections still force line breaks let latex = "a+\\color{red}{b+c}+d" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") let maxWidth: CGFloat = 200 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Current behavior: colored sections may force breaks // This test documents current behavior for future improvement XCTAssertNotNil(display, "Colored sections render (may force breaks)") } func testCurrentBehavior_MatricesWithSurroundingContent() throws { // Documents current behavior: matrices still force line breaks let latex = "A=\\begin{pmatrix}1&2\\\\3&4\\end{pmatrix}+B" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") let maxWidth: CGFloat = 300 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Current behavior: matrices force breaks // This test documents current behavior for future improvement XCTAssertNotNil(display, "Matrices render (force breaks)") } func testRealWorldExample_QuadraticFormula() throws { // Real-world test: quadratic formula with width constraint let latex = "x=\\frac{-b\\pm\\sqrt{b^{2}-4ac}}{2a}" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") let maxWidth: CGFloat = 200 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should render the formula (may break if too wide) XCTAssertNotNil(display, "Quadratic formula renders") XCTAssertGreaterThan(display!.width, 0, "Formula has non-zero width") } func testRealWorldExample_ComplexFraction() throws { // Real-world test: continued fraction let latex = "\\frac{1}{2+\\frac{1}{3+\\frac{1}{4}}}" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") let maxWidth: CGFloat = 150 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // Should render nested fractions XCTAssertNotNil(display, "Nested fractions render") XCTAssertGreaterThan(display!.width, 0, "Formula has non-zero width") } func testRealWorldExample_MixedOperationsWithFractions() throws { // Real-world test: mixed arithmetic with multiple fractions let latex = "\\frac{1}{2}+\\frac{2}{3}+\\frac{3}{4}+\\frac{4}{5}" let mathList = MTMathListBuilder.build(fromString: latex) XCTAssertNotNil(mathList, "Failed to parse LaTeX") let maxWidth: CGFloat = 180 let display = MTTypesetter.createLineForMathList(mathList, font: self.font, style: .display, maxWidth: maxWidth) XCTAssertNotNil(display) // With new implementation, fractions should stay inline when possible // May break into 2-3 lines depending on actual widths XCTAssertGreaterThan(display!.subDisplays.count, 0, "Multiple fractions render") // Verify width constraints are respected for (index, subDisplay) in display!.subDisplays.enumerated() { XCTAssertLessThanOrEqual(subDisplay.width, maxWidth * 1.3, "Line \(index) should respect width constraint reasonably") } } }