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
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.
`
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.
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.
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.
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
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)
## 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:
* Foundation.framework
@@ -97,8 +96,8 @@ import SwiftMath
struct MathView: UIViewRepresentable {
@Binding var equation: String
@Binding var fontSize: CGFloat
var equation: String
var fontSize: CGFloat
func makeUIView(context: Context) -> 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
import SwiftUI
@@ -123,8 +122,8 @@ import SwiftMath
struct MathView: NSViewRepresentable {
@Binding var equation: String
@Binding var fontSize: CGFloat
var equation: String
var fontSize: CGFloat
func makeNSView(context: Context) -> MTMathUILabel {
let view = MTMathUILabel()
@@ -168,7 +167,7 @@ This is a list of formula types that the library currently supports:
### 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.
### Advanced configuration
@@ -204,7 +203,7 @@ label.fontSize = 30
The default font is *Latin Modern Math*. This can be changed as:
```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

View File

@@ -1,8 +1,10 @@
//
// MTBezierPath.swift
// MathRenderSwift
//
// 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

View File

@@ -1,8 +1,10 @@
//
// MTColor.swift
// MathRenderSwift
//
// 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
@@ -19,7 +21,8 @@ extension MTColor {
scanner.scanHexInt64(&rgbValue)
return MTColor(red: CGFloat((rgbValue & 0xFF0000) >> 16)/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
//
// 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)
import UIKit

View File

@@ -1,17 +1,11 @@
//
// MTFont.swift
// MathRenderSwift
//
// Created by Mike Griebling on 2022-12-31.
//
import Foundation
import CoreGraphics
import CoreText
//
// Created by Kostub Deshmukh on 5/18/16.
// Modified by Michael Griebling on 17 Jan 2023.
// 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.
@@ -26,10 +20,10 @@ public class MTFont {
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) {
// 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()
print("Loading font \(name)")
let bundle = MTFont.fontBundle
@@ -51,7 +45,8 @@ public class MTFont {
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()
newFont.defaultCGFont = self.defaultCGFont
newFont.ctFont = CTFontCreateWithGraphicsFont(self.defaultCGFont, size, nil, nil)
@@ -69,6 +64,7 @@ public class MTFont {
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.
// 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

View File

@@ -1,15 +1,16 @@
//
// MTFontMathTable.swift
// MathRenderSwift
//
// 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 CoreGraphics
import CoreText
class GlyphPart {
struct GlyphPart {
/// The glyph that represents this part
var glyph: CGGlyph!
@@ -33,14 +34,13 @@ class GlyphPart {
How the constants in this class affect the display is documented here:
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.
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.
@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 {
// The font for this math table.
@@ -100,21 +100,21 @@ class MTFontMathTable {
var skewedFractionVerticalGap:CGFloat { constantFromTable("SkewedFractionVerticalGap") } // \sigma_21 in TeX
// MARK: - Non-standard
// FractionDelimiterSize and FractionDelimiterDisplayStyleSize are not constants
// 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
// 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
// metrics values of the original TeX fonts.
// 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.
// The XeTeX implementation sets \sigma21 = fontSize and \sigma20 = DelimitedSubFormulaMinHeight which
// will produce smaller delimiters.
// Of all the approaches we've implemented LuaTeX's approach since it mimics LaTeX most accurately.
var fractionDelimiterSize: CGFloat { return 1.01 * _fontSize }
/// FractionDelimiterSize and FractionDelimiterDisplayStyleSize are not constants
/// 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
/// 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
/// metrics values of the original TeX fonts.
/// 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.
/// The XeTeX implementation sets \sigma21 = fontSize and \sigma20 = DelimitedSubFormulaMinHeight which
/// will produce smaller delimiters.
/// Of all the approaches we've implemented LuaTeX's approach since it mimics LaTeX most accurately.
var fractionDelimiterSize: CGFloat { 1.01 * _fontSize }
/// 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
var stackTopDisplayStyleShiftUp:CGFloat { constantFromTable("StackTopDisplayStyleShiftUp") } // \sigma_8 in TeX
@@ -303,7 +303,7 @@ class MTFontMathTable {
var rv = [GlyphPart]()
for part in parts! {
let partInfo = part as! NSDictionary?
let part = GlyphPart()
var part = GlyphPart()
let adv = partInfo!["advance"] as! NSNumber?
part.fullAdvance = self.fontUnitsToPt(adv!.intValue)
let end = partInfo!["endConnector"] as! NSNumber?

View File

@@ -1,8 +1,9 @@
//
// MTLabel.swift
// MathRenderSwift
//
// 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
@@ -25,6 +26,7 @@ public class MTLabel : NSTextField {
super.init(coder: coder)
}
// MARK: - Customized getter and setter methods for property text.
var text:String? {
get { super.stringValue }
set { super.stringValue = newValue! }

View File

@@ -1,12 +1,14 @@
//
// MTMathAtomFactory.swift
// MathRenderSwift
//
// 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
/** A factory to create commonly used MTMathAtoms. */
public class MTMathAtomFactory {
public static let aliases = [
@@ -60,11 +62,10 @@ public class MTMathAtomFactory {
"rfloor" : "\u{230B}"
]
var _delimValueToName: [String: String]? = nil
public var delimValueToName: [String: String] {
if _delimValueToName == nil {
static var _delimValueToName = [String: String]()
public static var delimValueToName: [String: String] {
if _delimValueToName.isEmpty {
var output = [String: String]()
for (key, value) in Self.delimiters {
if let existingValue = output[value] {
if key.count > existingValue.count {
@@ -75,12 +76,11 @@ public class MTMathAtomFactory {
}
}
}
output[value] = key
}
_delimValueToName = output
}
return _delimValueToName!
return _delimValueToName
}
public static let accents = [
@@ -389,8 +389,8 @@ public class MTMathAtomFactory {
"scriptscriptstyle" : MTMathStyle(style: .scriptOfScript),
]
var _textToLatexSymbolName: [String: String]? = nil
public var textToLatexSymbolName: [String: String] {
static var _textToLatexSymbolName: [String: String]? = nil
public static var textToLatexSymbolName: [String: String] {
get {
if self._textToLatexSymbolName == nil {
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] = [
"mathnormal" : .defaultStyle,
@@ -449,7 +449,7 @@ public class MTMathAtomFactory {
]
public static func fontStyleWithName(_ fontName:String) -> MTFontStyle? {
return fontStyles[fontName]
fontStyles[fontName]
}
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 {
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 {
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 {
MTMathAtom(type: .placeholder, value: UnicodeSymbol.whiteSquare)
}
/** Returns a fraction with a placeholder for the numerator and denominator */
public static func placeholderFraction() -> MTFraction {
let frac = MTFraction()
frac.numerator = MTMathList()
@@ -491,6 +492,7 @@ public class MTMathAtomFactory {
return frac
}
/** Returns a square root with a placeholder as the radicand. */
public static func placeholderSquareRoot() -> MTRadical {
let rad = MTRadical()
rad.radicand = MTMathList()
@@ -498,6 +500,7 @@ public class MTMathAtomFactory {
return rad
}
/** Returns a radical with a placeholder as the radicand. */
public static func placeholderRadical() -> MTRadical {
let rad = MTRadical()
rad.radicand = MTMathList()
@@ -507,6 +510,7 @@ public class MTMathAtomFactory {
return rad
}
// MARK: -
/** Gets the atom with the right type for the given character. If an atom
cannot be determined for a given character this returns nil.
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
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. */
public static func atomList(for string: String) -> 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
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`.
@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
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? {
if atom.nucleus.count == 0 {
return nil
}
return sharedInstance.textToLatexSymbolName[atom.nucleus]
guard !atom.nucleus.isEmpty else { return nil }
return Self.textToLatexSymbolName[atom.nucleus]
}
/** 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.
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) {
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
@@ -647,10 +648,8 @@ public class MTMathAtomFactory {
`<` and `langle`) and this function always returns the shorter name.
*/
public static func getDelimiterName(of boundary: MTMathAtom) -> String? {
if boundary.type != .boundary {
return nil
}
return Self.sharedInstance.delimValueToName[boundary.nucleus]
guard boundary.type == .boundary else { return nil }
return Self.delimValueToName[boundary.nucleus]
}
/** 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.
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 {
let num = Self.atomList(for: numStr)
let denom = Self.atomList(for: denomStr)
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 = [
"matrix": [],
"pmatrix": ["(", ")"],
@@ -696,6 +688,13 @@ public class MTMathAtomFactory {
"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? {
let table = MTMathTable(environment: env)

View File

@@ -1,42 +1,76 @@
//
// MTMathList.swift
// MathRenderSwift
//
// 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
// type defines spacing and how it is rendered
/**
The type of atom in a `MTMathList`.
The type of the atom determines how it is rendered, and spacing between the atoms.
*/
public enum MTMathAtomType: Int, CustomStringConvertible, Comparable {
/// A number or text in ordinary format - Ord in TeX
case ordinary = 1
/// A number - Does not exist in TeX
case number
/// A variable (i.e. text in italic format) - Does not exist in TeX
case variable
/// A large operator such as (sin/cos, integral etc.) - Op in TeX
case largeOperator
/// A binary operator - Bin in TeX
case binaryOperator
/// A unary operator - Does not exist in TeX.
case unaryOperator
/// A relation, e.g. = > < etc. - Rel in TeX
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
case ordinary = 1 // number or text
case number // number
case variable // text in italic
case largeOperator // sin/cos, integral
case binaryOperator // \bin
case unaryOperator //
case relation // = < >
case open // open bracket
case close // close bracket
case fraction // \frac
case radical // \sqrt
case punctuation // ,
case placeholder // inner atom
case inner // embedded list
case underline // underlined atom
case overline // overlined atom
case accent // accented atom
// Atoms after this point do not support subscripts or superscripts
// these atoms do not support subscripts/superscripts:
/// A left atom - Left & Right in TeX. We don't need two since we track boundaries separately.
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
// Denotes style changes during randering
/// Denotes style changes during rendering.
case style
case color
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
func isNotBinaryOperator() -> Bool {
@@ -79,9 +113,14 @@ public enum MTMathAtomType: Int, CustomStringConvertible, Comparable {
// comparable support
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 {
/// The default latex rendering style. i.e. variables are italic and numbers are roman.
case defaultStyle = 0,
@@ -107,9 +146,19 @@ public enum MTFontStyle:Int {
// MARK: - MTMathAtom
public class MTMathAtom: NSObject {
/** 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 {
/** The type of the atom. */
public var type = MTMathAtomType.ordinary
/** An optional subscript. */
public var subScript: MTMathList? {
didSet {
if subScript != nil && !self.isScriptAllowed() {
@@ -118,6 +167,7 @@ public class MTMathAtom: NSObject {
}
}
}
/** An optional superscript. */
public var superScript: MTMathList? {
didSet {
if superScript != nil && !self.isScriptAllowed() {
@@ -127,11 +177,19 @@ public class MTMathAtom: NSObject {
}
}
/** The nucleus of the atom. */
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:
/** The font style to be used for the atom. */
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?) {
guard let atom = atom else { return }
@@ -146,11 +204,15 @@ public class MTMathAtom: NSObject {
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) {
self.type = type
self.nucleus = type == .radical ? "" : value
}
/// Returns a copy of `self`.
public func copy() -> MTMathAtom {
switch self.type {
case .largeOperator:
@@ -194,6 +256,7 @@ public class MTMathAtom: NSObject {
return string
}
/// Returns a finalized copy of the atom
public var finalized: MTMathAtom {
let finalized : MTMathAtom = self.copy()
finalized.superScript = finalized.superScript?.finalized
@@ -212,6 +275,7 @@ public class MTMathAtom: NSObject {
return str
}
// Fuse the given atom with this one by combining their nucleii.
func fuse(with atom: MTMathAtom) {
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)");
@@ -240,7 +304,9 @@ public class MTMathAtom: NSObject {
self.subScript = atom.subScript
}
/** Returns true if this atom allows scripts (sub or super). */
func isScriptAllowed() -> Bool { self.type.isScriptAllowed() }
func isNotBinaryOperator() -> Bool { self.type.isNotBinaryOperator() }
}
@@ -254,19 +320,21 @@ func isNotBinaryOperator(_ prevNode:MTMathAtom?) -> Bool {
public class MTFraction: MTMathAtom {
public var hasRule: Bool = true
public var leftDelimiter: String?
public var rightDelimiter: String?
public var leftDelimiter = ""
public var rightDelimiter = ""
public var numerator: MTMathList?
public var denominator: MTMathList?
init(_ frac: MTFraction?) {
super.init(frac)
self.type = .fraction
self.numerator = MTMathList(frac!.numerator)
self.denominator = MTMathList(frac!.denominator)
self.hasRule = frac!.hasRule
self.leftDelimiter = frac!.leftDelimiter
self.rightDelimiter = frac!.rightDelimiter
if let frac = frac {
self.numerator = MTMathList(frac.numerator)
self.denominator = MTMathList(frac.denominator)
self.hasRule = frac.hasRule
self.leftDelimiter = frac.leftDelimiter
self.rightDelimiter = frac.rightDelimiter
}
}
init(hasRule rule:Bool = true) {
@@ -276,28 +344,20 @@ public class MTFraction: MTMathAtom {
}
override public var description: String {
var string = ""
if self.hasRule {
string += "\\atop"
} else {
string += "\\frac"
var string = self.hasRule ? "\\frac" : "\\atop"
if !self.leftDelimiter.isEmpty {
string += "[\(self.leftDelimiter)]"
}
if self.leftDelimiter != nil {
string += "[\(self.leftDelimiter!)]"
if !self.rightDelimiter.isEmpty {
string += "[\(self.rightDelimiter)]"
}
if self.rightDelimiter != nil {
string += "[\(self.rightDelimiter!)]"
}
string += "{\(self.numerator?.description ?? "placeholder")}{\(self.denominator?.description ?? "placeholder")}"
if self.superScript != nil {
string += "^{\(self.superScript!.description)}"
}
if self.subScript != nil {
string += "_{\(self.subScript!.description)}"
}
return string
}
@@ -311,12 +371,13 @@ public class MTFraction: MTMathAtom {
}
// MARK: - MTRadical
/** An atom of type radical (square root). */
public class MTRadical: MTMathAtom {
// Under the roof
/// Denotes the term under the square root sign
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?
init(_ rad:MTRadical?) {
@@ -335,22 +396,18 @@ public class MTRadical: MTMathAtom {
override public var description: String {
var string = "\\sqrt"
if self.degree != nil {
string += "[\(self.degree!.description)]"
}
if self.radicand != nil {
string += "{\(self.radicand?.description ?? "placeholder")}"
}
if self.superScript != nil {
string += "^{\(self.superScript!.description)}"
}
if self.subScript != nil {
string += "_{\(self.subScript!.description)}"
}
return string
}
@@ -363,8 +420,13 @@ public class MTRadical: MTMathAtom {
}
// MARK: - MTLargeOperator
/** A `MTMathAtom` of type `kMTMathAtom.largeOperator`. */
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
init(_ op:MTLargeOperator?) {
@@ -380,20 +442,25 @@ public class MTLargeOperator: MTMathAtom {
}
// 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 {
/// The inner math list
public var innerList: MTMathList?
/// The left boundary atom. This must be a node of type kMTMathAtomBoundary
public var leftBoundary: MTMathAtom? {
didSet {
if leftBoundary != nil && leftBoundary!.type != .boundary {
if let left = leftBoundary, left.type != .boundary {
leftBoundary = nil
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? {
didSet {
if rightBoundary != nil && rightBoundary!.type != .boundary {
if let right = rightBoundary, right.type != .boundary {
rightBoundary = nil
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 {
var string = "\\inner"
if self.leftBoundary != nil {
string += "[\(self.leftBoundary!.nucleus)]"
}
string += "{\(self.innerList!.description)}"
if self.rightBoundary != nil {
string += "[\(self.rightBoundary!.nucleus)]"
}
if self.superScript != nil {
string += "^{\(self.superScript!.description)}"
}
if self.subScript != nil {
string += "_{\(self.subScript!.description)}"
}
return string
}
@@ -443,7 +506,7 @@ public class MTInner: MTMathAtom {
}
// MARK: - MTOverLIne
/** An atom with a line over the contained math list. */
public class MTOverLine: MTMathAtom {
public var innerList: MTMathList?
@@ -466,7 +529,7 @@ public class MTOverLine: MTMathAtom {
}
// MARK: - MTUnderLine
/** An atom with a line under the contained math list. */
public class MTUnderLine: MTMathAtom {
public var innerList: MTMathList?
@@ -513,10 +576,16 @@ public class MTAccent: MTMathAtom {
}
// 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 {
/** The amount of space represented by this object in mu units. */
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?) {
super.init(space)
self.type = .space
@@ -530,11 +599,17 @@ public class MTMathSpace: MTMathAtom {
}
}
/**
Styling of a line of math
*/
public enum MTLineStyle:Int, Comparable {
/// Display style
case display
/// Text style (inline)
case text
/// Script style (for sub/super scripts)
case script
/// Script script style (for scripts of scripts)
case scriptOfScript
public func inc() -> MTLineStyle {
@@ -548,7 +623,10 @@ public enum MTLineStyle:Int, Comparable {
}
// 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 var style: MTLineStyle = .display
@@ -566,7 +644,10 @@ public class MTMathStyle: MTMathAtom {
}
// 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 var colorString:String=""
public var innerList:MTMathList?
@@ -595,9 +676,12 @@ public class MTMathColor: MTMathAtom {
}
// 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 var colorString:String=""
public var colorString=""
public var innerList:MTMathList?
init(_ cbox: MTMathColorbox?) {
@@ -623,6 +707,9 @@ public class MTMathColorbox: MTMathAtom {
}
}
/**
Alignment for a column of MTMathTable
*/
public enum MTColumnAlignment {
case left
case center
@@ -630,13 +717,28 @@ public enum MTColumnAlignment {
}
// 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 {
/// The alignment for each column (left, right, center). The default alignment
/// for a column (if not set) is center.
public var alignments = [MTColumnAlignment]()
/// The cells in the table as a two dimensional array.
public var cells = [[MTMathList]]()
public var environment: String?
/// The name of the environment that this table denotes.
public var environment = ""
/// Spacing between each column in mu units.
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
override public var finalized: MTMathAtom {
@@ -652,7 +754,7 @@ public class MTMathTable: MTMathAtom {
init(environment: String?) {
super.init()
self.type = .table
self.environment = environment
self.environment = environment ?? ""
}
init(_ table:MTMathTable) {
@@ -678,6 +780,7 @@ public class MTMathTable: MTMathAtom {
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) {
if self.cells.count <= row {
for _ in self.cells.count...row {
@@ -693,6 +796,8 @@ public class MTMathTable: MTMathAtom {
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) {
if self.alignments.count <= col {
for _ in self.alignments.count...col {
@@ -703,6 +808,8 @@ public class MTMathTable: MTMathAtom {
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 {
if self.alignments.count <= col {
return MTColumnAlignment.center
@@ -724,12 +831,21 @@ public class MTMathTable: MTMathAtom {
// MARK: - MTMathList
// represent list of math objects
extension MTMathList {
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 }
}
/** 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 {
init?(_ list:MTMathList?) {
@@ -739,8 +855,12 @@ public class MTMathList : NSObject {
}
}
/// A list of MathAtoms
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 {
let finalizedList = MTMathList()
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()
}
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 }
if self.isAtomAllowed(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)
guard let atom = atom 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 }
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 {
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)
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.upperBound)
self.atoms.removeSubrange(range)

View File

@@ -1,8 +1,9 @@
//
// MTMathListBuilder.swift
// MathRenderSwift
//
// 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
@@ -23,10 +24,9 @@ struct MTEnvProperties {
}
/**
@typedef case s
@brief The error encountered when parsing a LaTeX string.
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.
*/
enum MTParseErrors:Int {
@@ -62,7 +62,10 @@ enum MTParseErrors:Int {
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 currentCharIndex: String.Index
var currentInnerAtom: MTInner?
@@ -73,8 +76,42 @@ public class MTMathListBuilder {
/** Contains any error that occurred during parsing. */
var error:NSError?
// MARK: - Character-handling routines
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] = [
3 : ",",
4 : ">",
@@ -99,7 +136,10 @@ public class MTMathListBuilder {
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)
if self.hasCharacters && error == nil {
self.setError(.mismatchBraces, message: "Mismatched braces: \(self.string)")
@@ -111,13 +151,20 @@ public class MTMathListBuilder {
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? {
let builder = MTMathListBuilder(string: string)
var builder = MTMathListBuilder(string: string)
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? {
let builder = MTMathListBuilder(string: string)
var builder = MTMathListBuilder(string: string)
let output = builder.build()
if builder.error != nil {
error = builder.error
@@ -126,227 +173,29 @@ public class MTMathListBuilder {
return output
}
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 {
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. "
public mutating func buildInternal(_ oneCharOnly: Bool) -> MTMathList? {
self.buildInternal(oneCharOnly, stopChar: nil)
}
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 {
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? {
public mutating func buildInternal(_ oneCharOnly: Bool, stopChar stop: Character?) -> MTMathList? {
let list = MTMathList()
assert(!(oneCharOnly && stop != nil), "Cannot set both oneCharOnly and stopChar.")
var prevAtom: MTMathAtom? = nil
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
let char = self.getNextCharacter()
if oneCharOnly {
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()
return list
}
}
// If there is a stop character, keep scanning 'til we find it
if stop != nil && char == stop! {
return list
}
@@ -359,7 +208,8 @@ public class MTMathListBuilder {
prevAtom = MTMathAtom(type: .ordinary, value: "")
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)
continue
} else if char == "_" {
@@ -370,6 +220,8 @@ public class MTMathListBuilder {
prevAtom = MTMathAtom(type: .ordinary, value: "")
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)
continue
} else if char == "{" {
@@ -382,6 +234,7 @@ public class MTMathListBuilder {
}
continue
} else if char == "}" {
// \ means a command
assert(!oneCharOnly, "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
@@ -428,18 +281,22 @@ public class MTMathListBuilder {
return nil
}
} else if char == "&" {
// used for column separation in tables
assert(!oneCharOnly, "This should have been handled before")
if self.currentEnv != nil {
return list
} else {
// Create a new table with the current list and a default env
let table = self.buildTable(env: nil, firstList: list, isRow: false)
return MTMathList(atom: table!)
}
} else if spacesAllowed && char == " " {
// If spaces are allowed then spaces do not need escaping with a \ before being used.
atom = MTMathAtomFactory.atom(forLatexSymbol: " ")
} else {
atom = MTMathAtomFactory.atom(forCharacter: char)
if atom == nil {
// Not a recognized character
continue
}
}
@@ -453,7 +310,6 @@ public class MTMathListBuilder {
return list
}
}
if stop != nil {
if stop == "}" {
// We did not find a corresponding closing brace.
@@ -467,7 +323,194 @@ public class MTMathListBuilder {
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) {
return atom
}
@@ -557,7 +600,7 @@ public class MTMathListBuilder {
}
}
func readColor() -> String? {
mutating func readColor() -> String? {
if !self.expectCharacter("{") {
// We didn't find an opening brace, so no env found.
self.setError(.characterNotFound, message:"Missing {")
@@ -588,7 +631,7 @@ public class MTMathListBuilder {
return mutable;
}
func skipSpaces() {
mutating func skipSpaces() {
while self.hasCharacters {
let ch = self.getNextCharacter().utf32Char
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 currentInnerAtom == nil {
let errorMessage = "Missing \\left";
@@ -676,7 +719,7 @@ public class MTMathListBuilder {
}
// 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 atom?.type != .largeOperator {
let errorMessage = "Limits can only be applied to an operator."
@@ -699,14 +742,14 @@ public class MTMathListBuilder {
return false
}
func setError(_ code:MTParseErrors, message:String) {
mutating func setError(_ code:MTParseErrors, message:String) {
// Only record the first error.
if error == nil {
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) {
return atom
}
@@ -786,7 +829,7 @@ public class MTMathListBuilder {
}
}
func readEnvironment() -> String? {
mutating func readEnvironment() -> String? {
if !self.expectCharacter("{") {
// We didn't find an opening brace, so no env found.
self.setError(.characterNotFound, message: "Missing {")
@@ -808,24 +851,7 @@ public class MTMathListBuilder {
assert(ch >= "\u{21}" && ch <= "\u{7E}", "Expected non-space character \(ch)")
}
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
}
func buildTable(env: String?, firstList: MTMathList?, isRow: Bool) -> MTMathAtom? {
mutating func buildTable(env: String?, firstList: MTMathList?, isRow: Bool) -> MTMathAtom? {
// Save the current env till an new one gets built.
let oldEnv = self.currentEnv
@@ -876,7 +902,7 @@ public class MTMathListBuilder {
return table
}
func getBoundaryAtom(_ delimiterType: String) -> MTMathAtom? {
mutating func getBoundaryAtom(_ delimiterType: String) -> MTMathAtom? {
let delim = self.readDelimiter()
if delim == nil {
let errorMessage = "Missing delimiter for \\\(delimiterType)"
@@ -892,7 +918,7 @@ public class MTMathListBuilder {
return boundary
}
func readDelimiter() -> String? {
mutating func readDelimiter() -> String? {
self.skipSpaces()
while self.hasCharacters {
let char = self.getNextCharacter()
@@ -910,7 +936,7 @@ public class MTMathListBuilder {
return nil
}
func readCommand() -> String {
mutating func readCommand() -> String {
let singleChars = "{}$#%_| ,>;!\\"
if self.hasCharacters {
let char = self.getNextCharacter()
@@ -923,7 +949,7 @@ public class MTMathListBuilder {
return self.readString()
}
func readString() -> String {
mutating func readString() -> String {
// a string of all upper and lower case characters.
var output = ""
while self.hasCharacters {

View File

@@ -1,8 +1,9 @@
//
// MTMathListDisplay.swift
// MathRenderSwift
//
// 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
@@ -26,6 +27,7 @@ func isIos6Supported() -> Bool {
return MTDisplay.supported
}
// The Downshift protocol allows an MTDisplay to be shifted down by a given amount.
protocol DownShift {
var shiftDown:CGFloat { set get }
}
@@ -52,7 +54,7 @@ public class MTDisplay:NSObject {
/// Gets the bounding rectangle for the MTDisplay
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.
@@ -81,17 +83,17 @@ public class MTDisplay:NSObject {
#endif
/// 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
var descent:CGFloat = 0
public var descent:CGFloat = 0
/// 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.
var position=CGPoint.zero
var position = CGPoint.zero
/// 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.
var hasScript:Bool = false
public var hasScript:Bool = false
/// The text color for this display
var textColor: MTColor?
/// 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 {
var shiftDown: CGFloat = 0
@@ -110,10 +113,10 @@ class MTDisplayDS : MTDisplay, DownShift {
// MARK: - MTCTLineDisplay
/// A rendering of a single CTLine as an MTDisplay
class MTCTLineDisplay : MTDisplay {
public class MTCTLineDisplay : MTDisplay {
/// 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 display. So set only when
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
var atoms = [MTMathAtom]()
public fileprivate(set) var atoms = [MTMathAtom]()
init(withString attrString:NSAttributedString?, position:CGPoint, range:NSRange, font:MTFont?, atoms:[MTMathAtom]) {
super.init()
@@ -197,10 +200,9 @@ class MTCTLineDisplay : MTDisplay {
public class MTMathListDisplay : MTDisplay {
/**
@typedef MTLinePosition
@brief The type of position for a line, i.e. subscript/superscript or regular.
The type of position for a line, i.e. subscript/superscript or regular.
*/
enum LinePosition : Int {
public enum LinePosition : Int {
/// Regular
case regular
/// Positioned at a subscript
@@ -210,13 +212,13 @@ public class MTMathListDisplay : MTDisplay {
}
/// 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
/// 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
/// regular list this is NSNotFound
var index: Int = 0
public var index: Int = 0
init(withDisplays displays:[MTDisplay], range:NSRange) {
super.init()
@@ -287,23 +289,19 @@ public class MTMathListDisplay : MTDisplay {
// MARK: - MTFractionDisplay
/// 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
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
to the parent is not treated as a sub-display.
*/
var denominator:MTMathListDisplay?
public fileprivate(set) var denominator:MTMathListDisplay?
var numeratorUp:CGFloat=0 {
didSet { self.updateNumeratorPosition() }
}
var denominatorDown:CGFloat=0 {
didSet { self.updateDenominatorPosition() }
}
var numeratorUp:CGFloat=0 { didSet { self.updateNumeratorPosition() } }
var denominatorDown:CGFloat=0 { didSet { self.updateDenominatorPosition() } }
var linePosition: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)")
}
override var ascent:CGFloat {
override public var ascent:CGFloat {
set { super.ascent = newValue }
get { numerator!.ascent + self.numeratorUp }
}
override var descent:CGFloat {
override public var descent:CGFloat {
set { super.descent = newValue }
get { denominator!.descent + self.denominatorDown }
}
override var width:CGFloat {
override public var width:CGFloat {
set { super.width = newValue }
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
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
to the parent is not treated as a sub-display.
*/
var degree:MTMathListDisplay?
public fileprivate(set) var degree:MTMathListDisplay?
override var position: CGPoint {
set {
@@ -536,21 +534,13 @@ class MTGlyphDisplay : MTDisplayDS {
}
override var ascent:CGFloat {
set {
super.ascent = newValue
}
get {
return super.ascent - self.shiftDown;
}
set { super.ascent = newValue }
get { super.ascent - self.shiftDown }
}
override var descent:CGFloat {
set {
super.descent = newValue
}
get {
return super.descent + self.shiftDown;
}
set { super.descent = newValue }
get { super.descent + self.shiftDown }
}
}
@@ -593,21 +583,13 @@ class MTGlyphConstructionDisplay:MTDisplayDS {
}
override var ascent:CGFloat {
set {
super.ascent = newValue
}
get {
return super.ascent - self.shiftDown;
}
set { super.ascent = newValue }
get { super.ascent - self.shiftDown }
}
override var descent:CGFloat {
set {
super.descent = newValue
}
get {
return super.descent + self.shiftDown;
}
set { super.descent = newValue }
get { super.descent + self.shiftDown }
}
}
@@ -627,16 +609,8 @@ class MTLargeOpLimitsDisplay : MTDisplay {
var lowerLimit:MTMathListDisplay?
var limitShift:CGFloat=0
var upperLimitGap:CGFloat=0 {
didSet {
self.updateUpperLimitPosition()
}
}
var lowerLimitGap:CGFloat=0 {
didSet {
self.updateUpperLimitPosition()
}
}
var upperLimitGap:CGFloat=0 { didSet { self.updateUpperLimitPosition() } }
var lowerLimitGap:CGFloat=0 { didSet { self.updateLowerLimitPosition() } }
var extraPadding:CGFloat=0
var nucleus:MTDisplay?
@@ -658,9 +632,7 @@ class MTLargeOpLimitsDisplay : MTDisplay {
}
override var ascent:CGFloat {
set {
super.ascent = newValue
}
set { super.ascent = newValue }
get {
if self.upperLimit != nil {
return nucleus!.ascent + extraPadding + self.upperLimit!.ascent + upperLimitGap + self.upperLimit!.descent
@@ -671,9 +643,7 @@ class MTLargeOpLimitsDisplay : MTDisplay {
}
override var descent:CGFloat {
set {
super.descent = newValue
}
set { super.descent = newValue }
get {
if self.lowerLimit != nil {
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.
// 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
/**
* 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 {
/**
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 {
/// 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 superScript
case subScript
/// The subindex indexes into the superscript.
case superscript
/// The subindex indexes into the subscript
case ssubscript
/// The subindex indexes into the numerator (only valid for fractions)
case numerator
/// The subindex indexes into the denominator (only valid for fractions)
case denominator
/// The subindex indexes into the radicand (only valid for radicals)
case radicand
/// The subindex indexes into the degree (only valid for radicals)
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? {
if self.subIndexType == .none {
if self.atomIndex > 0 {
@@ -50,6 +80,7 @@ public class MTMathListIndex {
return nil
}
/// Returns the next index.
func nextIndex() -> MTMathListIndex {
if self.subIndexType == .none {
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
* line.
*/
func isBeginningOfLine() -> Bool {
return self.finalIndex == 0
}
func isBeginningOfLine() -> Bool { self.finalIndex == 0 }
func isAtSameLevel(with index: MTMathListIndex?) -> Bool {
if self.subIndexType != index?.subIndexType {

View File

@@ -1,8 +1,9 @@
//
// MTMathUILabel.swift
// MathRenderSwift
// Created by Mike Griebling on 2022-12-31.
// 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

View File

@@ -1,15 +1,16 @@
//
// MTTypesetter.swift
// MathRenderSwift
// Created by Mike Griebling on 2022-12-31.
// 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 CoreGraphics
import CoreText
// MARK: - - Inter Element Spacing
// MARK: - Inter Element Spacing
enum InterElementSpaceType : Int {
case invalid = -1
@@ -76,7 +77,7 @@ func getInterElementSpaceArrayIndexForType(_ type:MTMathAtomType, row:Bool) -> I
}
}
// MARK: - - Italics
// MARK: - Italics
// mathit
func getItalicized(_ ch:Character) -> UTF32Char {
var unicode = ch.utf32Char
@@ -235,7 +236,7 @@ func getSansSerif(_ ch:Character) -> UTF32Char {
// mathfrak
func getFraktur(_ ch:Character) -> UTF32Char {
// Fraktur has exceptions:
switch(ch) {
switch ch {
case "C":
return 0x212D; // C Fraktur
case "H":
@@ -337,7 +338,7 @@ func getBboxDetails(_ bbox:CGRect, ascent:inout CGFloat, descent:inout CGFloat)
descent = max(0, 0 - CGRectGetMinY(bbox))
}
// MARK: - - MTTypesetter
// MARK: - MTTypesetter
class MTTypesetter {
var font:MTFont!
@@ -879,7 +880,7 @@ class MTTypesetter {
currentPosition.x += max(superScript!.width + delta, ssubscript!.width) + styleFont.mathTable!.spaceAfterScript;
}
// MARK: - - Fractions
// MARK: - Fractions
func numeratorShiftUp(_ hasRule:Bool) -> CGFloat {
if hasRule {
@@ -1002,7 +1003,7 @@ class MTTypesetter {
display.denominatorDown = denominatorShiftDown;
display.lineThickness = barThickness;
display.linePosition = barLocation;
if frac!.leftDelimiter == nil && frac!.rightDelimiter == nil {
if frac!.leftDelimiter.isEmpty && frac!.rightDelimiter.isEmpty {
return display
} else {
return self.addDelimitersToFractionDisplay(display, forFraction:frac)
@@ -1010,13 +1011,13 @@ class MTTypesetter {
}
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]()
let glyphHeight = self.fractionDelimiterHeight
var position = CGPoint.zero
if !frac!.leftDelimiter!.isEmpty {
let leftGlyph = self.findGlyphForBoundary(frac!.leftDelimiter!, withHeight:glyphHeight())
if !frac!.leftDelimiter.isEmpty {
let leftGlyph = self.findGlyphForBoundary(frac!.leftDelimiter, withHeight:glyphHeight())
leftGlyph!.position = position
position.x += leftGlyph!.width
innerElements.append(leftGlyph!)
@@ -1026,8 +1027,8 @@ class MTTypesetter {
position.x += display!.width
innerElements.append(display!)
if !frac!.rightDelimiter!.isEmpty {
let rightGlyph = self.findGlyphForBoundary(frac!.rightDelimiter!, withHeight:glyphHeight())
if !frac!.rightDelimiter.isEmpty {
let rightGlyph = self.findGlyphForBoundary(frac!.rightDelimiter, withHeight:glyphHeight())
rightGlyph!.position = position
position.x += rightGlyph!.width
innerElements.append(rightGlyph!)

View File

@@ -1,8 +1,9 @@
//
// MTUnicode.swift
// MathRenderSwift
//
// 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

View File

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