added iOS source code
[wl-app.git] / iOS / Pods / FontBlaster / Sources / FontBlaster.swift
1 //
2 //  FontBlaster.swift
3 //  FontBlaster
4 //
5 //  Created by Arthur Sabintsev on 5/5/15.
6 //  Copyright (c) 2015 Arthur Ariel Sabintsev. All rights reserved.
7 //
8
9 import CoreGraphics
10 import CoreText
11 import Foundation
12 import UIKit
13
14 // MARK: - FontBlaster
15
16 final public class FontBlaster {
17
18     fileprivate enum SupportedFontExtensions: String {
19         case TrueTypeFont = ".ttf"
20         case OpenTypeFont = ".otf"
21     }
22
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)
27
28     /// Toggles debug print() statements
29     public static var debugEnabled = false
30
31     /// A list of the loaded fonts
32     public static var loadedFonts: [String] = []
33
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)
37     }
38
39     /**
40      Load all fonts found in a specific bundle. If no value is entered, it defaults to the main bundle.
41
42      - returns: An array of strings constaining the names of the fonts that were loaded.
43      */
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)
48         handler?(loadedFonts)
49     }
50 }
51
52 // MARK: - Helpers (Font Loading)
53
54 private extension FontBlaster {
55     /// Loads all fonts found in a bundle.
56     ///
57     /// - Parameter path: The absolute path to the bundle.
58     class func loadFontsForBundle(withPath path: String) {
59         do {
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 {
64                     loadFont(font: font)
65                 }
66             } else {
67                 printDebugMessage(message: "No fonts were found in the bundle path: \(path).")
68             }
69         } catch let error as NSError {
70             printDebugMessage(message: "There was an error loading fonts from the bundle. \nPath: \(path).\nError: \(error)")
71         }
72     }
73
74     /// Loads all fonts found in a bundle that is loaded within another bundle.
75     ///
76     /// - Parameter path: The absolute path to the bundle.
77     class func loadFontsFromBundlesFoundInBundle(path: String) {
78         do {
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)
85                 }
86             }
87         } catch let error as NSError {
88             printDebugMessage(message: "There was an error accessing bundle with path. \nPath: \(path).\nError: \(error)")
89         }
90     }
91
92     /// Loads a specific font.
93     ///
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)
100
101         var fontError: Unmanaged<CFError>?
102         if let fontData = try? Data(contentsOf: fontFileURL) as CFData,
103             let dataProvider = CGDataProvider(data: fontData) {
104
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
109             _ = UIFont()
110
111             let fontRef = CGFont(dataProvider)
112
113             if CTFontManagerRegisterGraphicsFont(fontRef!, &fontError) {
114
115                 if let postScriptName = fontRef?.postScriptName {
116                     printDebugMessage(message: "Successfully loaded font: '\(postScriptName)'.")
117                     loadedFonts.append(String(postScriptName))
118                 }
119
120             } else if let fontError = fontError?.takeRetainedValue() {
121                 let errorDescription = CFErrorCopyDescription(fontError)
122                 printDebugMessage(message: "Failed to load font '\(fontName)': \(String(describing: errorDescription))")
123             }
124         } else {
125             guard let fontError = fontError?.takeRetainedValue() else {
126                 printDebugMessage(message: "Failed to load font '\(fontName)'.")
127                 return
128             }
129
130             let errorDescription = CFErrorCopyDescription(fontError)
131             printDebugMessage(message: "Failed to load font '\(fontName)': \(String(describing: errorDescription))")
132         }
133     }
134 }
135
136 // MARK: - Helpers (Miscellaneous)
137
138 private extension FontBlaster {
139     /// Parses all of the fonts into their name and extension components.
140     ///
141     /// - Parameters:
142     ///     - path: The absolute path to the font file.
143     ///     - contents: The contents of an Bundle as an array of String objects.
144     /// 
145     /// - Returns: A an array of Font objects.
146     class func fonts(fromPath path: String, withContents contents: [String]) -> [Font] {
147         var fonts = [Font]()
148         for fontName in contents {
149             var parsedFont: (FontName, FontExtension)?
150
151             if fontName.contains(SupportedFontExtensions.TrueTypeFont.rawValue) || fontName.contains(FontBlaster.SupportedFontExtensions.OpenTypeFont.rawValue) {
152                 parsedFont = font(fromName: fontName)
153             }
154
155             if let parsedFont = parsedFont {
156                 let font: Font = (path, parsedFont.0, parsedFont.1)
157                 fonts.append(font)
158             }
159         }
160
161         return fonts
162     }
163
164     /// Parses a font into its name and extension components.
165     ///
166     /// - Parameter name: The name of the font.
167     /// 
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])
172     }
173
174     /// Prints debug messages to the console if debugEnabled is set to true.
175     ///
176     /// - Parameter message: The status to print to the console.
177     class func printDebugMessage(message: String) {
178         if debugEnabled == true {
179             print("[FontBlaster]: \(message)")
180         }
181     }
182 }