diff --git a/Sources/SwiftMath/MathRender/MTConfig.swift b/Sources/SwiftMath/MathRender/MTConfig.swift index 1ef5c70..f936d9d 100644 --- a/Sources/SwiftMath/MathRender/MTConfig.swift +++ b/Sources/SwiftMath/MathRender/MTConfig.swift @@ -18,6 +18,7 @@ public typealias MTBezierPath = UIBezierPath public typealias MTLabel = UILabel public typealias MTEdgeInsets = UIEdgeInsets public typealias MTRect = CGRect +public typealias MTImage = UIImage let MTEdgeInsetsZero = UIEdgeInsets.zero func MTGraphicsGetCurrentContext() -> CGContext? { UIGraphicsGetCurrentContext() } @@ -31,6 +32,7 @@ public typealias MTColor = NSColor public typealias MTBezierPath = NSBezierPath public typealias MTEdgeInsets = NSEdgeInsets public typealias MTRect = NSRect +public typealias MTImage = NSImage let MTEdgeInsetsZero = NSEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 0) func MTGraphicsGetCurrentContext() -> CGContext? { NSGraphicsContext.current?.cgContext } diff --git a/Sources/SwiftMath/MathRender/MTFontManager.swift b/Sources/SwiftMath/MathRender/MTFontManager.swift index 0ebb015..a2e0538 100644 --- a/Sources/SwiftMath/MathRender/MTFontManager.swift +++ b/Sources/SwiftMath/MathRender/MTFontManager.swift @@ -11,17 +11,16 @@ import Foundation public class MTFontManager { - static public private(set) var manager:MTFontManager! = nil + static public private(set) var manager: MTFontManager = { + MTFontManager() + }() let kDefaultFontSize = CGFloat(20) static var fontManager : MTFontManager { - if manager == nil { - manager = MTFontManager() - } - return manager! + return manager } - + public init() { } var nameToFontMap = [String: MTFont]() diff --git a/Sources/SwiftMath/MathRender/MTMathImage.swift b/Sources/SwiftMath/MathRender/MTMathImage.swift new file mode 100644 index 0000000..577cca7 --- /dev/null +++ b/Sources/SwiftMath/MathRender/MTMathImage.swift @@ -0,0 +1,111 @@ +// +// File.swift +// +// +// Created by Peter Tang on 12/9/2023. +// + +import Foundation + +#if os(iOS) + import UIKit +#endif + +#if os(macOS) + import AppKit +#endif + +public class MTMathImage { + public var font: MTFont? = nil + public let fontSize: CGFloat + public let textColor: MTColor + + public let labelMode: MTMathUILabelMode + public let textAlignment: MTTextAlignment + + public var contentInsets: MTEdgeInsets = MTEdgeInsetsZero + + public let latex: String + private(set) var intrinsicContentSize = CGSize.zero + + public init(latex: String, fontSize: CGFloat, textColor: MTColor, labelMode: MTMathUILabelMode = .display, textAlignment: MTTextAlignment = .center) { + self.latex = latex + self.fontSize = fontSize + self.textColor = textColor + self.labelMode = labelMode + self.textAlignment = textAlignment + } +} +extension MTMathImage { + public var currentStyle: MTLineStyle { + switch labelMode { + case .display: return .display + case .text: return .text + } + } + private func intrinsicContentSize(_ displayList: MTMathListDisplay) -> CGSize { + CGSize(width: displayList.width + contentInsets.left + contentInsets.right, + height: displayList.ascent + displayList.descent + contentInsets.top + contentInsets.bottom) + } + public func asImage() -> (NSError?, MTImage?) { + func layoutImage(size: CGSize, displayList: MTMathListDisplay) { + var textX = CGFloat(0) + switch self.textAlignment { + case .left: textX = contentInsets.left + case .center: textX = (size.width - contentInsets.left - contentInsets.right - displayList.width) / 2 + contentInsets.left + case .right: textX = size.width - displayList.width - contentInsets.right + } + let availableHeight = size.height - contentInsets.bottom - contentInsets.top + + // center things vertically + var height = displayList.ascent + displayList.descent + if height < fontSize/2 { + height = fontSize/2 // set height to half the font size + } + let textY = (availableHeight - height) / 2 + displayList.descent + contentInsets.bottom + displayList.position = CGPoint(x: textX, y: textY) + } + if font == nil { + self.font = MTFontManager.fontManager.defaultFont + } + var error: NSError? + guard let mathList = MTMathListBuilder.build(fromString: latex, error: &error), error == nil, + let displayList = MTTypesetter.createLineForMathList(mathList, font: font, style: currentStyle) else { + return (error, nil) + } + + intrinsicContentSize = intrinsicContentSize(displayList) + displayList.textColor = textColor + + let size = intrinsicContentSize + layoutImage(size: size, displayList: displayList) + + #if os(iOS) + let renderer = UIGraphicsImageRenderer(size: size) + let image = renderer.image { rendererContext in + rendererContext.cgContext.saveGState() + rendererContext.cgContext.concatenate(.flippedVertically(size.height)) + displayList.draw(rendererContext.cgContext) + rendererContext.cgContext.restoreGState() + } + return (nil, image) + #endif + #if os(macOS) + let image = NSImage(size: size, flipped: true) { bounds in + guard let context = NSGraphicsContext.current?.cgContext else { return false } + context.saveGState() + displayList.draw(context) + context.restoreGState() + return true + } + return (nil, image) + #endif + } +} +private extension CGAffineTransform { + static func flippedVertically(_ height: CGFloat) -> CGAffineTransform { + var transform = CGAffineTransform(scaleX: 1, y: -1) + transform = transform.translatedBy(x: 0, y: -height) + return transform + } +}