// FontStore.swift // Observable store that tracks the active terminal font and point size, and // persists both values across launches via UserDefaults. import Foundation import UIKit import Combine private let kFontIdKey = "terminal.font" private let kFontSizeKey = "terminal.fontSize" private let kDefaultSize: CGFloat = 13 @MainActor public final class FontStore: ObservableObject { // MARK: Singleton public static let shared = FontStore() // MARK: Published state @Published public private(set) var current: TerminalFont @Published public private(set) var size: CGFloat // MARK: Available fonts (ordered: default first) public let available: [TerminalFont] = [.sfMono, .menlo, .jetBrainsMono] // MARK: Init private init() { // Restore point size (clamped to a sane range). let storedSize = UserDefaults.standard.object(forKey: kFontSizeKey) as? CGFloat size = storedSize.map { max(8, min(32, $0)) } ?? kDefaultSize // Restore selected font id. let all: [TerminalFont] = [.sfMono, .menlo, .jetBrainsMono] if let savedId = UserDefaults.standard.string(forKey: kFontIdKey), let saved = all.first(where: { $0.id == savedId }) { current = saved } else { current = .sfMono } } // MARK: Public API /// Makes `font` the active font and persists the choice. public func select(_ font: TerminalFont) { current = font UserDefaults.standard.set(font.id, forKey: kFontIdKey) } /// Sets the point size (clamped to 8–32 pt) and persists it. public func setSize(_ newSize: CGFloat) { let clamped = max(8, min(32, newSize)) size = clamped UserDefaults.standard.set(clamped, forKey: kFontSizeKey) } // MARK: Derived helpers /// Returns the current font scaled to the current point size. public var scaledFont: UIFont { current.uiFont.withSize(size) } }