Add display model
This commit is contained in:
24
Sources/SwiftUIMath/Internal/Display/DisplayAccent.swift
Normal file
24
Sources/SwiftUIMath/Internal/Display/DisplayAccent.swift
Normal file
@@ -0,0 +1,24 @@
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class DisplayAccent: DisplayNode {
|
||||
var accentee: DisplayList?
|
||||
var accent: DisplayGlyph?
|
||||
|
||||
init(accent: DisplayGlyph?, accentee: DisplayList?, range: NSRange) {
|
||||
self.accent = accent
|
||||
self.accentee = accentee
|
||||
super.init()
|
||||
self.range = range
|
||||
}
|
||||
|
||||
override var position: CGPoint {
|
||||
didSet { updateAccenteePosition() }
|
||||
}
|
||||
|
||||
private func updateAccenteePosition() {
|
||||
accentee?.position = CGPoint(x: position.x, y: position.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Sources/SwiftUIMath/Internal/Display/DisplayFraction.swift
Normal file
60
Sources/SwiftUIMath/Internal/Display/DisplayFraction.swift
Normal file
@@ -0,0 +1,60 @@
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class DisplayFraction: DisplayNode {
|
||||
var numerator: DisplayList?
|
||||
var denominator: DisplayList?
|
||||
|
||||
var numeratorUp: CGFloat = 0 { didSet { updateNumeratorPosition() } }
|
||||
var denominatorDown: CGFloat = 0 { didSet { updateDenominatorPosition() } }
|
||||
var linePosition: CGFloat = 0
|
||||
var lineThickness: CGFloat = 0
|
||||
|
||||
init(numerator: DisplayList?, denominator: DisplayList?, position: CGPoint, range: NSRange) {
|
||||
self.numerator = numerator
|
||||
self.denominator = denominator
|
||||
super.init()
|
||||
self.position = position
|
||||
self.range = range
|
||||
}
|
||||
|
||||
override var ascent: CGFloat {
|
||||
get { (numerator?.ascent ?? 0) + numeratorUp }
|
||||
set { super.ascent = newValue }
|
||||
}
|
||||
|
||||
override var descent: CGFloat {
|
||||
get { (denominator?.descent ?? 0) + denominatorDown }
|
||||
set { super.descent = newValue }
|
||||
}
|
||||
|
||||
override var width: CGFloat {
|
||||
get { max(numerator?.width ?? 0, denominator?.width ?? 0) }
|
||||
set { super.width = newValue }
|
||||
}
|
||||
|
||||
override var position: CGPoint {
|
||||
didSet {
|
||||
updateDenominatorPosition()
|
||||
updateNumeratorPosition()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateDenominatorPosition() {
|
||||
guard let denominator else { return }
|
||||
denominator.position = CGPoint(
|
||||
x: position.x + (width - denominator.width) / 2,
|
||||
y: position.y - denominatorDown
|
||||
)
|
||||
}
|
||||
|
||||
private func updateNumeratorPosition() {
|
||||
guard let numerator else { return }
|
||||
numerator.position = CGPoint(
|
||||
x: position.x + (width - numerator.width) / 2,
|
||||
y: position.y + numeratorUp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Sources/SwiftUIMath/Internal/Display/DisplayGlyph.swift
Normal file
27
Sources/SwiftUIMath/Internal/Display/DisplayGlyph.swift
Normal file
@@ -0,0 +1,27 @@
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class DisplayGlyph: DisplayShiftedNode {
|
||||
var glyph: UInt16
|
||||
var font: Math.Font
|
||||
|
||||
init(glyph: UInt16, font: Math.Font, range: NSRange) {
|
||||
self.glyph = glyph
|
||||
self.font = font
|
||||
super.init()
|
||||
self.position = .zero
|
||||
self.range = range
|
||||
}
|
||||
|
||||
override var ascent: CGFloat {
|
||||
get { super.ascent - shiftDown }
|
||||
set { super.ascent = newValue }
|
||||
}
|
||||
|
||||
override var descent: CGFloat {
|
||||
get { super.descent + shiftDown }
|
||||
set { super.descent = newValue }
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Sources/SwiftUIMath/Internal/Display/DisplayGlyphRun.swift
Normal file
28
Sources/SwiftUIMath/Internal/Display/DisplayGlyphRun.swift
Normal file
@@ -0,0 +1,28 @@
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class DisplayGlyphRun: DisplayShiftedNode {
|
||||
var glyphs: [UInt16]
|
||||
var offsets: [CGFloat]
|
||||
var font: Math.Font
|
||||
|
||||
init(glyphs: [UInt16], offsets: [CGFloat], font: Math.Font) {
|
||||
self.glyphs = glyphs
|
||||
self.offsets = offsets
|
||||
self.font = font
|
||||
super.init()
|
||||
self.position = .zero
|
||||
}
|
||||
|
||||
override var ascent: CGFloat {
|
||||
get { super.ascent - shiftDown }
|
||||
set { super.ascent = newValue }
|
||||
}
|
||||
|
||||
override var descent: CGFloat {
|
||||
get { super.descent + shiftDown }
|
||||
set { super.descent = newValue }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class DisplayLargeOperator: DisplayNode {
|
||||
var upperLimit: DisplayList?
|
||||
var lowerLimit: DisplayList?
|
||||
var nucleus: DisplayNode?
|
||||
|
||||
var limitShift: CGFloat = 0
|
||||
var upperLimitGap: CGFloat = 0 { didSet { updateUpperLimitPosition() } }
|
||||
var lowerLimitGap: CGFloat = 0 { didSet { updateLowerLimitPosition() } }
|
||||
var extraPadding: CGFloat = 0
|
||||
|
||||
init(nucleus: DisplayNode?, upperLimit: DisplayList?, lowerLimit: DisplayList?, limitShift: CGFloat, extraPadding: CGFloat) {
|
||||
self.upperLimit = upperLimit
|
||||
self.lowerLimit = lowerLimit
|
||||
self.nucleus = nucleus
|
||||
self.limitShift = limitShift
|
||||
self.extraPadding = extraPadding
|
||||
super.init()
|
||||
|
||||
var maxWidth = max(nucleus?.width ?? 0, upperLimit?.width ?? 0)
|
||||
maxWidth = max(maxWidth, lowerLimit?.width ?? 0)
|
||||
width = maxWidth
|
||||
}
|
||||
|
||||
override var ascent: CGFloat {
|
||||
get {
|
||||
guard let nucleus else { return 0 }
|
||||
if let upperLimit {
|
||||
return nucleus.ascent + extraPadding + upperLimit.ascent + upperLimitGap + upperLimit.descent
|
||||
}
|
||||
return nucleus.ascent
|
||||
}
|
||||
set { super.ascent = newValue }
|
||||
}
|
||||
|
||||
override var descent: CGFloat {
|
||||
get {
|
||||
guard let nucleus else { return 0 }
|
||||
if let lowerLimit {
|
||||
return nucleus.descent + extraPadding + lowerLimitGap + lowerLimit.descent + lowerLimit.ascent
|
||||
}
|
||||
return nucleus.descent
|
||||
}
|
||||
set { super.descent = newValue }
|
||||
}
|
||||
|
||||
override var position: CGPoint {
|
||||
didSet {
|
||||
updateLowerLimitPosition()
|
||||
updateUpperLimitPosition()
|
||||
updateNucleusPosition()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateLowerLimitPosition() {
|
||||
guard let lowerLimit, let nucleus else { return }
|
||||
lowerLimit.position = CGPoint(
|
||||
x: position.x - limitShift + (width - lowerLimit.width) / 2,
|
||||
y: position.y - nucleus.descent - lowerLimitGap - lowerLimit.ascent
|
||||
)
|
||||
}
|
||||
|
||||
private func updateUpperLimitPosition() {
|
||||
guard let upperLimit, let nucleus else { return }
|
||||
upperLimit.position = CGPoint(
|
||||
x: position.x + limitShift + (width - upperLimit.width) / 2,
|
||||
y: position.y + nucleus.ascent + upperLimitGap + upperLimit.descent
|
||||
)
|
||||
}
|
||||
|
||||
private func updateNucleusPosition() {
|
||||
guard let nucleus else { return }
|
||||
nucleus.position = CGPoint(x: position.x + (width - nucleus.width) / 2, y: position.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Sources/SwiftUIMath/Internal/Display/DisplayLine.swift
Normal file
25
Sources/SwiftUIMath/Internal/Display/DisplayLine.swift
Normal file
@@ -0,0 +1,25 @@
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class DisplayLine: DisplayNode {
|
||||
var inner: DisplayList?
|
||||
var lineShiftUp: CGFloat = 0
|
||||
var lineThickness: CGFloat = 0
|
||||
|
||||
init(inner: DisplayList?, position: CGPoint, range: NSRange) {
|
||||
self.inner = inner
|
||||
super.init()
|
||||
self.position = position
|
||||
self.range = range
|
||||
}
|
||||
|
||||
override var position: CGPoint {
|
||||
didSet { updateInnerPosition() }
|
||||
}
|
||||
|
||||
private func updateInnerPosition() {
|
||||
inner?.position = CGPoint(x: position.x, y: position.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Sources/SwiftUIMath/Internal/Display/DisplayList.swift
Normal file
44
Sources/SwiftUIMath/Internal/Display/DisplayList.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class DisplayList: DisplayNode {
|
||||
enum LinePosition: Int {
|
||||
case regular
|
||||
case `subscript`
|
||||
case superscript
|
||||
}
|
||||
|
||||
var linePosition: LinePosition = .regular
|
||||
var children: [DisplayNode] = []
|
||||
var index: Int = NSNotFound
|
||||
|
||||
init(children: [DisplayNode], range: NSRange) {
|
||||
super.init()
|
||||
self.children = children
|
||||
self.position = .zero
|
||||
self.index = NSNotFound
|
||||
self.range = range
|
||||
recomputeDimensions()
|
||||
}
|
||||
|
||||
func recomputeDimensions() {
|
||||
var maxAscent: CGFloat = 0
|
||||
var maxDescent: CGFloat = 0
|
||||
var maxWidth: CGFloat = 0
|
||||
for node in children {
|
||||
let ascent = max(0, node.position.y + node.ascent)
|
||||
maxAscent = max(maxAscent, ascent)
|
||||
|
||||
let descent = max(0, 0 - (node.position.y - node.descent))
|
||||
maxDescent = max(maxDescent, descent)
|
||||
|
||||
let width = node.width + node.position.x
|
||||
maxWidth = max(maxWidth, width)
|
||||
}
|
||||
self.ascent = maxAscent
|
||||
self.descent = maxDescent
|
||||
self.width = maxWidth
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Sources/SwiftUIMath/Internal/Display/DisplayNode.swift
Normal file
17
Sources/SwiftUIMath/Internal/Display/DisplayNode.swift
Normal file
@@ -0,0 +1,17 @@
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
class DisplayNode {
|
||||
var ascent: CGFloat = 0
|
||||
var descent: CGFloat = 0
|
||||
var width: CGFloat = 0
|
||||
var position: CGPoint = .zero
|
||||
var range: NSRange = NSRange(location: 0, length: 0)
|
||||
var hasScript: Bool = false
|
||||
|
||||
func bounds() -> CGRect {
|
||||
CGRect(x: position.x, y: position.y - descent, width: width, height: ascent + descent)
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Sources/SwiftUIMath/Internal/Display/DisplayRadical.swift
Normal file
34
Sources/SwiftUIMath/Internal/Display/DisplayRadical.swift
Normal file
@@ -0,0 +1,34 @@
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class DisplayRadical: DisplayNode {
|
||||
var radicand: DisplayList?
|
||||
var degree: DisplayList?
|
||||
var radicalGlyph: DisplayNode?
|
||||
|
||||
var radicalShift: CGFloat = 0
|
||||
var topKern: CGFloat = 0
|
||||
var lineThickness: CGFloat = 0
|
||||
|
||||
init(radicand: DisplayList?, glyph: DisplayNode, position: CGPoint, range: NSRange) {
|
||||
self.radicand = radicand
|
||||
self.radicalGlyph = glyph
|
||||
super.init()
|
||||
self.position = position
|
||||
self.range = range
|
||||
}
|
||||
|
||||
override var position: CGPoint {
|
||||
didSet { updateRadicandPosition() }
|
||||
}
|
||||
|
||||
func updateRadicandPosition() {
|
||||
guard let radicand, let radicalGlyph else { return }
|
||||
radicand.position = CGPoint(
|
||||
x: position.x + radicalShift + radicalGlyph.width,
|
||||
y: position.y
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
class DisplayShiftedNode: DisplayNode {
|
||||
var shiftDown: CGFloat = 0
|
||||
}
|
||||
}
|
||||
19
Sources/SwiftUIMath/Internal/Display/DisplayTextRun.swift
Normal file
19
Sources/SwiftUIMath/Internal/Display/DisplayTextRun.swift
Normal file
@@ -0,0 +1,19 @@
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
extension Math {
|
||||
final class DisplayTextRun: DisplayNode {
|
||||
var text: String
|
||||
var font: Math.Font
|
||||
var atoms: [Math.Atom]
|
||||
|
||||
init(text: String, font: Math.Font, position: CGPoint = .zero, range: NSRange, atoms: [Math.Atom]) {
|
||||
self.text = text
|
||||
self.font = font
|
||||
self.atoms = atoms
|
||||
super.init()
|
||||
self.position = position
|
||||
self.range = range
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user