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