Merge pull request #4 from JohnSundell/cross-platform-fix

Streamline cross-platform implementation
This commit is contained in:
John Sundell 2018-08-26 01:03:58 +02:00 committed by GitHub
commit 0ec69daecd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 119 additions and 117 deletions

View File

@ -4,13 +4,9 @@
* MIT license - see LICENSE.md * MIT license - see LICENSE.md
*/ */
#if os(macOS) #if !os(Linux)
import Cocoa
#endif
#if os(iOS) import Foundation
import UIKit
#endif
/// Output format to use to generate an NSAttributedString from the /// Output format to use to generate an NSAttributedString from the
/// highlighted code. A `Theme` is used to determine what fonts and /// highlighted code. A `Theme` is used to determine what fonts and
@ -30,7 +26,7 @@ public struct AttributedStringOutputFormat: OutputFormat {
public extension AttributedStringOutputFormat { public extension AttributedStringOutputFormat {
struct Builder: OutputBuilder { struct Builder: OutputBuilder {
private let theme: Theme private let theme: Theme
private lazy var font = loadFont() private lazy var font = theme.font.load()
private var string = NSMutableAttributedString() private var string = NSMutableAttributedString()
fileprivate init(theme: Theme) { fileprivate init(theme: Theme) {
@ -54,48 +50,13 @@ public extension AttributedStringOutputFormat {
public func build() -> NSAttributedString { public func build() -> NSAttributedString {
return NSAttributedString(attributedString: string) 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 { private extension NSMutableAttributedString {
func append(_ string: String, font: NSFont, color: Color) { func append(_ string: String, font: Font.Loaded, color: Color) {
let color = NSColor(
red: CGFloat(color.red),
green: CGFloat(color.green),
blue: CGFloat(color.blue),
alpha: CGFloat(color.alpha)
)
let attributedString = NSAttributedString(string: string, attributes: [ let attributedString = NSAttributedString(string: string, attributes: [
.foregroundColor: color, .foregroundColor: color.renderable,
.font: font .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 #endif

View File

@ -6,6 +6,8 @@
import Foundation import Foundation
#if !os(Linux)
/// A representation of a color, for use with a `Theme`. /// A representation of a color, for use with a `Theme`.
/// Since Splash aims to be cross-platform, it uses this /// Since Splash aims to be cross-platform, it uses this
/// simplified color representation rather than `NSColor` /// simplified color representation rather than `NSColor`
@ -23,3 +25,34 @@ public struct Color {
self.alpha = alpha self.alpha = alpha
} }
} }
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 import Foundation
#if !os(Linux)
/// A representation of a font, for use with a `Theme`. /// A representation of a font, for use with a `Theme`.
/// Since Splash aims to be cross-platform, it uses this /// Since Splash aims to be cross-platform, it uses this
/// simplified color representation rather than `NSFont` /// simplified font representation rather than `NSFont`
/// or `UIFont`. /// or `UIFont`.
public struct Font { public struct Font {
/// The underlying resource used to load the 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 /// Initialize an instance with a path to a font file
/// on disk and a size. /// on disk and a size.
public init(path: String, size: Double) { public init(path: String, size: Double) {
#if os(macOS)
resource = .path((path as NSString).expandingTildeInPath) resource = .path((path as NSString).expandingTildeInPath)
#else
resource = .path(path)
#endif
self.size = size self.size = size
} }
@ -41,7 +38,69 @@ public extension Font {
enum Resource { enum Resource {
/// Use an appropriate system font /// Use an appropriate system font
case system case system
/// Use a pre-loaded font
case preloaded(Loaded)
/// Load a font file from a given file system path /// Load a font file from a given file system path
case path(String) 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

View File

@ -6,13 +6,9 @@
import Foundation import Foundation
/* #if !os(Linux)
* Extends the Theme struct with static properties that
* represent common Xcode defaults as well as community
* favourites.
*/
public extension Theme {
public extension Theme {
/// Create a theme matching the "Sundell's Colors" Xcode theme /// Create a theme matching the "Sundell's Colors" Xcode theme
static func sundellsColors(withFont font: Font) -> Theme { static func sundellsColors(withFont font: Font) -> Theme {
return 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 { static func midnight(withFont font: Font) -> Theme {
return Theme( return Theme(
font: font, 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 { static func wwdc17(withFont font: Font) -> Theme {
return Theme( return Theme(
font: font, 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 { static func wwdc18(withFont font: Font) -> Theme {
return Theme( return Theme(
font: font, font: font,
@ -102,6 +101,7 @@ public extension Theme {
) )
} }
/// Create a theme matching Xcode's "Sunset" theme
static func sunset(withFont font: Font) -> Theme { static func sunset(withFont font: Font) -> Theme {
return Theme( return Theme(
font: font, font: font,
@ -124,6 +124,7 @@ public extension Theme {
) )
} }
/// Create a theme matching Xcode's "Presentation" theme
static func presentation(withFont font: Font) -> Theme { static func presentation(withFont font: Font) -> Theme {
return Theme( return Theme(
font: font, font: font,
@ -145,5 +146,6 @@ public extension Theme {
] ]
) )
} }
} }
#endif

View File

@ -6,10 +6,11 @@
import Foundation import Foundation
#if !os(Linux)
/// A theme describes what fonts and colors to use when rendering /// A theme describes what fonts and colors to use when rendering
/// certain output formats - such as `NSAttributedString`. A default /// certain output formats - such as `NSAttributedString`. Several
/// implementation is provided that matches the "Sundell's Colors" /// default implementations are provided - see Theme+Defaults.swift.
/// Xcode theme, by using the `sundellsColors(withFont:)` method.
public struct Theme { public struct Theme {
/// What font to use to render the highlighted text /// What font to use to render the highlighted text
public var font: Font public var font: Font
@ -24,3 +25,5 @@ public struct Theme {
self.tokenColors = tokenColors self.tokenColors = tokenColors
} }
} }
#endif