vds_ios/VDS/Typography/Typography.swift
Matt Bruce 814c71fbe7 debug extension for TextStyle
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2023-05-23 14:45:05 -05:00

338 lines
11 KiB
Swift

//
// Typography.swift
// VDS
//
// Created by Matt Bruce on 8/16/22.
//
import Foundation
import VDSTypographyTokens
public enum TextPosition: String, CaseIterable {
case left, right, center
var textAlignment: NSTextAlignment {
switch self {
case .left:
return NSTextAlignment.left
case .right:
return NSTextAlignment.right
case .center:
return NSTextAlignment.center
}
}
}
public enum TextStyle: String, CaseIterable {
case featureXLarge
case boldFeatureXLarge
case featureLarge
case boldFeatureLarge
case featureMedium
case boldFeatureMedium
case featureSmall
case boldFeatureSmall
case featureXSmall
case boldFeatureXSmall
case title2XLarge
case boldTitle2XLarge
case titleXLarge
case boldTitleXLarge
case titleLarge
case boldTitleLarge
case titleMedium
case boldTitleMedium
case titleSmall
case boldTitleSmall
case bodyLarge
case boldBodyLarge
case bodyMedium
case boldBodyMedium
case bodySmall
case boldBodySmall
case micro
case boldMicro
public static var defaultStyle: TextStyle {
return .bodyLarge
}
}
//MARK: FontCategory
extension TextStyle {
public enum FontCategory: String, CaseIterable {
case feature
case title
case body
case micro
public var sizes: [FontSize] {
switch self {
case .feature:
return [.xlarge, .large, .medium, .small, .xsmall]
case .title:
return [.xxlarge, .xlarge, .large, .medium, .small]
case .body:
return [.large, .medium, .small]
case .micro:
return []
}
}
public func style(for fontSize: FontSize?, isBold: Bool = false) -> TextStyle? {
var styleName = ""
if isBold {
let newRaw = rawValue.prefix(1).description.uppercased() + rawValue.dropFirst()
styleName = "\(isBold ? "bold" : "")\(newRaw)\(fontSize?.rawValue ?? "")"
} else {
styleName = "\(rawValue)\(fontSize?.rawValue ?? "")"
}
guard let style = TextStyle(rawValue: styleName) else {
return nil
}
return style
}
}
}
//MARK: FontSize
extension TextStyle {
public enum FontSize: String, CaseIterable {
case xxlarge = "2XLarge"
case xlarge = "XLarge"
case large = "Large"
case medium = "Medium"
case small = "Small"
case xsmall = "XSmall"
}
}
//MARK: PointSize
extension TextStyle {
public var pointSize: CGFloat {
switch self {
case .featureXLarge, .boldFeatureXLarge:
return UIDevice.isIPad ? VDSTypography.fontSizeFeature144 : VDSTypography.fontSizeFeature96
case .featureLarge, .boldFeatureLarge:
return UIDevice.isIPad ? VDSTypography.fontSizeFeature128 : VDSTypography.fontSizeFeature80
case .featureMedium, .boldFeatureMedium:
return UIDevice.isIPad ? VDSTypography.fontSizeFeature96 : VDSTypography.fontSizeFeature64
case .featureSmall, .boldFeatureSmall:
return UIDevice.isIPad ? VDSTypography.fontSizeFeature80 : VDSTypography.fontSizeFeature48
case .featureXSmall, .boldFeatureXSmall:
return UIDevice.isIPad ? VDSTypography.fontSizeFeature64 : VDSTypography.fontSizeFeature40
case .title2XLarge, .boldTitle2XLarge:
return UIDevice.isIPad ? VDSTypography.fontSizeTitle64 : VDSTypography.fontSizeTitle40
case .titleXLarge, .boldTitleXLarge:
return UIDevice.isIPad ? VDSTypography.fontSizeTitle48 : VDSTypography.fontSizeTitle32
case .titleLarge, .boldTitleLarge:
return UIDevice.isIPad ? VDSTypography.fontSizeTitle32 : VDSTypography.fontSizeTitle24
case .titleMedium, .boldTitleMedium:
return UIDevice.isIPad ? VDSTypography.fontSizeTitle24 : VDSTypography.fontSizeTitle20
case .titleSmall, .boldTitleSmall:
return UIDevice.isIPad ? VDSTypography.fontSizeTitle20 : VDSTypography.fontSizeTitle16
case .bodyLarge, .boldBodyLarge:
return VDSTypography.fontSizeBody16
case .bodyMedium, .boldBodyMedium:
return VDSTypography.fontSizeBody14
case .bodySmall, .boldBodySmall:
return VDSTypography.fontSizeBody12
case .micro, .boldMicro:
return VDSTypography.fontSizeMicro11
}
}
}
//MARK: LineHeight
extension TextStyle {
public var lineHeight: CGFloat {
switch self {
case .featureXLarge, .boldFeatureXLarge:
return UIDevice.isIPad ? VDSTypography.lineHeightFeature136 : VDSTypography.lineHeightFeature88
case .featureLarge, .boldFeatureLarge:
return UIDevice.isIPad ? VDSTypography.lineHeightFeature120 : VDSTypography.lineHeightFeature76
case .featureMedium, .boldFeatureMedium:
return UIDevice.isIPad ? VDSTypography.lineHeightFeature88 : VDSTypography.lineHeightFeature64
case .featureSmall, .boldFeatureSmall:
return UIDevice.isIPad ? VDSTypography.lineHeightFeature76 : VDSTypography.lineHeightFeature48
case .featureXSmall, .boldFeatureXSmall:
return UIDevice.isIPad ? VDSTypography.lineHeightFeature64 : VDSTypography.lineHeightFeature40
case .title2XLarge, .boldTitle2XLarge:
return UIDevice.isIPad ? VDSTypography.lineHeightTitle64 : VDSTypography.lineHeightTitle40
case .titleXLarge, .boldTitleXLarge:
return UIDevice.isIPad ? VDSTypography.lineHeightTitle48 : VDSTypography.lineHeightTitle36
case .titleLarge, .boldTitleLarge:
return UIDevice.isIPad ? VDSTypography.lineHeightTitle36 : VDSTypography.lineHeightTitle28
case .titleMedium, .boldTitleMedium:
return UIDevice.isIPad ? VDSTypography.lineHeightTitle28 : VDSTypography.lineHeightTitle24
case .titleSmall, .boldTitleSmall:
return UIDevice.isIPad ? VDSTypography.lineHeightTitle24 : VDSTypography.lineHeightTitle20
case .bodyLarge, .boldBodyLarge:
return VDSTypography.lineHeightBody20
case .bodyMedium, .boldBodyMedium:
return VDSTypography.lineHeightBody18
case .bodySmall, .boldBodySmall:
return VDSTypography.lineHeightBody16
case .micro, .boldMicro:
return VDSTypography.lineHeightMicro16
}
}
}
//MARK: LetterSpacing
extension TextStyle {
public var letterSpacing: CGFloat {
switch self {
case .featureXLarge,
.featureLarge,
.featureMedium,
.featureSmall,
.featureXSmall,
.title2XLarge,
.titleXLarge,
.titleLarge:
return 0.25
case .boldBodyLarge, .bodyLarge,
.boldBodyMedium, .bodyMedium:
return 0.5
default:
return 0.0
}
}
}
//MARK: Alignments
extension TextStyle {
public var aligments: [TextPosition] {
return [.left, .center]
}
}
//MARK: Fonts
extension TextStyle {
public var fontFace: Fonts {
switch self {
case .boldFeatureXLarge,
.boldFeatureLarge,
.boldFeatureMedium,
.boldFeatureSmall,
.boldFeatureXSmall,
.boldTitle2XLarge,
.boldTitleXLarge,
.boldTitleLarge,
.boldTitleMedium,
.boldTitleSmall,
.boldBodyLarge,
.boldBodyMedium:
return .dsBold
case .featureXLarge,
.featureLarge,
.featureMedium,
.featureSmall,
.featureXSmall,
.title2XLarge,
.titleXLarge:
return .dsLight
case .titleLarge,
.titleMedium,
.titleSmall,
.bodyLarge,
.bodyMedium:
return .dsRegular
case .boldBodySmall,
.boldMicro:
return .txBold
case .bodySmall,
.micro:
return .txRegular
}
}
public var font: UIFont {
return fontFace.font(ofSize: pointSize)
}
public var superScriptFont: UIFont {
return fontFace.font(ofSize: pointSize / 2)
}
}
extension TextStyle {
public static func style(for fontName: String, size: CGFloat) -> TextStyle? {
//filter all styles by fontName
let styles = allCases.filter{$0.fontFace.fontName == fontName }.sorted { lhs, rhs in lhs.pointSize < rhs.pointSize }
//if there are no styles then return nil
guard styles.count > 0 else { return nil }
//if there is an exact match on a style with this pointSize then return it
if let style = styles.first(where: {$0.pointSize == size }) {
return style
} else if let largerIndex = styles.firstIndex(where: { $0.pointSize > size}) { //find the closet one to pointSize
return styles[max(largerIndex - 1, 0)]
} else { //return the last style
return styles.last!
}
}
}
extension TextStyle {
public func isWithin(_ collection: [TextStyle]) -> Bool {
(collection.first(where: {$0 == self}) != nil)
}
}
extension TextStyle {
public struct SpacingConfig {
public var defaultSpacing: CGFloat = 8.0
public var configs: [TextStyle.DeviceSpacingConfig]
public func spacing(for style: TextStyle, neighboring: TextStyle) -> CGFloat {
let deviceType: TextStyle.DeviceSpacingConfig.DeviceType = UIDevice.isIPad ? .iPad : .iPhone
if let config = configs.first(where:
{ style.isWithin($0.primaryStyles) && neighboring.isWithin($0.neighboringStyles) &&
($0.deviceType == deviceType || $0.deviceType == .all )})
{
return config.spacing
}
return defaultSpacing
}
}
public struct DeviceSpacingConfig {
public enum DeviceType {
case iPhone, iPad, all
}
public var spacing: CGFloat
public var deviceType: DeviceType = .iPhone
public var primaryStyles: [TextStyle]
public var neighboringStyles: [TextStyle]
public init(_ primaryStyles: [TextStyle], neighboring: [TextStyle], spacing: CGFloat, deviceType: DeviceType = .iPhone) {
self.spacing = spacing
self.primaryStyles = primaryStyles
self.neighboringStyles = neighboring
self.deviceType = deviceType
}
}
}
extension TextStyle: CustomDebugStringConvertible {
public var debugDescription: String {
"Name: \(self.rawValue) FontFace: \(font.fontName) FontWeight: \(self.rawValue.hasPrefix("bold") ? "bold" : "normal") PointSize: \(font.pointSize) LetterSpacing: \(letterSpacing) LineHeight: \(lineHeight)"
}
}