Add font infrastructure
This commit is contained in:
35
Sources/SwiftUIMath/Font.swift
Normal file
35
Sources/SwiftUIMath/Font.swift
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Math {
|
||||||
|
public struct Font: Hashable, Sendable {
|
||||||
|
public struct Name: Hashable, Sendable, RawRepresentable, ExpressibleByStringLiteral {
|
||||||
|
public let rawValue: String
|
||||||
|
|
||||||
|
public init(rawValue: String) {
|
||||||
|
self.rawValue = rawValue
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(stringLiteral value: StringLiteralType) {
|
||||||
|
self.rawValue = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public let name: Name
|
||||||
|
public let size: CGFloat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Math.Font.Name {
|
||||||
|
public static let latinModern: Self = "latinmodern-math"
|
||||||
|
public static let kpMathLight: Self = "KpMath-Light"
|
||||||
|
public static let kpMathSans: Self = "KpMath-Sans"
|
||||||
|
public static let xits: Self = "xits-math"
|
||||||
|
public static let termes: Self = "texgyretermes-math"
|
||||||
|
public static let asana: Self = "Asana-Math"
|
||||||
|
public static let euler: Self = "Euler-Math"
|
||||||
|
public static let fira: Self = "FiraMath-Regular"
|
||||||
|
public static let notoSans: Self = "NotoSansMath-Regular"
|
||||||
|
public static let libertinus: Self = "LibertinusMath-Regular"
|
||||||
|
public static let garamond: Self = "Garamond-Math"
|
||||||
|
public static let leteSans: Self = "LeteSansMath"
|
||||||
|
}
|
||||||
415
Sources/SwiftUIMath/Internal/Font/FontMetrics.swift
Normal file
415
Sources/SwiftUIMath/Internal/Font/FontMetrics.swift
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
@preconcurrency import CoreGraphics
|
||||||
|
@preconcurrency import CoreText
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Math {
|
||||||
|
struct FontMetrics: Sendable {
|
||||||
|
struct GlyphPart: Sendable {
|
||||||
|
let glyph: CGGlyph
|
||||||
|
let fullAdvance: CGFloat
|
||||||
|
let startConnectorLength: CGFloat
|
||||||
|
let endConnectorLength: CGFloat
|
||||||
|
let isExtender: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var mathUnit: CGFloat {
|
||||||
|
font.size / 18
|
||||||
|
}
|
||||||
|
|
||||||
|
private let font: Font
|
||||||
|
private let unitsPerEm: UInt
|
||||||
|
private let table: Table
|
||||||
|
|
||||||
|
init(font: Font, unitsPerEm: UInt, table: Table) {
|
||||||
|
self.font = font
|
||||||
|
self.unitsPerEm = unitsPerEm
|
||||||
|
self.table = table
|
||||||
|
}
|
||||||
|
|
||||||
|
func verticalVariants(forGlyph glyph: CGGlyph) -> [CGGlyph] {
|
||||||
|
guard
|
||||||
|
let graphicsFont = FontRegistry.shared.graphicsFont(named: font.name),
|
||||||
|
let glyphName = graphicsFont.name(for: glyph) as String?,
|
||||||
|
let variantGlyphs = table.vVariants[glyphName]
|
||||||
|
else {
|
||||||
|
return [glyph]
|
||||||
|
}
|
||||||
|
|
||||||
|
return variantGlyphs.map {
|
||||||
|
graphicsFont.getGlyphWithGlyphName(name: $0 as CFString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func horizontalVariants(forGlyph glyph: CGGlyph) -> [CGGlyph] {
|
||||||
|
guard
|
||||||
|
let graphicsFont = FontRegistry.shared.graphicsFont(named: font.name),
|
||||||
|
let glyphName = graphicsFont.name(for: glyph) as String?,
|
||||||
|
let variantGlyphs = table.hVariants[glyphName]
|
||||||
|
else {
|
||||||
|
return [glyph]
|
||||||
|
}
|
||||||
|
|
||||||
|
return variantGlyphs.map {
|
||||||
|
graphicsFont.getGlyphWithGlyphName(name: $0 as CFString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func largerGlyph(forGlyph glyph: CGGlyph) -> CGGlyph {
|
||||||
|
guard
|
||||||
|
let graphicsFont = FontRegistry.shared.graphicsFont(named: font.name),
|
||||||
|
let glyphName = graphicsFont.name(for: glyph) as String?,
|
||||||
|
let variantGlyphs = table.vVariants[glyphName]
|
||||||
|
else {
|
||||||
|
return glyph
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
variantGlyphs
|
||||||
|
.first { $0 != glyphName }
|
||||||
|
.map {
|
||||||
|
graphicsFont.getGlyphWithGlyphName(name: $0 as CFString)
|
||||||
|
} ?? glyph
|
||||||
|
}
|
||||||
|
|
||||||
|
func italicCorrection(forGlyph glyph: CGGlyph) -> CGFloat {
|
||||||
|
guard
|
||||||
|
let graphicsFont = FontRegistry.shared.graphicsFont(named: font.name),
|
||||||
|
let glyphName = graphicsFont.name(for: glyph) as String?,
|
||||||
|
let value = table.italic[glyphName]
|
||||||
|
else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return unitsToPoints(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func topAccentAdjustment(forGlyph glyph: CGGlyph) -> CGFloat {
|
||||||
|
guard
|
||||||
|
let graphicsFont = FontRegistry.shared.graphicsFont(named: font.name),
|
||||||
|
let glyphName = graphicsFont.name(for: glyph) as String?,
|
||||||
|
let value = table.accents[glyphName]
|
||||||
|
else {
|
||||||
|
// If no top accent is defined then it is the center of the advance width
|
||||||
|
return advance(forGlyph: glyph).width / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
return unitsToPoints(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verticalAssembly(forGlyph glyph: CGGlyph) -> [GlyphPart] {
|
||||||
|
guard
|
||||||
|
let graphicsFont = FontRegistry.shared.graphicsFont(named: font.name),
|
||||||
|
let glyphName = graphicsFont.name(for: glyph) as String?,
|
||||||
|
let assembly = table.vAssembly[glyphName]
|
||||||
|
else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return assembly.parts.map { part in
|
||||||
|
GlyphPart(
|
||||||
|
glyph: graphicsFont.getGlyphWithGlyphName(name: part.glyph as CFString),
|
||||||
|
fullAdvance: unitsToPoints(part.advance),
|
||||||
|
startConnectorLength: unitsToPoints(part.startConnector),
|
||||||
|
endConnectorLength: unitsToPoints(part.endConnector),
|
||||||
|
isExtender: part.extender
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Fractions
|
||||||
|
|
||||||
|
extension Math.FontMetrics {
|
||||||
|
var fractionNumeratorDisplayStyleShiftUp: CGFloat {
|
||||||
|
constant(named: "FractionNumeratorDisplayStyleShiftUp")
|
||||||
|
}
|
||||||
|
|
||||||
|
var fractionNumeratorShiftUp: CGFloat {
|
||||||
|
constant(named: "FractionNumeratorShiftUp")
|
||||||
|
}
|
||||||
|
|
||||||
|
var fractionDenominatorDisplayStyleShiftDown: CGFloat {
|
||||||
|
constant(named: "FractionDenominatorDisplayStyleShiftDown")
|
||||||
|
}
|
||||||
|
|
||||||
|
var fractionDenominatorShiftDown: CGFloat {
|
||||||
|
constant(named: "FractionDenominatorShiftDown")
|
||||||
|
}
|
||||||
|
|
||||||
|
var fractionNumeratorDisplayStyleGapMin: CGFloat {
|
||||||
|
constant(named: "FractionNumDisplayStyleGapMin")
|
||||||
|
}
|
||||||
|
|
||||||
|
var fractionNumeratorGapMin: CGFloat {
|
||||||
|
constant(named: "FractionNumeratorGapMin")
|
||||||
|
}
|
||||||
|
|
||||||
|
var fractionDenominatorDisplayStyleGapMin: CGFloat {
|
||||||
|
constant(named: "FractionDenomDisplayStyleGapMin")
|
||||||
|
}
|
||||||
|
|
||||||
|
var fractionDenominatorGapMin: CGFloat {
|
||||||
|
constant(named: "FractionDenominatorGapMin")
|
||||||
|
}
|
||||||
|
|
||||||
|
var fractionRuleThickness: CGFloat {
|
||||||
|
constant(named: "FractionRuleThickness")
|
||||||
|
}
|
||||||
|
|
||||||
|
var fractionDelimiterSize: CGFloat {
|
||||||
|
1.01 * font.size
|
||||||
|
}
|
||||||
|
|
||||||
|
var fractionDelimiterDisplayStyleSize: CGFloat {
|
||||||
|
2.39 * font.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Stacks
|
||||||
|
|
||||||
|
extension Math.FontMetrics {
|
||||||
|
var skewedFractionHorizonalGap: CGFloat {
|
||||||
|
constant(named: "SkewedFractionHorizontalGap")
|
||||||
|
}
|
||||||
|
|
||||||
|
var skewedFractionVerticalGap: CGFloat {
|
||||||
|
constant(named: "SkewedFractionVerticalGap")
|
||||||
|
}
|
||||||
|
|
||||||
|
var stackTopDisplayStyleShiftUp: CGFloat {
|
||||||
|
constant(named: "StackTopDisplayStyleShiftUp")
|
||||||
|
}
|
||||||
|
|
||||||
|
var stackTopShiftUp: CGFloat {
|
||||||
|
constant(named: "StackTopShiftUp")
|
||||||
|
}
|
||||||
|
|
||||||
|
var stackDisplayStyleGapMin: CGFloat {
|
||||||
|
constant(named: "StackDisplayStyleGapMin")
|
||||||
|
}
|
||||||
|
|
||||||
|
var stackGapMin: CGFloat {
|
||||||
|
constant(named: "StackGapMin")
|
||||||
|
}
|
||||||
|
|
||||||
|
var stackBottomDisplayStyleShiftDown: CGFloat {
|
||||||
|
constant(named: "StackBottomDisplayStyleShiftDown")
|
||||||
|
}
|
||||||
|
|
||||||
|
var stackBottomShiftDown: CGFloat {
|
||||||
|
constant(named: "StackBottomShiftDown")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Superscripts / Subscripts
|
||||||
|
|
||||||
|
extension Math.FontMetrics {
|
||||||
|
var superscriptShiftUp: CGFloat {
|
||||||
|
constant(named: "SuperscriptShiftUp")
|
||||||
|
}
|
||||||
|
|
||||||
|
var superscriptShiftUpCramped: CGFloat {
|
||||||
|
constant(named: "SuperscriptShiftUpCramped")
|
||||||
|
}
|
||||||
|
|
||||||
|
var subscriptShiftDown: CGFloat {
|
||||||
|
constant(named: "SubscriptShiftDown")
|
||||||
|
}
|
||||||
|
|
||||||
|
var superscriptBaselineDropMax: CGFloat {
|
||||||
|
constant(named: "SuperscriptBaselineDropMax")
|
||||||
|
}
|
||||||
|
|
||||||
|
var subscriptBaselineDropMin: CGFloat {
|
||||||
|
constant(named: "SubscriptBaselineDropMin")
|
||||||
|
}
|
||||||
|
|
||||||
|
var superscriptBottomMin: CGFloat {
|
||||||
|
constant(named: "SuperscriptBottomMin")
|
||||||
|
}
|
||||||
|
|
||||||
|
var subscriptTopMax: CGFloat {
|
||||||
|
constant(named: "SubscriptTopMax")
|
||||||
|
}
|
||||||
|
|
||||||
|
var subSuperscriptGapMin: CGFloat {
|
||||||
|
constant(named: "SubSuperscriptGapMin")
|
||||||
|
}
|
||||||
|
|
||||||
|
var superscriptBottomMaxWithSubscript: CGFloat {
|
||||||
|
constant(named: "SuperscriptBottomMaxWithSubscript")
|
||||||
|
}
|
||||||
|
|
||||||
|
var spaceAfterScript: CGFloat {
|
||||||
|
constant(named: "SpaceAfterScript")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Radicals
|
||||||
|
|
||||||
|
extension Math.FontMetrics {
|
||||||
|
var radicalExtraAscender: CGFloat {
|
||||||
|
constant(named: "RadicalExtraAscender")
|
||||||
|
}
|
||||||
|
|
||||||
|
var radicalRuleThickness: CGFloat {
|
||||||
|
constant(named: "RadicalRuleThickness")
|
||||||
|
}
|
||||||
|
|
||||||
|
var radicalDisplayStyleVerticalGap: CGFloat {
|
||||||
|
constant(named: "RadicalDisplayStyleVerticalGap")
|
||||||
|
}
|
||||||
|
|
||||||
|
var radicalVerticalGap: CGFloat {
|
||||||
|
constant(named: "RadicalVerticalGap")
|
||||||
|
}
|
||||||
|
|
||||||
|
var radicalKernBeforeDegree: CGFloat {
|
||||||
|
constant(named: "RadicalKernBeforeDegree")
|
||||||
|
}
|
||||||
|
|
||||||
|
var radicalKernAfterDegree: CGFloat {
|
||||||
|
constant(named: "RadicalKernAfterDegree")
|
||||||
|
}
|
||||||
|
|
||||||
|
var radicalDegreeBottomRaisePercent: CGFloat {
|
||||||
|
constantPercent(named: "RadicalDegreeBottomRaisePercent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Limits
|
||||||
|
|
||||||
|
extension Math.FontMetrics {
|
||||||
|
var upperLimitBaselineRiseMin: CGFloat {
|
||||||
|
constant(named: "UpperLimitBaselineRiseMin")
|
||||||
|
}
|
||||||
|
|
||||||
|
var upperLimitGapMin: CGFloat {
|
||||||
|
constant(named: "UpperLimitGapMin")
|
||||||
|
}
|
||||||
|
|
||||||
|
var lowerLimitGapMin: CGFloat {
|
||||||
|
constant(named: "LowerLimitGapMin")
|
||||||
|
}
|
||||||
|
|
||||||
|
var lowerLimitBaselineDropMin: CGFloat {
|
||||||
|
constant(named: "LowerLimitBaselineDropMin")
|
||||||
|
}
|
||||||
|
|
||||||
|
var limitExtraAscenderDescender: CGFloat {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Underline
|
||||||
|
|
||||||
|
extension Math.FontMetrics {
|
||||||
|
var underbarVerticalGap: CGFloat {
|
||||||
|
constant(named: "UnderbarVerticalGap")
|
||||||
|
}
|
||||||
|
|
||||||
|
var underbarRuleThickness: CGFloat {
|
||||||
|
constant(named: "UnderbarRuleThickness")
|
||||||
|
}
|
||||||
|
|
||||||
|
var underbarExtraDescender: CGFloat {
|
||||||
|
constant(named: "UnderbarExtraDescender")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Overline
|
||||||
|
|
||||||
|
extension Math.FontMetrics {
|
||||||
|
var overbarVerticalGap: CGFloat {
|
||||||
|
constant(named: "OverbarVerticalGap")
|
||||||
|
}
|
||||||
|
|
||||||
|
var overbarRuleThickness: CGFloat {
|
||||||
|
constant(named: "OverbarRuleThickness")
|
||||||
|
}
|
||||||
|
|
||||||
|
var overbarExtraAscender: CGFloat {
|
||||||
|
constant(named: "OverbarExtraAscender")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Constants
|
||||||
|
|
||||||
|
extension Math.FontMetrics {
|
||||||
|
var axisHeight: CGFloat {
|
||||||
|
constant(named: "AxisHeight")
|
||||||
|
}
|
||||||
|
|
||||||
|
var scriptScaleDown: CGFloat {
|
||||||
|
constantPercent(named: "ScriptPercentScaleDown")
|
||||||
|
}
|
||||||
|
|
||||||
|
var scriptScriptScaleDown: CGFloat {
|
||||||
|
constantPercent(named: "ScriptScriptPercentScaleDown")
|
||||||
|
}
|
||||||
|
|
||||||
|
var mathLeading: CGFloat {
|
||||||
|
constant(named: "MathLeading")
|
||||||
|
}
|
||||||
|
|
||||||
|
var delimitedSubFormulaMinHeight: CGFloat {
|
||||||
|
constant(named: "DelimitedSubFormulaMinHeight")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Accent
|
||||||
|
|
||||||
|
extension Math.FontMetrics {
|
||||||
|
var accentBaseHeight: CGFloat {
|
||||||
|
constant(named: "AccentBaseHeight")
|
||||||
|
}
|
||||||
|
|
||||||
|
var flattenedAccentBaseHeight: CGFloat {
|
||||||
|
constant(named: "FlattenedAccentBaseHeight")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Glyph Construction
|
||||||
|
|
||||||
|
extension Math.FontMetrics {
|
||||||
|
var minConnectorOverlap: CGFloat {
|
||||||
|
constant(named: "MinConnectorOverlap")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
extension Math.FontMetrics {
|
||||||
|
private func constant(named name: String) -> CGFloat {
|
||||||
|
guard let value = table.constants[name] else {
|
||||||
|
return .zero
|
||||||
|
}
|
||||||
|
return unitsToPoints(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func constantPercent(named name: String) -> CGFloat {
|
||||||
|
guard let value = table.constants[name] else {
|
||||||
|
return .zero
|
||||||
|
}
|
||||||
|
return CGFloat(value) / 100
|
||||||
|
}
|
||||||
|
|
||||||
|
private func unitsToPoints(_ units: Int) -> CGFloat {
|
||||||
|
CGFloat(units) * font.size / CGFloat(unitsPerEm)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func advance(forGlyph glyph: CGGlyph) -> CGSize {
|
||||||
|
guard
|
||||||
|
let font = Math.FontRegistry.shared.font(named: font.name, size: font.size)
|
||||||
|
else {
|
||||||
|
return .zero
|
||||||
|
}
|
||||||
|
|
||||||
|
var glyph = glyph
|
||||||
|
var advance = CGSize.zero
|
||||||
|
|
||||||
|
CTFontGetAdvancesForGlyphs(font, .horizontal, &glyph, &advance, 1)
|
||||||
|
return advance
|
||||||
|
}
|
||||||
|
}
|
||||||
114
Sources/SwiftUIMath/Internal/Font/FontRegistry.swift
Normal file
114
Sources/SwiftUIMath/Internal/Font/FontRegistry.swift
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
@preconcurrency import CoreGraphics
|
||||||
|
@preconcurrency import CoreText
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Math {
|
||||||
|
final class FontRegistry: Sendable {
|
||||||
|
static let shared = FontRegistry()
|
||||||
|
|
||||||
|
private struct Cache {
|
||||||
|
var graphicsFonts: [Font.Name: CGFont] = [:]
|
||||||
|
var tables: [Font.Name: Table] = [:]
|
||||||
|
let fonts = NSCache<KeyBox<Font>, CTFont>()
|
||||||
|
}
|
||||||
|
|
||||||
|
private let cache = ReadWriteLockIsolated<Cache>(Cache())
|
||||||
|
|
||||||
|
func graphicsFont(named name: Font.Name) -> CGFont? {
|
||||||
|
cache.withValue { cache in
|
||||||
|
if let graphicsFont = cache.graphicsFonts[name] {
|
||||||
|
return graphicsFont
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let (graphicsFont, _) = registerGraphicsFont(named: name, cache: &cache) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return graphicsFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func table(named name: Font.Name) -> Table? {
|
||||||
|
cache.withValue { cache in
|
||||||
|
if let table = cache.tables[name] {
|
||||||
|
return table
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let (_, table) = registerGraphicsFont(named: name, cache: &cache) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return table
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func font(named name: Font.Name, size: CGFloat) -> CTFont? {
|
||||||
|
cache.withValue { cache in
|
||||||
|
let key = KeyBox(Font(name: name, size: size))
|
||||||
|
|
||||||
|
if let font = cache.fonts.object(forKey: key) {
|
||||||
|
return font
|
||||||
|
}
|
||||||
|
|
||||||
|
guard
|
||||||
|
let graphicsFont = cache.graphicsFonts[name]
|
||||||
|
?? registerGraphicsFont(named: name, cache: &cache)?.0
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let font = CTFontCreateWithGraphicsFont(graphicsFont, size, nil, nil)
|
||||||
|
cache.fonts.setObject(font, forKey: key)
|
||||||
|
|
||||||
|
return font
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func registerGraphicsFont(
|
||||||
|
named name: Font.Name,
|
||||||
|
cache: inout Cache
|
||||||
|
) -> (CGFont, Table)? {
|
||||||
|
guard let graphicsFont = CGFont.named(name), let table = Table.named(name) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard CTFontManagerRegisterGraphicsFont(graphicsFont, nil) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.graphicsFonts[name] = graphicsFont
|
||||||
|
cache.tables[name] = table
|
||||||
|
|
||||||
|
return (graphicsFont, table)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CGFont {
|
||||||
|
fileprivate static func named(_ name: Math.Font.Name) -> CGFont? {
|
||||||
|
guard
|
||||||
|
let bundleURL = Bundle.module.url(forResource: "mathFonts", withExtension: "bundle"),
|
||||||
|
let url = Bundle(url: bundleURL)?.url(forResource: name.rawValue, withExtension: "otf"),
|
||||||
|
let data = try? Data(contentsOf: url),
|
||||||
|
let dataProvider = CGDataProvider(data: data as CFData)
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return CGFont(dataProvider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Math.Table {
|
||||||
|
fileprivate static func named(_ name: Math.Font.Name) -> Math.Table? {
|
||||||
|
guard
|
||||||
|
let bundleURL = Bundle.module.url(forResource: "mathFonts", withExtension: "bundle"),
|
||||||
|
let url = Bundle(url: bundleURL)?.url(forResource: name.rawValue, withExtension: "plist"),
|
||||||
|
let data = try? Data(contentsOf: url)
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return try? PropertyListDecoder().decode(Math.Table.self, from: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
35
Sources/SwiftUIMath/Internal/Font/PlatformFont.swift
Normal file
35
Sources/SwiftUIMath/Internal/Font/PlatformFont.swift
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
@preconcurrency import CoreGraphics
|
||||||
|
@preconcurrency import CoreText
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Math {
|
||||||
|
final class PlatformFont: Sendable {
|
||||||
|
let font: Font
|
||||||
|
let cgFont: CGFont
|
||||||
|
let ctFont: CTFont
|
||||||
|
let metrics: FontMetrics
|
||||||
|
|
||||||
|
init?(font: Font) {
|
||||||
|
guard
|
||||||
|
let cgFont = FontRegistry.shared.graphicsFont(named: font.name),
|
||||||
|
let ctFont = FontRegistry.shared.font(named: font.name, size: font.size),
|
||||||
|
let table = FontRegistry.shared.table(named: font.name)
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.font = font
|
||||||
|
self.cgFont = cgFont
|
||||||
|
self.ctFont = ctFont
|
||||||
|
self.metrics = FontMetrics(
|
||||||
|
font: font,
|
||||||
|
unitsPerEm: UInt(CTFontGetUnitsPerEm(ctFont)),
|
||||||
|
table: table
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withSize(_ size: CGFloat) -> PlatformFont {
|
||||||
|
PlatformFont(font: .init(name: font.name, size: size))!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Sources/SwiftUIMath/Internal/Font/Table.swift
Normal file
39
Sources/SwiftUIMath/Internal/Font/Table.swift
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Math {
|
||||||
|
struct Table: Codable, Sendable {
|
||||||
|
struct Assembly: Codable, Sendable {
|
||||||
|
struct Part: Codable, Sendable {
|
||||||
|
let advance: Int
|
||||||
|
let endConnector: Int
|
||||||
|
let extender: Bool
|
||||||
|
let glyph: String
|
||||||
|
let startConnector: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
let italic: Int
|
||||||
|
let parts: [Part]
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case version
|
||||||
|
case accents
|
||||||
|
case constants
|
||||||
|
case italic
|
||||||
|
case hVariants = "h_variants"
|
||||||
|
case vVariants = "v_variants"
|
||||||
|
case vAssembly = "v_assembly"
|
||||||
|
}
|
||||||
|
|
||||||
|
let version: String
|
||||||
|
|
||||||
|
let accents: [String: Int]
|
||||||
|
let constants: [String: Int]
|
||||||
|
let italic: [String: Int]
|
||||||
|
|
||||||
|
let hVariants: [String: [String]]
|
||||||
|
let vVariants: [String: [String]]
|
||||||
|
|
||||||
|
let vAssembly: [String: Assembly]
|
||||||
|
}
|
||||||
|
}
|
||||||
22
Sources/SwiftUIMath/Internal/Helpers/KeyBox.swift
Normal file
22
Sources/SwiftUIMath/Internal/Helpers/KeyBox.swift
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class KeyBox<Value: Hashable>: NSObject {
|
||||||
|
let wrappedValue: Value
|
||||||
|
|
||||||
|
init(_ wrappedValue: Value) {
|
||||||
|
self.wrappedValue = wrappedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
override var hash: Int {
|
||||||
|
var hasher = Hasher()
|
||||||
|
hasher.combine(wrappedValue)
|
||||||
|
return hasher.finalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func isEqual(_ object: Any?) -> Bool {
|
||||||
|
guard let other = object as? KeyBox<Value> else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return wrappedValue == other.wrappedValue
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
// Derived from SwiftMath by Mike Griebling (MIT License)
|
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
#if canImport(UIKit)
|
#if canImport(UIKit)
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// Derived from SwiftMath by Mike Griebling (MIT License)
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension String {
|
extension String {
|
||||||
|
|||||||
14
Sources/SwiftUIMath/Math.swift
Normal file
14
Sources/SwiftUIMath/Math.swift
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public struct Math: View {
|
||||||
|
public init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
Text("TODO: implement")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
Math()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user