forked from Rightpoint/BonMot
-
Notifications
You must be signed in to change notification settings - Fork 1
/
FontFeatures.swift
188 lines (153 loc) · 7.02 KB
/
FontFeatures.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//
// FontFeatures.swift
// BonMot
//
// Created by Brian King on 8/31/16.
// Copyright © 2016 Raizlabs. All rights reserved.
//
#if os(OSX)
import AppKit
#else
import UIKit
#endif
// This is not supported on watchOS
#if os(iOS) || os(tvOS) || os(OSX)
/// Protocol to provide values to be used by `UIFontFeatureTypeIdentifierKey`
/// and `UIFontFeatureSelectorIdentifierKey`. You can typically find these
/// values in CoreText.SFNTLayoutTypes.
public protocol FontFeatureProvider {
func featureSettings() -> [(type: Int, selector: Int)]
}
public extension BONFont {
/// Create a new font and attempt to enable the specified font features.
/// The returned font will have all features enabled that it supports.
/// - parameter withFeatures: the features to attempt to enable on the
/// font.
/// - returns: a new font with the specified features enabled.
public func font(withFeatures featureProviders: [FontFeatureProvider]) -> BONFont {
var fontAttributes = fontDescriptor.fontAttributes
var features = fontAttributes[BONFontDescriptorFeatureSettingsAttribute] as? [StyleAttributes] ?? []
if featureProviders.count > 0 {
let newFeatures = featureProviders.map { $0.featureAttributes() }.flatMap { $0 }
features.append(contentsOf: newFeatures)
fontAttributes[BONFontDescriptorFeatureSettingsAttribute] = features
}
let descriptor = BONFontDescriptor(fontAttributes: fontAttributes)
#if os(OSX)
return BONFont(descriptor: descriptor, size: pointSize)!
#else
return BONFont(descriptor: descriptor, size: pointSize)
#endif
}
}
/// A feature provider for changing the number case, also known as "figure
/// style".
public enum NumberCase: FontFeatureProvider {
/// Uppercase numbers, also known as "lining figures", are the same height
/// as uppercase letters, and they do not extend below the baseline.
case upper
/// Lowercase numbers, also known as "oldstyle figures", are similar in
/// size and visual weight to lowercase letters, allowing them to
/// blend in better in a block of text. They may have descenders
/// which drop below the typographic baseline.
case lower
/// Fractions, when written on paper, are written on one line
/// with the numerator diagonally above and to the left of the
/// demoninator, with the slash ("/") between them.
case fraction
public func featureSettings() -> [(type: Int, selector: Int)] {
switch self {
case .upper:
return [(type: kNumberCaseType, selector: kUpperCaseNumbersSelector)]
case .lower:
return [(type: kNumberCaseType, selector: kLowerCaseNumbersSelector)]
case .fraction:
return [(type: kFractionsType, selector: kDiagonalFractionsSelector)]
}
}
}
/// A feature provider for changing the number spacing, also known as
/// "figure spacing".
public enum NumberSpacing: FontFeatureProvider {
/// Monospaced numbers, also known as "tabular figures", each take up
/// the same amount of horizontal space, meaning that different numbers
/// will line up when arranged in columns.
case monospaced
/// Proportionally spaced numbers, also known as "proprotional figures",
/// are of variable width. This makes them look better in most cases,
/// but they should be avoided when numbers need to line up in columns.
case proportional
public func featureSettings() -> [(type: Int, selector: Int)] {
switch self {
case .monospaced:
return [(type: kNumberSpacingType, selector: kMonospacedNumbersSelector)]
case .proportional:
return [(type: kNumberSpacingType, selector: kProportionalNumbersSelector)]
}
}
}
/// A feature provider for changing the vertical position of characters
/// using predefined styles in the font, such as superscript and subscript.
public enum VerticalPosition: FontFeatureProvider {
/// No vertical position adjustment is applied.
case normal
/// Superscript (superior) glpyh variants are used, as in footnotes¹.
case superscript
/// Subscript (inferior) glyph variants are used: vₑ.
case `subscript`
/// Ordinal glyph variants are used, as in the common typesetting of 4th.
case ordinals
/// Scientific inferior glyph variants are used: H₂O
case scientificInferiors
public func featureSettings() -> [(type: Int, selector: Int)] {
let selector: Int
switch self {
case .normal: selector = kNormalPositionSelector
case .superscript: selector = kSuperiorsSelector
case .`subscript`: selector = kInferiorsSelector
case .ordinals: selector = kOrdinalsSelector
case .scientificInferiors: selector = kScientificInferiorsSelector
}
return [(type: kVerticalPositionType, selector: selector)]
}
}
/// A feature provider for changing small caps behavior.
/// - Note: `fromUppercase` and `fromLowercase` can be combined: they are not
/// mutually exclusive.
public enum SmallCaps: FontFeatureProvider {
/// No small caps are used.
case disabled
/// Uppercase letters in the source string are replaced with small caps.
/// Lowercase letters remain unmodified.
case fromUppercase
/// Lowercase letters in the source string are replaced with small caps.
/// Uppercase letters remain unmodified.
case fromLowercase
public func featureSettings() -> [(type: Int, selector: Int)] {
switch self {
case .disabled:
return [
(type: kLowerCaseType, selector: kDefaultLowerCaseSelector),
(type: kUpperCaseType, selector: kDefaultUpperCaseSelector),
]
case .fromUppercase:
return [(type: kUpperCaseType, selector: kUpperCaseSmallCapsSelector)]
case .fromLowercase:
return [(type: kLowerCaseType, selector: kLowerCaseSmallCapsSelector)]
}
}
}
extension FontFeatureProvider {
/// - returns: an array of dictionaries, each representing one feature
/// for the attributes key in the font attributes dictionary.
func featureAttributes() -> [StyleAttributes] {
let featureSettings = self.featureSettings()
return featureSettings.map {
return [
BONFontFeatureTypeIdentifierKey: $0.type,
BONFontFeatureSelectorIdentifierKey: $0.selector,
]
}
}
}
#endif