Theme: Streamline cross-platform implementation

Now that we’re making Splash support iOS as well as
Mac + Linux, we need to create some nice abstractions
to make sure that we can share as much code as possible
between all platforms.

- Define Font.Loaded and Color.Renderable as platform-
specific typealiases for system fonts and colors.
- Don’t compile in non-Linux compatible code when building
for Linux.
- Make Font and Color handle all conversion themselves, so
that AttributedStringOutputFormat can be kept more clean.
This commit is contained in:
John Sundell 2018-08-26 00:54:44 +02:00
parent c7d50e6d9b
commit 1e532a6c4c
3 changed files with 102 additions and 107 deletions

View File

@ -4,13 +4,9 @@
* MIT license - see LICENSE.md
*/
#if os(macOS)
import Cocoa
#endif
#if !os(Linux)
#if os(iOS)
import UIKit
#endif
import Foundation
/// Output format to use to generate an NSAttributedString from the
/// highlighted code. A `Theme` is used to determine what fonts and
@ -30,7 +26,7 @@ public struct AttributedStringOutputFormat: OutputFormat {
public extension AttributedStringOutputFormat {
struct Builder: OutputBuilder {
private let theme: Theme
private lazy var font = loadFont()
private lazy var font = theme.font.load()
private var string = NSMutableAttributedString()
fileprivate init(theme: Theme) {
@ -54,48 +50,13 @@ public extension AttributedStringOutputFormat {
public func build() -> NSAttributedString {
return NSAttributedString(attributedString: string)
}
#if os(macOS)
private mutating func loadFont() -> NSFont {
let size = CGFloat(theme.font.size)
switch theme.font.resource {
case .system:
return .defaultFont(ofSize: size)
case .path(let path):
guard let font = NSFont.loaded(from: path, size: size) else {
return .defaultFont(ofSize: size)
}
return font
}
}
#endif
#if os(iOS)
private mutating func loadFont() -> UIFont {
let size = CGFloat(theme.font.size)
return .defaultFont(ofSize: size)
}
#endif
}
}
#if os(macOS)
private extension NSMutableAttributedString {
func append(_ string: String, font: NSFont, color: Color) {
let color = NSColor(
red: CGFloat(color.red),
green: CGFloat(color.green),
blue: CGFloat(color.blue),
alpha: CGFloat(color.alpha)
)
func append(_ string: String, font: Font.Loaded, color: Color) {
let attributedString = NSAttributedString(string: string, attributes: [
.foregroundColor: color,
.foregroundColor: color.renderable,
.font: font
])
@ -103,60 +64,4 @@ private extension NSMutableAttributedString {
}
}
private extension NSFont {
static func loaded(from path: String, size: CGFloat) -> NSFont? {
let url = CFURLCreateWithFileSystemPath(
kCFAllocatorDefault,
path as CFString,
.cfurlposixPathStyle,
false
)
guard let font = url.flatMap(CGDataProvider.init).flatMap(CGFont.init) else {
return nil
}
return CTFontCreateWithGraphicsFont(font, size, nil, nil)
}
static func defaultFont(ofSize size: CGFloat) -> NSFont {
guard let courier = loaded(from: "/Library/Fonts/Courier New.ttf", size: size) else {
return .systemFont(ofSize: size)
}
return courier
}
}
#endif
#if os(iOS)
private extension NSMutableAttributedString {
func append(_ string: String, font: UIFont, color: Color) {
let color = UIColor(
red: CGFloat(color.red),
green: CGFloat(color.green),
blue: CGFloat(color.blue),
alpha: CGFloat(color.alpha)
)
let attributedString = NSAttributedString(string: string, attributes: [
.foregroundColor: color,
.font: font
])
append(attributedString)
}
}
private extension UIFont {
static func defaultFont(ofSize size: CGFloat) -> UIFont {
guard let menlo = UIFont(name: "Menlo-Regular", size: size) else {
return .systemFont(ofSize: size)
}
return menlo
}
}
#endif

View File

@ -23,3 +23,34 @@ public struct Color {
self.alpha = alpha
}
}
#if !os(Linux)
internal extension Color {
var renderable: Renderable {
return Renderable(
red: CGFloat(red),
green: CGFloat(green),
blue: CGFloat(blue),
alpha: CGFloat(alpha)
)
}
}
#endif
#if os(iOS)
import UIKit
internal extension Color {
typealias Renderable = UIColor
}
#elseif os(macOS)
import Cocoa
internal extension Color {
typealias Renderable = NSColor
}
#endif

View File

@ -6,9 +6,11 @@
import Foundation
#if !os(Linux)
/// A representation of a font, for use with a `Theme`.
/// Since Splash aims to be cross-platform, it uses this
/// simplified color representation rather than `NSFont`
/// simplified font representation rather than `NSFont`
/// or `UIFont`.
public struct Font {
/// The underlying resource used to load the font
@ -19,12 +21,7 @@ public struct Font {
/// Initialize an instance with a path to a font file
/// on disk and a size.
public init(path: String, size: Double) {
#if os(macOS)
resource = .path((path as NSString).expandingTildeInPath)
#else
resource = .path(path)
#endif
self.size = size
}
@ -41,7 +38,69 @@ public extension Font {
enum Resource {
/// Use an appropriate system font
case system
/// Use a pre-loaded font
case preloaded(Loaded)
/// Load a font file from a given file system path
case path(String)
}
}
internal extension Font {
func load() -> Loaded {
switch resource {
case .system:
return loadDefaultFont()
case .preloaded(let font):
return font
case .path(let path):
return load(fromPath: path) ?? loadDefaultFont()
}
}
private func loadDefaultFont() -> Loaded {
let font: Loaded?
#if os(iOS)
font = UIFont(name: "Menlo-Regular", size: CGFloat(size))
#else
font = load(fromPath: "/Library/Fonts/Courier New.ttf")
#endif
return font ?? .systemFont(ofSize: CGFloat(size))
}
private func load(fromPath path: String) -> Loaded? {
let url = CFURLCreateWithFileSystemPath(
kCFAllocatorDefault,
path as CFString,
.cfurlposixPathStyle,
false
)
guard let font = url.flatMap(CGDataProvider.init).flatMap(CGFont.init) else {
return nil
}
return CTFontCreateWithGraphicsFont(font, CGFloat(size), nil, nil)
}
}
#endif
#if os(iOS)
import UIKit
public extension Font {
typealias Loaded = UIFont
}
#elseif os(macOS)
import Cocoa
public extension Font {
typealias Loaded = NSFont
}
#endif