Updated comments and minor fixes.
This commit is contained in:
31
README.md
31
README.md
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) }
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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! }
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
/// 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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!)
|
||||
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user