Implement Math view

This commit is contained in:
Guille Gonzalez
2026-01-04 15:33:48 +01:00
parent 6e1a38ede7
commit baea9de415
9 changed files with 356 additions and 8 deletions

View File

@@ -13,7 +13,7 @@ extension Math {
var localTextColor: CGColor?
var localBackgroundColor: CGColor?
func bounds() -> CGRect {
var bounds: CGRect {
CGRect(x: position.x, y: position.y - descent, width: width, height: ascent + descent)
}
}

View File

@@ -0,0 +1,118 @@
import Foundation
extension Math {
final class DisplayProvider: Sendable {
private struct Cache {
struct Key: Hashable {
let latex: String
let font: Font
let style: TypesettingStyle
let proposedWidth: CGFloat
}
let atomList = NSCache<NSString, AtomList>()
let displayNode = NSCache<KeyBox<Key>, DisplayNode>()
}
static let shared = DisplayProvider()
private let cache = ReadWriteLockIsolated<Cache>(Cache())
func sizeThatFits(
proposedWidth width: CGFloat,
latex: String,
font: Font,
style: TypesettingStyle
) -> CGSize {
display(
for: latex,
font: font,
style: style,
proposedWidth: width
)?.bounds.size ?? .zero
}
func display(
for latex: String,
font: Font,
style: TypesettingStyle,
proposedWidth: CGFloat
) -> DisplayNode? {
cache.withValue { cache in
let roundedWidth = proposedWidth.halfPointRounded()
let key = KeyBox(
Cache.Key(
latex: latex,
font: font,
style: style,
proposedWidth: roundedWidth
)
)
if let displayNode = cache.displayNode.object(forKey: key) {
return displayNode
}
guard
let atomList = atomList(for: latex, cache: &cache),
let displayNode = Typesetter.createLineForMathList(
atomList,
font: .init(font: font),
style: .init(style),
maxWidth: roundedWidth
)
else {
return nil
}
cache.displayNode.setObject(displayNode, forKey: key)
if displayNode.width != roundedWidth {
// Cache the measured width to avoid a miss between layout and draw passes
let secondaryKey = KeyBox(
Cache.Key(
latex: latex,
font: font,
style: style,
proposedWidth: displayNode.width.halfPointRounded()
)
)
cache.displayNode.setObject(displayNode, forKey: secondaryKey)
}
return displayNode
}
}
private func atomList(for latex: String, cache: inout Cache) -> AtomList? {
if let atomList = cache.atomList.object(forKey: latex as NSString) {
return atomList
}
guard let atomList = Parser.build(fromString: latex) else {
return nil
}
cache.atomList.setObject(atomList, forKey: latex as NSString)
return atomList
}
}
}
extension Math.Style.Level {
fileprivate init(_ typesettingStyle: Math.TypesettingStyle) {
switch typesettingStyle {
case .display:
self = .display
case .text:
self = .text
}
}
}
extension CGFloat {
fileprivate func halfPointRounded() -> CGFloat {
guard self > 0 else { return 0 }
return (self * 2).rounded() / 2
}
}

View File

@@ -6,6 +6,7 @@ extension GraphicsContext {
context.translateBy(x: 0, y: size.height)
context.scaleBy(x: 1, y: -1)
context.translateBy(x: 0, y: displayNode.descent)
let foregroundColor = foregroundColor.resolve(in: environment).cgColor

View File

@@ -1793,6 +1793,10 @@ extension Math {
currentLine.addAttribute(
kCTFontAttributeName as NSAttributedString.Key, value: styleFont.ctFont as Any,
range: NSMakeRange(0, currentLine.length))
currentLine.addAttribute(
NSAttributedString.Key(kCTForegroundColorFromContextAttributeName as String),
value: true,
range: NSMakeRange(0, currentLine.length))
/*assert(currentLineIndexRange.length == numCodePoints(currentLine.string),
"The length of the current line: %@ does not match the length of the range (%d, %d)",
currentLine, currentLineIndexRange.location, currentLineIndexRange.length);*/
@@ -2410,6 +2414,10 @@ extension Math {
line.addAttribute(
kCTFontAttributeName as NSAttributedString.Key, value: styleFont.ctFont,
range: NSMakeRange(0, line.length))
line.addAttribute(
NSAttributedString.Key(kCTForegroundColorFromContextAttributeName as String),
value: true,
range: NSMakeRange(0, line.length))
let attributedString = line.copy() as! NSAttributedString
let ctLine = CTLineCreateWithAttributedString(attributedString)
let displayAtom = DisplayTextRun(