// // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import SignalServiceKit import UIKit public extension UIFont { // MARK: - Icon Font class func awesomeFont(ofSize size: CGFloat) -> UIFont { return UIFont(name: "FontAwesome", size: size)! } // MARK: - class func regularFont(ofSize size: CGFloat) -> UIFont { return .systemFont(ofSize: size, weight: .regular) } /// Returns an instance of the system font with scaling for the user's /// selected content size category with the specified size being used for /// the standard Dynamic Type size. /// /// - Parameters: /// - standardSize: The size the font should be for the standard Dynamic Type size. /// - weight: The weight of the font. /// - Returns: The system font scaled to the current Dynamic Type setting. class func dynamicTypeFont( ofStandardSize standardSize: CGFloat, weight: UIFont.Weight = .regular ) -> UIFont { return UIFontMetrics.default.scaledFont(for: .systemFont(ofSize: standardSize, weight: weight)) } class func semiboldFont(ofSize size: CGFloat) -> UIFont { return .systemFont(ofSize: size, weight: .semibold) } class func monospacedDigitFont(ofSize size: CGFloat) -> UIFont { return .monospacedDigitSystemFont(ofSize: size, weight: .regular) } // MARK: - Dynamic Type class var dynamicTypeTitle1: UIFont { UIFont.preferredFont(forTextStyle: .title1, compatibleWith: .current) } class var dynamicTypeTitle2: UIFont { UIFont.preferredFont(forTextStyle: .title2, compatibleWith: .current) } class var dynamicTypeTitle3: UIFont { UIFont.preferredFont(forTextStyle: .title3, compatibleWith: .current) } class var dynamicTypeHeadline: UIFont { UIFont.preferredFont(forTextStyle: .headline, compatibleWith: .current) } class var dynamicTypeBody: UIFont { UIFont.preferredFont(forTextStyle: .body, compatibleWith: .current) } class var dynamicTypeCallout: UIFont { UIFont.preferredFont(forTextStyle: .callout, compatibleWith: .current) } class var dynamicTypeSubheadline: UIFont { UIFont.preferredFont(forTextStyle: .subheadline, compatibleWith: .current) } class var dynamicTypeFootnote: UIFont { UIFont.preferredFont(forTextStyle: .footnote, compatibleWith: .current) } class var dynamicTypeCaption1: UIFont { UIFont.preferredFont(forTextStyle: .caption1, compatibleWith: .current) } class var dynamicTypeCaption2: UIFont { UIFont.preferredFont(forTextStyle: .caption2, compatibleWith: .current) } // MARK: - Dynamic Type Clamped // We clamp the dynamic type sizes at the max size available // without "larger accessibility sizes" enabled. static private var maxPointSizeMap: [UIFont.TextStyle: CGFloat] = [ .title1: 34, .title2: 28, .title3: 26, .headline: 23, .body: 23, .callout: 22, .subheadline: 21, .footnote: 19, .caption1: 18, .caption2: 17, .largeTitle: 40 ] private class func preferredFontClamped(forTextStyle textStyle: UIFont.TextStyle) -> UIFont { // From the documentation of -[id adjustsFontForContentSizeCategory:] // Dynamic sizing is only supported with fonts that are: // a. Vended using UIFont.preferredFont(forTextStyle:) // b. Vended from UIFontMetrics.scaledFont(for:) or one of its variants // // If we clamp fonts by checking the resulting point size and then creating a new, smaller UIFont with // a fallback max size, we'll lose dynamic sizing. Max sizes can be specified using UIFontMetrics though. // // UIFontMetrics will only operate on unscaled fonts. So we do this dance to cap the system default styles // 1. Grab the standard, unscaled font by using the default trait collection // 2. Use UIFontMetrics to scale it up, capped at the desired max size let defaultTraitCollection = UITraitCollection(preferredContentSizeCategory: .large) let unscaledFont = UIFont.preferredFont(forTextStyle: textStyle, compatibleWith: defaultTraitCollection) let desiredStyleMetrics = UIFontMetrics(forTextStyle: textStyle) guard let maxPointSize = maxPointSizeMap[textStyle] else { owsFailDebug("Missing max point size for style: \(textStyle)") return desiredStyleMetrics.scaledFont(for: unscaledFont) } return desiredStyleMetrics.scaledFont( for: unscaledFont, maximumPointSize: maxPointSize, compatibleWith: .current ) } class var dynamicTypeLargeTitle1Clamped: UIFont { preferredFontClamped(forTextStyle: .largeTitle) } class var dynamicTypeTitle1Clamped: UIFont { preferredFontClamped(forTextStyle: .title1) } class var dynamicTypeTitle2Clamped: UIFont { preferredFontClamped(forTextStyle: .title2) } class var dynamicTypeTitle3Clamped: UIFont { preferredFontClamped(forTextStyle: .title3) } class var dynamicTypeHeadlineClamped: UIFont { preferredFontClamped(forTextStyle: .headline) } class var dynamicTypeBodyClamped: UIFont { preferredFontClamped(forTextStyle: .body) } class var dynamicTypeCalloutClamped: UIFont { preferredFontClamped(forTextStyle: .callout) } class var dynamicTypeSubheadlineClamped: UIFont { preferredFontClamped(forTextStyle: .subheadline) } class var dynamicTypeFootnoteClamped: UIFont { preferredFontClamped(forTextStyle: .footnote) } class var dynamicTypeCaption1Clamped: UIFont { preferredFontClamped(forTextStyle: .caption1) } class var dynamicTypeCaption2Clamped: UIFont { preferredFontClamped(forTextStyle: .caption2) } // MARK: - func italic() -> UIFont { guard let fontDescriptor = fontDescriptor.withSymbolicTraits(.traitItalic) else { return self } return UIFont(descriptor: fontDescriptor, size: 0) } func medium() -> UIFont { let fontTraits = [UIFontDescriptor.TraitKey.weight: UIFont.Weight.medium] let fontDescriptor = fontDescriptor.addingAttributes([.traits: fontTraits]) return UIFont(descriptor: fontDescriptor, size: 0) } func semibold() -> UIFont { let fontTraits = [UIFontDescriptor.TraitKey.weight: UIFont.Weight.semibold] let fontDescriptor = fontDescriptor.addingAttributes([.traits: fontTraits]) return UIFont(descriptor: fontDescriptor, size: 0) } func bold() -> UIFont { let fontTraits = [UIFontDescriptor.TraitKey.weight: UIFont.Weight.bold] let fontDescriptor = fontDescriptor.addingAttributes([.traits: fontTraits]) return UIFont(descriptor: fontDescriptor, size: 0) } func monospaced() -> UIFont { return .monospacedDigitFont(ofSize: pointSize) } }