5 // Created by Arthur Sabintsev on 5/5/15.
6 // Copyright (c) 2015 Arthur Ariel Sabintsev. All rights reserved.
14 // MARK: - FontBlaster
16 final public class FontBlaster {
18 fileprivate enum SupportedFontExtensions: String {
19 case TrueTypeFont = ".ttf"
20 case OpenTypeFont = ".otf"
23 fileprivate typealias FontPath = String
24 fileprivate typealias FontName = String
25 fileprivate typealias FontExtension = String
26 fileprivate typealias Font = (path: FontPath, name: FontName, ext: FontExtension)
28 /// Toggles debug print() statements
29 public static var debugEnabled = false
31 /// A list of the loaded fonts
32 public static var loadedFonts: [String] = []
34 /// Load all fonts found in a specific bundle. If no value is entered, it defaults to the main bundle.
35 public class func blast(bundle: Bundle = Bundle.main) {
36 blast(bundle: bundle, completion: nil)
40 Load all fonts found in a specific bundle. If no value is entered, it defaults to the main bundle.
42 - returns: An array of strings constaining the names of the fonts that were loaded.
44 public class func blast(bundle: Bundle = Bundle.main, completion handler: (([String])->Void)?) {
45 let path = bundle.bundlePath
46 loadFontsForBundle(withPath: path)
47 loadFontsFromBundlesFoundInBundle(path: path)
52 // MARK: - Helpers (Font Loading)
54 private extension FontBlaster {
55 /// Loads all fonts found in a bundle.
57 /// - Parameter path: The absolute path to the bundle.
58 class func loadFontsForBundle(withPath path: String) {
60 let contents = try FileManager.default.contentsOfDirectory(atPath: path) as [String]
61 let loadedFonts = fonts(fromPath: path, withContents: contents)
62 if !loadedFonts.isEmpty {
63 for font in loadedFonts {
67 printDebugMessage(message: "No fonts were found in the bundle path: \(path).")
69 } catch let error as NSError {
70 printDebugMessage(message: "There was an error loading fonts from the bundle. \nPath: \(path).\nError: \(error)")
74 /// Loads all fonts found in a bundle that is loaded within another bundle.
76 /// - Parameter path: The absolute path to the bundle.
77 class func loadFontsFromBundlesFoundInBundle(path: String) {
79 let contents = try FileManager.default.contentsOfDirectory(atPath: path)
80 for item in contents {
81 if let url = URL(string: path),
82 item.contains(".bundle") {
83 let urlPathString = url.appendingPathComponent(item).absoluteString
84 loadFontsForBundle(withPath: urlPathString)
87 } catch let error as NSError {
88 printDebugMessage(message: "There was an error accessing bundle with path. \nPath: \(path).\nError: \(error)")
92 /// Loads a specific font.
94 /// - Parameter font: The font to load.
95 class func loadFont(font: Font) {
96 let fontPath: FontPath = font.path
97 let fontName: FontName = font.name
98 let fontExtension: FontExtension = font.ext
99 let fontFileURL = URL(fileURLWithPath: fontPath).appendingPathComponent(fontName).appendingPathExtension(fontExtension)
101 var fontError: Unmanaged<CFError>?
102 if let fontData = try? Data(contentsOf: fontFileURL) as CFData,
103 let dataProvider = CGDataProvider(data: fontData) {
105 /// Fixes deadlocking issue caused by `let fontRef = CGFont(dataProvider)`.
106 /// Temporary fix until rdar://18778790 is addressed.
107 /// Open Radar at http://www.openradar.me/18778790
108 /// Discussion at https://github.com/ArtSabintsev/FontBlaster/issues/19
111 let fontRef = CGFont(dataProvider)
113 if CTFontManagerRegisterGraphicsFont(fontRef!, &fontError) {
115 if let postScriptName = fontRef?.postScriptName {
116 printDebugMessage(message: "Successfully loaded font: '\(postScriptName)'.")
117 loadedFonts.append(String(postScriptName))
120 } else if let fontError = fontError?.takeRetainedValue() {
121 let errorDescription = CFErrorCopyDescription(fontError)
122 printDebugMessage(message: "Failed to load font '\(fontName)': \(String(describing: errorDescription))")
125 guard let fontError = fontError?.takeRetainedValue() else {
126 printDebugMessage(message: "Failed to load font '\(fontName)'.")
130 let errorDescription = CFErrorCopyDescription(fontError)
131 printDebugMessage(message: "Failed to load font '\(fontName)': \(String(describing: errorDescription))")
136 // MARK: - Helpers (Miscellaneous)
138 private extension FontBlaster {
139 /// Parses all of the fonts into their name and extension components.
142 /// - path: The absolute path to the font file.
143 /// - contents: The contents of an Bundle as an array of String objects.
145 /// - Returns: A an array of Font objects.
146 class func fonts(fromPath path: String, withContents contents: [String]) -> [Font] {
148 for fontName in contents {
149 var parsedFont: (FontName, FontExtension)?
151 if fontName.contains(SupportedFontExtensions.TrueTypeFont.rawValue) || fontName.contains(FontBlaster.SupportedFontExtensions.OpenTypeFont.rawValue) {
152 parsedFont = font(fromName: fontName)
155 if let parsedFont = parsedFont {
156 let font: Font = (path, parsedFont.0, parsedFont.1)
164 /// Parses a font into its name and extension components.
166 /// - Parameter name: The name of the font.
168 /// - Returns: A tuple with the font's name and extension.
169 class func font(fromName name: String) -> (FontName, FontExtension) {
170 let components = name.split{$0 == "."}.map { String($0) }
171 return (components[0], components[1])
174 /// Prints debug messages to the console if debugEnabled is set to true.
176 /// - Parameter message: The status to print to the console.
177 class func printDebugMessage(message: String) {
178 if debugEnabled == true {
179 print("[FontBlaster]: \(message)")