Skip to content

Instantly share code, notes, and snippets.

@florianpircher
Created March 7, 2024 20:21
Show Gist options
  • Save florianpircher/40fd849c1982929fb3479d2702f75824 to your computer and use it in GitHub Desktop.
Save florianpircher/40fd849c1982929fb3479d2702f75824 to your computer and use it in GitHub Desktop.
NSFont extensions for setting OpenType features and variation axes
//
// Fonts.swift
//
// Copyright 2021 Florian Pircher
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Cocoa
extension NSFontDescriptor {
func with(variation: [Tag: Double]) -> NSFontDescriptor {
let entries = variation.map { ($0.key.value, $0.value) }
let variation = Dictionary(entries) { a, b in a }
return addingAttributes([.variation: variation])
}
func with(features: [Tag: Int]) -> NSFontDescriptor {
let featureSettings = features.map { tag, value -> [CFString: Any] in
[
kCTFontOpenTypeFeatureTag: tag.description,
kCTFontOpenTypeFeatureValue: value
]
}
return addingAttributes([.featureSettings: featureSettings])
}
}
extension NSFont {
func with(variation: [Tag: Double]) -> NSFont {
NSFont(descriptor: fontDescriptor.with(variation: variation), size: pointSize) ?? self
}
func with(features: [Tag: Int]) -> NSFont {
NSFont(descriptor: fontDescriptor.with(features: features), size: pointSize) ?? self
}
func withHighLegibilityStyle() -> NSFont {
with(features: [
"cv05": 1, // capital I with serifs
"cv06": 1, // small l with hook
"cv08": 1, // slashed zero
"ss01": 1, // straight-sided six and nine
])
}
}
struct Tag: Hashable, CustomStringConvertible, ExpressibleByStringLiteral {
let value: FourCharCode
var description: String {
let a = UInt8((value >> 24) & 0xFF)
let b = UInt8((value >> 16) & 0xFF)
let c = UInt8((value >> 8) & 0xFF)
let d = UInt8((value >> 0) & 0xFF)
return String(bytes: [a, b, c, d], encoding: .utf8)!
}
init(_ value: FourCharCode) {
self.value = value
}
init(_ name: StaticString) {
guard name.utf8CodeUnitCount == 4 else {
preconditionFailure("tag must contain four UTF-8 code units: \(name)")
}
self.value = name.withUTF8Buffer { p in
let a = FourCharCode(p[0]) << 24
let b = FourCharCode(p[1]) << 16
let c = FourCharCode(p[2]) << 8
let d = FourCharCode(p[3]) << 0
return a | b | c | d
}
}
init(stringLiteral value: StaticString) {
self.init(value)
}
init?(string name: String) {
let bytes = Array(name.utf8)
guard bytes.count == 4 else {
return nil
}
let a = FourCharCode(bytes[0]) << 24
let b = FourCharCode(bytes[1]) << 16
let c = FourCharCode(bytes[2]) << 8
let d = FourCharCode(bytes[3]) << 0
self.value = a | b | c | d
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment