Implement DisplayNode drawing
This commit is contained in:
@@ -5,10 +5,10 @@ import PackageDescription
|
||||
let package = Package(
|
||||
name: "swiftui-math",
|
||||
platforms: [
|
||||
.macOS(.v12),
|
||||
.iOS(.v15),
|
||||
.tvOS(.v15),
|
||||
.watchOS(.v8),
|
||||
.macOS(.v14),
|
||||
.iOS(.v17),
|
||||
.tvOS(.v17),
|
||||
.watchOS(.v10),
|
||||
.visionOS(.v1),
|
||||
],
|
||||
products: [
|
||||
|
||||
227
Sources/SwiftUIMath/Internal/Display/CGContext+DisplayNode.swift
Normal file
227
Sources/SwiftUIMath/Internal/Display/CGContext+DisplayNode.swift
Normal file
@@ -0,0 +1,227 @@
|
||||
import CoreGraphics
|
||||
import CoreText
|
||||
import Foundation
|
||||
|
||||
extension CGContext {
|
||||
func draw(_ displayNode: Math.DisplayNode, foregroundColor: CGColor) {
|
||||
let foregroundColor =
|
||||
displayNode.localTextColor?.cgColor
|
||||
?? displayNode.textColor?.cgColor
|
||||
?? foregroundColor
|
||||
|
||||
switch displayNode {
|
||||
case let list as Math.DisplayList:
|
||||
draw(list, foregroundColor: foregroundColor)
|
||||
case let textRun as Math.DisplayTextRun:
|
||||
draw(textRun, foregroundColor: foregroundColor)
|
||||
case let glyph as Math.DisplayGlyph:
|
||||
draw(glyph, foregroundColor: foregroundColor)
|
||||
case let glyphRun as Math.DisplayGlyphRun:
|
||||
draw(glyphRun, foregroundColor: foregroundColor)
|
||||
case let fraction as Math.DisplayFraction:
|
||||
draw(fraction, foregroundColor: foregroundColor)
|
||||
case let radical as Math.DisplayRadical:
|
||||
draw(radical, foregroundColor: foregroundColor)
|
||||
case let line as Math.DisplayLine:
|
||||
draw(line, foregroundColor: foregroundColor)
|
||||
case let largeOperator as Math.DisplayLargeOperator:
|
||||
draw(largeOperator, foregroundColor: foregroundColor)
|
||||
case let accent as Math.DisplayAccent:
|
||||
draw(accent, foregroundColor: foregroundColor)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CGContext {
|
||||
private func draw(_ list: Math.DisplayList, foregroundColor: CGColor) {
|
||||
saveGState()
|
||||
|
||||
translateBy(x: list.position.x, y: list.position.y)
|
||||
textPosition = .zero
|
||||
|
||||
for child in list.children {
|
||||
draw(child, foregroundColor: foregroundColor)
|
||||
}
|
||||
|
||||
restoreGState()
|
||||
}
|
||||
|
||||
private func draw(_ textRun: Math.DisplayTextRun, foregroundColor: CGColor) {
|
||||
guard let platformFont = Math.PlatformFont(font: textRun.font) else {
|
||||
return
|
||||
}
|
||||
|
||||
let attributes: [NSAttributedString.Key: Any] = [
|
||||
NSAttributedString.Key(kCTFontAttributeName as String): platformFont.ctFont,
|
||||
NSAttributedString.Key(kCTForegroundColorAttributeName as String): foregroundColor,
|
||||
]
|
||||
let attributedString = NSAttributedString(string: textRun.text, attributes: attributes)
|
||||
let line = CTLineCreateWithAttributedString(attributedString)
|
||||
|
||||
saveGState()
|
||||
textPosition = textRun.position
|
||||
CTLineDraw(line, self)
|
||||
restoreGState()
|
||||
}
|
||||
|
||||
private func draw(_ glyph: Math.DisplayGlyph, foregroundColor: CGColor) {
|
||||
guard let platformFont = Math.PlatformFont(font: glyph.font) else {
|
||||
return
|
||||
}
|
||||
|
||||
saveGState()
|
||||
|
||||
translateBy(x: glyph.position.x, y: glyph.position.y - glyph.shiftDown)
|
||||
textPosition = .zero
|
||||
|
||||
setFillColor(foregroundColor)
|
||||
|
||||
var cgGlyph = CGGlyph(glyph.glyph)
|
||||
var pos = CGPoint.zero
|
||||
CTFontDrawGlyphs(platformFont.ctFont, &cgGlyph, &pos, 1, self)
|
||||
|
||||
restoreGState()
|
||||
}
|
||||
|
||||
private func draw(_ glyphRun: Math.DisplayGlyphRun, foregroundColor: CGColor) {
|
||||
guard let platformFont = Math.PlatformFont(font: glyphRun.font) else {
|
||||
return
|
||||
}
|
||||
|
||||
saveGState()
|
||||
|
||||
translateBy(x: glyphRun.position.x, y: glyphRun.position.y - glyphRun.shiftDown)
|
||||
textPosition = .zero
|
||||
|
||||
setFillColor(foregroundColor)
|
||||
|
||||
var glyphs = glyphRun.glyphs.map { CGGlyph($0) }
|
||||
var positions = glyphRun.offsets.map { CGPoint(x: 0, y: $0) }
|
||||
CTFontDrawGlyphs(platformFont.ctFont, &glyphs, &positions, glyphs.count, self)
|
||||
|
||||
restoreGState()
|
||||
}
|
||||
|
||||
private func draw(_ fraction: Math.DisplayFraction, foregroundColor: CGColor) {
|
||||
if let numerator = fraction.numerator {
|
||||
draw(numerator, foregroundColor: foregroundColor)
|
||||
}
|
||||
if let denominator = fraction.denominator {
|
||||
draw(denominator, foregroundColor: foregroundColor)
|
||||
}
|
||||
|
||||
guard fraction.lineThickness > 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
saveGState()
|
||||
|
||||
setStrokeColor(foregroundColor)
|
||||
setLineWidth(fraction.lineThickness)
|
||||
|
||||
let lineStart = CGPoint(
|
||||
x: fraction.position.x,
|
||||
y: fraction.position.y + fraction.linePosition
|
||||
)
|
||||
let lineEnd = CGPoint(
|
||||
x: fraction.position.x + fraction.width,
|
||||
y: lineStart.y
|
||||
)
|
||||
|
||||
move(to: lineStart)
|
||||
addLine(to: lineEnd)
|
||||
strokePath()
|
||||
|
||||
restoreGState()
|
||||
}
|
||||
|
||||
private func draw(_ radical: Math.DisplayRadical, foregroundColor: CGColor) {
|
||||
if let radicand = radical.radicand {
|
||||
draw(radicand, foregroundColor: foregroundColor)
|
||||
}
|
||||
if let degree = radical.degree {
|
||||
draw(degree, foregroundColor: foregroundColor)
|
||||
}
|
||||
|
||||
saveGState()
|
||||
|
||||
setStrokeColor(foregroundColor)
|
||||
setFillColor(foregroundColor)
|
||||
|
||||
translateBy(x: radical.position.x + radical.radicalShift, y: radical.position.y)
|
||||
textPosition = .zero
|
||||
|
||||
if let radicalGlyph = radical.radicalGlyph {
|
||||
draw(radicalGlyph, foregroundColor: foregroundColor)
|
||||
}
|
||||
|
||||
let heightFromTop = radical.topKern
|
||||
let glyphWidth = radical.radicalGlyph?.width ?? 0
|
||||
let radicandWidth = radical.radicand?.width ?? 0
|
||||
let lineStart = CGPoint(
|
||||
x: glyphWidth,
|
||||
y: radical.ascent - heightFromTop - radical.lineThickness / 2
|
||||
)
|
||||
let lineEnd = CGPoint(x: lineStart.x + radicandWidth, y: lineStart.y)
|
||||
|
||||
setLineWidth(radical.lineThickness)
|
||||
setLineCap(.round)
|
||||
move(to: lineStart)
|
||||
addLine(to: lineEnd)
|
||||
strokePath()
|
||||
|
||||
restoreGState()
|
||||
}
|
||||
|
||||
private func draw(_ line: Math.DisplayLine, foregroundColor: CGColor) {
|
||||
if let inner = line.inner {
|
||||
draw(inner, foregroundColor: foregroundColor)
|
||||
}
|
||||
|
||||
saveGState()
|
||||
|
||||
setStrokeColor(foregroundColor)
|
||||
setLineWidth(line.lineThickness)
|
||||
|
||||
let lineStart = CGPoint(x: line.position.x, y: line.position.y + line.lineShiftUp)
|
||||
let lineEnd = CGPoint(x: lineStart.x + (line.inner?.width ?? 0), y: lineStart.y)
|
||||
|
||||
move(to: lineStart)
|
||||
addLine(to: lineEnd)
|
||||
strokePath()
|
||||
|
||||
restoreGState()
|
||||
}
|
||||
|
||||
private func draw(_ largeOperator: Math.DisplayLargeOperator, foregroundColor: CGColor) {
|
||||
if let upperLimit = largeOperator.upperLimit {
|
||||
draw(upperLimit, foregroundColor: foregroundColor)
|
||||
}
|
||||
if let lowerLimit = largeOperator.lowerLimit {
|
||||
draw(lowerLimit, foregroundColor: foregroundColor)
|
||||
}
|
||||
if let nucleus = largeOperator.nucleus {
|
||||
draw(nucleus, foregroundColor: foregroundColor)
|
||||
}
|
||||
}
|
||||
|
||||
private func draw(_ accent: Math.DisplayAccent, foregroundColor: CGColor) {
|
||||
if let accentee = accent.accentee {
|
||||
draw(accentee, foregroundColor: foregroundColor)
|
||||
}
|
||||
|
||||
guard let accentGlyph = accent.accent else {
|
||||
return
|
||||
}
|
||||
|
||||
saveGState()
|
||||
|
||||
translateBy(x: accent.position.x, y: accent.position.y)
|
||||
textPosition = .zero
|
||||
draw(accentGlyph, foregroundColor: foregroundColor)
|
||||
|
||||
restoreGState()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import SwiftUI
|
||||
|
||||
extension GraphicsContext {
|
||||
func draw(_ displayNode: Math.DisplayNode, size: CGSize, foregroundColor: Color) {
|
||||
var context = self
|
||||
|
||||
context.translateBy(x: 0, y: size.height)
|
||||
context.scaleBy(x: 1, y: -1)
|
||||
|
||||
let foregroundColor = foregroundColor.resolve(in: environment).cgColor
|
||||
|
||||
context.withCGContext { cgContext in
|
||||
cgContext.draw(displayNode, foregroundColor: foregroundColor)
|
||||
}
|
||||
}
|
||||
|
||||
func draw(_ displayNode: Math.DisplayNode, size: CGSize, with shading: GraphicsContext.Shading) {
|
||||
var context = self
|
||||
|
||||
context.fill(Path(CGRect(origin: .zero, size: size)), with: shading)
|
||||
context.blendMode = .destinationIn
|
||||
|
||||
context.drawLayer {
|
||||
$0.draw(displayNode, size: size, foregroundColor: .black)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user