Files
swiftui-math/Sources/SwiftUIMath/Internal/Display/DisplayProvider.swift
2026-01-05 14:19:42 +01:00

119 lines
2.9 KiB
Swift

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
}
}