141 lines
3.7 KiB
Swift
141 lines
3.7 KiB
Swift
//
|
|
// FontUtils.swift
|
|
// TheNoiseClock
|
|
//
|
|
// Created by Matt Bruce on 9/7/25.
|
|
//
|
|
|
|
import Foundation
|
|
import SwiftUI
|
|
import UIKit
|
|
|
|
/// Font sizing and typography utilities
|
|
struct FontUtils {
|
|
|
|
static func weightedFontName(
|
|
name: String,
|
|
weight: Font.Weight,
|
|
design: Font.Design
|
|
) -> String {
|
|
let weightSuffix = weight.uiFontWeightSuffix
|
|
|
|
switch design {
|
|
case .rounded:
|
|
if name.lowercased() == "system" { return "System" }
|
|
return name
|
|
+ (weightSuffix.isEmpty ? "-Rounded" : weightSuffix + "Rounded")
|
|
case .monospaced:
|
|
if name.lowercased() == "system" { return "Courier" }
|
|
return name == "Courier"
|
|
? name + weightSuffix
|
|
: name
|
|
+ (weightSuffix.isEmpty ? "-Mono" : weightSuffix + "Mono")
|
|
case .serif:
|
|
if name.lowercased() == "system" { return "TimesNewRomanPS" }
|
|
return name + weightSuffix
|
|
default:
|
|
return name + weightSuffix
|
|
}
|
|
}
|
|
|
|
static func calculateOptimalFontSize(
|
|
digit: String,
|
|
fontName: FontFamily,
|
|
weight: Font.Weight,
|
|
design: Font.Design,
|
|
for size: CGSize
|
|
) -> CGFloat {
|
|
var low: CGFloat = 1.0
|
|
var high: CGFloat = 2000.0
|
|
|
|
while high - low > 0.01 {
|
|
let mid = (low + high) / 2
|
|
let testFont = createUIFont(
|
|
name: fontName,
|
|
weight: weight,
|
|
design: design,
|
|
size: mid
|
|
)
|
|
let textSize = tightBoundingBox(for: digit, withFont: testFont)
|
|
|
|
if textSize.width <= size.width && textSize.height <= size.height {
|
|
low = mid
|
|
} else {
|
|
high = mid
|
|
}
|
|
}
|
|
|
|
return low
|
|
}
|
|
|
|
private static func tightBoundingBox(
|
|
for text: String,
|
|
withFont font: UIFont
|
|
) -> CGSize {
|
|
let attributedString = NSAttributedString(
|
|
string: text,
|
|
attributes: [.font: font]
|
|
)
|
|
let rect = attributedString.boundingRect(
|
|
with: CGSize(
|
|
width: CGFloat.greatestFiniteMagnitude,
|
|
height: CGFloat.greatestFiniteMagnitude
|
|
),
|
|
options: [.usesLineFragmentOrigin, .usesFontLeading],
|
|
context: nil
|
|
)
|
|
return CGSize(width: ceil(rect.width), height: ceil(rect.height))
|
|
}
|
|
|
|
static func createUIFont(
|
|
name: FontFamily,
|
|
weight: Font.Weight,
|
|
design: Font.Design,
|
|
size: CGFloat
|
|
) -> UIFont {
|
|
if name == .system {
|
|
return UIFont.systemFont(
|
|
ofSize: size,
|
|
weight: weight.uiFontWeight,
|
|
width: design.uiFontWidth
|
|
)
|
|
}
|
|
|
|
if let font = UIFont(
|
|
name: weightedFontName(
|
|
name: name.rawValue,
|
|
weight: weight,
|
|
design: design
|
|
),
|
|
size: size
|
|
) {
|
|
return font
|
|
}
|
|
|
|
return UIFont.systemFont(
|
|
ofSize: size,
|
|
weight: weight.uiFontWeight,
|
|
width: design.uiFontWidth
|
|
)
|
|
}
|
|
|
|
static func createFont(
|
|
name: FontFamily,
|
|
weight: Font.Weight,
|
|
design: Font.Design,
|
|
size: CGFloat
|
|
) -> Font {
|
|
let fontName = weightedFontName(
|
|
name: name.rawValue,
|
|
weight: weight,
|
|
design: design
|
|
)
|
|
print(fontName)
|
|
if name == .system {
|
|
return .system(size: size, weight: weight, design: design)
|
|
} else {
|
|
return .custom(fontName, size: size)
|
|
}
|
|
}
|
|
}
|