diff --git a/Package.swift b/Package.swift index 9327152..9830ae2 100644 --- a/Package.swift +++ b/Package.swift @@ -2,6 +2,7 @@ // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription +import CoreGraphics let package = Package( name: "SwiftMathRender", @@ -20,7 +21,10 @@ let package = Package( // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "SwiftMathRender", - dependencies: []), + dependencies: [], + resources: [ + .copy("mathFonts.bundle") + ]), .testTarget( name: "SwiftMathRenderTests", dependencies: ["SwiftMathRender"]), diff --git a/Sources/SwiftMathRender/MathRender/MTFont.swift b/Sources/SwiftMathRender/MathRender/MTFont.swift index 5689753..e82316a 100644 --- a/Sources/SwiftMathRender/MathRender/MTFont.swift +++ b/Sources/SwiftMathRender/MathRender/MTFont.swift @@ -33,12 +33,12 @@ public class MTFont { // In particular it does not have the math italic characters which breaks our variable rendering. // So we first load a CGFont from the file and then convert it to a CTFont. self.init() - print("Loading font %@", name); + print("Loading font \(name)") let bundle = MTFont.fontBundle let fontPath = bundle.path(forResource: name, ofType: "otf") let fontDataProvider = CGDataProvider(filename: fontPath!) self.defaultCGFont = CGFont(fontDataProvider!)! - print("Num glyphs: %zd", self.defaultCGFont.numberOfGlyphs) + print("Num glyphs: \(self.defaultCGFont.numberOfGlyphs)") self.ctFont = CTFontCreateWithGraphicsFont(self.defaultCGFont, size, nil, nil); @@ -50,7 +50,8 @@ public class MTFont { static var fontBundle:Bundle { // Uses bundle for class so that this can be access by the unit tests. - return Bundle(url: Bundle(for: self).url(forResource: "iosMathFonts", withExtension: "bundle")!)! + let url = Bundle.module.url(forResource: "mathFonts", withExtension: "bundle")! + return Bundle(url: url)! } func copy(withSize size: CGFloat) -> MTFont { diff --git a/Sources/SwiftMathRender/MathRender/MTMathAtomFactory.swift b/Sources/SwiftMathRender/MathRender/MTMathAtomFactory.swift index 8ca2800..9947fb9 100644 --- a/Sources/SwiftMathRender/MathRender/MTMathAtomFactory.swift +++ b/Sources/SwiftMathRender/MathRender/MTMathAtomFactory.swift @@ -120,6 +120,11 @@ public class MTMathAtomFactory { return _accentValueToName! } + static var supportedLatexSymbolNames:[String] { + let commands = MTMathAtomFactory.supportedLatexSymbols + return commands.keys.map { String($0) } + } + static var supportedLatexSymbols: [String: MTMathAtom] = [ "square" : MTMathAtomFactory.placeholder(), @@ -663,13 +668,21 @@ public class MTMathAtomFactory { /** Returns a fraction with the given numerator and denominator. */ public static func fraction(withNumerator num: MTMathList, denominator denom: MTMathList) -> MTFraction { let frac = MTFraction() - frac.numerator = num frac.denominator = denom - return frac } + public static func mathListForCharacters(_ chars:String) -> MTMathList? { + let list = MTMathList() + for ch in chars { + if let atom = self.atom(forCharacter: ch) { + list.add(atom) + } + } + return list + } + /** Simplification of above function when numerator and denominator are simple strings. This function uses `mathListForCharacters` to convert the strings to `MTMathList`s. */ public static func fraction(withNumeratorString numStr: String, denominatorString denomStr: String) -> MTFraction { diff --git a/Sources/SwiftMathRender/MathRender/MTMathList.swift b/Sources/SwiftMathRender/MathRender/MTMathList.swift index 2444798..3de9f36 100644 --- a/Sources/SwiftMathRender/MathRender/MTMathList.swift +++ b/Sources/SwiftMathRender/MathRender/MTMathList.swift @@ -8,8 +8,9 @@ import Foundation // type defines spacing and how it is rendered -public enum MTMathAtomType: String, CustomStringConvertible { - case ordinary // number or text +public enum MTMathAtomType: Int, CustomStringConvertible, Comparable { + + case ordinary = 1 // number or text case number // number case variable // text in italic case largeOperator // sin/cos, integral @@ -28,15 +29,15 @@ public enum MTMathAtomType: String, CustomStringConvertible { case accent // accented atom // these atoms do not support subscripts/superscripts: - case boundary - case space + case boundary = 101 + case space = 201 // Denotes style changes during randering case style case color case colorBox - case table + case table = 1001 func isNotBinaryOperator() -> Bool { switch self { @@ -51,8 +52,38 @@ public enum MTMathAtomType: String, CustomStringConvertible { // we want string representations to be capitalized public var description: String { - self.rawValue.capitalized + switch self { + case .ordinary: return "Ordinary" + case .number: return "Number" + case .variable: return "Variable" + case .largeOperator: return "Large Operator" + case .binaryOperator: return "Binary Operator" + case .unaryOperator: return "Unary Operator" + case .relation: return "Relation" + case .open: return "Open" + case .close: return "Close" + case .fraction: return "Fraction" + case .radical: return "Radical" + case .punctuation: return "Punctuation" + case .placeholder: return "Placeholder" + case .inner: return "Inner" + case .underline: return "Underline" + case .overline: return "Overline" + case .accent: return "Accent" + case .boundary: return "Boundary" + case .space: return "Space" + case .style: return "Style" + case .color: return "Color" + case .colorBox: return "Colorbox" + case .table: return "Table" + } } + + // comparable support + public static func < (lhs: MTMathAtomType, rhs: MTMathAtomType) -> Bool { + lhs.rawValue < rhs.rawValue + } + } public enum MTFontStyle:Int { diff --git a/Sources/SwiftMathRender/MathRender/MTTypesetter.swift b/Sources/SwiftMathRender/MathRender/MTTypesetter.swift index defef58..0769875 100644 --- a/Sources/SwiftMathRender/MathRender/MTTypesetter.swift +++ b/Sources/SwiftMathRender/MathRender/MTTypesetter.swift @@ -378,7 +378,11 @@ class MTTypesetter { var currentLine:NSMutableAttributedString! var currentAtoms = [MTMathAtom]() // List of atoms that make the line var currentLineIndexRange = NSMakeRange(0, 0) - var style:MTLineStyle = .display + var style:MTLineStyle { + didSet { + self.styleFont = self.font.copy(withSize: Self.getStyleSize(self.style, font: self.font)) + } + } var styleFont:MTFont! var cramped = false var spaced = false @@ -472,11 +476,6 @@ class MTTypesetter { } } - func setStyle(_ style:MTLineStyle) { - self.style = style - self.styleFont = self.font.copy(withSize: Self.getStyleSize(self.style, font: self.font)) //copyFontWithSize:[self.class] getStyleSize:style font:font]) - } - func addInterElementSpace(_ prevNode:MTMathAtom?, currentType type:MTMathAtomType) { var interElementSpace = CGFloat(0) if prevNode != nil { @@ -754,7 +753,7 @@ class MTTypesetter { if (currentLine.length > 0) { self.addDisplayLine() } - if spaced && !lastType.rawValue.isEmpty { + if spaced { // If spaced then add an interelement space between the last type and close let display = displayAtoms.last let interElementSpace = self.getInterElementSpace(lastType, right:.close) diff --git a/Sources/SwiftMathRender/SwiftMathRender.swift b/Sources/SwiftMathRender/SwiftMathRender.swift deleted file mode 100644 index a8d86e2..0000000 --- a/Sources/SwiftMathRender/SwiftMathRender.swift +++ /dev/null @@ -1,6 +0,0 @@ -public struct SwiftMathRender { - public private(set) var text = "Hello, World!" - - public init() { - } -} diff --git a/Sources/SwiftMathRender/mathFonts.bundle/Info.plist b/Sources/SwiftMathRender/mathFonts.bundle/Info.plist new file mode 100644 index 0000000..03fb083 Binary files /dev/null and b/Sources/SwiftMathRender/mathFonts.bundle/Info.plist differ diff --git a/Sources/SwiftMathRender/mathFonts.bundle/en.lproj/InfoPlist.strings b/Sources/SwiftMathRender/mathFonts.bundle/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..3967e06 Binary files /dev/null and b/Sources/SwiftMathRender/mathFonts.bundle/en.lproj/InfoPlist.strings differ diff --git a/Sources/SwiftMathRender/mathFonts.bundle/latinmodern-math.otf b/Sources/SwiftMathRender/mathFonts.bundle/latinmodern-math.otf new file mode 100644 index 0000000..0e4642e Binary files /dev/null and b/Sources/SwiftMathRender/mathFonts.bundle/latinmodern-math.otf differ diff --git a/Sources/SwiftMathRender/mathFonts.bundle/latinmodern-math.plist b/Sources/SwiftMathRender/mathFonts.bundle/latinmodern-math.plist new file mode 100644 index 0000000..c9240d8 Binary files /dev/null and b/Sources/SwiftMathRender/mathFonts.bundle/latinmodern-math.plist differ diff --git a/Sources/SwiftMathRender/mathFonts.bundle/texgyretermes-math.otf b/Sources/SwiftMathRender/mathFonts.bundle/texgyretermes-math.otf new file mode 100755 index 0000000..4cef7ca Binary files /dev/null and b/Sources/SwiftMathRender/mathFonts.bundle/texgyretermes-math.otf differ diff --git a/Sources/SwiftMathRender/mathFonts.bundle/texgyretermes-math.plist b/Sources/SwiftMathRender/mathFonts.bundle/texgyretermes-math.plist new file mode 100644 index 0000000..41a4607 Binary files /dev/null and b/Sources/SwiftMathRender/mathFonts.bundle/texgyretermes-math.plist differ diff --git a/Sources/SwiftMathRender/mathFonts.bundle/xits-math.otf b/Sources/SwiftMathRender/mathFonts.bundle/xits-math.otf new file mode 100644 index 0000000..ebc28f5 Binary files /dev/null and b/Sources/SwiftMathRender/mathFonts.bundle/xits-math.otf differ diff --git a/Sources/SwiftMathRender/mathFonts.bundle/xits-math.plist b/Sources/SwiftMathRender/mathFonts.bundle/xits-math.plist new file mode 100644 index 0000000..15b65ae Binary files /dev/null and b/Sources/SwiftMathRender/mathFonts.bundle/xits-math.plist differ diff --git a/Tests/SwiftMathRenderTests/MTTypesetterTests.swift b/Tests/SwiftMathRenderTests/MTTypesetterTests.swift new file mode 100644 index 0000000..d7e51d3 --- /dev/null +++ b/Tests/SwiftMathRenderTests/MTTypesetterTests.swift @@ -0,0 +1,1568 @@ +import XCTest +@testable import SwiftMathRender + +// +// MathTypesetterTests.swift +// MathTypesetterTests +// +// Created by Mike Griebling on 2023-01-02. +// + +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.24, 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.12, 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.12, 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.12, accuracy: 0.01) + XCTAssertEqual(display.width, 92.36, accuracy: 0.01) + } + +// #define XCTAssertTrue(NSEqualPoints(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(NSEqualPoints(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.24, 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(NSEqualPoints(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.954, 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(NSEqualPoints(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(NSEqualPoints(display3.position, CGPointMake(11.44, -5.278))) + 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.292, 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(NSEqualPoints(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); + + // dimensions + XCTAssertEqual(display.ascent, 19.34, accuracy: 0.01) + XCTAssertEqual(display.descent, 1.48, 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(NSEqualPoints(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(NSEqualPoints(display3.position, CGPointMake(6.12, 10.716))) + 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.48, 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(NSEqualPoints(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(NSEqualPoints(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.18, 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(NSEqualPoints(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(NSEqualPoints(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.18, 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(NSEqualPoints(fraction.position, CGPointMake(14.72, 0))) + XCTAssertNotNil(fraction.numerator); + XCTAssertNotNil(fraction.denominator); + + let display2 = fraction.numerator! + XCTAssertEqual(display2.type, .regular); + XCTAssertTrue(NSEqualPoints(display2.position, CGPointMake(14.72, 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(NSEqualPoints(display3.position, CGPointMake(14.72, -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); + + let subRight = display0.subDisplays[2]; + XCTAssertTrue(subRight is MTGlyphDisplay); + let glyph2 = subRight as! MTGlyphDisplay + XCTAssertTrue(NSEqualPoints(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.93, accuracy: 0.001); + XCTAssertEqual(display.descent, 18.93, 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(NSEqualPoints(line2.position, CGPointMake(27.893, 0))) + 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.24, 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(NSEqualPoints(line2.position, CGPointMake(23.313, 0))) + XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(1, 1)), "Got \(line2.range) instead") + XCTAssertFalse(line2.hasScript); + + XCTAssertEqual(display.ascent, 27.23, accuracy: 0.01) + XCTAssertEqual(display.descent, 17.23, 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(NSEqualPoints(display0.position, CGPointMake(19.98, 23.73))) + 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(NSEqualPoints(display1.position, CGPointMake(8.16, -20.03))) + 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(NSEqualPoints(line2.position, CGPointMake(31.433, 0))) + XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(1, 1)), "Got \(line2.range) instead") + XCTAssertFalse(line1.hasScript); + + XCTAssertEqual(display.ascent, 33.054, accuracy: 0.001); + XCTAssertEqual(display.descent, 20.352, 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(NSEqualPoints(display2.position, CGPointMake(6.89, -12.02))) + 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(NSEqualPoints(line2.position, CGPointMake(31.1133, 0))) + 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.188, 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(NSEqualPoints(display2.position, CGPointMake(10.94, -21.674))) + 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(NSEqualPoints(displayU.position, CGPointMake(7.44, 23.178))) + 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(NSEqualPoints(line2.position, CGPointMake(32.2133, 0))) + XCTAssertTrue(NSEqualRanges(line2.range, NSMakeRange(1, 1)), "Got \(line2.range) instead") + XCTAssertFalse(line2.hasScript); + + XCTAssertEqual(display.ascent, 29.366, accuracy: 0.001); + XCTAssertEqual(display.descent, 21.996, 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(NSEqualPoints(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(NSEqualPoints(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.97, accuracy: 0.001); + XCTAssertEqual(display.descent, 4.97, 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(NSEqualPoints(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.02, 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(NSEqualPoints(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.02, 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 x is italicized + XCTAssertEqual(line2.attributedString?.string, "𝑦"); + XCTAssertTrue(NSEqualPoints(line2.position, CGPointMake(21.44, 0))) + 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.18, accuracy: 0.01) + XCTAssertEqual(display.descent, 21.308, 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(NSEqualPoints(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.31, -2.67, -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(NSEqualPoints(row.position, CGPointMake(0, rowPos[i]))) + 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(NSEqualPoints(col.position, CGPointMake(cellPos[i][j], 0))) + 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(NSEqualPoints(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(atom: frac) + // let style = MTMathStyle(style: .text) + let textList = MTMathList(atom: 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(NSEqualPoints(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(NSEqualPoints(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.24, 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(NSEqualPoints(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(NSEqualPoints(glyph.position, CGPointMake(3.47, 0))) + XCTAssertTrue(NSEqualRanges(glyph.range, NSMakeRange(0, 1))) + XCTAssertFalse(glyph.hasScript); + + // dimensions + XCTAssertEqual(display.ascent, 14.98, accuracy: 0.01) + XCTAssertEqual(display.descent, 4.12, accuracy: 0.01) + XCTAssertEqual(display.width, 44.86, accuracy: 0.01) + } + +} +