Updated comments and minor fixes.

This commit is contained in:
Michael Griebling
2023-01-20 10:54:45 -05:00
parent 648905173f
commit f23a5b9fb5
19 changed files with 765 additions and 596 deletions

View File

@@ -4,20 +4,19 @@
for displaying beautifully rendered math equations in iOS and MacOS applications. It typesets formulae written for displaying beautifully rendered math equations in iOS and MacOS applications. It typesets formulae written
using LaTeX in a `UILabel` equivalent class. It uses the same typesetting rules as LaTeX and using LaTeX in a `UILabel` equivalent class. It uses the same typesetting rules as LaTeX and
so the equations are rendered exactly as LaTeX would render them. so the equations are rendered exactly as LaTeX would render them.
`
SwiftMath` is a Swift translation of the latest `iosMath` v0.9.5 release but includes bug fixes `SwiftMath` is similar to [MathJax](https://www.mathjax.org) or
[KaTeX](https://github.com/Khan/KaTeX) for the web but for native iOS or MacOS
applications without having to use a `UIWebView` and Javascript. More
importantly, it is significantly faster than using a `UIWebView`.
`SwiftMath` is a Swift translation of the latest `iosMath` v0.9.5 release but includes bug fixes
and enhancements like a new \lbar (lambda bar) character and cyrillic alphabet support. and enhancements like a new \lbar (lambda bar) character and cyrillic alphabet support.
The original `iosMath` test suites have also been translated to Swift and run without errors. The original `iosMath` test suites have also been translated to Swift and run without errors.
Note: Error test conditions are ignored to avoid tagging everything with silly `throw`s. Note: Error test conditions are ignored to avoid tagging everything with silly `throw`s.
Please let me know of any bugs or bug fixes that you find. Please let me know of any bugs or bug fixes that you find.
`SwiftMath` prepackages everything needed for direct access via the Swift Package Manager. `SwiftMath` prepackages everything needed for direct access via the Swift Package Manager.
No need for complicated alien pods that never seem to work quite right.
It is similar to [MathJax](https://www.mathjax.org) or
[KaTeX](https://github.com/Khan/KaTeX) for the web but for native iOS or MacOS
applications without having to use a `UIWebView` and Javascript. More
importantly, it is significantly faster than using a `UIWebView`.
## Examples ## Examples
Here are screenshots of some formulae that were rendered with this library: Here are screenshots of some formulae that were rendered with this library:
@@ -50,7 +49,7 @@ f(x) = \int\limits_{-\infty}^\infty\!\hat f(\xi)\,e^{2 \pi i \xi x}\,\mathrm{d}\
More examples are included in [EXAMPLES](EXAMPLES.md) More examples are included in [EXAMPLES](EXAMPLES.md)
## Requirements ## Requirements
`SwiftMath` works on iOS 6+ or MacOS 10.8+ and requires ARC to build. It depends `SwiftMath` works on iOS 11+ or MacOS 11+. It depends
on the following Apple frameworks: on the following Apple frameworks:
* Foundation.framework * Foundation.framework
@@ -97,8 +96,8 @@ import SwiftMath
struct MathView: UIViewRepresentable { struct MathView: UIViewRepresentable {
@Binding var equation: String var equation: String
@Binding var fontSize: CGFloat var fontSize: CGFloat
func makeUIView(context: Context) -> MTMathUILabel { func makeUIView(context: Context) -> MTMathUILabel {
let view = MTMathUILabel() let view = MTMathUILabel()
@@ -115,7 +114,7 @@ struct MathView: UIViewRepresentable {
} }
``` ```
If you need code that works with SwiftUI running natively under MacOS you'll need the following: For code that works with SwiftUI running natively under MacOS use the following:
```swift ```swift
import SwiftUI import SwiftUI
@@ -123,8 +122,8 @@ import SwiftMath
struct MathView: NSViewRepresentable { struct MathView: NSViewRepresentable {
@Binding var equation: String var equation: String
@Binding var fontSize: CGFloat var fontSize: CGFloat
func makeNSView(context: Context) -> MTMathUILabel { func makeNSView(context: Context) -> MTMathUILabel {
let view = MTMathUILabel() let view = MTMathUILabel()
@@ -168,7 +167,7 @@ This is a list of formula types that the library currently supports:
### Example ### Example
The [SwiftMathDemo](https://github.com/mgriebling/SwiftMathDemo) is a Swift version The [SwiftMathDemo](https://github.com/mgriebling/SwiftMathDemo) is a SwiftUI version
of the Objective-C demo included in `iosMath` that uses `SwiftMath` as a Swift package dependency. of the Objective-C demo included in `iosMath` that uses `SwiftMath` as a Swift package dependency.
### Advanced configuration ### Advanced configuration
@@ -204,7 +203,7 @@ label.fontSize = 30
The default font is *Latin Modern Math*. This can be changed as: The default font is *Latin Modern Math*. This can be changed as:
```swift ```swift
label.font = MTFontManager().termesFont(withSize:20) label.font = MTFontManager.fontmanager.termesFont(withSize:20)
``` ```
This project has 3 fonts bundled with it, but you can use any OTF math This project has 3 fonts bundled with it, but you can use any OTF math

View File

@@ -1,8 +1,10 @@
//
// MTBezierPath.swift
// MathRenderSwift
// //
// Created by Mike Griebling on 2022-12-31. // Created by Mike Griebling on 2022-12-31.
// Translated from an Objective-C implementation by .
//
// This software may be modified and distributed under the terms of the
// MIT license. See the LICENSE file for details.
// //
import Foundation import Foundation

View File

@@ -1,8 +1,10 @@
//
// MTColor.swift
// MathRenderSwift
// //
// Created by Mike Griebling on 2022-12-31. // Created by Mike Griebling on 2022-12-31.
// Translated from an Objective-C implementation by Markus Sähn.
//
// This software may be modified and distributed under the terms of the
// MIT license. See the LICENSE file for details.
// //
import Foundation import Foundation
@@ -19,7 +21,8 @@ extension MTColor {
scanner.scanHexInt64(&rgbValue) scanner.scanHexInt64(&rgbValue)
return MTColor(red: CGFloat((rgbValue & 0xFF0000) >> 16)/255.0, return MTColor(red: CGFloat((rgbValue & 0xFF0000) >> 16)/255.0,
green: CGFloat((rgbValue & 0xFF00) >> 8)/255.0, green: CGFloat((rgbValue & 0xFF00) >> 8)/255.0,
blue: CGFloat((rgbValue & 0xFF))/255.0, alpha: 1.0) blue: CGFloat((rgbValue & 0xFF))/255.0,
alpha: 1.0)
} }
} }

View File

@@ -1,12 +1,13 @@
//
// MTConfig.swift
// MathRenderSwift
//
// Created by Mike Griebling on 2023-01-01.
//
import Foundation import Foundation
//
// Created by Mike Griebling on 2022-12-31.
// Translated from an Objective-C implementation by .
//
// This software may be modified and distributed under the terms of the
// MIT license. See the LICENSE file for details.
//
#if os(iOS) #if os(iOS)
import UIKit import UIKit

View File

@@ -1,17 +1,11 @@
//
// MTFont.swift
// MathRenderSwift
//
// Created by Mike Griebling on 2022-12-31.
//
import Foundation import Foundation
import CoreGraphics import CoreGraphics
import CoreText import CoreText
// //
// Created by Kostub Deshmukh on 5/18/16. // Created by Mike Griebling on 2022-12-31.
// Modified by Michael Griebling on 17 Jan 2023. // Translated from an Objective-C implementation by Kostub Deshmukh.
// //
// This software may be modified and distributed under the terms of the // This software may be modified and distributed under the terms of the
// MIT license. See the LICENSE file for details. // MIT license. See the LICENSE file for details.
@@ -26,10 +20,10 @@ public class MTFont {
init() {} init() {}
/// `MTFont(fontWithName:)` does not load the complete math font, it only has about half the glyphs of the full math font.
/// 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.
convenience init(fontWithName name: String, size:CGFloat) { convenience init(fontWithName name: String, size:CGFloat) {
// CTFontCreateWithName does not load the complete math font, it only has about half the glyphs of the full math font.
// 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() self.init()
print("Loading font \(name)") print("Loading font \(name)")
let bundle = MTFont.fontBundle let bundle = MTFont.fontBundle
@@ -51,7 +45,8 @@ public class MTFont {
Bundle(url: Bundle.module.url(forResource: "mathFonts", withExtension: "bundle")!)! Bundle(url: Bundle.module.url(forResource: "mathFonts", withExtension: "bundle")!)!
} }
func copy(withSize size: CGFloat) -> MTFont { /** Returns a copy of this font but with a different size. */
public func copy(withSize size: CGFloat) -> MTFont {
let newFont = MTFont() let newFont = MTFont()
newFont.defaultCGFont = self.defaultCGFont newFont.defaultCGFont = self.defaultCGFont
newFont.ctFont = CTFontCreateWithGraphicsFont(self.defaultCGFont, size, nil, nil) newFont.ctFont = CTFontCreateWithGraphicsFont(self.defaultCGFont, size, nil, nil)
@@ -69,6 +64,7 @@ public class MTFont {
defaultCGFont.getGlyphWithGlyphName(name: name as CFString) defaultCGFont.getGlyphWithGlyphName(name: name as CFString)
} }
var fontSize:CGFloat { CTFontGetSize(self.ctFont) } /** The size of this font in points. */
public var fontSize:CGFloat { CTFontGetSize(self.ctFont) }
} }

View File

@@ -1,8 +1,10 @@
//
// MTFontManager.swift
// MathRenderSwift
// //
// Created by Mike Griebling on 2022-12-31. // Created by Mike Griebling on 2022-12-31.
// Translated from an Objective-C implementation by Kostub Deshmukh.
//
// This software may be modified and distributed under the terms of the
// MIT license. See the LICENSE file for details.
// //
import Foundation import Foundation

View File

@@ -1,15 +1,16 @@
// //
// MTFontMathTable.swift
// MathRenderSwift
//
// Created by Mike Griebling on 2022-12-31. // Created by Mike Griebling on 2022-12-31.
// Translated from an Objective-C implementation by Kostub Deshmukh.
//
// This software may be modified and distributed under the terms of the
// MIT license. See the LICENSE file for details.
// //
import Foundation import Foundation
import CoreGraphics import CoreGraphics
import CoreText import CoreText
class GlyphPart { struct GlyphPart {
/// The glyph that represents this part /// The glyph that represents this part
var glyph: CGGlyph! var glyph: CGGlyph!
@@ -33,14 +34,13 @@ class GlyphPart {
How the constants in this class affect the display is documented here: How the constants in this class affect the display is documented here:
http://www.tug.org/TUGboat/tb30-1/tb94vieth.pdf http://www.tug.org/TUGboat/tb30-1/tb94vieth.pdf
@note We don't parse the math table from the open type font. Rather we parse it Note: We don't parse the math table from the open type font. Rather we parse it
in python and convert it to a .plist file which is easily consumed by this class. in python and convert it to a .plist file which is easily consumed by this class.
This approach is preferable to spending an inordinate amount of time figuring out This approach is preferable to spending an inordinate amount of time figuring out
how to parse the returned NSData object using the open type rules. how to parse the returned NSData object using the open type rules.
@remark This class is not meant to be used outside of this library. Remark: This class is not meant to be used outside of this library.
*/ */
class MTFontMathTable { class MTFontMathTable {
// The font for this math table. // The font for this math table.
@@ -100,21 +100,21 @@ class MTFontMathTable {
var skewedFractionVerticalGap:CGFloat { constantFromTable("SkewedFractionVerticalGap") } // \sigma_21 in TeX var skewedFractionVerticalGap:CGFloat { constantFromTable("SkewedFractionVerticalGap") } // \sigma_21 in TeX
// MARK: - Non-standard // MARK: - Non-standard
// FractionDelimiterSize and FractionDelimiterDisplayStyleSize are not constants /// FractionDelimiterSize and FractionDelimiterDisplayStyleSize are not constants
// specified in the OpenType Math specification. Rather these are proposed LuaTeX extensions /// specified in the OpenType Math specification. Rather these are proposed LuaTeX extensions
// for the TeX parameters \sigma_20 (delim1) and \sigma_21 (delim2). Since these do not /// for the TeX parameters \sigma_20 (delim1) and \sigma_21 (delim2). Since these do not
// exist in the fonts that we have, we use the same approach as LuaTeX and use the fontSize /// exist in the fonts that we have, we use the same approach as LuaTeX and use the fontSize
// to determine these values. The constants used are the same as LuaTeX and KaTeX and match the /// to determine these values. The constants used are the same as LuaTeX and KaTeX and match the
// metrics values of the original TeX fonts. /// metrics values of the original TeX fonts.
// Note: An alternative approach is to use DelimitedSubFormulaMinHeight for \sigma21 and use a factor /// Note: An alternative approach is to use DelimitedSubFormulaMinHeight for \sigma21 and use a factor
// of 2 to get \sigma 20 as proposed in Vieth paper. /// of 2 to get \sigma 20 as proposed in Vieth paper.
// The XeTeX implementation sets \sigma21 = fontSize and \sigma20 = DelimitedSubFormulaMinHeight which /// The XeTeX implementation sets \sigma21 = fontSize and \sigma20 = DelimitedSubFormulaMinHeight which
// will produce smaller delimiters. /// will produce smaller delimiters.
// Of all the approaches we've implemented LuaTeX's approach since it mimics LaTeX most accurately. /// Of all the approaches we've implemented LuaTeX's approach since it mimics LaTeX most accurately.
var fractionDelimiterSize: CGFloat { return 1.01 * _fontSize } var fractionDelimiterSize: CGFloat { 1.01 * _fontSize }
/// Modified constant from 2.4 to 2.39, it matches KaTeX and looks better. /// Modified constant from 2.4 to 2.39, it matches KaTeX and looks better.
var fractionDelimiterDisplayStyleSize: CGFloat { return 2.39 * _fontSize } var fractionDelimiterDisplayStyleSize: CGFloat { 2.39 * _fontSize }
// MARK: - Stacks // MARK: - Stacks
var stackTopDisplayStyleShiftUp:CGFloat { constantFromTable("StackTopDisplayStyleShiftUp") } // \sigma_8 in TeX var stackTopDisplayStyleShiftUp:CGFloat { constantFromTable("StackTopDisplayStyleShiftUp") } // \sigma_8 in TeX
@@ -303,7 +303,7 @@ class MTFontMathTable {
var rv = [GlyphPart]() var rv = [GlyphPart]()
for part in parts! { for part in parts! {
let partInfo = part as! NSDictionary? let partInfo = part as! NSDictionary?
let part = GlyphPart() var part = GlyphPart()
let adv = partInfo!["advance"] as! NSNumber? let adv = partInfo!["advance"] as! NSNumber?
part.fullAdvance = self.fontUnitsToPt(adv!.intValue) part.fullAdvance = self.fontUnitsToPt(adv!.intValue)
let end = partInfo!["endConnector"] as! NSNumber? let end = partInfo!["endConnector"] as! NSNumber?

View File

@@ -1,8 +1,9 @@
// //
// MTLabel.swift
// MathRenderSwift
//
// Created by Mike Griebling on 2022-12-31. // Created by Mike Griebling on 2022-12-31.
// Translated from an Objective-C implementation by .
//
// This software may be modified and distributed under the terms of the
// MIT license. See the LICENSE file for details.
// //
import Foundation import Foundation
@@ -25,6 +26,7 @@ public class MTLabel : NSTextField {
super.init(coder: coder) super.init(coder: coder)
} }
// MARK: - Customized getter and setter methods for property text.
var text:String? { var text:String? {
get { super.stringValue } get { super.stringValue }
set { super.stringValue = newValue! } set { super.stringValue = newValue! }

View File

@@ -1,12 +1,14 @@
// //
// MTMathAtomFactory.swift
// MathRenderSwift
//
// Created by Mike Griebling on 2022-12-31. // Created by Mike Griebling on 2022-12-31.
// Translated from an Objective-C implementation by Kostub Deshmukh.
//
// This software may be modified and distributed under the terms of the
// MIT license. See the LICENSE file for details.
// //
import Foundation import Foundation
/** A factory to create commonly used MTMathAtoms. */
public class MTMathAtomFactory { public class MTMathAtomFactory {
public static let aliases = [ public static let aliases = [
@@ -60,11 +62,10 @@ public class MTMathAtomFactory {
"rfloor" : "\u{230B}" "rfloor" : "\u{230B}"
] ]
var _delimValueToName: [String: String]? = nil static var _delimValueToName = [String: String]()
public var delimValueToName: [String: String] { public static var delimValueToName: [String: String] {
if _delimValueToName == nil { if _delimValueToName.isEmpty {
var output = [String: String]() var output = [String: String]()
for (key, value) in Self.delimiters { for (key, value) in Self.delimiters {
if let existingValue = output[value] { if let existingValue = output[value] {
if key.count > existingValue.count { if key.count > existingValue.count {
@@ -75,12 +76,11 @@ public class MTMathAtomFactory {
} }
} }
} }
output[value] = key output[value] = key
} }
_delimValueToName = output _delimValueToName = output
} }
return _delimValueToName! return _delimValueToName
} }
public static let accents = [ public static let accents = [
@@ -389,8 +389,8 @@ public class MTMathAtomFactory {
"scriptscriptstyle" : MTMathStyle(style: .scriptOfScript), "scriptscriptstyle" : MTMathStyle(style: .scriptOfScript),
] ]
var _textToLatexSymbolName: [String: String]? = nil static var _textToLatexSymbolName: [String: String]? = nil
public var textToLatexSymbolName: [String: String] { public static var textToLatexSymbolName: [String: String] {
get { get {
if self._textToLatexSymbolName == nil { if self._textToLatexSymbolName == nil {
var output = [String: String]() var output = [String: String]()
@@ -421,7 +421,7 @@ public class MTMathAtomFactory {
} }
} }
public static let sharedInstance = MTMathAtomFactory() // public static let sharedInstance = MTMathAtomFactory()
static let fontStyles : [String: MTFontStyle] = [ static let fontStyles : [String: MTFontStyle] = [
"mathnormal" : .defaultStyle, "mathnormal" : .defaultStyle,
@@ -449,7 +449,7 @@ public class MTMathAtomFactory {
] ]
public static func fontStyleWithName(_ fontName:String) -> MTFontStyle? { public static func fontStyleWithName(_ fontName:String) -> MTFontStyle? {
return fontStyles[fontName] fontStyles[fontName]
} }
public static func fontNameForStyle(_ fontStyle:MTFontStyle) -> String { public static func fontNameForStyle(_ fontStyle:MTFontStyle) -> String {
@@ -467,21 +467,22 @@ public class MTMathAtomFactory {
} }
} }
// Return an atom for times sign \times or * /// Returns an atom for the multiplication sign (i.e., \times or "*")
public static func times() -> MTMathAtom { public static func times() -> MTMathAtom {
MTMathAtom(type: .binaryOperator, value: UnicodeSymbol.multiplication) MTMathAtom(type: .binaryOperator, value: UnicodeSymbol.multiplication)
} }
// Return an atom for division sign \div or / /// Returns an atom for the division sign (i.e., \div or "/")
public static func divide() -> MTMathAtom { public static func divide() -> MTMathAtom {
MTMathAtom(type: .binaryOperator, value: UnicodeSymbol.division) MTMathAtom(type: .binaryOperator, value: UnicodeSymbol.division)
} }
// Return an atom aka placeholder square /// Returns an atom which is a placeholder square
public static func placeholder() -> MTMathAtom { public static func placeholder() -> MTMathAtom {
MTMathAtom(type: .placeholder, value: UnicodeSymbol.whiteSquare) MTMathAtom(type: .placeholder, value: UnicodeSymbol.whiteSquare)
} }
/** Returns a fraction with a placeholder for the numerator and denominator */
public static func placeholderFraction() -> MTFraction { public static func placeholderFraction() -> MTFraction {
let frac = MTFraction() let frac = MTFraction()
frac.numerator = MTMathList() frac.numerator = MTMathList()
@@ -491,6 +492,7 @@ public class MTMathAtomFactory {
return frac return frac
} }
/** Returns a square root with a placeholder as the radicand. */
public static func placeholderSquareRoot() -> MTRadical { public static func placeholderSquareRoot() -> MTRadical {
let rad = MTRadical() let rad = MTRadical()
rad.radicand = MTMathList() rad.radicand = MTMathList()
@@ -498,6 +500,7 @@ public class MTMathAtomFactory {
return rad return rad
} }
/** Returns a radical with a placeholder as the radicand. */
public static func placeholderRadical() -> MTRadical { public static func placeholderRadical() -> MTRadical {
let rad = MTRadical() let rad = MTRadical()
rad.radicand = MTMathList() rad.radicand = MTMathList()
@@ -507,6 +510,7 @@ public class MTMathAtomFactory {
return rad return rad
} }
// MARK: -
/** Gets the atom with the right type for the given character. If an atom /** Gets the atom with the right type for the given character. If an atom
cannot be determined for a given character this returns nil. cannot be determined for a given character this returns nil.
This function follows latex conventions for assigning types to the atoms. This function follows latex conventions for assigning types to the atoms.
@@ -554,7 +558,7 @@ public class MTMathAtomFactory {
} }
/** Returns a `MTMathList` with one atom per character in the given string. This function /** Returns a `MTMathList` with one atom per character in the given string. This function
does not do any LaTeX conversion or interpretation. It simply uses `atomForCharacter` to does not do any LaTeX conversion or interpretation. It simply uses `atom(forCharacter:)` to
convert the characters to atoms. Any character that cannot be converted is ignored. */ convert the characters to atoms. Any character that cannot be converted is ignored. */
public static func atomList(for string: String) -> MTMathList { public static func atomList(for string: String) -> MTMathList {
let list = MTMathList() let list = MTMathList()
@@ -583,26 +587,23 @@ public class MTMathAtomFactory {
/** Finds the name of the LaTeX symbol name for the given atom. This function is a reverse /** Finds the name of the LaTeX symbol name for the given atom. This function is a reverse
of the above function. If no latex symbol name corresponds to the atom, then this returns `nil` of the above function. If no latex symbol name corresponds to the atom, then this returns `nil`
If nucleus of the atom is empty, then this will return `nil`. If nucleus of the atom is empty, then this will return `nil`.
@note: This is not an exact reverse of the above in the case of aliases. If an LaTeX alias Note: This is not an exact reverse of the above in the case of aliases. If an LaTeX alias
points to a given symbol, then this function will return the original symbol name and not the points to a given symbol, then this function will return the original symbol name and not the
alias. alias.
@note: This function does not convert MathSpaces to latex command names either. Note: This function does not convert MathSpaces to latex command names either.
*/ */
public static func latexSymbolName(for atom: MTMathAtom) -> String? { public static func latexSymbolName(for atom: MTMathAtom) -> String? {
if atom.nucleus.count == 0 { guard !atom.nucleus.isEmpty else { return nil }
return nil return Self.textToLatexSymbolName[atom.nucleus]
}
return sharedInstance.textToLatexSymbolName[atom.nucleus]
} }
/** Define a latex symbol for rendering. This function allows defining custom symbols that are /** Define a latex symbol for rendering. This function allows defining custom symbols that are
not already present in the default set, or override existing symbols with new meaning. not already present in the default set, or override existing symbols with new meaning.
e.g. to define a symbol for "lcm" one can call: e.g. to define a symbol for "lcm" one can call:
`[MTMathAtomFactory addLatexSymbol:@"lcm" value:[MTMathAtomFactory operatorWithName:@"lcm" limits: false)]` */ `MTMathAtomFactory.add(latexSymbol:"lcm", value:MTMathAtomFactory.operatorWithName("lcm", limits: false))` */
public static func add(latexSymbol name: String, value: MTMathAtom) { public static func add(latexSymbol name: String, value: MTMathAtom) {
supportedLatexSymbols[name] = value supportedLatexSymbols[name] = value
sharedInstance.textToLatexSymbolName[value.nucleus] = name Self.textToLatexSymbolName[value.nucleus] = name
} }
/** Returns a large opertor for the given name. If limits is true, limits are set up on /** Returns a large opertor for the given name. If limits is true, limits are set up on
@@ -647,10 +648,8 @@ public class MTMathAtomFactory {
`<` and `langle`) and this function always returns the shorter name. `<` and `langle`) and this function always returns the shorter name.
*/ */
public static func getDelimiterName(of boundary: MTMathAtom) -> String? { public static func getDelimiterName(of boundary: MTMathAtom) -> String? {
if boundary.type != .boundary { guard boundary.type == .boundary else { return nil }
return nil return Self.delimValueToName[boundary.nucleus]
}
return Self.sharedInstance.delimValueToName[boundary.nucleus]
} }
/** Returns a fraction with the given numerator and denominator. */ /** Returns a fraction with the given numerator and denominator. */
@@ -672,21 +671,14 @@ public class MTMathAtomFactory {
} }
/** Simplification of above function when numerator and denominator are simple strings. /** Simplification of above function when numerator and denominator are simple strings.
This function uses `mathListForCharacters` to convert the strings to `MTMathList`s. */ This function converts the strings to a `MTFraction`. */
public static func fraction(withNumeratorString numStr: String, denominatorString denomStr: String) -> MTFraction { public static func fraction(withNumeratorString numStr: String, denominatorString denomStr: String) -> MTFraction {
let num = Self.atomList(for: numStr) let num = Self.atomList(for: numStr)
let denom = Self.atomList(for: denomStr) let denom = Self.atomList(for: denomStr)
return Self.fraction(withNumerator: num, denominator: denom) return Self.fraction(withNumerator: num, denominator: denom)
} }
/** Builds a table for a given environment with the given rows. Returns a `MTMathAtom` containing the
table and any other atoms necessary for the given environment. Returns nil and sets error
if the table could not be built.
@param env The environment to use to build the table. If the env is nil, then the default table is built.
@note The reason this function returns a `MTMathAtom` and not a `MTMathTable` is because some
matrix environments are have builtin delimiters added to the table and hence are returned as inner atoms.
*/
static let matrixEnvs = [ static let matrixEnvs = [
"matrix": [], "matrix": [],
"pmatrix": ["(", ")"], "pmatrix": ["(", ")"],
@@ -696,6 +688,13 @@ public class MTMathAtomFactory {
"Vmatrix": ["Vert", "Vert"] "Vmatrix": ["Vert", "Vert"]
] ]
/** Builds a table for a given environment with the given rows. Returns a `MTMathAtom` containing the
table and any other atoms necessary for the given environment. Returns nil and sets error
if the table could not be built.
@param env The environment to use to build the table. If the env is nil, then the default table is built.
@note The reason this function returns a `MTMathAtom` and not a `MTMathTable` is because some
matrix environments are have builtin delimiters added to the table and hence are returned as inner atoms.
*/
public static func table(withEnvironment env: String?, rows: [[MTMathList]], error:inout NSError?) -> MTMathAtom? { public static func table(withEnvironment env: String?, rows: [[MTMathList]], error:inout NSError?) -> MTMathAtom? {
let table = MTMathTable(environment: env) let table = MTMathTable(environment: env)

View File

@@ -1,42 +1,76 @@
// //
// MTMathList.swift
// MathRenderSwift
//
// Created by Mike Griebling on 2022-12-31. // Created by Mike Griebling on 2022-12-31.
// Translated from an Objective-C implementation by Kostub Deshmukh.
//
// This software may be modified and distributed under the terms of the
// MIT license. See the LICENSE file for details.
// //
import Foundation import Foundation
// type defines spacing and how it is rendered /**
public enum MTMathAtomType: Int, CustomStringConvertible, Comparable { The type of atom in a `MTMathList`.
case ordinary = 1 // number or text The type of the atom determines how it is rendered, and spacing between the atoms.
case number // number */
case variable // text in italic public enum MTMathAtomType: Int, CustomStringConvertible, Comparable {
case largeOperator // sin/cos, integral /// A number or text in ordinary format - Ord in TeX
case binaryOperator // \bin case ordinary = 1
case unaryOperator // /// A number - Does not exist in TeX
case relation // = < > case number
case open // open bracket /// A variable (i.e. text in italic format) - Does not exist in TeX
case close // close bracket case variable
case fraction // \frac /// A large operator such as (sin/cos, integral etc.) - Op in TeX
case radical // \sqrt case largeOperator
case punctuation // , /// A binary operator - Bin in TeX
case placeholder // inner atom case binaryOperator
case inner // embedded list /// A unary operator - Does not exist in TeX.
case underline // underlined atom case unaryOperator
case overline // overlined atom /// A relation, e.g. = > < etc. - Rel in TeX
case accent // accented atom case relation
/// Open brackets - Open in TeX
case open
/// Close brackets - Close in TeX
case close
/// A fraction e.g 1/2 - generalized fraction node in TeX
case fraction
/// A radical operator e.g. sqrt(2)
case radical
/// Punctuation such as , - Punct in TeX
case punctuation
/// A placeholder square for future input. Does not exist in TeX
case placeholder
/// An inner atom, i.e. an embedded math list - Inner in TeX
case inner
/// An underlined atom - Under in TeX
case underline
/// An overlined atom - Over in TeX
case overline
/// An accented atom - Accent in TeX
case accent
// these atoms do not support subscripts/superscripts: // Atoms after this point do not support subscripts or superscripts
/// A left atom - Left & Right in TeX. We don't need two since we track boundaries separately.
case boundary = 101 case boundary = 101
// Atoms after this are non-math TeX nodes that are still useful in math mode. They do not have
// the usual structure.
/// Spacing between math atoms. This denotes both glue and kern for TeX. We do not
/// distinguish between glue and kern.
case space = 201 case space = 201
// Denotes style changes during randering /// Denotes style changes during rendering.
case style case style
case color case color
case colorBox case colorBox
// Atoms after this point are not part of TeX and do not have the usual structure.
/// An table atom. This atom does not exist in TeX. It is equivalent to the TeX command
/// halign which is handled outside of the TeX math rendering engine. We bring it into our
/// math typesetting to handle matrices and other tables.
case table = 1001 case table = 1001
func isNotBinaryOperator() -> Bool { func isNotBinaryOperator() -> Bool {
@@ -79,9 +113,14 @@ public enum MTMathAtomType: Int, CustomStringConvertible, Comparable {
// comparable support // comparable support
public static func < (lhs: MTMathAtomType, rhs: MTMathAtomType) -> Bool { lhs.rawValue < rhs.rawValue } public static func < (lhs: MTMathAtomType, rhs: MTMathAtomType) -> Bool { lhs.rawValue < rhs.rawValue }
} }
/**
The font style of a character.
The fontstyle of the atom determines what style the character is rendered in. This only applies to atoms
of type kMTMathAtomVariable and kMTMathAtomNumber. None of the other atom types change their font style.
*/
public enum MTFontStyle:Int { public enum MTFontStyle:Int {
/// The default latex rendering style. i.e. variables are italic and numbers are roman. /// The default latex rendering style. i.e. variables are italic and numbers are roman.
case defaultStyle = 0, case defaultStyle = 0,
@@ -107,9 +146,19 @@ public enum MTFontStyle:Int {
// MARK: - MTMathAtom // MARK: - MTMathAtom
/** A `MTMathAtom` is the basic unit of a math list. Each atom represents a single character
or mathematical operator in a list. However certain atoms can represent more complex structures
such as fractions and radicals. Each atom has a type which determines how the atom is rendered and
a nucleus. The nucleus contains the character(s) that need to be rendered. However the nucleus may
be empty for certain types of atoms. An atom has an optional subscript or superscript which represents
the subscript or superscript that is to be rendered.
Certain types of atoms inherit from `MTMathAtom` and may have additional fields.
*/
public class MTMathAtom: NSObject { public class MTMathAtom: NSObject {
/** The type of the atom. */
public var type = MTMathAtomType.ordinary public var type = MTMathAtomType.ordinary
/** An optional subscript. */
public var subScript: MTMathList? { public var subScript: MTMathList? {
didSet { didSet {
if subScript != nil && !self.isScriptAllowed() { if subScript != nil && !self.isScriptAllowed() {
@@ -118,6 +167,7 @@ public class MTMathAtom: NSObject {
} }
} }
} }
/** An optional superscript. */
public var superScript: MTMathList? { public var superScript: MTMathList? {
didSet { didSet {
if superScript != nil && !self.isScriptAllowed() { if superScript != nil && !self.isScriptAllowed() {
@@ -127,11 +177,19 @@ public class MTMathAtom: NSObject {
} }
} }
/** The nucleus of the atom. */
public var nucleus: String = "" public var nucleus: String = ""
/// The index range in the MTMathList this MTMathAtom tracks. This is used by the finalizing and preprocessing steps
/// which fuse MTMathAtoms to track the position of the current MTMathAtom in the original list.
public var indexRange = NSRange(location: 0, length: 0) // indexRange in list that this atom tracks: public var indexRange = NSRange(location: 0, length: 0) // indexRange in list that this atom tracks:
/** The font style to be used for the atom. */
var fontStyle: MTFontStyle = .defaultStyle var fontStyle: MTFontStyle = .defaultStyle
var fusedAtoms = [MTMathAtom]() // atoms that fused to create this one
/// If this atom was formed by fusion of multiple atoms, then this stores the list of atoms that were fused to create this one.
/// This is used in the finalizing and preprocessing steps.
var fusedAtoms = [MTMathAtom]()
init(_ atom:MTMathAtom?) { init(_ atom:MTMathAtom?) {
guard let atom = atom else { return } guard let atom = atom else { return }
@@ -146,11 +204,15 @@ public class MTMathAtom: NSObject {
override init() { } override init() { }
/// Factory function to create an atom with a given type and value.
/// - parameter type: The type of the atom to instantiate.
/// - parameter value: The value of the atoms nucleus. The value is ignored for fractions and radicals.
init(type:MTMathAtomType, value:String) { init(type:MTMathAtomType, value:String) {
self.type = type self.type = type
self.nucleus = type == .radical ? "" : value self.nucleus = type == .radical ? "" : value
} }
/// Returns a copy of `self`.
public func copy() -> MTMathAtom { public func copy() -> MTMathAtom {
switch self.type { switch self.type {
case .largeOperator: case .largeOperator:
@@ -194,6 +256,7 @@ public class MTMathAtom: NSObject {
return string return string
} }
/// Returns a finalized copy of the atom
public var finalized: MTMathAtom { public var finalized: MTMathAtom {
let finalized : MTMathAtom = self.copy() let finalized : MTMathAtom = self.copy()
finalized.superScript = finalized.superScript?.finalized finalized.superScript = finalized.superScript?.finalized
@@ -212,6 +275,7 @@ public class MTMathAtom: NSObject {
return str return str
} }
// Fuse the given atom with this one by combining their nucleii.
func fuse(with atom: MTMathAtom) { func fuse(with atom: MTMathAtom) {
assert(self.subScript == nil, "Cannot fuse into an atom which has a subscript: \(self)"); assert(self.subScript == nil, "Cannot fuse into an atom which has a subscript: \(self)");
assert(self.superScript == nil, "Cannot fuse into an atom which has a superscript: \(self)"); assert(self.superScript == nil, "Cannot fuse into an atom which has a superscript: \(self)");
@@ -240,7 +304,9 @@ public class MTMathAtom: NSObject {
self.subScript = atom.subScript self.subScript = atom.subScript
} }
/** Returns true if this atom allows scripts (sub or super). */
func isScriptAllowed() -> Bool { self.type.isScriptAllowed() } func isScriptAllowed() -> Bool { self.type.isScriptAllowed() }
func isNotBinaryOperator() -> Bool { self.type.isNotBinaryOperator() } func isNotBinaryOperator() -> Bool { self.type.isNotBinaryOperator() }
} }
@@ -254,19 +320,21 @@ func isNotBinaryOperator(_ prevNode:MTMathAtom?) -> Bool {
public class MTFraction: MTMathAtom { public class MTFraction: MTMathAtom {
public var hasRule: Bool = true public var hasRule: Bool = true
public var leftDelimiter: String? public var leftDelimiter = ""
public var rightDelimiter: String? public var rightDelimiter = ""
public var numerator: MTMathList? public var numerator: MTMathList?
public var denominator: MTMathList? public var denominator: MTMathList?
init(_ frac: MTFraction?) { init(_ frac: MTFraction?) {
super.init(frac) super.init(frac)
self.type = .fraction self.type = .fraction
self.numerator = MTMathList(frac!.numerator) if let frac = frac {
self.denominator = MTMathList(frac!.denominator) self.numerator = MTMathList(frac.numerator)
self.hasRule = frac!.hasRule self.denominator = MTMathList(frac.denominator)
self.leftDelimiter = frac!.leftDelimiter self.hasRule = frac.hasRule
self.rightDelimiter = frac!.rightDelimiter self.leftDelimiter = frac.leftDelimiter
self.rightDelimiter = frac.rightDelimiter
}
} }
init(hasRule rule:Bool = true) { init(hasRule rule:Bool = true) {
@@ -276,28 +344,20 @@ public class MTFraction: MTMathAtom {
} }
override public var description: String { override public var description: String {
var string = "" var string = self.hasRule ? "\\frac" : "\\atop"
if self.hasRule { if !self.leftDelimiter.isEmpty {
string += "\\atop" string += "[\(self.leftDelimiter)]"
} else {
string += "\\frac"
} }
if self.leftDelimiter != nil { if !self.rightDelimiter.isEmpty {
string += "[\(self.leftDelimiter!)]" string += "[\(self.rightDelimiter)]"
} }
if self.rightDelimiter != nil {
string += "[\(self.rightDelimiter!)]"
}
string += "{\(self.numerator?.description ?? "placeholder")}{\(self.denominator?.description ?? "placeholder")}" string += "{\(self.numerator?.description ?? "placeholder")}{\(self.denominator?.description ?? "placeholder")}"
if self.superScript != nil { if self.superScript != nil {
string += "^{\(self.superScript!.description)}" string += "^{\(self.superScript!.description)}"
} }
if self.subScript != nil { if self.subScript != nil {
string += "_{\(self.subScript!.description)}" string += "_{\(self.subScript!.description)}"
} }
return string return string
} }
@@ -311,12 +371,13 @@ public class MTFraction: MTMathAtom {
} }
// MARK: - MTRadical // MARK: - MTRadical
/** An atom of type radical (square root). */
public class MTRadical: MTMathAtom { public class MTRadical: MTMathAtom {
// Under the roof /// Denotes the term under the square root sign
public var radicand: MTMathList? public var radicand: MTMathList?
// Value on radical sign /// Denotes the degree of the radical, i.e. the value to the top left of the radical sign
/// This can be null if there is no degree.
public var degree: MTMathList? public var degree: MTMathList?
init(_ rad:MTRadical?) { init(_ rad:MTRadical?) {
@@ -335,22 +396,18 @@ public class MTRadical: MTMathAtom {
override public var description: String { override public var description: String {
var string = "\\sqrt" var string = "\\sqrt"
if self.degree != nil { if self.degree != nil {
string += "[\(self.degree!.description)]" string += "[\(self.degree!.description)]"
} }
if self.radicand != nil { if self.radicand != nil {
string += "{\(self.radicand?.description ?? "placeholder")}" string += "{\(self.radicand?.description ?? "placeholder")}"
} }
if self.superScript != nil { if self.superScript != nil {
string += "^{\(self.superScript!.description)}" string += "^{\(self.superScript!.description)}"
} }
if self.subScript != nil { if self.subScript != nil {
string += "_{\(self.subScript!.description)}" string += "_{\(self.subScript!.description)}"
} }
return string return string
} }
@@ -363,8 +420,13 @@ public class MTRadical: MTMathAtom {
} }
// MARK: - MTLargeOperator // MARK: - MTLargeOperator
/** A `MTMathAtom` of type `kMTMathAtom.largeOperator`. */
public class MTLargeOperator: MTMathAtom { public class MTLargeOperator: MTMathAtom {
/** Indicates whether the limits (if present) should be displayed
above and below the operator in display mode. If limits is false
then the limits (if present) are displayed like a regular subscript/superscript.
*/
public var limits: Bool = false public var limits: Bool = false
init(_ op:MTLargeOperator?) { init(_ op:MTLargeOperator?) {
@@ -380,20 +442,25 @@ public class MTLargeOperator: MTMathAtom {
} }
// MARK: - MTInner // MARK: - MTInner
/** An inner atom. This denotes an atom which contains a math list inside it. An inner atom
has optional boundaries. Note: Only one boundary may be present, it is not required to have
both. */
public class MTInner: MTMathAtom { public class MTInner: MTMathAtom {
/// The inner math list
public var innerList: MTMathList? public var innerList: MTMathList?
/// The left boundary atom. This must be a node of type kMTMathAtomBoundary
public var leftBoundary: MTMathAtom? { public var leftBoundary: MTMathAtom? {
didSet { didSet {
if leftBoundary != nil && leftBoundary!.type != .boundary { if let left = leftBoundary, left.type != .boundary {
leftBoundary = nil leftBoundary = nil
NSException(name: NSExceptionName(rawValue: "Error"), reason: "Left boundary must be of type .boundary").raise() NSException(name: NSExceptionName(rawValue: "Error"), reason: "Left boundary must be of type .boundary").raise()
} }
} }
} }
/// The right boundary atom. This must be a node of type kMTMathAtomBoundary
public var rightBoundary: MTMathAtom? { public var rightBoundary: MTMathAtom? {
didSet { didSet {
if rightBoundary != nil && rightBoundary!.type != .boundary { if let right = rightBoundary, right.type != .boundary {
rightBoundary = nil rightBoundary = nil
NSException(name: NSExceptionName(rawValue: "Error"), reason: "Right boundary must be of type .boundary").raise() NSException(name: NSExceptionName(rawValue: "Error"), reason: "Right boundary must be of type .boundary").raise()
} }
@@ -415,23 +482,19 @@ public class MTInner: MTMathAtom {
override public var description: String { override public var description: String {
var string = "\\inner" var string = "\\inner"
if self.leftBoundary != nil { if self.leftBoundary != nil {
string += "[\(self.leftBoundary!.nucleus)]" string += "[\(self.leftBoundary!.nucleus)]"
} }
string += "{\(self.innerList!.description)}" string += "{\(self.innerList!.description)}"
if self.rightBoundary != nil { if self.rightBoundary != nil {
string += "[\(self.rightBoundary!.nucleus)]" string += "[\(self.rightBoundary!.nucleus)]"
} }
if self.superScript != nil { if self.superScript != nil {
string += "^{\(self.superScript!.description)}" string += "^{\(self.superScript!.description)}"
} }
if self.subScript != nil { if self.subScript != nil {
string += "_{\(self.subScript!.description)}" string += "_{\(self.subScript!.description)}"
} }
return string return string
} }
@@ -443,7 +506,7 @@ public class MTInner: MTMathAtom {
} }
// MARK: - MTOverLIne // MARK: - MTOverLIne
/** An atom with a line over the contained math list. */
public class MTOverLine: MTMathAtom { public class MTOverLine: MTMathAtom {
public var innerList: MTMathList? public var innerList: MTMathList?
@@ -466,7 +529,7 @@ public class MTOverLine: MTMathAtom {
} }
// MARK: - MTUnderLine // MARK: - MTUnderLine
/** An atom with a line under the contained math list. */
public class MTUnderLine: MTMathAtom { public class MTUnderLine: MTMathAtom {
public var innerList: MTMathList? public var innerList: MTMathList?
@@ -513,10 +576,16 @@ public class MTAccent: MTMathAtom {
} }
// MARK: - MTMathSpace // MARK: - MTMathSpace
/** An atom representing space.
Note: None of the usual fields of the `MTMathAtom` apply even though this
class inherits from `MTMathAtom`. i.e. it is meaningless to have a value
in the nucleus, subscript or superscript fields. */
public class MTMathSpace: MTMathAtom { public class MTMathSpace: MTMathAtom {
/** The amount of space represented by this object in mu units. */
public var space: CGFloat = 0 public var space: CGFloat = 0
/// Creates a new `MTMathSpace` with the given spacing.
/// - parameter space: The amount of space in mu units.
init(_ space: MTMathSpace?) { init(_ space: MTMathSpace?) {
super.init(space) super.init(space)
self.type = .space self.type = .space
@@ -530,11 +599,17 @@ public class MTMathSpace: MTMathAtom {
} }
} }
/**
Styling of a line of math
*/
public enum MTLineStyle:Int, Comparable { public enum MTLineStyle:Int, Comparable {
/// Display style
case display case display
/// Text style (inline)
case text case text
/// Script style (for sub/super scripts)
case script case script
/// Script script style (for scripts of scripts)
case scriptOfScript case scriptOfScript
public func inc() -> MTLineStyle { public func inc() -> MTLineStyle {
@@ -548,7 +623,10 @@ public enum MTLineStyle:Int, Comparable {
} }
// MARK: - MTMathStyle // MARK: - MTMathStyle
/** An atom representing a style change.
Note: None of the usual fields of the `MTMathAtom` apply even though this
class inherits from `MTMathAtom`. i.e. it is meaningless to have a value
in the nucleus, subscript or superscript fields. */
public class MTMathStyle: MTMathAtom { public class MTMathStyle: MTMathAtom {
public var style: MTLineStyle = .display public var style: MTLineStyle = .display
@@ -566,7 +644,10 @@ public class MTMathStyle: MTMathAtom {
} }
// MARK: - MTMathColor // MARK: - MTMathColor
/** An atom representing an color element.
Note: None of the usual fields of the `MTMathAtom` apply even though this
class inherits from `MTMathAtom`. i.e. it is meaningless to have a value
in the nucleus, subscript or superscript fields. */
public class MTMathColor: MTMathAtom { public class MTMathColor: MTMathAtom {
public var colorString:String="" public var colorString:String=""
public var innerList:MTMathList? public var innerList:MTMathList?
@@ -595,9 +676,12 @@ public class MTMathColor: MTMathAtom {
} }
// MARK: - MTMathColorbox // MARK: - MTMathColorbox
/** An atom representing an colorbox element.
Note: None of the usual fields of the `MTMathAtom` apply even though this
class inherits from `MTMathAtom`. i.e. it is meaningless to have a value
in the nucleus, subscript or superscript fields. */
public class MTMathColorbox: MTMathAtom { public class MTMathColorbox: MTMathAtom {
public var colorString:String="" public var colorString=""
public var innerList:MTMathList? public var innerList:MTMathList?
init(_ cbox: MTMathColorbox?) { init(_ cbox: MTMathColorbox?) {
@@ -623,6 +707,9 @@ public class MTMathColorbox: MTMathAtom {
} }
} }
/**
Alignment for a column of MTMathTable
*/
public enum MTColumnAlignment { public enum MTColumnAlignment {
case left case left
case center case center
@@ -630,13 +717,28 @@ public enum MTColumnAlignment {
} }
// MARK: - MTMathTable // MARK: - MTMathTable
/** An atom representing an table element. This atom is not like other
atoms and is not present in TeX. We use it to represent the `\halign` command
in TeX with some simplifications. This is used for matrices, equation
alignments and other uses of multiline environments.
The cells in the table are represented as a two dimensional array of
`MTMathList` objects. The `MTMathList`s could be empty to denote a missing
value in the cell. Additionally an array of alignments indicates how each
column will be aligned.
*/
public class MTMathTable: MTMathAtom { public class MTMathTable: MTMathAtom {
/// The alignment for each column (left, right, center). The default alignment
/// for a column (if not set) is center.
public var alignments = [MTColumnAlignment]() public var alignments = [MTColumnAlignment]()
/// The cells in the table as a two dimensional array.
public var cells = [[MTMathList]]() public var cells = [[MTMathList]]()
/// The name of the environment that this table denotes.
public var environment: String? public var environment = ""
/// Spacing between each column in mu units.
public var interColumnSpacing: CGFloat = 0 public var interColumnSpacing: CGFloat = 0
/// Additional spacing between rows in jots (one jot is 0.3 times font size).
/// If the additional spacing is 0, then normal row spacing is used are used.
public var interRowAdditionalSpacing: CGFloat = 0 public var interRowAdditionalSpacing: CGFloat = 0
override public var finalized: MTMathAtom { override public var finalized: MTMathAtom {
@@ -652,7 +754,7 @@ public class MTMathTable: MTMathAtom {
init(environment: String?) { init(environment: String?) {
super.init() super.init()
self.type = .table self.type = .table
self.environment = environment self.environment = environment ?? ""
} }
init(_ table:MTMathTable) { init(_ table:MTMathTable) {
@@ -678,6 +780,7 @@ public class MTMathTable: MTMathAtom {
self.type = .table self.type = .table
} }
/// Set the value of a given cell. The table is automatically resized to contain this cell.
public func set(cell list: MTMathList, forRow row:Int, column:Int) { public func set(cell list: MTMathList, forRow row:Int, column:Int) {
if self.cells.count <= row { if self.cells.count <= row {
for _ in self.cells.count...row { for _ in self.cells.count...row {
@@ -693,6 +796,8 @@ public class MTMathTable: MTMathAtom {
self.cells[row][column] = list self.cells[row][column] = list
} }
/// Set the alignment of a particular column. The table is automatically resized to
/// contain this column and any new columns added have their alignment set to center.
public func set(alignment: MTColumnAlignment, forColumn col: Int) { public func set(alignment: MTColumnAlignment, forColumn col: Int) {
if self.alignments.count <= col { if self.alignments.count <= col {
for _ in self.alignments.count...col { for _ in self.alignments.count...col {
@@ -703,6 +808,8 @@ public class MTMathTable: MTMathAtom {
self.alignments[col] = alignment self.alignments[col] = alignment
} }
/// Gets the alignment for a given column. If the alignment is not specified it defaults
/// to center.
public func get(alignmentForColumn col: Int) -> MTColumnAlignment { public func get(alignmentForColumn col: Int) -> MTColumnAlignment {
if self.alignments.count <= col { if self.alignments.count <= col {
return MTColumnAlignment.center return MTColumnAlignment.center
@@ -724,12 +831,21 @@ public class MTMathTable: MTMathAtom {
// MARK: - MTMathList // MARK: - MTMathList
// represent list of math objects
extension MTMathList { extension MTMathList {
public override var description: String { self.atoms.description } public override var description: String { self.atoms.description }
/// converts the MTMathList to a string form. Note: This is not the LaTeX form.
public var string: String { self.description } public var string: String { self.description }
} }
/** A representation of a list of math objects.
This list can be constructed directly or built with
the help of the MTMathListBuilder. It is not required that the mathematics represented make sense
(i.e. this can represent something like "x 2 = +". This list can be used for display using MTLine
or can be a list of tokens to be used by a parser after finalizedMathList is called.
Note: This class is for **advanced** usage only.
*/
public class MTMathList : NSObject { public class MTMathList : NSObject {
init?(_ list:MTMathList?) { init?(_ list:MTMathList?) {
@@ -739,8 +855,12 @@ public class MTMathList : NSObject {
} }
} }
/// A list of MathAtoms
public var atoms = [MTMathAtom]() public var atoms = [MTMathAtom]()
/// Create a new math list as a final expression and update atoms
/// by combining like atoms that occur together and converting unary operators to binary operators.
/// This function does not modify the current MTMathList
public var finalized: MTMathList { public var finalized: MTMathList {
let finalizedList = MTMathList() let finalizedList = MTMathList()
let zeroRange = NSMakeRange(0, 0) let zeroRange = NSMakeRange(0, 0)
@@ -800,7 +920,10 @@ public class MTMathList : NSObject {
NSException(name: NSExceptionName(rawValue: "Error"), reason: "Index \(index) out of bounds").raise() NSException(name: NSExceptionName(rawValue: "Error"), reason: "Index \(index) out of bounds").raise()
} }
func add(_ atom: MTMathAtom?) { /// Add an atom to the end of the list.
/// - parameter atom: The atom to be inserted. This cannot be `nil` and cannot have the type `kMTMathAtomBoundary`.
/// - throws NSException if the atom is of type `kMTMathAtomBoundary`
public func add(_ atom: MTMathAtom?) {
guard let atom = atom else { return } guard let atom = atom else { return }
if self.isAtomAllowed(atom) { if self.isAtomAllowed(atom) {
self.atoms.append(atom) self.atoms.append(atom)
@@ -809,7 +932,14 @@ public class MTMathList : NSObject {
} }
} }
func insert(_ atom: MTMathAtom?, at index: Int) { /// Inserts an atom at the given index. If index is already occupied, the objects at index and beyond are
/// shifted by adding 1 to their indices to make room. An insert to an `index` greater than the number of atoms
/// is ignored. Insertions of nil atoms is ignored.
/// - parameter atom: The atom to be inserted. This cannot be `nil` and cannot have the type `kMTMathAtom.boundary`.
/// - parameter index: The index where the atom is to be inserted. The index should be less than or equal to the
/// number of elements in the math list.
/// - throws NSException if the atom is of type kMTMathAtomBoundary
public func insert(_ atom: MTMathAtom?, at index: Int) {
// NSParamException(atom) // NSParamException(atom)
guard let atom = atom else { return } guard let atom = atom else { return }
guard self.atoms.indices.contains(index) || index == self.atoms.endIndex else { return } guard self.atoms.indices.contains(index) || index == self.atoms.endIndex else { return }
@@ -822,23 +952,30 @@ public class MTMathList : NSObject {
} }
} }
func append(_ list: MTMathList?) { /// Append the given list to the end of the current list.
/// - parameter list: The list to append.
public func append(_ list: MTMathList?) {
guard let list = list else { return } guard let list = list else { return }
self.atoms += list.atoms self.atoms += list.atoms
} }
func removeLastAtom() { /** Removes the last atom from the math list. If there are no atoms in the list this does nothing. */
public func removeLastAtom() {
if !self.atoms.isEmpty { if !self.atoms.isEmpty {
self.atoms.removeLast() self.atoms.removeLast()
} }
} }
func removeAtom(at index: Int) { /// Removes the atom at the given index.
/// - parameter index: The index at which to remove the atom. Must be less than the number of atoms
/// in the list.
public func removeAtom(at index: Int) {
NSIndexException(self.atoms, index:index) NSIndexException(self.atoms, index:index)
self.atoms.remove(at: index) self.atoms.remove(at: index)
} }
func removeAtoms(in range: ClosedRange<Int>) { /** Removes all the atoms within the given range. */
public func removeAtoms(in range: ClosedRange<Int>) {
NSIndexException(self.atoms, index: range.lowerBound) NSIndexException(self.atoms, index: range.lowerBound)
NSIndexException(self.atoms, index: range.upperBound) NSIndexException(self.atoms, index: range.upperBound)
self.atoms.removeSubrange(range) self.atoms.removeSubrange(range)

View File

@@ -1,8 +1,9 @@
// //
// MTMathListBuilder.swift
// MathRenderSwift
//
// Created by Mike Griebling on 2022-12-31. // Created by Mike Griebling on 2022-12-31.
// Translated from an Objective-C implementation by Kostub Deshmukh.
//
// This software may be modified and distributed under the terms of the
// MIT license. See the LICENSE file for details.
// //
import Foundation import Foundation
@@ -23,10 +24,9 @@ struct MTEnvProperties {
} }
/** /**
@typedef case s The error encountered when parsing a LaTeX string.
@brief The error encountered when parsing a LaTeX string.
The `code` in the `NSError` is one of the following indiciating why the LaTeX string The `code` in the `NSError` is one of the following indicating why the LaTeX string
could not be parsed. could not be parsed.
*/ */
enum MTParseErrors:Int { enum MTParseErrors:Int {
@@ -62,7 +62,10 @@ enum MTParseErrors:Int {
let MTParseError = "ParseError" let MTParseError = "ParseError"
public class MTMathListBuilder { /** `MTMathListBuilder` is a class for parsing LaTeX into an `MTMathList` that
can be rendered and processed mathematically.
*/
public struct MTMathListBuilder {
var string: String var string: String
var currentCharIndex: String.Index var currentCharIndex: String.Index
var currentInnerAtom: MTInner? var currentInnerAtom: MTInner?
@@ -73,8 +76,42 @@ public class MTMathListBuilder {
/** Contains any error that occurred during parsing. */ /** Contains any error that occurred during parsing. */
var error:NSError? var error:NSError?
// MARK: - Character-handling routines
var hasCharacters: Bool { currentCharIndex < string.endIndex } var hasCharacters: Bool { currentCharIndex < string.endIndex }
// gets the next character and increments the index
mutating func getNextCharacter() -> Character {
assert(self.hasCharacters, "Retrieving character at index \(self.currentCharIndex) beyond length \(self.string.count)")
let ch = string[currentCharIndex]
currentCharIndex = string.index(after: currentCharIndex)
return ch
}
mutating func unlookCharacter() {
assert(currentCharIndex > string.startIndex, "Unlooking when at the first character.")
if currentCharIndex > string.startIndex {
currentCharIndex = string.index(before: currentCharIndex)
}
}
mutating func expectCharacter(_ ch: Character) -> Bool {
MTAssertNotSpace(ch)
self.skipSpaces()
if self.hasCharacters {
let nextChar = self.getNextCharacter()
MTAssertNotSpace(nextChar)
if nextChar == ch {
return true
} else {
self.unlookCharacter()
return false
}
}
return false
}
public static let spaceToCommands: [CGFloat: String] = [ public static let spaceToCommands: [CGFloat: String] = [
3 : ",", 3 : ",",
4 : ">", 4 : ">",
@@ -99,7 +136,10 @@ public class MTMathListBuilder {
self.spacesAllowed = false self.spacesAllowed = false
} }
public func build() -> MTMathList? { // MARK: - MTMathList builder functions
/// Builds a mathlist from the internal `string`. Returns nil if there is an error.
public mutating func build() -> MTMathList? {
let list = self.buildInternal(false) let list = self.buildInternal(false)
if self.hasCharacters && error == nil { if self.hasCharacters && error == nil {
self.setError(.mismatchBraces, message: "Mismatched braces: \(self.string)") self.setError(.mismatchBraces, message: "Mismatched braces: \(self.string)")
@@ -111,13 +151,20 @@ public class MTMathListBuilder {
return list return list
} }
/** Construct a math list from a given string. If there is parse error, returns
nil. To retrieve the error use the function `MTMathListBuilder.build(fromString:error:)`.
*/
public static func build(fromString string: String) -> MTMathList? { public static func build(fromString string: String) -> MTMathList? {
let builder = MTMathListBuilder(string: string) var builder = MTMathListBuilder(string: string)
return builder.build() return builder.build()
} }
/** Construct a math list from a given string. If there is an error while
constructing the string, this returns nil. The error is returned in the
`error` parameter.
*/
public static func build(fromString string: String, error:inout NSError?) -> MTMathList? { public static func build(fromString string: String, error:inout NSError?) -> MTMathList? {
let builder = MTMathListBuilder(string: string) var builder = MTMathListBuilder(string: string)
let output = builder.build() let output = builder.build()
if builder.error != nil { if builder.error != nil {
error = builder.error error = builder.error
@@ -126,227 +173,29 @@ public class MTMathListBuilder {
return output return output
} }
public static func mathListToString(_ ml: MTMathList?) -> String { public mutating func buildInternal(_ oneCharOnly: Bool) -> MTMathList? {
var str = "" self.buildInternal(oneCharOnly, stopChar: nil)
var currentfontStyle = MTFontStyle.defaultStyle
if let atomList = ml {
for atom in atomList.atoms {
if currentfontStyle != atom.fontStyle {
if currentfontStyle != .defaultStyle {
str += "}"
}
if atom.fontStyle != .defaultStyle {
let fontStyleName = MTMathAtomFactory.fontNameForStyle(atom.fontStyle)
str += "\\\(fontStyleName){"
}
currentfontStyle = atom.fontStyle
}
if atom.type == .fraction {
if let frac = atom as? MTFraction {
if frac.hasRule {
str += "\\frac{\(mathListToString(frac.numerator!))}{\(mathListToString(frac.denominator!))}"
} else {
var command: String? = nil
if frac.leftDelimiter == nil && frac.rightDelimiter == nil {
command = "atop"
} else if frac.leftDelimiter == "(" && frac.rightDelimiter == ")" {
command = "choose"
} else if frac.leftDelimiter == "{" && frac.rightDelimiter == "}" {
command = "brace"
} else if frac.leftDelimiter == "[" && frac.rightDelimiter == "]" {
command = "brack"
} else {
command = "atopwithdelims\(frac.leftDelimiter!)\(frac.rightDelimiter!)"
}
str += "{\(mathListToString(frac.numerator!)) \\\(command!) \(mathListToString(frac.denominator!))}"
}
}
} else if atom.type == .radical {
str += "\\sqrt"
if let rad = atom as? MTRadical {
if rad.degree != nil {
str += "[\(mathListToString(rad.degree!))]"
}
str += "{\(mathListToString(rad.radicand!))}"
}
} else if atom.type == .inner {
if let inner = atom as? MTInner {
if inner.leftBoundary != nil || inner.rightBoundary != nil {
if inner.leftBoundary != nil {
str += "\\left\(delimToString(delim: inner.leftBoundary!)) "
} else {
str += "\\left. "
}
str += mathListToString(inner.innerList!)
if inner.rightBoundary != nil {
str += "\\right\(delimToString(delim: inner.rightBoundary!)) "
} else {
str += "\\right. "
}
} else {
str += "{\(mathListToString(inner.innerList!))}"
}
}
} else if atom.type == .table {
if let table = atom as? MTMathTable {
if table.environment != nil {
str += "\\begin{\(table.environment!)}"
}
for i in 0..<table.numRows {
let row = table.cells[i]
for j in 0..<row.count {
let cell = row[j]
if table.environment == "matrix" {
if cell.atoms.count >= 1 && cell.atoms[0].type == .style {
// remove first atom
cell.atoms.removeFirst()
}
}
if table.environment == "eqalign" || table.environment == "aligned" || table.environment == "split" {
if j == 1 && cell.atoms.count >= 1 && cell.atoms[0].type == .ordinary && cell.atoms[0].nucleus.count == 0 {
// remove empty nucleus added for spacing
cell.atoms.removeFirst()
}
}
str += mathListToString(cell)
if j < row.count - 1 {
str += "&"
}
}
if i < table.numRows - 1 {
str += "\\\\ "
}
}
if table.environment != nil {
str += "\\end{\(table.environment!)}"
}
}
} else if atom.type == .overline {
if let overline = atom as? MTOverLine {
str += "\\overline"
str += "{\(mathListToString(overline.innerList!))}"
}
} else if atom.type == .underline {
if let underline = atom as? MTUnderLine {
str += "\\underline"
str += "{\(mathListToString(underline.innerList!))}"
}
} else if atom.type == .accent {
if let accent = atom as? MTAccent {
str += "\\\(MTMathAtomFactory.accentName(accent)!){\(mathListToString(accent.innerList!))}"
}
} else if atom.type == .largeOperator {
let op = atom as! MTLargeOperator
let command = MTMathAtomFactory.latexSymbolName(for: atom)
let originalOp = MTMathAtomFactory.atom(forLatexSymbol: command!) as! MTLargeOperator
str += "\\\(command!) "
if originalOp.limits != op.limits {
if op.limits {
str += "\\limits "
} else {
str += "\\nolimits "
}
}
} else if atom.type == .space {
if let space = atom as? MTMathSpace {
if let command = MTMathListBuilder.spaceToCommands[space.space] {
str += "\\\(command) "
} else {
str += String(format: "\\mkern%.1fmu", space.space)
}
}
} else if atom.type == .style {
if let style = atom as? MTMathStyle {
if let command = MTMathListBuilder.styleToCommands[style.style] {
str += "\\\(command) "
}
}
} else if atom.nucleus.isEmpty {
str += "{}"
} else if atom.nucleus == "\u{2236}" {
// math colon
str += ":"
} else if atom.nucleus == "\u{2212}" {
// math minus
str += "-"
} else {
if let command = MTMathAtomFactory.latexSymbolName(for: atom) {
str += "\\\(command) "
} else {
str += "\(atom.nucleus)"
}
}
if atom.superScript != nil {
str += "^{\(mathListToString(atom.superScript!))}"
}
if atom.subScript != nil {
str += "_{\(mathListToString(atom.subScript!))}"
}
}
}
if currentfontStyle != .defaultStyle {
str += "}"
}
return str
} }
public static func delimToString(delim: MTMathAtom) -> String { public mutating func buildInternal(_ oneCharOnly: Bool, stopChar stop: Character?) -> MTMathList? {
if let command = MTMathAtomFactory.getDelimiterName(of: delim) {
let singleChars = [ "(", ")", "[", "]", "<", ">", "|", ".", "/"]
if singleChars.contains(command) {
return command
} else if command == "||" {
return "\\|"
} else {
return "\\\(command)"
}
}
return ""
}
func getNextCharacter() -> Character {
assert(self.hasCharacters, "Retrieving character at index \(self.currentCharIndex) beyond length \(self.string.count)")
let ch = string[currentCharIndex]
currentCharIndex = string.index(after: currentCharIndex)
return ch
}
func unlookCharacter() {
assert(currentCharIndex > string.startIndex, "Unlooking when at the first character.")
if currentCharIndex > string.startIndex {
currentCharIndex = string.index(before: currentCharIndex)
} else {
print("unlooking at first character")
}
}
public func buildInternal(_ oneCharOnly: Bool) -> MTMathList? {
return self.buildInternal(oneCharOnly, stopChar: nil)
}
public func buildInternal(_ oneCharOnly: Bool, stopChar stop: Character?) -> MTMathList? {
let list = MTMathList() let list = MTMathList()
assert(!(oneCharOnly && stop != nil), "Cannot set both oneCharOnly and stopChar.") assert(!(oneCharOnly && stop != nil), "Cannot set both oneCharOnly and stopChar.")
var prevAtom: MTMathAtom? = nil var prevAtom: MTMathAtom? = nil
while self.hasCharacters { while self.hasCharacters {
if error != nil { return nil } if error != nil { return nil } // If there is an error thus far then bail out.
var atom: MTMathAtom? = nil var atom: MTMathAtom? = nil
let char = self.getNextCharacter() let char = self.getNextCharacter()
if oneCharOnly { if oneCharOnly {
if char == "^" || char == "}" || char == "_" || char == "&" { if char == "^" || char == "}" || char == "_" || char == "&" {
// this is not the character we are looking for.
// They are meant for the caller to look at.
self.unlookCharacter() self.unlookCharacter()
return list return list
} }
} }
// If there is a stop character, keep scanning 'til we find it
if stop != nil && char == stop! { if stop != nil && char == stop! {
return list return list
} }
@@ -359,7 +208,8 @@ public class MTMathListBuilder {
prevAtom = MTMathAtom(type: .ordinary, value: "") prevAtom = MTMathAtom(type: .ordinary, value: "")
list.add(prevAtom!) list.add(prevAtom!)
} }
// this is a superscript for the previous atom
// note: if the next char is the stopChar it will be consumed by the ^ and so it doesn't count as stop
prevAtom!.superScript = self.buildInternal(true) prevAtom!.superScript = self.buildInternal(true)
continue continue
} else if char == "_" { } else if char == "_" {
@@ -370,6 +220,8 @@ public class MTMathListBuilder {
prevAtom = MTMathAtom(type: .ordinary, value: "") prevAtom = MTMathAtom(type: .ordinary, value: "")
list.add(prevAtom!) list.add(prevAtom!)
} }
// this is a subscript for the previous atom
// note: if the next char is the stopChar it will be consumed by the _ and so it doesn't count as stop
prevAtom!.subScript = self.buildInternal(true) prevAtom!.subScript = self.buildInternal(true)
continue continue
} else if char == "{" { } else if char == "{" {
@@ -382,6 +234,7 @@ public class MTMathListBuilder {
} }
continue continue
} else if char == "}" { } else if char == "}" {
// \ means a command
assert(!oneCharOnly, "This should have been handled before") assert(!oneCharOnly, "This should have been handled before")
assert(stop == nil, "This should have been handled before") assert(stop == nil, "This should have been handled before")
// We encountered a closing brace when there is no stop set, that means there was no // We encountered a closing brace when there is no stop set, that means there was no
@@ -428,18 +281,22 @@ public class MTMathListBuilder {
return nil return nil
} }
} else if char == "&" { } else if char == "&" {
// used for column separation in tables
assert(!oneCharOnly, "This should have been handled before") assert(!oneCharOnly, "This should have been handled before")
if self.currentEnv != nil { if self.currentEnv != nil {
return list return list
} else { } else {
// Create a new table with the current list and a default env
let table = self.buildTable(env: nil, firstList: list, isRow: false) let table = self.buildTable(env: nil, firstList: list, isRow: false)
return MTMathList(atom: table!) return MTMathList(atom: table!)
} }
} else if spacesAllowed && char == " " { } else if spacesAllowed && char == " " {
// If spaces are allowed then spaces do not need escaping with a \ before being used.
atom = MTMathAtomFactory.atom(forLatexSymbol: " ") atom = MTMathAtomFactory.atom(forLatexSymbol: " ")
} else { } else {
atom = MTMathAtomFactory.atom(forCharacter: char) atom = MTMathAtomFactory.atom(forCharacter: char)
if atom == nil { if atom == nil {
// Not a recognized character
continue continue
} }
} }
@@ -453,7 +310,6 @@ public class MTMathListBuilder {
return list return list
} }
} }
if stop != nil { if stop != nil {
if stop == "}" { if stop == "}" {
// We did not find a corresponding closing brace. // We did not find a corresponding closing brace.
@@ -467,7 +323,194 @@ public class MTMathListBuilder {
return list return list
} }
func atomForCommand(_ command:String) -> MTMathAtom? {
// MARK: - MTMathList to LaTeX conversion
/// This converts the MTMathList to LaTeX.
public static func mathListToString(_ ml: MTMathList?) -> String {
var str = ""
var currentfontStyle = MTFontStyle.defaultStyle
if let atomList = ml {
for atom in atomList.atoms {
if currentfontStyle != atom.fontStyle {
if currentfontStyle != .defaultStyle {
str += "}"
}
if atom.fontStyle != .defaultStyle {
let fontStyleName = MTMathAtomFactory.fontNameForStyle(atom.fontStyle)
str += "\\\(fontStyleName){"
}
currentfontStyle = atom.fontStyle
}
if atom.type == .fraction {
if let frac = atom as? MTFraction {
if frac.hasRule {
str += "\\frac{\(mathListToString(frac.numerator!))}{\(mathListToString(frac.denominator!))}"
} else {
let command: String
if frac.leftDelimiter.isEmpty && frac.rightDelimiter.isEmpty {
command = "atop"
} else if frac.leftDelimiter == "(" && frac.rightDelimiter == ")" {
command = "choose"
} else if frac.leftDelimiter == "{" && frac.rightDelimiter == "}" {
command = "brace"
} else if frac.leftDelimiter == "[" && frac.rightDelimiter == "]" {
command = "brack"
} else {
command = "atopwithdelims\(frac.leftDelimiter)\(frac.rightDelimiter)"
}
str += "{\(mathListToString(frac.numerator!)) \\\(command) \(mathListToString(frac.denominator!))}"
}
}
} else if atom.type == .radical {
str += "\\sqrt"
if let rad = atom as? MTRadical {
if rad.degree != nil {
str += "[\(mathListToString(rad.degree!))]"
}
str += "{\(mathListToString(rad.radicand!))}"
}
} else if atom.type == .inner {
if let inner = atom as? MTInner {
if inner.leftBoundary != nil || inner.rightBoundary != nil {
if inner.leftBoundary != nil {
str += "\\left\(delimToString(delim: inner.leftBoundary!)) "
} else {
str += "\\left. "
}
str += mathListToString(inner.innerList!)
if inner.rightBoundary != nil {
str += "\\right\(delimToString(delim: inner.rightBoundary!)) "
} else {
str += "\\right. "
}
} else {
str += "{\(mathListToString(inner.innerList!))}"
}
}
} else if atom.type == .table {
if let table = atom as? MTMathTable {
if !table.environment.isEmpty {
str += "\\begin{\(table.environment)}"
}
for i in 0..<table.numRows {
let row = table.cells[i]
for j in 0..<row.count {
let cell = row[j]
if table.environment == "matrix" {
if cell.atoms.count >= 1 && cell.atoms[0].type == .style {
// remove first atom
cell.atoms.removeFirst()
}
}
if table.environment == "eqalign" || table.environment == "aligned" || table.environment == "split" {
if j == 1 && cell.atoms.count >= 1 && cell.atoms[0].type == .ordinary && cell.atoms[0].nucleus.count == 0 {
// remove empty nucleus added for spacing
cell.atoms.removeFirst()
}
}
str += mathListToString(cell)
if j < row.count - 1 {
str += "&"
}
}
if i < table.numRows - 1 {
str += "\\\\ "
}
}
if !table.environment.isEmpty {
str += "\\end{\(table.environment)}"
}
}
} else if atom.type == .overline {
if let overline = atom as? MTOverLine {
str += "\\overline"
str += "{\(mathListToString(overline.innerList!))}"
}
} else if atom.type == .underline {
if let underline = atom as? MTUnderLine {
str += "\\underline"
str += "{\(mathListToString(underline.innerList!))}"
}
} else if atom.type == .accent {
if let accent = atom as? MTAccent {
str += "\\\(MTMathAtomFactory.accentName(accent)!){\(mathListToString(accent.innerList!))}"
}
} else if atom.type == .largeOperator {
let op = atom as! MTLargeOperator
let command = MTMathAtomFactory.latexSymbolName(for: atom)
let originalOp = MTMathAtomFactory.atom(forLatexSymbol: command!) as! MTLargeOperator
str += "\\\(command!) "
if originalOp.limits != op.limits {
if op.limits {
str += "\\limits "
} else {
str += "\\nolimits "
}
}
} else if atom.type == .space {
if let space = atom as? MTMathSpace {
if let command = Self.spaceToCommands[space.space] {
str += "\\\(command) "
} else {
str += String(format: "\\mkern%.1fmu", space.space)
}
}
} else if atom.type == .style {
if let style = atom as? MTMathStyle {
if let command = Self.styleToCommands[style.style] {
str += "\\\(command) "
}
}
} else if atom.nucleus.isEmpty {
str += "{}"
} else if atom.nucleus == "\u{2236}" {
// math colon
str += ":"
} else if atom.nucleus == "\u{2212}" {
// math minus
str += "-"
} else {
if let command = MTMathAtomFactory.latexSymbolName(for: atom) {
str += "\\\(command) "
} else {
str += "\(atom.nucleus)"
}
}
if atom.superScript != nil {
str += "^{\(mathListToString(atom.superScript!))}"
}
if atom.subScript != nil {
str += "_{\(mathListToString(atom.subScript!))}"
}
}
}
if currentfontStyle != .defaultStyle {
str += "}"
}
return str
}
public static func delimToString(delim: MTMathAtom) -> String {
if let command = MTMathAtomFactory.getDelimiterName(of: delim) {
let singleChars = [ "(", ")", "[", "]", "<", ">", "|", ".", "/"]
if singleChars.contains(command) {
return command
} else if command == "||" {
return "\\|"
} else {
return "\\\(command)"
}
}
return ""
}
mutating func atomForCommand(_ command:String) -> MTMathAtom? {
if let atom = MTMathAtomFactory.atom(forLatexSymbol: command) { if let atom = MTMathAtomFactory.atom(forLatexSymbol: command) {
return atom return atom
} }
@@ -557,7 +600,7 @@ public class MTMathListBuilder {
} }
} }
func readColor() -> String? { mutating func readColor() -> String? {
if !self.expectCharacter("{") { if !self.expectCharacter("{") {
// We didn't find an opening brace, so no env found. // We didn't find an opening brace, so no env found.
self.setError(.characterNotFound, message:"Missing {") self.setError(.characterNotFound, message:"Missing {")
@@ -588,7 +631,7 @@ public class MTMathListBuilder {
return mutable; return mutable;
} }
func skipSpaces() { mutating func skipSpaces() {
while self.hasCharacters { while self.hasCharacters {
let ch = self.getNextCharacter().utf32Char let ch = self.getNextCharacter().utf32Char
if ch < 0x21 || ch > 0x7E { if ch < 0x21 || ch > 0x7E {
@@ -611,7 +654,7 @@ public class MTMathListBuilder {
] ]
} }
func stopCommand(_ command: String, list:MTMathList, stopChar:Character?) -> MTMathList? { mutating func stopCommand(_ command: String, list:MTMathList, stopChar:Character?) -> MTMathList? {
if command == "right" { if command == "right" {
if currentInnerAtom == nil { if currentInnerAtom == nil {
let errorMessage = "Missing \\left"; let errorMessage = "Missing \\left";
@@ -676,7 +719,7 @@ public class MTMathListBuilder {
} }
// Applies the modifier to the atom. Returns true if modifier applied. // Applies the modifier to the atom. Returns true if modifier applied.
func applyModifier(_ modifier:String, atom:MTMathAtom?) -> Bool { mutating func applyModifier(_ modifier:String, atom:MTMathAtom?) -> Bool {
if modifier == "limits" { if modifier == "limits" {
if atom?.type != .largeOperator { if atom?.type != .largeOperator {
let errorMessage = "Limits can only be applied to an operator." let errorMessage = "Limits can only be applied to an operator."
@@ -699,14 +742,14 @@ public class MTMathListBuilder {
return false return false
} }
func setError(_ code:MTParseErrors, message:String) { mutating func setError(_ code:MTParseErrors, message:String) {
// Only record the first error. // Only record the first error.
if error == nil { if error == nil {
error = NSError(domain: MTParseError, code: code.rawValue, userInfo: [ NSLocalizedDescriptionKey : message ]) error = NSError(domain: MTParseError, code: code.rawValue, userInfo: [ NSLocalizedDescriptionKey : message ])
} }
} }
func atom(forCommand command: String) -> MTMathAtom? { mutating func atom(forCommand command: String) -> MTMathAtom? {
if let atom = MTMathAtomFactory.atom(forLatexSymbol: command) { if let atom = MTMathAtomFactory.atom(forLatexSymbol: command) {
return atom return atom
} }
@@ -786,7 +829,7 @@ public class MTMathListBuilder {
} }
} }
func readEnvironment() -> String? { mutating func readEnvironment() -> String? {
if !self.expectCharacter("{") { if !self.expectCharacter("{") {
// We didn't find an opening brace, so no env found. // We didn't find an opening brace, so no env found.
self.setError(.characterNotFound, message: "Missing {") self.setError(.characterNotFound, message: "Missing {")
@@ -808,24 +851,7 @@ public class MTMathListBuilder {
assert(ch >= "\u{21}" && ch <= "\u{7E}", "Expected non-space character \(ch)") assert(ch >= "\u{21}" && ch <= "\u{7E}", "Expected non-space character \(ch)")
} }
func expectCharacter(_ ch: Character) -> Bool { mutating func buildTable(env: String?, firstList: MTMathList?, isRow: Bool) -> MTMathAtom? {
MTAssertNotSpace(ch)
self.skipSpaces()
if self.hasCharacters {
let nextChar = self.getNextCharacter()
MTAssertNotSpace(nextChar)
if nextChar == ch {
return true
} else {
self.unlookCharacter()
return false
}
}
return false
}
func buildTable(env: String?, firstList: MTMathList?, isRow: Bool) -> MTMathAtom? {
// Save the current env till an new one gets built. // Save the current env till an new one gets built.
let oldEnv = self.currentEnv let oldEnv = self.currentEnv
@@ -876,7 +902,7 @@ public class MTMathListBuilder {
return table return table
} }
func getBoundaryAtom(_ delimiterType: String) -> MTMathAtom? { mutating func getBoundaryAtom(_ delimiterType: String) -> MTMathAtom? {
let delim = self.readDelimiter() let delim = self.readDelimiter()
if delim == nil { if delim == nil {
let errorMessage = "Missing delimiter for \\\(delimiterType)" let errorMessage = "Missing delimiter for \\\(delimiterType)"
@@ -892,7 +918,7 @@ public class MTMathListBuilder {
return boundary return boundary
} }
func readDelimiter() -> String? { mutating func readDelimiter() -> String? {
self.skipSpaces() self.skipSpaces()
while self.hasCharacters { while self.hasCharacters {
let char = self.getNextCharacter() let char = self.getNextCharacter()
@@ -910,7 +936,7 @@ public class MTMathListBuilder {
return nil return nil
} }
func readCommand() -> String { mutating func readCommand() -> String {
let singleChars = "{}$#%_| ,>;!\\" let singleChars = "{}$#%_| ,>;!\\"
if self.hasCharacters { if self.hasCharacters {
let char = self.getNextCharacter() let char = self.getNextCharacter()
@@ -923,7 +949,7 @@ public class MTMathListBuilder {
return self.readString() return self.readString()
} }
func readString() -> String { mutating func readString() -> String {
// a string of all upper and lower case characters. // a string of all upper and lower case characters.
var output = "" var output = ""
while self.hasCharacters { while self.hasCharacters {

View File

@@ -1,8 +1,9 @@
// //
// MTMathListDisplay.swift
// MathRenderSwift
//
// Created by Mike Griebling on 2022-12-31. // Created by Mike Griebling on 2022-12-31.
// Translated from an Objective-C implementation by Kostub Deshmukh.
//
// This software may be modified and distributed under the terms of the
// MIT license. See the LICENSE file for details.
// //
import Foundation import Foundation
@@ -26,6 +27,7 @@ func isIos6Supported() -> Bool {
return MTDisplay.supported return MTDisplay.supported
} }
// The Downshift protocol allows an MTDisplay to be shifted down by a given amount.
protocol DownShift { protocol DownShift {
var shiftDown:CGFloat { set get } var shiftDown:CGFloat { set get }
} }
@@ -52,7 +54,7 @@ public class MTDisplay:NSObject {
/// Gets the bounding rectangle for the MTDisplay /// Gets the bounding rectangle for the MTDisplay
func displayBounds() -> CGRect { func displayBounds() -> CGRect {
return CGRectMake(self.position.x, self.position.y - self.descent, self.width, self.ascent + self.descent) CGRectMake(self.position.x, self.position.y - self.descent, self.width, self.ascent + self.descent)
} }
/// For debugging. Shows the object in quick look in Xcode. /// For debugging. Shows the object in quick look in Xcode.
@@ -81,17 +83,17 @@ public class MTDisplay:NSObject {
#endif #endif
/// The distance from the axis to the top of the display /// The distance from the axis to the top of the display
var ascent:CGFloat = 0 public var ascent:CGFloat = 0
/// The distance from the axis to the bottom of the display /// The distance from the axis to the bottom of the display
var descent:CGFloat = 0 public var descent:CGFloat = 0
/// The width of the display /// The width of the display
var width:CGFloat = 0 public var width:CGFloat = 0
/// Position of the display with respect to the parent view or display. /// Position of the display with respect to the parent view or display.
var position=CGPoint.zero var position = CGPoint.zero
/// The range of characters supported by this item /// The range of characters supported by this item
var range:NSRange=NSMakeRange(0, 0) public var range:NSRange=NSMakeRange(0, 0)
/// Whether the display has a subscript/superscript following it. /// Whether the display has a subscript/superscript following it.
var hasScript:Bool = false public var hasScript:Bool = false
/// The text color for this display /// The text color for this display
var textColor: MTColor? var textColor: MTColor?
/// The local color, if the color was mutated local with the color command /// The local color, if the color was mutated local with the color command
@@ -101,6 +103,7 @@ public class MTDisplay:NSObject {
} }
/// Special class to be inherited from that implements the DownShift protocol
class MTDisplayDS : MTDisplay, DownShift { class MTDisplayDS : MTDisplay, DownShift {
var shiftDown: CGFloat = 0 var shiftDown: CGFloat = 0
@@ -110,10 +113,10 @@ class MTDisplayDS : MTDisplay, DownShift {
// MARK: - MTCTLineDisplay // MARK: - MTCTLineDisplay
/// A rendering of a single CTLine as an MTDisplay /// A rendering of a single CTLine as an MTDisplay
class MTCTLineDisplay : MTDisplay { public class MTCTLineDisplay : MTDisplay {
/// The CTLine being displayed /// The CTLine being displayed
var line:CTLine! public var line:CTLine!
/// The attributed string used to generate the CTLineRef. Note setting this does not reset the dimensions of /// The attributed string used to generate the CTLineRef. Note setting this does not reset the dimensions of
/// the display. So set only when /// the display. So set only when
var attributedString:NSAttributedString? { var attributedString:NSAttributedString? {
@@ -123,7 +126,7 @@ class MTCTLineDisplay : MTDisplay {
} }
/// An array of MTMathAtoms that this CTLine displays. Used for indexing back into the MTMathList /// An array of MTMathAtoms that this CTLine displays. Used for indexing back into the MTMathList
var atoms = [MTMathAtom]() public fileprivate(set) var atoms = [MTMathAtom]()
init(withString attrString:NSAttributedString?, position:CGPoint, range:NSRange, font:MTFont?, atoms:[MTMathAtom]) { init(withString attrString:NSAttributedString?, position:CGPoint, range:NSRange, font:MTFont?, atoms:[MTMathAtom]) {
super.init() super.init()
@@ -197,10 +200,9 @@ class MTCTLineDisplay : MTDisplay {
public class MTMathListDisplay : MTDisplay { public class MTMathListDisplay : MTDisplay {
/** /**
@typedef MTLinePosition The type of position for a line, i.e. subscript/superscript or regular.
@brief The type of position for a line, i.e. subscript/superscript or regular.
*/ */
enum LinePosition : Int { public enum LinePosition : Int {
/// Regular /// Regular
case regular case regular
/// Positioned at a subscript /// Positioned at a subscript
@@ -210,13 +212,13 @@ public class MTMathListDisplay : MTDisplay {
} }
/// Where the line is positioned /// Where the line is positioned
var type:LinePosition = .regular public var type:LinePosition = .regular
/// An array of MTDisplays which are positioned relative to the position of the /// An array of MTDisplays which are positioned relative to the position of the
/// the current display. /// the current display.
var subDisplays = [MTDisplay]() public fileprivate(set) var subDisplays = [MTDisplay]()
/// If a subscript or superscript this denotes the location in the parent MTList. For a /// If a subscript or superscript this denotes the location in the parent MTList. For a
/// regular list this is NSNotFound /// regular list this is NSNotFound
var index: Int = 0 public var index: Int = 0
init(withDisplays displays:[MTDisplay], range:NSRange) { init(withDisplays displays:[MTDisplay], range:NSRange) {
super.init() super.init()
@@ -287,23 +289,19 @@ public class MTMathListDisplay : MTDisplay {
// MARK: - MTFractionDisplay // MARK: - MTFractionDisplay
/// Rendering of an MTFraction as an MTDisplay /// Rendering of an MTFraction as an MTDisplay
class MTFractionDisplay : MTDisplay { public class MTFractionDisplay : MTDisplay {
/** A display representing the numerator of the fraction. Its position is relative /** A display representing the numerator of the fraction. Its position is relative
to the parent and is not treated as a sub-display. to the parent and is not treated as a sub-display.
*/ */
var numerator:MTMathListDisplay? public fileprivate(set) var numerator:MTMathListDisplay?
/** A display representing the denominator of the fraction. Its position is relative /** A display representing the denominator of the fraction. Its position is relative
to the parent is not treated as a sub-display. to the parent is not treated as a sub-display.
*/ */
var denominator:MTMathListDisplay? public fileprivate(set) var denominator:MTMathListDisplay?
var numeratorUp:CGFloat=0 { var numeratorUp:CGFloat=0 { didSet { self.updateNumeratorPosition() } }
didSet { self.updateNumeratorPosition() } var denominatorDown:CGFloat=0 { didSet { self.updateDenominatorPosition() } }
}
var denominatorDown:CGFloat=0 {
didSet { self.updateDenominatorPosition() }
}
var linePosition:CGFloat=0 var linePosition:CGFloat=0
var lineThickness:CGFloat=0 var lineThickness:CGFloat=0
@@ -316,17 +314,17 @@ class MTFractionDisplay : MTDisplay {
assert(self.range.length == 1, "Fraction range length not 1 - range (\(range.location), \(range.length)") assert(self.range.length == 1, "Fraction range length not 1 - range (\(range.location), \(range.length)")
} }
override var ascent:CGFloat { override public var ascent:CGFloat {
set { super.ascent = newValue } set { super.ascent = newValue }
get { numerator!.ascent + self.numeratorUp } get { numerator!.ascent + self.numeratorUp }
} }
override var descent:CGFloat { override public var descent:CGFloat {
set { super.descent = newValue } set { super.descent = newValue }
get { denominator!.descent + self.denominatorDown } get { denominator!.descent + self.denominatorDown }
} }
override var width:CGFloat { override public var width:CGFloat {
set { super.width = newValue } set { super.width = newValue }
get { max(numerator!.width, denominator!.width) } get { max(numerator!.width, denominator!.width) }
} }
@@ -391,11 +389,11 @@ class MTRadicalDisplay : MTDisplay {
/** A display representing the radicand of the radical. Its position is relative /** A display representing the radicand of the radical. Its position is relative
to the parent is not treated as a sub-display. to the parent is not treated as a sub-display.
*/ */
var radicand:MTMathListDisplay? public fileprivate(set) var radicand:MTMathListDisplay?
/** A display representing the degree of the radical. Its position is relative /** A display representing the degree of the radical. Its position is relative
to the parent is not treated as a sub-display. to the parent is not treated as a sub-display.
*/ */
var degree:MTMathListDisplay? public fileprivate(set) var degree:MTMathListDisplay?
override var position: CGPoint { override var position: CGPoint {
set { set {
@@ -536,21 +534,13 @@ class MTGlyphDisplay : MTDisplayDS {
} }
override var ascent:CGFloat { override var ascent:CGFloat {
set { set { super.ascent = newValue }
super.ascent = newValue get { super.ascent - self.shiftDown }
}
get {
return super.ascent - self.shiftDown;
}
} }
override var descent:CGFloat { override var descent:CGFloat {
set { set { super.descent = newValue }
super.descent = newValue get { super.descent + self.shiftDown }
}
get {
return super.descent + self.shiftDown;
}
} }
} }
@@ -593,21 +583,13 @@ class MTGlyphConstructionDisplay:MTDisplayDS {
} }
override var ascent:CGFloat { override var ascent:CGFloat {
set { set { super.ascent = newValue }
super.ascent = newValue get { super.ascent - self.shiftDown }
}
get {
return super.ascent - self.shiftDown;
}
} }
override var descent:CGFloat { override var descent:CGFloat {
set { set { super.descent = newValue }
super.descent = newValue get { super.descent + self.shiftDown }
}
get {
return super.descent + self.shiftDown;
}
} }
} }
@@ -627,16 +609,8 @@ class MTLargeOpLimitsDisplay : MTDisplay {
var lowerLimit:MTMathListDisplay? var lowerLimit:MTMathListDisplay?
var limitShift:CGFloat=0 var limitShift:CGFloat=0
var upperLimitGap:CGFloat=0 { var upperLimitGap:CGFloat=0 { didSet { self.updateUpperLimitPosition() } }
didSet { var lowerLimitGap:CGFloat=0 { didSet { self.updateLowerLimitPosition() } }
self.updateUpperLimitPosition()
}
}
var lowerLimitGap:CGFloat=0 {
didSet {
self.updateUpperLimitPosition()
}
}
var extraPadding:CGFloat=0 var extraPadding:CGFloat=0
var nucleus:MTDisplay? var nucleus:MTDisplay?
@@ -658,9 +632,7 @@ class MTLargeOpLimitsDisplay : MTDisplay {
} }
override var ascent:CGFloat { override var ascent:CGFloat {
set { set { super.ascent = newValue }
super.ascent = newValue
}
get { get {
if self.upperLimit != nil { if self.upperLimit != nil {
return nucleus!.ascent + extraPadding + self.upperLimit!.ascent + upperLimitGap + self.upperLimit!.descent return nucleus!.ascent + extraPadding + self.upperLimit!.ascent + upperLimitGap + self.upperLimit!.descent
@@ -671,9 +643,7 @@ class MTLargeOpLimitsDisplay : MTDisplay {
} }
override var descent:CGFloat { override var descent:CGFloat {
set { set { super.descent = newValue }
super.descent = newValue
}
get { get {
if self.lowerLimit != nil { if self.lowerLimit != nil {
return nucleus!.descent + extraPadding + lowerLimitGap + self.lowerLimit!.descent + self.lowerLimit!.ascent; return nucleus!.descent + extraPadding + lowerLimitGap + self.lowerLimit!.descent + self.lowerLimit!.ascent;

View File

@@ -1,22 +1,51 @@
// //
// MTMathListIndex.swift
// MathRenderSwift
//
// Created by Mike Griebling on 2022-12-31. // Created by Mike Griebling on 2022-12-31.
// Translated from an Objective-C implementation by Kostub Deshmukh.
//
// This software may be modified and distributed under the terms of the
// MIT license. See the LICENSE file for details.
// //
import Foundation import Foundation
/**
* An index that points to a particular character in the MTMathList. The index is a LinkedList that represents
* a path from the beginning of the MTMathList to reach a particular atom in the list. The next node of the path
* is represented by the subIndex. The path terminates when the subIndex is nil.
*
* If there is a subIndex, the subIndexType denotes what branch the path takes (i.e. superscript, subscript,
* numerator, denominator etc.).
* e.g in the expression 25^{2/4} the index of the character 4 is represented as:
* (1, superscript) -> (0, denominator) -> (0, none)
* This can be interpreted as start at index 1 (i.e. the 5) go up to the superscript.
* Then look at index 0 (i.e. 2/4) and go to the denominator. Then look up index 0 (i.e. the 4) which this final
* index.
*
* The level of an index is the number of nodes in the LinkedList to get to the final path.
*/
public class MTMathListIndex { public class MTMathListIndex {
/**
The type of the subindex.
The type of the subindex denotes what branch the path to the atom that this index points to takes.
*/
public enum MTMathListSubIndexType: Int { public enum MTMathListSubIndexType: Int {
case none = 0 /// The index denotes the whole atom, subIndex is nil.
case none = 0
/// The position in the subindex is an index into the nucleus
case nucleus case nucleus
case superScript /// The subindex indexes into the superscript.
case subScript case superscript
/// The subindex indexes into the subscript
case ssubscript
/// The subindex indexes into the numerator (only valid for fractions)
case numerator case numerator
/// The subindex indexes into the denominator (only valid for fractions)
case denominator case denominator
/// The subindex indexes into the radicand (only valid for radicals)
case radicand case radicand
/// The subindex indexes into the degree (only valid for radicals)
case degree case degree
} }
@@ -37,6 +66,7 @@ public class MTMathListIndex {
} }
} }
/// Returns the previous index if present. Returns `nil` if there is no previous index.
func prevIndex() -> MTMathListIndex? { func prevIndex() -> MTMathListIndex? {
if self.subIndexType == .none { if self.subIndexType == .none {
if self.atomIndex > 0 { if self.atomIndex > 0 {
@@ -50,6 +80,7 @@ public class MTMathListIndex {
return nil return nil
} }
/// Returns the next index.
func nextIndex() -> MTMathListIndex { func nextIndex() -> MTMathListIndex {
if self.subIndexType == .none { if self.subIndexType == .none {
return MTMathListIndex(level0Index: self.atomIndex + 1) return MTMathListIndex(level0Index: self.atomIndex + 1)
@@ -65,9 +96,7 @@ public class MTMathListIndex {
* e.g. a superscript or a fraction numerator. This returns true if the innermost subindex points to the beginning of a * e.g. a superscript or a fraction numerator. This returns true if the innermost subindex points to the beginning of a
* line. * line.
*/ */
func isBeginningOfLine() -> Bool { func isBeginningOfLine() -> Bool { self.finalIndex == 0 }
return self.finalIndex == 0
}
func isAtSameLevel(with index: MTMathListIndex?) -> Bool { func isAtSameLevel(with index: MTMathListIndex?) -> Bool {
if self.subIndexType != index?.subIndexType { if self.subIndexType != index?.subIndexType {

View File

@@ -1,8 +1,9 @@
// //
// MTMathUILabel.swift // Created by Mike Griebling on 2022-12-31.
// MathRenderSwift // Translated from an Objective-C implementation by Kostub Deshmukh.
// //
// Created by Mike Griebling on 2023-01-01. // This software may be modified and distributed under the terms of the
// MIT license. See the LICENSE file for details.
// //
import Foundation import Foundation

View File

@@ -1,15 +1,16 @@
// //
// MTTypesetter.swift // Created by Mike Griebling on 2022-12-31.
// MathRenderSwift // Translated from an Objective-C implementation by Kostub Deshmukh.
// //
// Created by Mike Griebling on 2023-01-01. // This software may be modified and distributed under the terms of the
// MIT license. See the LICENSE file for details.
// //
import Foundation import Foundation
import CoreGraphics import CoreGraphics
import CoreText import CoreText
// MARK: - - Inter Element Spacing // MARK: - Inter Element Spacing
enum InterElementSpaceType : Int { enum InterElementSpaceType : Int {
case invalid = -1 case invalid = -1
@@ -76,7 +77,7 @@ func getInterElementSpaceArrayIndexForType(_ type:MTMathAtomType, row:Bool) -> I
} }
} }
// MARK: - - Italics // MARK: - Italics
// mathit // mathit
func getItalicized(_ ch:Character) -> UTF32Char { func getItalicized(_ ch:Character) -> UTF32Char {
var unicode = ch.utf32Char var unicode = ch.utf32Char
@@ -235,7 +236,7 @@ func getSansSerif(_ ch:Character) -> UTF32Char {
// mathfrak // mathfrak
func getFraktur(_ ch:Character) -> UTF32Char { func getFraktur(_ ch:Character) -> UTF32Char {
// Fraktur has exceptions: // Fraktur has exceptions:
switch(ch) { switch ch {
case "C": case "C":
return 0x212D; // C Fraktur return 0x212D; // C Fraktur
case "H": case "H":
@@ -337,7 +338,7 @@ func getBboxDetails(_ bbox:CGRect, ascent:inout CGFloat, descent:inout CGFloat)
descent = max(0, 0 - CGRectGetMinY(bbox)) descent = max(0, 0 - CGRectGetMinY(bbox))
} }
// MARK: - - MTTypesetter // MARK: - MTTypesetter
class MTTypesetter { class MTTypesetter {
var font:MTFont! var font:MTFont!
@@ -879,7 +880,7 @@ class MTTypesetter {
currentPosition.x += max(superScript!.width + delta, ssubscript!.width) + styleFont.mathTable!.spaceAfterScript; currentPosition.x += max(superScript!.width + delta, ssubscript!.width) + styleFont.mathTable!.spaceAfterScript;
} }
// MARK: - - Fractions // MARK: - Fractions
func numeratorShiftUp(_ hasRule:Bool) -> CGFloat { func numeratorShiftUp(_ hasRule:Bool) -> CGFloat {
if hasRule { if hasRule {
@@ -1002,7 +1003,7 @@ class MTTypesetter {
display.denominatorDown = denominatorShiftDown; display.denominatorDown = denominatorShiftDown;
display.lineThickness = barThickness; display.lineThickness = barThickness;
display.linePosition = barLocation; display.linePosition = barLocation;
if frac!.leftDelimiter == nil && frac!.rightDelimiter == nil { if frac!.leftDelimiter.isEmpty && frac!.rightDelimiter.isEmpty {
return display return display
} else { } else {
return self.addDelimitersToFractionDisplay(display, forFraction:frac) return self.addDelimitersToFractionDisplay(display, forFraction:frac)
@@ -1010,13 +1011,13 @@ class MTTypesetter {
} }
func addDelimitersToFractionDisplay(_ display:MTFractionDisplay?, forFraction frac:MTFraction?) -> MTDisplay? { func addDelimitersToFractionDisplay(_ display:MTFractionDisplay?, forFraction frac:MTFraction?) -> MTDisplay? {
assert(frac!.leftDelimiter != nil || frac!.rightDelimiter != nil, "Fraction should have a delimiters to call this function"); assert(!frac!.leftDelimiter.isEmpty || !frac!.rightDelimiter.isEmpty, "Fraction should have a delimiters to call this function");
var innerElements = [MTDisplay]() var innerElements = [MTDisplay]()
let glyphHeight = self.fractionDelimiterHeight let glyphHeight = self.fractionDelimiterHeight
var position = CGPoint.zero var position = CGPoint.zero
if !frac!.leftDelimiter!.isEmpty { if !frac!.leftDelimiter.isEmpty {
let leftGlyph = self.findGlyphForBoundary(frac!.leftDelimiter!, withHeight:glyphHeight()) let leftGlyph = self.findGlyphForBoundary(frac!.leftDelimiter, withHeight:glyphHeight())
leftGlyph!.position = position leftGlyph!.position = position
position.x += leftGlyph!.width position.x += leftGlyph!.width
innerElements.append(leftGlyph!) innerElements.append(leftGlyph!)
@@ -1026,8 +1027,8 @@ class MTTypesetter {
position.x += display!.width position.x += display!.width
innerElements.append(display!) innerElements.append(display!)
if !frac!.rightDelimiter!.isEmpty { if !frac!.rightDelimiter.isEmpty {
let rightGlyph = self.findGlyphForBoundary(frac!.rightDelimiter!, withHeight:glyphHeight()) let rightGlyph = self.findGlyphForBoundary(frac!.rightDelimiter, withHeight:glyphHeight())
rightGlyph!.position = position rightGlyph!.position = position
position.x += rightGlyph!.width position.x += rightGlyph!.width
innerElements.append(rightGlyph!) innerElements.append(rightGlyph!)

View File

@@ -1,63 +1,64 @@
// //
// MTUnicode.swift
// MathRenderSwift
//
// Created by Mike Griebling on 2022-12-31. // Created by Mike Griebling on 2022-12-31.
// Translated from an Objective-C implementation by Kostub Deshmukh.
//
// This software may be modified and distributed under the terms of the
// MIT license. See the LICENSE file for details.
// //
import Foundation import Foundation
public struct UnicodeSymbol { public struct UnicodeSymbol {
static let multiplication = "\u{00D7}" static let multiplication = "\u{00D7}"
static let division = "\u{00F7}" static let division = "\u{00F7}"
static let fractionSlash = "\u{2044}" static let fractionSlash = "\u{2044}"
static let whiteSquare = "\u{25A1}" static let whiteSquare = "\u{25A1}"
static let blackSquare = "\u{25A0}" static let blackSquare = "\u{25A0}"
static let lessEqual = "\u{2264}" static let lessEqual = "\u{2264}"
static let greaterEqual = "\u{2265}" static let greaterEqual = "\u{2265}"
static let notEqual = "\u{2260}" static let notEqual = "\u{2260}"
static let squareRoot = "\u{221A}" // \sqrt static let squareRoot = "\u{221A}" // \sqrt
static let cubeRoot = "\u{221B}" static let cubeRoot = "\u{221B}"
static let infinity = "\u{221E}" // \infty static let infinity = "\u{221E}" // \infty
static let angle = "\u{2220}" // \angle static let angle = "\u{2220}" // \angle
static let degree = "\u{00B0}" // \circ static let degree = "\u{00B0}" // \circ
static let capitalGreekStart = UInt32(0x0391) static let capitalGreekStart = UInt32(0x0391)
static let capitalGreekEnd = UInt32(0x03A9) static let capitalGreekEnd = UInt32(0x03A9)
static let lowerGreekStart = UInt32(0x03B1) static let lowerGreekStart = UInt32(0x03B1)
static let lowerGreekEnd = UInt32(0x03C9) static let lowerGreekEnd = UInt32(0x03C9)
static let planksConstant = UInt32(0x210e) static let planksConstant = UInt32(0x210e)
static let lowerItalicStart = UInt32(0x1D44E) static let lowerItalicStart = UInt32(0x1D44E)
static let capitalItalicStart = UInt32(0x1D434) static let capitalItalicStart = UInt32(0x1D434)
static let greekLowerItalicStart = UInt32(0x1D6FC) static let greekLowerItalicStart = UInt32(0x1D6FC)
static let greekCapitalItalicStart = UInt32(0x1D6E2) static let greekCapitalItalicStart = UInt32(0x1D6E2)
static let greekSymbolItalicStart = UInt32(0x1D716) static let greekSymbolItalicStart = UInt32(0x1D716)
static let mathCapitalBoldStart = UInt32(0x1D400) static let mathCapitalBoldStart = UInt32(0x1D400)
static let mathLowerBoldStart = UInt32(0x1D41A) static let mathLowerBoldStart = UInt32(0x1D41A)
static let greekCapitalBoldStart = UInt32(0x1D6A8) static let greekCapitalBoldStart = UInt32(0x1D6A8)
static let greekLowerBoldStart = UInt32(0x1D6C2) static let greekLowerBoldStart = UInt32(0x1D6C2)
static let greekSymbolBoldStart = UInt32(0x1D6DC) static let greekSymbolBoldStart = UInt32(0x1D6DC)
static let numberBoldStart = UInt32(0x1D7CE) static let numberBoldStart = UInt32(0x1D7CE)
static let mathCapitalBoldItalicStart = UInt32(0x1D468) static let mathCapitalBoldItalicStart = UInt32(0x1D468)
static let mathLowerBoldItalicStart = UInt32(0x1D482) static let mathLowerBoldItalicStart = UInt32(0x1D482)
static let greekCapitalBoldItalicStart = UInt32(0x1D71C) static let greekCapitalBoldItalicStart = UInt32(0x1D71C)
static let greekLowerBoldItalicStart = UInt32(0x1D736) static let greekLowerBoldItalicStart = UInt32(0x1D736)
static let greekSymbolBoldItalicStart = UInt32(0x1D750) static let greekSymbolBoldItalicStart = UInt32(0x1D750)
static let mathCapitalScriptStart = UInt32(0x1D49C) static let mathCapitalScriptStart = UInt32(0x1D49C)
static let mathCapitalTTStart = UInt32(0x1D670) static let mathCapitalTTStart = UInt32(0x1D670)
static let mathLowerTTStart = UInt32(0x1D68A) static let mathLowerTTStart = UInt32(0x1D68A)
static let numberTTStart = UInt32(0x1D7F6) static let numberTTStart = UInt32(0x1D7F6)
static let mathCapitalSansSerifStart = UInt32(0x1D5A0) static let mathCapitalSansSerifStart = UInt32(0x1D5A0)
static let mathLowerSansSerifStart = UInt32(0x1D5BA) static let mathLowerSansSerifStart = UInt32(0x1D5BA)
static let numberSansSerifStart = UInt32(0x1D7E2) static let numberSansSerifStart = UInt32(0x1D7E2)
static let mathCapitalFrakturStart = UInt32(0x1D504) static let mathCapitalFrakturStart = UInt32(0x1D504)
static let mathLowerFrakturStart = UInt32(0x1D51E) static let mathLowerFrakturStart = UInt32(0x1D51E)
static let mathCapitalBlackboardStart = UInt32(0x1D538) static let mathCapitalBlackboardStart = UInt32(0x1D538)
static let mathLowerBlackboardStart = UInt32(0x1D552) static let mathLowerBlackboardStart = UInt32(0x1D552)
static let numberBlackboardStart = UInt32(0x1D7D8) static let numberBlackboardStart = UInt32(0x1D7D8)
} }
extension Character { extension Character {

View File

@@ -352,8 +352,8 @@ final class MTMathListBuilderTests: XCTestCase {
XCTAssertEqual(frac.type, .fraction, desc) XCTAssertEqual(frac.type, .fraction, desc)
XCTAssertEqual(frac.nucleus, "", desc) XCTAssertEqual(frac.nucleus, "", desc)
XCTAssertTrue(frac.hasRule); XCTAssertTrue(frac.hasRule);
XCTAssertNil(frac.rightDelimiter); XCTAssertTrue(frac.rightDelimiter.isEmpty)
XCTAssertNil(frac.leftDelimiter); XCTAssertTrue(frac.leftDelimiter.isEmpty)
var subList = frac.numerator! var subList = frac.numerator!
XCTAssertNotNil(subList, desc) XCTAssertNotNil(subList, desc)
@@ -550,8 +550,8 @@ final class MTMathListBuilderTests: XCTestCase {
XCTAssertEqual(frac.type, .fraction, desc); XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc); XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertTrue(frac.hasRule); XCTAssertTrue(frac.hasRule);
XCTAssertNil(frac.rightDelimiter); XCTAssertTrue(frac.rightDelimiter.isEmpty)
XCTAssertNil(frac.leftDelimiter); XCTAssertTrue(frac.leftDelimiter.isEmpty)
var subList = frac.numerator! var subList = frac.numerator!
XCTAssertNotNil(subList, desc); XCTAssertNotNil(subList, desc);
@@ -587,8 +587,8 @@ final class MTMathListBuilderTests: XCTestCase {
XCTAssertEqual(frac.type, .fraction, desc); XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc); XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertTrue(frac.hasRule); XCTAssertTrue(frac.hasRule);
XCTAssertNil(frac.rightDelimiter); XCTAssertTrue(frac.rightDelimiter.isEmpty)
XCTAssertNil(frac.leftDelimiter); XCTAssertTrue(frac.leftDelimiter.isEmpty)
var subList = frac.numerator! var subList = frac.numerator!
XCTAssertNotNil(subList, desc); XCTAssertNotNil(subList, desc);
@@ -621,8 +621,8 @@ final class MTMathListBuilderTests: XCTestCase {
XCTAssertEqual(frac.type, .fraction, desc); XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc); XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertFalse(frac.hasRule); XCTAssertFalse(frac.hasRule);
XCTAssertNil(frac.rightDelimiter); XCTAssertTrue(frac.rightDelimiter.isEmpty)
XCTAssertNil(frac.leftDelimiter); XCTAssertTrue(frac.leftDelimiter.isEmpty)
var subList = frac.numerator! var subList = frac.numerator!
XCTAssertNotNil(subList, desc); XCTAssertNotNil(subList, desc);
@@ -658,8 +658,8 @@ final class MTMathListBuilderTests: XCTestCase {
XCTAssertEqual(frac.type, .fraction, desc); XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc); XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertFalse(frac.hasRule); XCTAssertFalse(frac.hasRule);
XCTAssertNil(frac.rightDelimiter); XCTAssertTrue(frac.rightDelimiter.isEmpty)
XCTAssertNil(frac.leftDelimiter); XCTAssertTrue(frac.leftDelimiter.isEmpty)
var subList = frac.numerator! var subList = frac.numerator!
XCTAssertNotNil(subList, desc); XCTAssertNotNil(subList, desc);
@@ -1020,7 +1020,7 @@ final class MTMathListBuilderTests: XCTestCase {
let table = list.atoms[0] as! MTMathTable let table = list.atoms[0] as! MTMathTable
XCTAssertEqual(table.type, .table); XCTAssertEqual(table.type, .table);
XCTAssertEqual(table.nucleus, ""); XCTAssertEqual(table.nucleus, "");
XCTAssertNil(table.environment); XCTAssertTrue(table.environment.isEmpty);
XCTAssertEqual(table.interRowAdditionalSpacing, 1); XCTAssertEqual(table.interRowAdditionalSpacing, 1);
XCTAssertEqual(table.interColumnSpacing, 0); XCTAssertEqual(table.interColumnSpacing, 0);
XCTAssertEqual(table.numRows, 2); XCTAssertEqual(table.numRows, 2);
@@ -1051,7 +1051,7 @@ final class MTMathListBuilderTests: XCTestCase {
let table = list.atoms[0] as! MTMathTable let table = list.atoms[0] as! MTMathTable
XCTAssertEqual(table.type, .table); XCTAssertEqual(table.type, .table);
XCTAssertEqual(table.nucleus, ""); XCTAssertEqual(table.nucleus, "");
XCTAssertNil(table.environment); XCTAssertTrue(table.environment.isEmpty);
XCTAssertEqual(table.interRowAdditionalSpacing, 1); XCTAssertEqual(table.interRowAdditionalSpacing, 1);
XCTAssertEqual(table.interColumnSpacing, 0); XCTAssertEqual(table.interColumnSpacing, 0);
XCTAssertEqual(table.numRows, 2); XCTAssertEqual(table.numRows, 2);