Refactor model
This commit is contained in:
@@ -18,9 +18,9 @@ extension Math {
|
||||
|
||||
private let font: Font
|
||||
private let unitsPerEm: UInt
|
||||
private let table: Table
|
||||
private let table: FontTable
|
||||
|
||||
init(font: Font, unitsPerEm: UInt, table: Table) {
|
||||
init(font: Font, unitsPerEm: UInt, table: FontTable) {
|
||||
self.font = font
|
||||
self.unitsPerEm = unitsPerEm
|
||||
self.table = table
|
||||
|
||||
@@ -8,7 +8,7 @@ extension Math {
|
||||
|
||||
private struct Cache {
|
||||
var graphicsFonts: [Font.Name: CGFont] = [:]
|
||||
var tables: [Font.Name: Table] = [:]
|
||||
var tables: [Font.Name: FontTable] = [:]
|
||||
let fonts = NSCache<KeyBox<Font>, CTFont>()
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ extension Math {
|
||||
}
|
||||
}
|
||||
|
||||
func table(named name: Font.Name) -> Table? {
|
||||
func table(named name: Font.Name) -> FontTable? {
|
||||
cache.withValue { cache in
|
||||
if let table = cache.tables[name] {
|
||||
return table
|
||||
@@ -67,8 +67,8 @@ extension Math {
|
||||
private func registerGraphicsFont(
|
||||
named name: Font.Name,
|
||||
cache: inout Cache
|
||||
) -> (CGFont, Table)? {
|
||||
guard let graphicsFont = CGFont.named(name), let table = Table.named(name) else {
|
||||
) -> (CGFont, FontTable)? {
|
||||
guard let graphicsFont = CGFont.named(name), let table = FontTable.named(name) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -99,8 +99,8 @@ extension CGFont {
|
||||
}
|
||||
}
|
||||
|
||||
extension Math.Table {
|
||||
fileprivate static func named(_ name: Math.Font.Name) -> Math.Table? {
|
||||
extension Math.FontTable {
|
||||
fileprivate static func named(_ name: Math.Font.Name) -> Math.FontTable? {
|
||||
guard
|
||||
let bundleURL = Bundle.module.url(forResource: "mathFonts", withExtension: "bundle"),
|
||||
let url = Bundle(url: bundleURL)?.url(forResource: name.rawValue, withExtension: "plist"),
|
||||
@@ -109,6 +109,6 @@ extension Math.Table {
|
||||
return nil
|
||||
}
|
||||
|
||||
return try? PropertyListDecoder().decode(Math.Table.self, from: data)
|
||||
return try? PropertyListDecoder().decode(Math.FontTable.self, from: data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
struct Table: Codable, Sendable {
|
||||
struct FontTable: Codable, Sendable {
|
||||
struct Assembly: Codable, Sendable {
|
||||
struct Part: Codable, Sendable {
|
||||
let advance: Int
|
||||
@@ -19,8 +19,8 @@ final class ReadWriteLockIsolated<Value>: @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
func withValue<T: Sendable>(
|
||||
_ operation: @Sendable (inout Value) throws -> T
|
||||
func withValue<T>(
|
||||
_ operation: (inout Value) throws -> T
|
||||
) rethrows -> T {
|
||||
try self.lock.sync {
|
||||
var value = self._value
|
||||
|
||||
858
Sources/SwiftUIMath/Internal/Model/AtomFactory.swift
Normal file
858
Sources/SwiftUIMath/Internal/Model/AtomFactory.swift
Normal file
@@ -0,0 +1,858 @@
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
enum AtomFactory {
|
||||
static let aliases: [String: String] = [
|
||||
"lnot": "neg",
|
||||
"land": "wedge",
|
||||
"lor": "vee",
|
||||
"ne": "neq",
|
||||
"le": "leq",
|
||||
"ge": "geq",
|
||||
"lbrace": "{",
|
||||
"rbrace": "}",
|
||||
"Vert": "|",
|
||||
"gets": "leftarrow",
|
||||
"to": "rightarrow",
|
||||
"iff": "Longleftrightarrow",
|
||||
"AA": "angstrom",
|
||||
]
|
||||
|
||||
static let delimiters: [String: String] = [
|
||||
".": "", // . means no delimiter
|
||||
"(": "(",
|
||||
")": ")",
|
||||
"[": "[",
|
||||
"]": "]",
|
||||
"<": "\u{2329}",
|
||||
">": "\u{232A}",
|
||||
"/": "/",
|
||||
"\\": "\\",
|
||||
"|": "|",
|
||||
"lgroup": "\u{27EE}",
|
||||
"rgroup": "\u{27EF}",
|
||||
"||": "\u{2016}",
|
||||
"Vert": "\u{2016}",
|
||||
"vert": "|",
|
||||
"uparrow": "\u{2191}",
|
||||
"downarrow": "\u{2193}",
|
||||
"updownarrow": "\u{2195}",
|
||||
"Uparrow": "\u{21D1}",
|
||||
"Downarrow": "\u{21D3}",
|
||||
"Updownarrow": "\u{21D5}",
|
||||
"backslash": "\\",
|
||||
"rangle": "\u{232A}",
|
||||
"langle": "\u{2329}",
|
||||
"rbrace": "}",
|
||||
"}": "}",
|
||||
"{": "{",
|
||||
"lbrace": "{",
|
||||
"lceil": "\u{2308}",
|
||||
"rceil": "\u{2309}",
|
||||
"lfloor": "\u{230A}",
|
||||
"rfloor": "\u{230B}",
|
||||
]
|
||||
|
||||
static let delimValueToName: [String: String] = {
|
||||
var output = [String: String]()
|
||||
for (key, value) in delimiters {
|
||||
if let existingValue = output[value] {
|
||||
if key.count > existingValue.count {
|
||||
continue
|
||||
} else if key.count == existingValue.count {
|
||||
if key.compare(existingValue) == .orderedDescending {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
output[value] = key
|
||||
}
|
||||
return output
|
||||
}()
|
||||
|
||||
static let accents: [String: String] = [
|
||||
"grave": "\u{0300}",
|
||||
"acute": "\u{0301}",
|
||||
"hat": "\u{0302}", // In our implementation hat and widehat behave the same.
|
||||
"tilde": "\u{0303}", // In our implementation tilde and widetilde behave the same.
|
||||
"bar": "\u{0304}",
|
||||
"breve": "\u{0306}",
|
||||
"dot": "\u{0307}",
|
||||
"ddot": "\u{0308}",
|
||||
"check": "\u{030C}",
|
||||
"vec": "\u{20D7}",
|
||||
"widehat": "\u{0302}",
|
||||
"widetilde": "\u{0303}",
|
||||
]
|
||||
|
||||
static let accentValueToName: [String: String] = {
|
||||
var output = [String: String]()
|
||||
for (key, value) in accents {
|
||||
if let existingValue = output[value] {
|
||||
if key.count > existingValue.count {
|
||||
continue
|
||||
} else if key.count == existingValue.count {
|
||||
if key.compare(existingValue) == .orderedDescending {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
output[value] = key
|
||||
}
|
||||
return output
|
||||
}()
|
||||
|
||||
static var supportedLatexSymbolNames: [String] {
|
||||
supportedLatexSymbols.withValue { Array($0.keys) }
|
||||
}
|
||||
|
||||
private static let supportedLatexSymbols = ReadWriteLockIsolated<[String: Atom]>([
|
||||
"square": placeholder(),
|
||||
|
||||
// Greek characters
|
||||
"alpha": Atom(type: .variable, nucleus: "\u{03B1}"),
|
||||
"beta": Atom(type: .variable, nucleus: "\u{03B2}"),
|
||||
"gamma": Atom(type: .variable, nucleus: "\u{03B3}"),
|
||||
"delta": Atom(type: .variable, nucleus: "\u{03B4}"),
|
||||
"varepsilon": Atom(type: .variable, nucleus: "\u{03B5}"),
|
||||
"zeta": Atom(type: .variable, nucleus: "\u{03B6}"),
|
||||
"eta": Atom(type: .variable, nucleus: "\u{03B7}"),
|
||||
"theta": Atom(type: .variable, nucleus: "\u{03B8}"),
|
||||
"iota": Atom(type: .variable, nucleus: "\u{03B9}"),
|
||||
"kappa": Atom(type: .variable, nucleus: "\u{03BA}"),
|
||||
"lambda": Atom(type: .variable, nucleus: "\u{03BB}"),
|
||||
"mu": Atom(type: .variable, nucleus: "\u{03BC}"),
|
||||
"nu": Atom(type: .variable, nucleus: "\u{03BD}"),
|
||||
"xi": Atom(type: .variable, nucleus: "\u{03BE}"),
|
||||
"omicron": Atom(type: .variable, nucleus: "\u{03BF}"),
|
||||
"pi": Atom(type: .variable, nucleus: "\u{03C0}"),
|
||||
"rho": Atom(type: .variable, nucleus: "\u{03C1}"),
|
||||
"varsigma": Atom(type: .variable, nucleus: "\u{03C1}"),
|
||||
"sigma": Atom(type: .variable, nucleus: "\u{03C3}"),
|
||||
"tau": Atom(type: .variable, nucleus: "\u{03C4}"),
|
||||
"upsilon": Atom(type: .variable, nucleus: "\u{03C5}"),
|
||||
"varphi": Atom(type: .variable, nucleus: "\u{03C6}"),
|
||||
"chi": Atom(type: .variable, nucleus: "\u{03C7}"),
|
||||
"psi": Atom(type: .variable, nucleus: "\u{03C8}"),
|
||||
"omega": Atom(type: .variable, nucleus: "\u{03C9}"),
|
||||
// We mark the following greek chars as ordinary so that we don't try
|
||||
// to automatically italicize them as we do with variables.
|
||||
// These characters fall outside the rules of italicization that we have defined.
|
||||
"epsilon": Atom(type: .ordinary, nucleus: "\u{1D716}"),
|
||||
"vartheta": Atom(type: .ordinary, nucleus: "\u{1D717}"),
|
||||
"phi": Atom(type: .ordinary, nucleus: "\u{1D719}"),
|
||||
"varrho": Atom(type: .ordinary, nucleus: "\u{1D71A}"),
|
||||
"varpi": Atom(type: .ordinary, nucleus: "\u{1D71B}"),
|
||||
|
||||
// Capital greek characters
|
||||
"Gamma": Atom(type: .variable, nucleus: "\u{0393}"),
|
||||
"Delta": Atom(type: .variable, nucleus: "\u{0394}"),
|
||||
"Theta": Atom(type: .variable, nucleus: "\u{0398}"),
|
||||
"Lambda": Atom(type: .variable, nucleus: "\u{039B}"),
|
||||
"Xi": Atom(type: .variable, nucleus: "\u{039E}"),
|
||||
"Pi": Atom(type: .variable, nucleus: "\u{03A0}"),
|
||||
"Sigma": Atom(type: .variable, nucleus: "\u{03A3}"),
|
||||
"Upsilon": Atom(type: .variable, nucleus: "\u{03A5}"),
|
||||
"Phi": Atom(type: .variable, nucleus: "\u{03A6}"),
|
||||
"Psi": Atom(type: .variable, nucleus: "\u{03A8}"),
|
||||
"Omega": Atom(type: .variable, nucleus: "\u{03A9}"),
|
||||
|
||||
// Open
|
||||
"lceil": Atom(type: .open, nucleus: "\u{2308}"),
|
||||
"lfloor": Atom(type: .open, nucleus: "\u{230A}"),
|
||||
"langle": Atom(type: .open, nucleus: "\u{27E8}"),
|
||||
"lgroup": Atom(type: .open, nucleus: "\u{27EE}"),
|
||||
|
||||
// Close
|
||||
"rceil": Atom(type: .close, nucleus: "\u{2309}"),
|
||||
"rfloor": Atom(type: .close, nucleus: "\u{230B}"),
|
||||
"rangle": Atom(type: .close, nucleus: "\u{27E9}"),
|
||||
"rgroup": Atom(type: .close, nucleus: "\u{27EF}"),
|
||||
|
||||
// Arrows
|
||||
"leftarrow": Atom(type: .relation, nucleus: "\u{2190}"),
|
||||
"uparrow": Atom(type: .relation, nucleus: "\u{2191}"),
|
||||
"rightarrow": Atom(type: .relation, nucleus: "\u{2192}"),
|
||||
"downarrow": Atom(type: .relation, nucleus: "\u{2193}"),
|
||||
"leftrightarrow": Atom(type: .relation, nucleus: "\u{2194}"),
|
||||
"updownarrow": Atom(type: .relation, nucleus: "\u{2195}"),
|
||||
"nwarrow": Atom(type: .relation, nucleus: "\u{2196}"),
|
||||
"nearrow": Atom(type: .relation, nucleus: "\u{2197}"),
|
||||
"searrow": Atom(type: .relation, nucleus: "\u{2198}"),
|
||||
"swarrow": Atom(type: .relation, nucleus: "\u{2199}"),
|
||||
"mapsto": Atom(type: .relation, nucleus: "\u{21A6}"),
|
||||
"Leftarrow": Atom(type: .relation, nucleus: "\u{21D0}"),
|
||||
"Uparrow": Atom(type: .relation, nucleus: "\u{21D1}"),
|
||||
"Rightarrow": Atom(type: .relation, nucleus: "\u{21D2}"),
|
||||
"Downarrow": Atom(type: .relation, nucleus: "\u{21D3}"),
|
||||
"Leftrightarrow": Atom(type: .relation, nucleus: "\u{21D4}"),
|
||||
"Updownarrow": Atom(type: .relation, nucleus: "\u{21D5}"),
|
||||
"longleftarrow": Atom(type: .relation, nucleus: "\u{27F5}"),
|
||||
"longrightarrow": Atom(type: .relation, nucleus: "\u{27F6}"),
|
||||
"longleftrightarrow": Atom(type: .relation, nucleus: "\u{27F7}"),
|
||||
"Longleftarrow": Atom(type: .relation, nucleus: "\u{27F8}"),
|
||||
"Longrightarrow": Atom(type: .relation, nucleus: "\u{27F9}"),
|
||||
"Longleftrightarrow": Atom(type: .relation, nucleus: "\u{27FA}"),
|
||||
|
||||
// Relations
|
||||
"leq": Atom(type: .relation, nucleus: .lessEqual),
|
||||
"geq": Atom(type: .relation, nucleus: .greaterEqual),
|
||||
"neq": Atom(type: .relation, nucleus: .notEqual),
|
||||
"in": Atom(type: .relation, nucleus: "\u{2208}"),
|
||||
"notin": Atom(type: .relation, nucleus: "\u{2209}"),
|
||||
"ni": Atom(type: .relation, nucleus: "\u{220B}"),
|
||||
"propto": Atom(type: .relation, nucleus: "\u{221D}"),
|
||||
"mid": Atom(type: .relation, nucleus: "\u{2223}"),
|
||||
"parallel": Atom(type: .relation, nucleus: "\u{2225}"),
|
||||
"sim": Atom(type: .relation, nucleus: "\u{223C}"),
|
||||
"simeq": Atom(type: .relation, nucleus: "\u{2243}"),
|
||||
"cong": Atom(type: .relation, nucleus: "\u{2245}"),
|
||||
"approx": Atom(type: .relation, nucleus: "\u{2248}"),
|
||||
"asymp": Atom(type: .relation, nucleus: "\u{224D}"),
|
||||
"doteq": Atom(type: .relation, nucleus: "\u{2250}"),
|
||||
"equiv": Atom(type: .relation, nucleus: "\u{2261}"),
|
||||
"gg": Atom(type: .relation, nucleus: "\u{226B}"),
|
||||
"ll": Atom(type: .relation, nucleus: "\u{226A}"),
|
||||
"prec": Atom(type: .relation, nucleus: "\u{227A}"),
|
||||
"succ": Atom(type: .relation, nucleus: "\u{227B}"),
|
||||
"subset": Atom(type: .relation, nucleus: "\u{2282}"),
|
||||
"supset": Atom(type: .relation, nucleus: "\u{2283}"),
|
||||
"subseteq": Atom(type: .relation, nucleus: "\u{2286}"),
|
||||
"supseteq": Atom(type: .relation, nucleus: "\u{2287}"),
|
||||
"sqsubset": Atom(type: .relation, nucleus: "\u{228F}"),
|
||||
"sqsupset": Atom(type: .relation, nucleus: "\u{2290}"),
|
||||
"sqsubseteq": Atom(type: .relation, nucleus: "\u{2291}"),
|
||||
"sqsupseteq": Atom(type: .relation, nucleus: "\u{2292}"),
|
||||
"models": Atom(type: .relation, nucleus: "\u{22A7}"),
|
||||
"perp": Atom(type: .relation, nucleus: "\u{27C2}"),
|
||||
"implies": Atom(type: .relation, nucleus: "\u{27F9}"),
|
||||
|
||||
// operators
|
||||
"times": times(),
|
||||
"div": divide(),
|
||||
"pm": Atom(type: .binaryOperator, nucleus: "\u{00B1}"),
|
||||
"dagger": Atom(type: .binaryOperator, nucleus: "\u{2020}"),
|
||||
"ddagger": Atom(type: .binaryOperator, nucleus: "\u{2021}"),
|
||||
"mp": Atom(type: .binaryOperator, nucleus: "\u{2213}"),
|
||||
"setminus": Atom(type: .binaryOperator, nucleus: "\u{2216}"),
|
||||
"ast": Atom(type: .binaryOperator, nucleus: "\u{2217}"),
|
||||
"circ": Atom(type: .binaryOperator, nucleus: "\u{2218}"),
|
||||
"bullet": Atom(type: .binaryOperator, nucleus: "\u{2219}"),
|
||||
"wedge": Atom(type: .binaryOperator, nucleus: "\u{2227}"),
|
||||
"vee": Atom(type: .binaryOperator, nucleus: "\u{2228}"),
|
||||
"cap": Atom(type: .binaryOperator, nucleus: "\u{2229}"),
|
||||
"cup": Atom(type: .binaryOperator, nucleus: "\u{222A}"),
|
||||
"wr": Atom(type: .binaryOperator, nucleus: "\u{2240}"),
|
||||
"uplus": Atom(type: .binaryOperator, nucleus: "\u{228E}"),
|
||||
"sqcap": Atom(type: .binaryOperator, nucleus: "\u{2293}"),
|
||||
"sqcup": Atom(type: .binaryOperator, nucleus: "\u{2294}"),
|
||||
"oplus": Atom(type: .binaryOperator, nucleus: "\u{2295}"),
|
||||
"ominus": Atom(type: .binaryOperator, nucleus: "\u{2296}"),
|
||||
"otimes": Atom(type: .binaryOperator, nucleus: "\u{2297}"),
|
||||
"oslash": Atom(type: .binaryOperator, nucleus: "\u{2298}"),
|
||||
"odot": Atom(type: .binaryOperator, nucleus: "\u{2299}"),
|
||||
"star": Atom(type: .binaryOperator, nucleus: "\u{22C6}"),
|
||||
"cdot": Atom(type: .binaryOperator, nucleus: "\u{22C5}"),
|
||||
"amalg": Atom(type: .binaryOperator, nucleus: "\u{2A3F}"),
|
||||
|
||||
// No limit operators
|
||||
"log": operatorWithName("log", limits: false),
|
||||
"lg": operatorWithName("lg", limits: false),
|
||||
"ln": operatorWithName("ln", limits: false),
|
||||
"sin": operatorWithName("sin", limits: false),
|
||||
"arcsin": operatorWithName("arcsin", limits: false),
|
||||
"sinh": operatorWithName("sinh", limits: false),
|
||||
"cos": operatorWithName("cos", limits: false),
|
||||
"arccos": operatorWithName("arccos", limits: false),
|
||||
"cosh": operatorWithName("cosh", limits: false),
|
||||
"tan": operatorWithName("tan", limits: false),
|
||||
"arctan": operatorWithName("arctan", limits: false),
|
||||
"tanh": operatorWithName("tanh", limits: false),
|
||||
"cot": operatorWithName("cot", limits: false),
|
||||
"coth": operatorWithName("coth", limits: false),
|
||||
"sec": operatorWithName("sec", limits: false),
|
||||
"csc": operatorWithName("csc", limits: false),
|
||||
"arg": operatorWithName("arg", limits: false),
|
||||
"ker": operatorWithName("ker", limits: false),
|
||||
"dim": operatorWithName("dim", limits: false),
|
||||
"hom": operatorWithName("hom", limits: false),
|
||||
"exp": operatorWithName("exp", limits: false),
|
||||
"deg": operatorWithName("deg", limits: false),
|
||||
"mod": operatorWithName("mod", limits: false),
|
||||
|
||||
// Limit operators
|
||||
"lim": operatorWithName("lim", limits: true),
|
||||
"limsup": operatorWithName("lim sup", limits: true),
|
||||
"liminf": operatorWithName("lim inf", limits: true),
|
||||
"max": operatorWithName("max", limits: true),
|
||||
"min": operatorWithName("min", limits: true),
|
||||
"sup": operatorWithName("sup", limits: true),
|
||||
"inf": operatorWithName("inf", limits: true),
|
||||
"det": operatorWithName("det", limits: true),
|
||||
"Pr": operatorWithName("Pr", limits: true),
|
||||
"gcd": operatorWithName("gcd", limits: true),
|
||||
|
||||
// Large operators
|
||||
"prod": operatorWithName("\u{220F}", limits: true),
|
||||
"coprod": operatorWithName("\u{2210}", limits: true),
|
||||
"sum": operatorWithName("\u{2211}", limits: true),
|
||||
"int": operatorWithName("\u{222B}", limits: false),
|
||||
"iint": operatorWithName("\u{222C}", limits: false),
|
||||
"iiint": operatorWithName("\u{222D}", limits: false),
|
||||
"iiiint": operatorWithName("\u{2A0C}", limits: false),
|
||||
"oint": operatorWithName("\u{222E}", limits: false),
|
||||
"bigwedge": operatorWithName("\u{22C0}", limits: true),
|
||||
"bigvee": operatorWithName("\u{22C1}", limits: true),
|
||||
"bigcap": operatorWithName("\u{22C2}", limits: true),
|
||||
"bigcup": operatorWithName("\u{22C3}", limits: true),
|
||||
"bigodot": operatorWithName("\u{2A00}", limits: true),
|
||||
"bigoplus": operatorWithName("\u{2A01}", limits: true),
|
||||
"bigotimes": operatorWithName("\u{2A02}", limits: true),
|
||||
"biguplus": operatorWithName("\u{2A04}", limits: true),
|
||||
"bigsqcup": operatorWithName("\u{2A06}", limits: true),
|
||||
|
||||
// Latex command characters
|
||||
"{": Atom(type: .open, nucleus: "{"),
|
||||
"}": Atom(type: .close, nucleus: "}"),
|
||||
"$": Atom(type: .ordinary, nucleus: "$"),
|
||||
"&": Atom(type: .ordinary, nucleus: "&"),
|
||||
"#": Atom(type: .ordinary, nucleus: "#"),
|
||||
"%": Atom(type: .ordinary, nucleus: "%"),
|
||||
"_": Atom(type: .ordinary, nucleus: "_"),
|
||||
" ": Atom(type: .ordinary, nucleus: " "),
|
||||
"backslash": Atom(type: .ordinary, nucleus: "\\"),
|
||||
|
||||
// Punctuation
|
||||
// Note: \colon is different from : which is a relation
|
||||
"colon": Atom(type: .punctuation, nucleus: ":"),
|
||||
"cdotp": Atom(type: .punctuation, nucleus: "\u{00B7}"),
|
||||
|
||||
// Other symbols
|
||||
"degree": Atom(type: .ordinary, nucleus: "\u{00B0}"),
|
||||
"neg": Atom(type: .ordinary, nucleus: "\u{00AC}"),
|
||||
"angstrom": Atom(type: .ordinary, nucleus: "\u{00C5}"),
|
||||
"aa": Atom(type: .ordinary, nucleus: "\u{00E5}"),
|
||||
"ae": Atom(type: .ordinary, nucleus: "\u{00E6}"),
|
||||
"o": Atom(type: .ordinary, nucleus: "\u{00F8}"),
|
||||
"oe": Atom(type: .ordinary, nucleus: "\u{0153}"),
|
||||
"ss": Atom(type: .ordinary, nucleus: "\u{00DF}"),
|
||||
"cc": Atom(type: .ordinary, nucleus: "\u{00E7}"),
|
||||
"CC": Atom(type: .ordinary, nucleus: "\u{00C7}"),
|
||||
"O": Atom(type: .ordinary, nucleus: "\u{00D8}"),
|
||||
"AE": Atom(type: .ordinary, nucleus: "\u{00C6}"),
|
||||
"OE": Atom(type: .ordinary, nucleus: "\u{0152}"),
|
||||
"|": Atom(type: .ordinary, nucleus: "\u{2016}"),
|
||||
"vert": Atom(type: .ordinary, nucleus: "|"),
|
||||
"ldots": Atom(type: .ordinary, nucleus: "\u{2026}"),
|
||||
"prime": Atom(type: .ordinary, nucleus: "\u{2032}"),
|
||||
"hbar": Atom(type: .ordinary, nucleus: "\u{210F}"),
|
||||
"lbar": Atom(type: .ordinary, nucleus: "\u{019B}"),
|
||||
"Im": Atom(type: .ordinary, nucleus: "\u{2111}"),
|
||||
"ell": Atom(type: .ordinary, nucleus: "\u{2113}"),
|
||||
"wp": Atom(type: .ordinary, nucleus: "\u{2118}"),
|
||||
"Re": Atom(type: .ordinary, nucleus: "\u{211C}"),
|
||||
"mho": Atom(type: .ordinary, nucleus: "\u{2127}"),
|
||||
"aleph": Atom(type: .ordinary, nucleus: "\u{2135}"),
|
||||
"forall": Atom(type: .ordinary, nucleus: "\u{2200}"),
|
||||
"exists": Atom(type: .ordinary, nucleus: "\u{2203}"),
|
||||
"nexists": Atom(type: .ordinary, nucleus: "\u{2204}"),
|
||||
"emptyset": Atom(type: .ordinary, nucleus: "\u{2205}"),
|
||||
"nabla": Atom(type: .ordinary, nucleus: "\u{2207}"),
|
||||
"infty": Atom(type: .ordinary, nucleus: "\u{221E}"),
|
||||
"angle": Atom(type: .ordinary, nucleus: "\u{2220}"),
|
||||
"top": Atom(type: .ordinary, nucleus: "\u{22A4}"),
|
||||
"bot": Atom(type: .ordinary, nucleus: "\u{22A5}"),
|
||||
"vdots": Atom(type: .ordinary, nucleus: "\u{22EE}"),
|
||||
"cdots": Atom(type: .ordinary, nucleus: "\u{22EF}"),
|
||||
"ddots": Atom(type: .ordinary, nucleus: "\u{22F1}"),
|
||||
"triangle": Atom(type: .ordinary, nucleus: "\u{25B3}"),
|
||||
"imath": Atom(type: .ordinary, nucleus: "\u{1D6A4}"),
|
||||
"jmath": Atom(type: .ordinary, nucleus: "\u{1D6A5}"),
|
||||
"upquote": Atom(type: .ordinary, nucleus: "\u{0027}"),
|
||||
"partial": Atom(type: .ordinary, nucleus: "\u{1D715}"),
|
||||
|
||||
// Spacing
|
||||
",": Space(amount: 3),
|
||||
">": Space(amount: 4),
|
||||
";": Space(amount: 5),
|
||||
"!": Space(amount: -3),
|
||||
"quad": Space(amount: 18),
|
||||
"qquad": Space(amount: 36),
|
||||
|
||||
// Style
|
||||
"displaystyle": Style(level: .display),
|
||||
"textstyle": Style(level: .text),
|
||||
"scriptstyle": Style(level: .script),
|
||||
"scriptscriptstyle": Style(level: .scriptOfScript),
|
||||
])
|
||||
|
||||
static let supportedAccentedCharacters: [Character: (String, String)] = [
|
||||
"\u{00E1}": ("acute", "a"), "\u{00E9}": ("acute", "e"), "\u{00ED}": ("acute", "i"),
|
||||
"\u{00F3}": ("acute", "o"), "\u{00FA}": ("acute", "u"), "\u{00FD}": ("acute", "y"),
|
||||
"\u{00E0}": ("grave", "a"), "\u{00E8}": ("grave", "e"), "\u{00EC}": ("grave", "i"),
|
||||
"\u{00F2}": ("grave", "o"), "\u{00F9}": ("grave", "u"),
|
||||
"\u{00E2}": ("hat", "a"), "\u{00EA}": ("hat", "e"), "\u{00EE}": ("hat", "i"),
|
||||
"\u{00F4}": ("hat", "o"), "\u{00FB}": ("hat", "u"),
|
||||
"\u{00E4}": ("ddot", "a"), "\u{00EB}": ("ddot", "e"), "\u{00EF}": ("ddot", "i"),
|
||||
"\u{00F6}": ("ddot", "o"), "\u{00FC}": ("ddot", "u"), "\u{00FF}": ("ddot", "y"),
|
||||
"\u{00E3}": ("tilde", "a"), "\u{00F1}": ("tilde", "n"), "\u{00F5}": ("tilde", "o"),
|
||||
"\u{00E7}": ("cc", ""), "\u{00F8}": ("o", ""), "\u{00E5}": ("aa", ""), "\u{00E6}": ("ae", ""),
|
||||
"\u{0153}": ("oe", ""), "\u{00DF}": ("ss", ""),
|
||||
"\u{0027}": ("upquote", ""),
|
||||
"\u{00C1}": ("acute", "A"), "\u{00C9}": ("acute", "E"), "\u{00CD}": ("acute", "I"),
|
||||
"\u{00D3}": ("acute", "O"), "\u{00DA}": ("acute", "U"), "\u{00DD}": ("acute", "Y"),
|
||||
"\u{00C0}": ("grave", "A"), "\u{00C8}": ("grave", "E"), "\u{00CC}": ("grave", "I"),
|
||||
"\u{00D2}": ("grave", "O"), "\u{00D9}": ("grave", "U"),
|
||||
"\u{00C2}": ("hat", "A"), "\u{00CA}": ("hat", "E"), "\u{00CE}": ("hat", "I"),
|
||||
"\u{00D4}": ("hat", "O"), "\u{00DB}": ("hat", "U"),
|
||||
"\u{00C4}": ("ddot", "A"), "\u{00CB}": ("ddot", "E"), "\u{00CF}": ("ddot", "I"),
|
||||
"\u{00D6}": ("ddot", "O"), "\u{00DC}": ("ddot", "U"),
|
||||
"\u{00C3}": ("tilde", "A"), "\u{00D1}": ("tilde", "N"), "\u{00D5}": ("tilde", "O"),
|
||||
"\u{00C7}": ("CC", ""),
|
||||
"\u{00D8}": ("O", ""),
|
||||
"\u{00C5}": ("AA", ""),
|
||||
"\u{00C6}": ("AE", ""),
|
||||
"\u{0152}": ("OE", ""),
|
||||
]
|
||||
|
||||
private static let textToLatexSymbolName = ReadWriteLockIsolated<[String: String]?>(nil)
|
||||
|
||||
private static let fontStyles: [String: Atom.FontStyle] = [
|
||||
"mathnormal": .default,
|
||||
"mathrm": .roman,
|
||||
"textrm": .roman,
|
||||
"rm": .roman,
|
||||
"mathbf": .bold,
|
||||
"bf": .bold,
|
||||
"textbf": .bold,
|
||||
"mathcal": .caligraphic,
|
||||
"cal": .caligraphic,
|
||||
"mathtt": .typewriter,
|
||||
"texttt": .typewriter,
|
||||
"mathit": .italic,
|
||||
"textit": .italic,
|
||||
"mit": .italic,
|
||||
"mathsf": .sansSerif,
|
||||
"textsf": .sansSerif,
|
||||
"mathfrak": .fraktur,
|
||||
"frak": .fraktur,
|
||||
"mathbb": .blackboard,
|
||||
"mathbfit": .boldItalic,
|
||||
"bm": .boldItalic,
|
||||
"text": .roman,
|
||||
]
|
||||
|
||||
private static let matrixEnvs: [String: [String]] = [
|
||||
"matrix": [],
|
||||
"pmatrix": ["(", ")"],
|
||||
"bmatrix": ["[", "]"],
|
||||
"Bmatrix": ["{", "}"],
|
||||
"vmatrix": ["vert", "vert"],
|
||||
"Vmatrix": ["Vert", "Vert"],
|
||||
"smallmatrix": [],
|
||||
"matrix*": [],
|
||||
"pmatrix*": ["(", ")"],
|
||||
"bmatrix*": ["[", "]"],
|
||||
"Bmatrix*": ["{", "}"],
|
||||
"vmatrix*": ["vert", "vert"],
|
||||
"Vmatrix*": ["Vert", "Vert"],
|
||||
]
|
||||
|
||||
private enum ParseErrorCode: Int {
|
||||
case invalidEnv = 8
|
||||
case invalidNumColumns = 12
|
||||
}
|
||||
|
||||
private static let parseErrorDomain = "ParseError"
|
||||
|
||||
static func fontStyle(named fontName: String) -> Atom.FontStyle? {
|
||||
fontStyles[fontName]
|
||||
}
|
||||
|
||||
static func fontName(for style: Atom.FontStyle) -> String {
|
||||
switch style {
|
||||
case .default: return "mathnormal"
|
||||
case .roman: return "mathrm"
|
||||
case .bold: return "mathbf"
|
||||
case .fraktur: return "mathfrak"
|
||||
case .caligraphic: return "mathcal"
|
||||
case .italic: return "mathit"
|
||||
case .sansSerif: return "mathsf"
|
||||
case .blackboard: return "mathbb"
|
||||
case .typewriter: return "mathtt"
|
||||
case .boldItalic: return "bm"
|
||||
}
|
||||
}
|
||||
|
||||
static func times() -> Atom {
|
||||
Atom(type: .binaryOperator, nucleus: .multiplication)
|
||||
}
|
||||
|
||||
static func divide() -> Atom {
|
||||
Atom(type: .binaryOperator, nucleus: .division)
|
||||
}
|
||||
|
||||
static func placeholder() -> Atom {
|
||||
Atom(type: .placeholder, nucleus: .whiteSquare)
|
||||
}
|
||||
|
||||
static func placeholderFraction() -> Fraction {
|
||||
let frac = Fraction()
|
||||
frac.numerator = AtomList(atom: placeholder())
|
||||
frac.denominator = AtomList(atom: placeholder())
|
||||
return frac
|
||||
}
|
||||
|
||||
static func placeholderSquareRoot() -> Radical {
|
||||
let rad = Radical()
|
||||
rad.radicand = AtomList(atom: placeholder())
|
||||
return rad
|
||||
}
|
||||
|
||||
static func placeholderRadical() -> Radical {
|
||||
let rad = Radical()
|
||||
rad.radicand = AtomList(atom: placeholder())
|
||||
rad.degree = AtomList(atom: placeholder())
|
||||
return rad
|
||||
}
|
||||
|
||||
static func atom(fromAccentedCharacter ch: Character) -> Atom? {
|
||||
if let symbol = supportedAccentedCharacters[ch] {
|
||||
if let atom = atom(forLatexSymbol: symbol.0) {
|
||||
return atom
|
||||
}
|
||||
|
||||
if let accent = accent(withName: symbol.0) {
|
||||
let list = AtomList()
|
||||
let character = Array(symbol.1)[0]
|
||||
if let atom = atom(forCharacter: character) {
|
||||
list.append(atom)
|
||||
}
|
||||
accent.innerList = list
|
||||
return accent
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
static func atom(forCharacter ch: Character) -> Atom? {
|
||||
let stringValue = String(ch)
|
||||
switch stringValue {
|
||||
case "\u{0410}"..."\u{044F}":
|
||||
return Atom(type: .ordinary, nucleus: stringValue)
|
||||
case _ where supportedAccentedCharacters.keys.contains(ch):
|
||||
return atom(fromAccentedCharacter: ch)
|
||||
case _ where ch.utf32 < 0x0021 || ch.utf32 > 0x007E:
|
||||
return nil
|
||||
case "$", "%", "#", "&", "~", "\'", "^", "_", "{", "}", "\\":
|
||||
return nil
|
||||
case "(", "[":
|
||||
return Atom(type: .open, nucleus: stringValue)
|
||||
case ")", "]", "!", "?":
|
||||
return Atom(type: .close, nucleus: stringValue)
|
||||
case ",", ";":
|
||||
return Atom(type: .punctuation, nucleus: stringValue)
|
||||
case "=", ">", "<":
|
||||
return Atom(type: .relation, nucleus: stringValue)
|
||||
case ":":
|
||||
return Atom(type: .relation, nucleus: "\u{2236}")
|
||||
case "-":
|
||||
return Atom(type: .binaryOperator, nucleus: "\u{2212}")
|
||||
case "+", "*":
|
||||
return Atom(type: .binaryOperator, nucleus: stringValue)
|
||||
case ".", "0"..."9":
|
||||
return Atom(type: .number, nucleus: stringValue)
|
||||
case "a"..."z", "A"..."Z":
|
||||
return Atom(type: .variable, nucleus: stringValue)
|
||||
case "\"", "/", "@", "`", "|":
|
||||
return Atom(type: .ordinary, nucleus: stringValue)
|
||||
default:
|
||||
assertionFailure("Unknown ASCII character '\(ch)'. Should have been handled earlier.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
static func atomList(for string: String) -> AtomList {
|
||||
let list = AtomList()
|
||||
for character in string {
|
||||
if let newAtom = atom(forCharacter: character) {
|
||||
list.append(newAtom)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
static func atom(forLatexSymbol name: String) -> Atom? {
|
||||
let resolvedName = aliases[name] ?? name
|
||||
return supportedLatexSymbols.withValue { $0[resolvedName]?.copy() }
|
||||
}
|
||||
|
||||
static func latexSymbolName(for atom: Atom) -> String? {
|
||||
guard !atom.nucleus.isEmpty else { return nil }
|
||||
return textToLatexSymbolNameValue()[atom.nucleus]
|
||||
}
|
||||
|
||||
static func add(latexSymbol name: String, value: Atom) {
|
||||
let _ = textToLatexSymbolNameValue()
|
||||
supportedLatexSymbols.withValue { $0[name] = value }
|
||||
textToLatexSymbolName.withValue { map in
|
||||
guard !value.nucleus.isEmpty else { return }
|
||||
map?[value.nucleus] = name
|
||||
}
|
||||
}
|
||||
|
||||
static func operatorWithName(_ name: String, limits: Bool) -> LargeOperator {
|
||||
let op = LargeOperator(limits: limits)
|
||||
op.nucleus = name
|
||||
return op
|
||||
}
|
||||
|
||||
static func accent(withName name: String) -> Accent? {
|
||||
if let accentValue = accents[name] {
|
||||
return Accent(value: accentValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
static func accentName(_ accent: Accent) -> String? {
|
||||
accentValueToName[accent.nucleus]
|
||||
}
|
||||
|
||||
static func boundary(forDelimiter name: String) -> Atom? {
|
||||
if let delimValue = delimiters[name] {
|
||||
return Atom(type: .boundary, nucleus: delimValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
static func delimiterName(of boundary: Atom) -> String? {
|
||||
guard boundary.type == .boundary else { return nil }
|
||||
return delimValueToName[boundary.nucleus]
|
||||
}
|
||||
|
||||
static func fraction(withNumerator numerator: AtomList, denominator: AtomList) -> Fraction {
|
||||
let fraction = Fraction()
|
||||
fraction.numerator = numerator
|
||||
fraction.denominator = denominator
|
||||
return fraction
|
||||
}
|
||||
|
||||
static func mathListForCharacters(_ chars: String) -> AtomList? {
|
||||
let list = AtomList()
|
||||
for ch in chars {
|
||||
if let atom = atom(forCharacter: ch) {
|
||||
list.append(atom)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
static func fraction(
|
||||
withNumeratorString numerator: String, denominatorString denominator: String
|
||||
) -> Fraction {
|
||||
let num = atomList(for: numerator)
|
||||
let denom = atomList(for: denominator)
|
||||
return fraction(withNumerator: num, denominator: denom)
|
||||
}
|
||||
|
||||
static func table(
|
||||
withEnvironment env: String?,
|
||||
alignment: Table.ColumnAlignment? = nil,
|
||||
rows: [[AtomList]],
|
||||
error: inout NSError?
|
||||
) -> Atom? {
|
||||
let table = Table(environment: env ?? "")
|
||||
|
||||
for i in 0..<rows.count {
|
||||
let row = rows[i]
|
||||
for j in 0..<row.count {
|
||||
table.setCell(row[j], forRow: i, column: j)
|
||||
}
|
||||
}
|
||||
|
||||
if env == nil {
|
||||
table.interColumnSpacing = 0
|
||||
table.interRowAdditionalSpacing = 1
|
||||
for column in 0..<table.numberOfColumns {
|
||||
table.setAlignment(.left, forColumn: column)
|
||||
}
|
||||
return table
|
||||
} else if let env {
|
||||
if let delims = matrixEnvs[env] {
|
||||
table.environment = "matrix"
|
||||
|
||||
let isSmallMatrix = (env == "smallmatrix")
|
||||
|
||||
table.interRowAdditionalSpacing = 0
|
||||
table.interColumnSpacing = isSmallMatrix ? 6 : 18
|
||||
|
||||
let style = Style(level: isSmallMatrix ? .script : .text)
|
||||
|
||||
for i in 0..<table.cells.count {
|
||||
for j in 0..<table.cells[i].count {
|
||||
table.cells[i][j].insert(style, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
if let alignment {
|
||||
for column in 0..<table.numberOfColumns {
|
||||
table.setAlignment(alignment, forColumn: column)
|
||||
}
|
||||
}
|
||||
|
||||
if delims.count == 2 {
|
||||
let inner = Inner()
|
||||
inner.leftBoundary = boundary(forDelimiter: delims[0])
|
||||
inner.rightBoundary = boundary(forDelimiter: delims[1])
|
||||
inner.innerList = AtomList(atoms: [table])
|
||||
return inner
|
||||
} else {
|
||||
return table
|
||||
}
|
||||
} else if env == "eqalign" || env == "split" || env == "aligned" {
|
||||
if table.numberOfColumns != 2 {
|
||||
let message = "\(env) environment can only have 2 columns"
|
||||
if error == nil {
|
||||
error = NSError(
|
||||
domain: parseErrorDomain,
|
||||
code: ParseErrorCode.invalidNumColumns.rawValue,
|
||||
userInfo: [NSLocalizedDescriptionKey: message]
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let spacer = Atom(type: .ordinary, nucleus: "")
|
||||
|
||||
for i in 0..<table.cells.count {
|
||||
if table.cells[i].count >= 2 {
|
||||
table.cells[i][1].insert(spacer, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
table.interRowAdditionalSpacing = 1
|
||||
table.interColumnSpacing = 0
|
||||
|
||||
table.setAlignment(.right, forColumn: 0)
|
||||
table.setAlignment(.left, forColumn: 1)
|
||||
|
||||
return table
|
||||
} else if env == "displaylines" || env == "gather" {
|
||||
if table.numberOfColumns != 1 {
|
||||
let message = "\(env) environment can only have 1 column"
|
||||
if error == nil {
|
||||
error = NSError(
|
||||
domain: parseErrorDomain,
|
||||
code: ParseErrorCode.invalidNumColumns.rawValue,
|
||||
userInfo: [NSLocalizedDescriptionKey: message]
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
table.interRowAdditionalSpacing = 1
|
||||
table.interColumnSpacing = 0
|
||||
|
||||
table.setAlignment(.center, forColumn: 0)
|
||||
|
||||
return table
|
||||
} else if env == "eqnarray" {
|
||||
if table.numberOfColumns != 3 {
|
||||
let message = "\(env) environment can only have 3 columns"
|
||||
if error == nil {
|
||||
error = NSError(
|
||||
domain: parseErrorDomain,
|
||||
code: ParseErrorCode.invalidNumColumns.rawValue,
|
||||
userInfo: [NSLocalizedDescriptionKey: message]
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
table.interRowAdditionalSpacing = 1
|
||||
table.interColumnSpacing = 18
|
||||
|
||||
table.setAlignment(.right, forColumn: 0)
|
||||
table.setAlignment(.center, forColumn: 1)
|
||||
table.setAlignment(.left, forColumn: 2)
|
||||
|
||||
return table
|
||||
} else if env == "cases" {
|
||||
if table.numberOfColumns != 1 && table.numberOfColumns != 2 {
|
||||
let message = "cases environment can have 1 or 2 columns"
|
||||
if error == nil {
|
||||
error = NSError(
|
||||
domain: parseErrorDomain,
|
||||
code: ParseErrorCode.invalidNumColumns.rawValue,
|
||||
userInfo: [NSLocalizedDescriptionKey: message]
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
table.interRowAdditionalSpacing = 0
|
||||
table.interColumnSpacing = 18
|
||||
|
||||
table.setAlignment(.left, forColumn: 0)
|
||||
if table.numberOfColumns == 2 {
|
||||
table.setAlignment(.left, forColumn: 1)
|
||||
}
|
||||
|
||||
let style = Style(level: .text)
|
||||
for i in 0..<table.cells.count {
|
||||
for j in 0..<table.cells[i].count {
|
||||
table.cells[i][j].insert(style, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
let inner = Inner()
|
||||
inner.leftBoundary = boundary(forDelimiter: "{")
|
||||
inner.rightBoundary = boundary(forDelimiter: ".")
|
||||
let space = atom(forLatexSymbol: ",")!
|
||||
|
||||
inner.innerList = AtomList(atoms: [space, table])
|
||||
|
||||
return inner
|
||||
} else {
|
||||
let message = "Unknown environment \(env)"
|
||||
error = NSError(
|
||||
domain: parseErrorDomain,
|
||||
code: ParseErrorCode.invalidEnv.rawValue,
|
||||
userInfo: [NSLocalizedDescriptionKey: message]
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private static func textToLatexSymbolNameValue() -> [String: String] {
|
||||
textToLatexSymbolName.withValue { map in
|
||||
if let map {
|
||||
return map
|
||||
}
|
||||
|
||||
let symbols = supportedLatexSymbols.withValue { $0 }
|
||||
var output = [String: String]()
|
||||
for (key, atom) in symbols {
|
||||
if atom.nucleus.isEmpty {
|
||||
continue
|
||||
}
|
||||
if let existingText = output[atom.nucleus] {
|
||||
if key.count > existingText.count {
|
||||
continue
|
||||
} else if key.count == existingText.count {
|
||||
if key.compare(existingText) == .orderedDescending {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
output[atom.nucleus] = key
|
||||
}
|
||||
|
||||
map = output
|
||||
return output
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
411
Sources/SwiftUIMath/Internal/Model/AtomList.swift
Normal file
411
Sources/SwiftUIMath/Internal/Model/AtomList.swift
Normal file
@@ -0,0 +1,411 @@
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
enum AtomType: Int {
|
||||
// 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
|
||||
|
||||
// Atoms after this point do not support subscripts or superscripts
|
||||
|
||||
// A left atom - Left & Right in TeX. We don't need two since we track boundaries separately.
|
||||
case boundary = 101
|
||||
|
||||
// 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 rendering.
|
||||
case style
|
||||
case color
|
||||
case textColor
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
extension Math.AtomType {
|
||||
var disallowsFollowingBinaryOperator: Bool {
|
||||
switch self {
|
||||
case .binaryOperator, .relation, .open, .punctuation, .largeOperator:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var allowsScripts: Bool {
|
||||
self < .boundary
|
||||
}
|
||||
}
|
||||
|
||||
extension Math.AtomType: Comparable {
|
||||
static func < (lhs: Math.AtomType, rhs: Math.AtomType) -> Bool {
|
||||
lhs.rawValue < rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
extension Math.AtomType: CustomStringConvertible {
|
||||
var description: String {
|
||||
switch self {
|
||||
case .ordinary: return "Ordinary"
|
||||
case .number: return "Number"
|
||||
case .variable: return "Variable"
|
||||
case .largeOperator: return "Large Operator"
|
||||
case .binaryOperator: return "Binary Operator"
|
||||
case .unaryOperator: return "Unary Operator"
|
||||
case .relation: return "Relation"
|
||||
case .open: return "Open"
|
||||
case .close: return "Close"
|
||||
case .fraction: return "Fraction"
|
||||
case .radical: return "Radical"
|
||||
case .punctuation: return "Punctuation"
|
||||
case .placeholder: return "Placeholder"
|
||||
case .inner: return "Inner"
|
||||
case .underline: return "Underline"
|
||||
case .overline: return "Overline"
|
||||
case .accent: return "Accent"
|
||||
case .boundary: return "Boundary"
|
||||
case .space: return "Space"
|
||||
case .style: return "Style"
|
||||
case .color: return "Color"
|
||||
case .textColor: return "TextColor"
|
||||
case .colorBox: return "Colorbox"
|
||||
case .table: return "Table"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Math {
|
||||
class Atom: CustomStringConvertible {
|
||||
enum FontStyle: Int {
|
||||
case `default` = 0
|
||||
case roman
|
||||
case bold
|
||||
case caligraphic
|
||||
case typewriter
|
||||
case italic
|
||||
case sansSerif
|
||||
case fraktur
|
||||
case blackboard
|
||||
case boldItalic
|
||||
}
|
||||
|
||||
var description: String {
|
||||
[
|
||||
nucleus,
|
||||
superscript.map { "^{\($0)}" },
|
||||
`subscript`.map { "_{\($0)}" },
|
||||
]
|
||||
.compactMap(\.self)
|
||||
.joined()
|
||||
}
|
||||
|
||||
var type: AtomType
|
||||
var nucleus: String
|
||||
var indexRange: NSRange
|
||||
var fontStyle: FontStyle
|
||||
var fusedAtoms: [Atom]
|
||||
|
||||
var `subscript`: AtomList? {
|
||||
didSet {
|
||||
if `subscript` != nil, !allowsScripts {
|
||||
assertionFailure("Subscripts are not allowed for \(type)")
|
||||
`subscript` = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var superscript: AtomList? {
|
||||
didSet {
|
||||
if superscript != nil, !allowsScripts {
|
||||
assertionFailure("Superscripts are not allowed for \(type)")
|
||||
superscript = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var finalized: Atom {
|
||||
let finalized = copy()
|
||||
finalized.superscript = finalized.superscript?.finalized
|
||||
finalized.subscript = finalized.subscript?.finalized
|
||||
return finalized
|
||||
}
|
||||
|
||||
var string: String {
|
||||
description
|
||||
}
|
||||
|
||||
var allowsScripts: Bool {
|
||||
type.allowsScripts
|
||||
}
|
||||
|
||||
var disallowsFollowingBinaryOperator: Bool {
|
||||
type.disallowsFollowingBinaryOperator
|
||||
}
|
||||
|
||||
init(
|
||||
type: AtomType = .ordinary,
|
||||
nucleus: String = "",
|
||||
indexRange: NSRange = NSRange(),
|
||||
fontStyle: FontStyle = .default,
|
||||
fusedAtoms: [Atom] = [],
|
||||
subscript: AtomList? = nil,
|
||||
superscript: AtomList? = nil
|
||||
) {
|
||||
self.type = type
|
||||
self.nucleus = nucleus
|
||||
self.indexRange = indexRange
|
||||
self.fontStyle = fontStyle
|
||||
self.fusedAtoms = fusedAtoms
|
||||
self.subscript = `subscript`
|
||||
self.superscript = superscript
|
||||
}
|
||||
|
||||
init(_ other: Atom) {
|
||||
self.type = other.type
|
||||
self.nucleus = other.nucleus
|
||||
self.indexRange = other.indexRange
|
||||
self.fontStyle = other.fontStyle
|
||||
self.fusedAtoms = other.fusedAtoms
|
||||
self.subscript = other.`subscript`.map { AtomList($0) }
|
||||
self.superscript = other.superscript.map { AtomList($0) }
|
||||
}
|
||||
|
||||
convenience init(type: AtomType, value: String) {
|
||||
self.init(type: type, nucleus: type == .radical ? "" : value)
|
||||
}
|
||||
|
||||
func copy() -> Atom {
|
||||
switch type {
|
||||
case .fraction:
|
||||
return (self as? Fraction).map {
|
||||
Fraction($0)
|
||||
} ?? Fraction()
|
||||
case .radical:
|
||||
return (self as? Radical).map {
|
||||
Radical($0)
|
||||
} ?? Radical()
|
||||
case .largeOperator:
|
||||
return (self as? LargeOperator).map {
|
||||
LargeOperator($0)
|
||||
} ?? LargeOperator()
|
||||
case .inner:
|
||||
return (self as? Inner).map {
|
||||
Inner($0)
|
||||
} ?? Inner()
|
||||
case .overline:
|
||||
return (self as? Overline).map {
|
||||
Overline($0)
|
||||
} ?? Overline()
|
||||
case .underline:
|
||||
return (self as? Underline).map {
|
||||
Underline($0)
|
||||
} ?? Underline()
|
||||
case .accent:
|
||||
return (self as? Accent).map {
|
||||
Accent($0)
|
||||
} ?? Accent()
|
||||
case .space:
|
||||
return (self as? Space).map {
|
||||
Space($0)
|
||||
} ?? Space()
|
||||
case .style:
|
||||
return (self as? Style).map {
|
||||
Style($0)
|
||||
} ?? Style()
|
||||
case .color:
|
||||
return (self as? Color).map {
|
||||
Color($0)
|
||||
} ?? Color()
|
||||
case .textColor:
|
||||
return (self as? TextColor).map {
|
||||
TextColor($0)
|
||||
} ?? TextColor()
|
||||
case .colorBox:
|
||||
return (self as? ColorBox).map {
|
||||
ColorBox($0)
|
||||
} ?? ColorBox()
|
||||
case .table:
|
||||
return (self as? Table).map {
|
||||
Table($0)
|
||||
} ?? Table()
|
||||
default:
|
||||
return Atom(self)
|
||||
}
|
||||
}
|
||||
|
||||
func fuse(with atom: Atom) {
|
||||
guard `subscript` == nil, superscript == nil, type == atom.type else {
|
||||
assertionFailure("Cannot fuse \(self) with \(atom)")
|
||||
return
|
||||
}
|
||||
|
||||
if fusedAtoms.isEmpty {
|
||||
fusedAtoms.append(.init(self))
|
||||
}
|
||||
|
||||
if atom.fusedAtoms.isEmpty {
|
||||
fusedAtoms.append(atom)
|
||||
} else {
|
||||
fusedAtoms.append(contentsOf: atom.fusedAtoms)
|
||||
}
|
||||
|
||||
nucleus += atom.nucleus
|
||||
indexRange.length += atom.indexRange.length
|
||||
|
||||
self.superscript = atom.superscript
|
||||
self.`subscript` = atom.`subscript`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Math {
|
||||
final class AtomList {
|
||||
var atoms: [Atom]
|
||||
|
||||
var finalized: AtomList {
|
||||
let finalizedList = AtomList()
|
||||
|
||||
var previousAtom: Atom?
|
||||
|
||||
for atom in atoms {
|
||||
let finalizedAtom = atom.finalized
|
||||
|
||||
if atom.indexRange == NSRange() {
|
||||
let location = (previousAtom?.indexRange).map {
|
||||
$0.location + $0.length
|
||||
}
|
||||
finalizedAtom.indexRange = NSRange(location: location ?? 0, length: 1)
|
||||
}
|
||||
|
||||
switch finalizedAtom.type {
|
||||
case .binaryOperator where previousAtom.disallowsFollowingBinaryOperator:
|
||||
finalizedAtom.type = .unaryOperator
|
||||
case .relation, .punctuation, .close:
|
||||
if case .binaryOperator = previousAtom?.type {
|
||||
previousAtom?.type = .unaryOperator
|
||||
}
|
||||
case .number:
|
||||
if let previousAtom,
|
||||
case .number = previousAtom.type,
|
||||
previousAtom.`subscript` == nil,
|
||||
previousAtom.superscript == nil
|
||||
{
|
||||
previousAtom.fuse(with: finalizedAtom)
|
||||
continue // skip the current node, we are done here
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
finalizedList.append(finalizedAtom)
|
||||
previousAtom = finalizedAtom
|
||||
}
|
||||
|
||||
if let previousAtom, case .binaryOperator = previousAtom.type {
|
||||
previousAtom.type = .unaryOperator
|
||||
}
|
||||
|
||||
return finalizedList
|
||||
}
|
||||
|
||||
init(_ list: AtomList) {
|
||||
self.atoms = list.atoms.map {
|
||||
$0.copy()
|
||||
}
|
||||
}
|
||||
|
||||
convenience init(atom: Atom) {
|
||||
self.init(atoms: [atom])
|
||||
}
|
||||
|
||||
init(atoms: [Atom] = []) {
|
||||
self.atoms = atoms
|
||||
}
|
||||
|
||||
func append(_ atom: Atom) {
|
||||
guard canAdd(atom) else {
|
||||
assertionFailure("Can't append atom of type \(atom.type)")
|
||||
return
|
||||
}
|
||||
atoms.append(atom)
|
||||
}
|
||||
|
||||
func insert(_ atom: Atom, at index: Int) {
|
||||
guard atoms.indices.contains(index) || index == atoms.endIndex else {
|
||||
return
|
||||
}
|
||||
guard canAdd(atom) else {
|
||||
assertionFailure("Can't insert atom of type \(atom.type)")
|
||||
return
|
||||
}
|
||||
atoms.insert(atom, at: index)
|
||||
}
|
||||
|
||||
func append(contentsOf list: AtomList) {
|
||||
atoms.append(contentsOf: list.atoms)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Math.AtomList: CustomStringConvertible {
|
||||
var description: String {
|
||||
atoms.description
|
||||
}
|
||||
}
|
||||
|
||||
extension Math.AtomList {
|
||||
private func canAdd(_ atom: Math.Atom) -> Bool {
|
||||
atom.type != .boundary
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional where Wrapped: Math.Atom {
|
||||
var disallowsFollowingBinaryOperator: Bool {
|
||||
guard case .some(let wrapped) = self else {
|
||||
return true
|
||||
}
|
||||
return wrapped.disallowsFollowingBinaryOperator
|
||||
}
|
||||
}
|
||||
27
Sources/SwiftUIMath/Internal/Model/Atoms/Accent.swift
Normal file
27
Sources/SwiftUIMath/Internal/Model/Atoms/Accent.swift
Normal file
@@ -0,0 +1,27 @@
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class Accent: Atom {
|
||||
var innerList: AtomList?
|
||||
|
||||
override var finalized: Math.Atom {
|
||||
let finalized = super.finalized
|
||||
|
||||
if let accent = finalized as? Accent {
|
||||
accent.innerList = accent.innerList?.finalized
|
||||
}
|
||||
|
||||
return finalized
|
||||
}
|
||||
|
||||
init(_ accent: Accent) {
|
||||
self.innerList = accent.innerList.map { AtomList($0) }
|
||||
super.init(accent)
|
||||
}
|
||||
|
||||
init(value: String = "", innerList: AtomList? = nil) {
|
||||
self.innerList = innerList
|
||||
super.init(type: .accent, nucleus: value)
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Sources/SwiftUIMath/Internal/Model/Atoms/Color.swift
Normal file
41
Sources/SwiftUIMath/Internal/Model/Atoms/Color.swift
Normal file
@@ -0,0 +1,41 @@
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class Color: Atom {
|
||||
var colorString: String
|
||||
var innerList: AtomList?
|
||||
|
||||
override var description: String {
|
||||
[
|
||||
"\\color",
|
||||
"{\(colorString)}",
|
||||
innerList.map { "{\($0)}" },
|
||||
]
|
||||
.compactMap(\.self)
|
||||
.joined()
|
||||
}
|
||||
|
||||
override var finalized: Math.Atom {
|
||||
let finalized = super.finalized
|
||||
|
||||
if let color = finalized as? Color {
|
||||
color.innerList = color.innerList?.finalized
|
||||
}
|
||||
|
||||
return finalized
|
||||
}
|
||||
|
||||
init(_ color: Color) {
|
||||
self.colorString = color.colorString
|
||||
self.innerList = color.innerList.map { AtomList($0) }
|
||||
|
||||
super.init(color)
|
||||
}
|
||||
|
||||
init(colorString: String = "", innerList: AtomList? = nil) {
|
||||
self.colorString = colorString
|
||||
self.innerList = innerList
|
||||
super.init(type: .color)
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Sources/SwiftUIMath/Internal/Model/Atoms/ColorBox.swift
Normal file
41
Sources/SwiftUIMath/Internal/Model/Atoms/ColorBox.swift
Normal file
@@ -0,0 +1,41 @@
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class ColorBox: Atom {
|
||||
var colorString: String
|
||||
var innerList: AtomList?
|
||||
|
||||
override var description: String {
|
||||
[
|
||||
"\\colorbox",
|
||||
"{\(colorString)}",
|
||||
innerList.map { "{\($0)}" },
|
||||
]
|
||||
.compactMap(\.self)
|
||||
.joined()
|
||||
}
|
||||
|
||||
override var finalized: Math.Atom {
|
||||
let finalized = super.finalized
|
||||
|
||||
if let colorBox = finalized as? ColorBox {
|
||||
colorBox.innerList = colorBox.innerList?.finalized
|
||||
}
|
||||
|
||||
return finalized
|
||||
}
|
||||
|
||||
init(_ colorBox: ColorBox) {
|
||||
self.colorString = colorBox.colorString
|
||||
self.innerList = colorBox.innerList.map { AtomList($0) }
|
||||
|
||||
super.init(colorBox)
|
||||
}
|
||||
|
||||
init(colorString: String = "", innerList: AtomList? = nil) {
|
||||
self.colorString = colorString
|
||||
self.innerList = innerList
|
||||
super.init(type: .colorBox)
|
||||
}
|
||||
}
|
||||
}
|
||||
71
Sources/SwiftUIMath/Internal/Model/Atoms/Fraction.swift
Normal file
71
Sources/SwiftUIMath/Internal/Model/Atoms/Fraction.swift
Normal file
@@ -0,0 +1,71 @@
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class Fraction: Atom {
|
||||
var hasRule: Bool
|
||||
var leftDelimiter: String
|
||||
var rightDelimiter: String
|
||||
var numerator: AtomList?
|
||||
var denominator: AtomList?
|
||||
|
||||
var isContinuedFraction: Bool = false
|
||||
var alignment: String // "l", "r", "c" for left, right, center
|
||||
|
||||
override var description: String {
|
||||
[
|
||||
hasRule ? "\\frac" : "\\atop",
|
||||
leftDelimiter.isEmpty ? nil : "[\(leftDelimiter)]",
|
||||
rightDelimiter.isEmpty ? nil : "[\(rightDelimiter)]",
|
||||
"{\(numerator?.description ?? "placeholder")}",
|
||||
"{\(denominator?.description ?? "placeholder")}",
|
||||
superscript.map { "^{\($0)}" },
|
||||
`subscript`.map { "_{\($0)}" },
|
||||
]
|
||||
.compactMap(\.self)
|
||||
.joined()
|
||||
}
|
||||
|
||||
override var finalized: Math.Atom {
|
||||
let finalized = super.finalized
|
||||
|
||||
if let fraction = finalized as? Fraction {
|
||||
fraction.numerator = fraction.numerator?.finalized
|
||||
fraction.denominator = fraction.denominator?.finalized
|
||||
}
|
||||
|
||||
return finalized
|
||||
}
|
||||
|
||||
init(_ fraction: Fraction) {
|
||||
self.hasRule = fraction.hasRule
|
||||
self.leftDelimiter = fraction.leftDelimiter
|
||||
self.rightDelimiter = fraction.rightDelimiter
|
||||
self.numerator = fraction.numerator.map { AtomList($0) }
|
||||
self.denominator = fraction.denominator.map { AtomList($0) }
|
||||
self.isContinuedFraction = fraction.isContinuedFraction
|
||||
self.alignment = fraction.alignment
|
||||
|
||||
super.init(fraction)
|
||||
}
|
||||
|
||||
init(
|
||||
hasRule: Bool = true,
|
||||
leftDelimiter: String = "",
|
||||
rightDelimiter: String = "",
|
||||
numerator: AtomList? = nil,
|
||||
denominator: AtomList? = nil,
|
||||
isContinuedFraction: Bool = false,
|
||||
alignment: String = "c"
|
||||
) {
|
||||
self.hasRule = hasRule
|
||||
self.leftDelimiter = leftDelimiter
|
||||
self.rightDelimiter = rightDelimiter
|
||||
self.numerator = numerator
|
||||
self.denominator = denominator
|
||||
self.isContinuedFraction = isContinuedFraction
|
||||
self.alignment = alignment
|
||||
|
||||
super.init(type: .fraction)
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Sources/SwiftUIMath/Internal/Model/Atoms/Inner.swift
Normal file
68
Sources/SwiftUIMath/Internal/Model/Atoms/Inner.swift
Normal file
@@ -0,0 +1,68 @@
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class Inner: Atom {
|
||||
var innerList: AtomList?
|
||||
|
||||
var leftBoundary: Atom? {
|
||||
didSet {
|
||||
if let leftBoundary, leftBoundary.type != .boundary {
|
||||
assertionFailure("Left boundary must be of type 'boundary'")
|
||||
self.leftBoundary = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var rightBoundary: Atom? {
|
||||
didSet {
|
||||
if let rightBoundary, rightBoundary.type != .boundary {
|
||||
assertionFailure("Right boundary must be of type 'boundary'")
|
||||
self.rightBoundary = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override var description: String {
|
||||
[
|
||||
"\\inner",
|
||||
leftBoundary.map { "[\($0.nucleus)]" },
|
||||
innerList.map { "{\($0)}" },
|
||||
rightBoundary.map { "[\($0.nucleus)]" },
|
||||
superscript.map { "^{\($0)}" },
|
||||
`subscript`.map { "_{\($0)}" },
|
||||
]
|
||||
.compactMap(\.self)
|
||||
.joined()
|
||||
}
|
||||
|
||||
override var finalized: Math.Atom {
|
||||
let finalized = super.finalized
|
||||
|
||||
if let inner = finalized as? Inner {
|
||||
inner.innerList = inner.innerList?.finalized
|
||||
}
|
||||
|
||||
return finalized
|
||||
}
|
||||
|
||||
init(_ inner: Inner) {
|
||||
self.innerList = inner.innerList.map { AtomList($0) }
|
||||
self.leftBoundary = inner.leftBoundary.map { $0.copy() }
|
||||
self.rightBoundary = inner.rightBoundary.map { $0.copy() }
|
||||
|
||||
super.init(inner)
|
||||
}
|
||||
|
||||
init(
|
||||
innerList: AtomList? = nil,
|
||||
leftBoundary: Atom? = nil,
|
||||
rightBoundary: Atom? = nil
|
||||
) {
|
||||
self.innerList = innerList
|
||||
self.leftBoundary = leftBoundary
|
||||
self.rightBoundary = rightBoundary
|
||||
|
||||
super.init(type: .inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Sources/SwiftUIMath/Internal/Model/Atoms/LargeOperator.swift
Normal file
17
Sources/SwiftUIMath/Internal/Model/Atoms/LargeOperator.swift
Normal file
@@ -0,0 +1,17 @@
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class LargeOperator: Atom {
|
||||
var limits: Bool
|
||||
|
||||
init(_ largeOperator: LargeOperator) {
|
||||
self.limits = largeOperator.limits
|
||||
super.init(largeOperator)
|
||||
}
|
||||
|
||||
init(limits: Bool = false) {
|
||||
self.limits = limits
|
||||
super.init(type: .largeOperator)
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Sources/SwiftUIMath/Internal/Model/Atoms/Overline.swift
Normal file
27
Sources/SwiftUIMath/Internal/Model/Atoms/Overline.swift
Normal file
@@ -0,0 +1,27 @@
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class Overline: Atom {
|
||||
var innerList: AtomList?
|
||||
|
||||
override var finalized: Math.Atom {
|
||||
let finalized = super.finalized
|
||||
|
||||
if let overline = finalized as? Overline {
|
||||
overline.innerList = overline.innerList?.finalized
|
||||
}
|
||||
|
||||
return finalized
|
||||
}
|
||||
|
||||
init(_ overline: Overline) {
|
||||
self.innerList = overline.innerList.map { AtomList($0) }
|
||||
super.init(overline)
|
||||
}
|
||||
|
||||
init(innerList: AtomList? = nil) {
|
||||
self.innerList = innerList
|
||||
super.init(type: .overline)
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Sources/SwiftUIMath/Internal/Model/Atoms/Radical.swift
Normal file
45
Sources/SwiftUIMath/Internal/Model/Atoms/Radical.swift
Normal file
@@ -0,0 +1,45 @@
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class Radical: Atom {
|
||||
var radicand: AtomList?
|
||||
var degree: AtomList?
|
||||
|
||||
override var description: String {
|
||||
[
|
||||
"\\sqrt",
|
||||
degree.map { "[\($0)]" },
|
||||
"{\(radicand?.description ?? "placeholder")}",
|
||||
superscript.map { "^{\($0)}" },
|
||||
`subscript`.map { "_{\($0)}" },
|
||||
]
|
||||
.compactMap(\.self)
|
||||
.joined()
|
||||
}
|
||||
|
||||
override var finalized: Math.Atom {
|
||||
let finalized = super.finalized
|
||||
|
||||
if let radical = finalized as? Radical {
|
||||
radical.radicand = radical.radicand?.finalized
|
||||
radical.degree = radical.degree?.finalized
|
||||
}
|
||||
|
||||
return finalized
|
||||
}
|
||||
|
||||
init(_ radical: Radical) {
|
||||
self.radicand = radical.radicand.map { AtomList($0) }
|
||||
self.degree = radical.degree.map { AtomList($0) }
|
||||
|
||||
super.init(radical)
|
||||
}
|
||||
|
||||
init(radicand: AtomList? = nil, degree: AtomList? = nil) {
|
||||
self.radicand = radicand
|
||||
self.degree = degree
|
||||
|
||||
super.init(type: .radical)
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Sources/SwiftUIMath/Internal/Model/Atoms/Space.swift
Normal file
17
Sources/SwiftUIMath/Internal/Model/Atoms/Space.swift
Normal file
@@ -0,0 +1,17 @@
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class Space: Atom {
|
||||
var amount: CGFloat
|
||||
|
||||
init(_ space: Space) {
|
||||
self.amount = space.amount
|
||||
super.init(space)
|
||||
}
|
||||
|
||||
init(amount: CGFloat = 0) {
|
||||
self.amount = amount
|
||||
super.init(type: .space)
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Sources/SwiftUIMath/Internal/Model/Atoms/Style.swift
Normal file
41
Sources/SwiftUIMath/Internal/Model/Atoms/Style.swift
Normal file
@@ -0,0 +1,41 @@
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class Style: Atom {
|
||||
enum Level: Int {
|
||||
case display
|
||||
case text
|
||||
case script
|
||||
case scriptOfScript
|
||||
|
||||
var isScript: Bool {
|
||||
switch self {
|
||||
case .script, .scriptOfScript:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var isNotScript: Bool {
|
||||
!isScript
|
||||
}
|
||||
|
||||
func next() -> Level {
|
||||
Level(rawValue: rawValue + 1) ?? .display
|
||||
}
|
||||
}
|
||||
|
||||
var level: Level
|
||||
|
||||
init(_ style: Style) {
|
||||
self.level = style.level
|
||||
super.init(style)
|
||||
}
|
||||
|
||||
init(level: Level = .display) {
|
||||
self.level = level
|
||||
super.init(type: .style)
|
||||
}
|
||||
}
|
||||
}
|
||||
101
Sources/SwiftUIMath/Internal/Model/Atoms/Table.swift
Normal file
101
Sources/SwiftUIMath/Internal/Model/Atoms/Table.swift
Normal file
@@ -0,0 +1,101 @@
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class Table: Atom {
|
||||
enum ColumnAlignment {
|
||||
case left
|
||||
case center
|
||||
case right
|
||||
}
|
||||
|
||||
var alignments: [ColumnAlignment]
|
||||
var cells: [[AtomList]]
|
||||
var environment: String
|
||||
var interColumnSpacing: CGFloat
|
||||
var interRowAdditionalSpacing: CGFloat
|
||||
|
||||
override var finalized: Math.Atom {
|
||||
let finalized = super.finalized
|
||||
|
||||
if let table = finalized as? Table {
|
||||
table.cells = table.cells.map { row in
|
||||
row.map { $0.finalized }
|
||||
}
|
||||
}
|
||||
|
||||
return finalized
|
||||
}
|
||||
|
||||
init(_ table: Table) {
|
||||
self.alignments = table.alignments
|
||||
self.cells = table.cells.map { row in
|
||||
row.map { AtomList($0) }
|
||||
}
|
||||
self.environment = table.environment
|
||||
self.interColumnSpacing = table.interColumnSpacing
|
||||
self.interRowAdditionalSpacing = table.interRowAdditionalSpacing
|
||||
|
||||
super.init(table)
|
||||
}
|
||||
|
||||
init(
|
||||
alignments: [ColumnAlignment] = [],
|
||||
cells: [[AtomList]] = [],
|
||||
environment: String = "",
|
||||
interColumnSpacing: CGFloat = 0,
|
||||
interRowAdditionalSpacing: CGFloat = 0
|
||||
) {
|
||||
self.alignments = alignments
|
||||
self.cells = cells
|
||||
self.environment = environment
|
||||
self.interColumnSpacing = interColumnSpacing
|
||||
self.interRowAdditionalSpacing = interRowAdditionalSpacing
|
||||
super.init(type: .table)
|
||||
}
|
||||
|
||||
func setCell(_ cell: AtomList, forRow row: Int, column: Int) {
|
||||
if cells.count <= row {
|
||||
for _ in cells.count...row {
|
||||
cells.append([])
|
||||
}
|
||||
}
|
||||
|
||||
if cells[row].count <= column {
|
||||
for _ in cells[row].count...column {
|
||||
cells[row].append(AtomList())
|
||||
}
|
||||
}
|
||||
|
||||
cells[row][column] = cell
|
||||
}
|
||||
|
||||
func setAlignment(_ alignment: ColumnAlignment, forColumn column: Int) {
|
||||
if alignments.count <= column {
|
||||
for _ in alignments.count...column {
|
||||
alignments.append(.center)
|
||||
}
|
||||
}
|
||||
|
||||
alignments[column] = alignment
|
||||
}
|
||||
|
||||
func alignment(forColumn column: Int) -> ColumnAlignment {
|
||||
if alignments.count <= column {
|
||||
return .center
|
||||
}
|
||||
return alignments[column]
|
||||
}
|
||||
|
||||
var numberOfColumns: Int {
|
||||
var count = 0
|
||||
for row in cells {
|
||||
count = max(count, row.count)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
var numberOfRows: Int {
|
||||
cells.count
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Sources/SwiftUIMath/Internal/Model/Atoms/TextColor.swift
Normal file
41
Sources/SwiftUIMath/Internal/Model/Atoms/TextColor.swift
Normal file
@@ -0,0 +1,41 @@
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class TextColor: Atom {
|
||||
var colorString: String
|
||||
var innerList: AtomList?
|
||||
|
||||
override var description: String {
|
||||
[
|
||||
"\\textcolor",
|
||||
"{\(colorString)}",
|
||||
innerList.map { "{\($0)}" },
|
||||
]
|
||||
.compactMap(\.self)
|
||||
.joined()
|
||||
}
|
||||
|
||||
override var finalized: Math.Atom {
|
||||
let finalized = super.finalized
|
||||
|
||||
if let textColor = finalized as? TextColor {
|
||||
textColor.innerList = textColor.innerList?.finalized
|
||||
}
|
||||
|
||||
return finalized
|
||||
}
|
||||
|
||||
init(_ textColor: TextColor) {
|
||||
self.colorString = textColor.colorString
|
||||
self.innerList = textColor.innerList.map { AtomList($0) }
|
||||
|
||||
super.init(textColor)
|
||||
}
|
||||
|
||||
init(colorString: String = "", innerList: AtomList? = nil) {
|
||||
self.colorString = colorString
|
||||
self.innerList = innerList
|
||||
super.init(type: .textColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Sources/SwiftUIMath/Internal/Model/Atoms/Underline.swift
Normal file
27
Sources/SwiftUIMath/Internal/Model/Atoms/Underline.swift
Normal file
@@ -0,0 +1,27 @@
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class Underline: Atom {
|
||||
var innerList: AtomList?
|
||||
|
||||
override var finalized: Math.Atom {
|
||||
let finalized = super.finalized
|
||||
|
||||
if let underline = finalized as? Underline {
|
||||
underline.innerList = underline.innerList?.finalized
|
||||
}
|
||||
|
||||
return finalized
|
||||
}
|
||||
|
||||
init(_ underline: Underline) {
|
||||
self.innerList = underline.innerList.map { AtomList($0) }
|
||||
super.init(underline)
|
||||
}
|
||||
|
||||
init(innerList: AtomList? = nil) {
|
||||
self.innerList = innerList
|
||||
super.init(type: .underline)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user