import CoreText import Foundation import Synchronization import SwiftData import SwiftUI @MainActor @Observable final class FontSize { static let shared = FontSize() static let range = 9...20 private(set) var body = PrefsRecord.defaultFontSize private(set) var code = PrefsRecord.defaultFontSize private init() { load() } func load() { let r = record() body = r?.fontSize ?? PrefsRecord.defaultFontSize code = r?.codeFontSize ?? PrefsRecord.defaultFontSize } func set(body: Int, code: Int) { if let r = record() { r.fontSize = body r.codeFontSize = code r.saved = Date() } else { persistence.insert(PrefsRecord(fontSize: body, codeFontSize: code)) } saveRecords() self.body = body self.code = code } private func record() -> PrefsRecord? { fetchRecords(FetchDescriptor(sortBy: [SortDescriptor(\.saved, order: .reverse)])).first } } enum Fonts { private static func file(_ family: String, _ subset: String, _ italic: Bool, _ weight: CGFloat) -> CTFontDescriptor { let url = Bundle.main.resourceURL!.appending(path: "fonts/atkinson-hyperlegible-\(family)-\(subset)-wght-\(italic ? "italic" : "normal").woff2") let d = (CTFontManagerCreateFontDescriptorsFromURL(url as CFURL) as! [CTFontDescriptor])[0] return CTFontDescriptorCreateCopyWithVariation(d, 0x7767_6874 as CFNumber, weight) } private static func ct(_ size: CGFloat, weight: CGFloat, italic: Bool, mono: Bool) -> CTFont { let family = mono ? "mono" : "next" let ext = file(family, "latin-ext", italic, weight) let d = CTFontDescriptorCreateCopyWithAttributes(file(family, "latin", italic, weight), [kCTFontCascadeListAttribute: [ext]] as CFDictionary) return CTFontCreateWithFontDescriptor(d, size, nil) } private static let fonts = Mutex<[String: CTFont]>([:]) // Call sites give sizes relative to a 13pt body; the user's text or code size shifts them. @MainActor private static func cached(_ size: CGFloat, _ weight: CGFloat, _ italic: Bool, _ mono: Bool) -> CTFont { let size = size + CGFloat((mono ? FontSize.shared.code : FontSize.shared.body) - 13) let key = "\(size) \(weight) \(italic) \(mono)" return fonts.withLock { fonts in if let f = fonts[key] { return f } let f = ct(size, weight: weight, italic: italic, mono: mono) fonts[key] = f return f } } @MainActor static func font(_ size: CGFloat, weight: CGFloat = 400, italic: Bool = false, mono: Bool = false) -> Font { Font(cached(size, weight, italic, mono)) } @MainActor static func ns(_ size: CGFloat, weight: CGFloat = 400, italic: Bool = false, mono: Bool = false) -> NSFont { cached(size, weight, italic, mono) as NSFont } }