From 1e532a6c4c15750544f1db6ed4ea8a205ce367e5 Mon Sep 17 00:00:00 2001 From: John Sundell Date: Sun, 26 Aug 2018 00:54:44 +0200 Subject: [PATCH 1/3] Theme: Streamline cross-platform implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../Output/AttributedStringOutputFormat.swift | 107 +----------------- Sources/Splash/Theming/Color.swift | 31 +++++ Sources/Splash/Theming/Font.swift | 71 +++++++++++- 3 files changed, 102 insertions(+), 107 deletions(-) diff --git a/Sources/Splash/Output/AttributedStringOutputFormat.swift b/Sources/Splash/Output/AttributedStringOutputFormat.swift index 0aae5d9..ee5bbd6 100644 --- a/Sources/Splash/Output/AttributedStringOutputFormat.swift +++ b/Sources/Splash/Output/AttributedStringOutputFormat.swift @@ -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,109 +50,18 @@ 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 - ]) + ]) append(attributedString) } } -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 diff --git a/Sources/Splash/Theming/Color.swift b/Sources/Splash/Theming/Color.swift index a89a7cf..1fd24f8 100644 --- a/Sources/Splash/Theming/Color.swift +++ b/Sources/Splash/Theming/Color.swift @@ -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 diff --git a/Sources/Splash/Theming/Font.swift b/Sources/Splash/Theming/Font.swift index 6c7f994..4807cb5 100644 --- a/Sources/Splash/Theming/Font.swift +++ b/Sources/Splash/Theming/Font.swift @@ -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 From df73f6d066ec2dfc040f33474ef308921de11548 Mon Sep 17 00:00:00 2001 From: John Sundell Date: Sun, 26 Aug 2018 00:58:23 +0200 Subject: [PATCH 2/3] Disable Color + Theme on Linux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit They don’t really make sense, since there’s no way to use them on Linux, without linking to some form of Linux rendering framework. --- Sources/Splash/Theming/Color.swift | 4 +++- Sources/Splash/Theming/Theme.swift | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Sources/Splash/Theming/Color.swift b/Sources/Splash/Theming/Color.swift index 1fd24f8..a75a46e 100644 --- a/Sources/Splash/Theming/Color.swift +++ b/Sources/Splash/Theming/Color.swift @@ -6,6 +6,8 @@ import Foundation +#if !os(Linux) + /// A representation of a color, for use with a `Theme`. /// Since Splash aims to be cross-platform, it uses this /// simplified color representation rather than `NSColor` @@ -24,7 +26,6 @@ public struct Color { } } -#if !os(Linux) internal extension Color { var renderable: Renderable { return Renderable( @@ -35,6 +36,7 @@ internal extension Color { ) } } + #endif #if os(iOS) diff --git a/Sources/Splash/Theming/Theme.swift b/Sources/Splash/Theming/Theme.swift index 3b274bd..8ce0a51 100644 --- a/Sources/Splash/Theming/Theme.swift +++ b/Sources/Splash/Theming/Theme.swift @@ -6,10 +6,11 @@ import Foundation +#if !os(Linux) + /// A theme describes what fonts and colors to use when rendering -/// certain output formats - such as `NSAttributedString`. A default -/// implementation is provided that matches the "Sundell's Colors" -/// Xcode theme, by using the `sundellsColors(withFont:)` method. +/// certain output formats - such as `NSAttributedString`. Several +/// default implementations are provided - see Theme+Defaults.swift. public struct Theme { /// What font to use to render the highlighted text public var font: Font @@ -24,3 +25,5 @@ public struct Theme { self.tokenColors = tokenColors } } + +#endif From 530cdad12ff7781f63b8dc5d85e697b6a98c2971 Mon Sep 17 00:00:00 2001 From: John Sundell Date: Sun, 26 Aug 2018 00:59:22 +0200 Subject: [PATCH 3/3] Disable Theme+Defaults on Linux --- Sources/Splash/Theming/Theme+Defaults.swift | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Sources/Splash/Theming/Theme+Defaults.swift b/Sources/Splash/Theming/Theme+Defaults.swift index 66b5c86..bf38dfc 100644 --- a/Sources/Splash/Theming/Theme+Defaults.swift +++ b/Sources/Splash/Theming/Theme+Defaults.swift @@ -6,13 +6,9 @@ import Foundation -/* - * Extends the Theme struct with static properties that - * represent common Xcode defaults as well as community - * favourites. - */ -public extension Theme { +#if !os(Linux) +public extension Theme { /// Create a theme matching the "Sundell's Colors" Xcode theme static func sundellsColors(withFont font: Font) -> Theme { return Theme( @@ -36,6 +32,7 @@ public extension Theme { ) } + /// Create a theme matching Xcode's "Midnight" theme static func midnight(withFont font: Font) -> Theme { return Theme( font: font, @@ -58,6 +55,7 @@ public extension Theme { ) } + /// Creating a theme matching the colors used for the WWDC 2017 sample code static func wwdc17(withFont font: Font) -> Theme { return Theme( font: font, @@ -80,6 +78,7 @@ public extension Theme { ) } + /// Creating a theme matching the colors used for the WWDC 2018 sample code static func wwdc18(withFont font: Font) -> Theme { return Theme( font: font, @@ -102,6 +101,7 @@ public extension Theme { ) } + /// Create a theme matching Xcode's "Sunset" theme static func sunset(withFont font: Font) -> Theme { return Theme( font: font, @@ -124,6 +124,7 @@ public extension Theme { ) } + /// Create a theme matching Xcode's "Presentation" theme static func presentation(withFont font: Font) -> Theme { return Theme( font: font, @@ -145,5 +146,6 @@ public extension Theme { ] ) } - } + +#endif