-
Notifications
You must be signed in to change notification settings - Fork 40
/
Copy pathFontRenderer.swift
161 lines (123 loc) · 4.9 KB
/
FontRenderer.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//
// FontLoader.swift
// UIKit
//
// Created by Geordie Jay on 19.06.17.
// Copyright © 2017 flowkey. All rights reserved.
//
internal import SDL_ttf
extension FontRenderer {
/// Stores the renderers for a specific Name/Size UIFont configuration to avoid reiniting them all the time.
static var cache = [String: FontRenderer]()
/// Called whenever the UIScreen is destroyed to avoid strange font rendering bugs on reinit.
static func cleanupSession() {
cache.removeAll()
TTF_Quit()
}
private static func initialize() -> Bool {
return (TTF_WasInit() == 1) || (TTF_Init() != -1)
}
}
@MainActor
public class FontRenderer {
let rawPointer: UnsafeMutablePointer<TTF_Font>
deinit { TTF_CloseFont(rawPointer) }
init?(_ source: CGDataProvider, size: Int32) {
if !FontRenderer.initialize() { return nil }
let rwOp = SDL_RWFromConstMem(source.data, Int32(source.data.count))
guard let font = TTF_OpenFontRW(rwOp, 1, size) else { return nil }
TTF_SetFontHinting(font, TTF_HINTING_LIGHT) // recommended in docs for max quality
rawPointer = font
}
func getLineHeight() -> Int {
return Int(TTF_FontLineSkip(rawPointer))
}
func getFontStyleName() -> String? {
guard let styleName = TTF_FontFaceStyleName(rawPointer) else { return nil }
return String(cString: styleName)
}
func getFontFamilyName() -> String? {
guard let cStringFamilyName = TTF_FontFaceFamilyName(rawPointer) else { return nil }
return String(cString: cStringFamilyName)
}
func render(_ text: String?, color: UIColor, wrapLength: Int = 0) -> CGImage? {
guard let text = text else { return nil }
guard
let surface = (wrapLength > 0) ?
TTF_RenderUTF8_Blended_Wrapped(rawPointer, text, color.sdlColor, UInt32(wrapLength)) :
TTF_RenderUTF8_Blended(rawPointer, text, color.sdlColor)
else { return nil }
defer { SDL_FreeSurface(surface) }
return CGImage(surface: surface)
}
}
// MARK: Get size of text for current font
extension FontRenderer {
func singleLineSize(of text: String) -> CGSize {
let (width, height) = size(text)
return CGSize(width: CGFloat(width), height: CGFloat(height))
}
}
// Optimise perf
private let newLineR = Int32("\r".utf8.first!)
private let newLineN = Int32("\n".utf8.first!)
extension FontRenderer {
private func charIsDelimiter(_ char: CChar) -> Bool {
let wrappingDelimiters = " -\t\r\n"
return strchr(wrappingDelimiters, Int32(char)) != nil
}
internal func multilineSize(of text: String, wrapLength: UInt) -> CGSize {
return multilineSize(of: text, wrapLength: Int(wrapLength))
}
private func multilineSize(of text: UnsafePointer<CChar>, wrapLength: Int) -> CGSize {
guard wrapLength > 0 else { return .zero }
let lineSpace = 2
var textLineHeight: Int32 = 0
var tok = UnsafeMutablePointer(mutating: text)
let end = tok + SDL_strlen(text)
var lines = [UnsafeMutablePointer<CChar>]()
repeat {
lines.append(tok)
/* Look for the end of the line */
var searchIndex =
strchr(tok, newLineR) ??
strchr(tok, newLineN) ??
end
var firstCharOfNextLine = searchIndex + 1
/* Get the longest string that will fit in the desired space */
while true {
/* Strip trailing whitespace */
while searchIndex > tok && charIsDelimiter(searchIndex[-1]) {
searchIndex -= 1
}
if searchIndex == tok {
if charIsDelimiter(searchIndex.pointee) {
searchIndex.pointee = CChar(0)
}
break
}
let delim = searchIndex.pointee
searchIndex.pointee = CChar(0)
var textLineWidth: Int32 = 0
TTF_SizeUTF8(self.rawPointer, tok, &textLineWidth, &textLineHeight)
if (UInt32(textLineWidth) <= wrapLength) {
break
} else {
/* Back up and try again... */
searchIndex.pointee = delim
}
while searchIndex > tok && !charIsDelimiter(searchIndex[-1]) {
searchIndex -= 1
}
if searchIndex > tok {
firstCharOfNextLine = searchIndex
}
}
tok = firstCharOfNextLine
} while (tok < end)
let linesCount = lines.count
let combinedTextHeight = Int(textLineHeight) * linesCount
let combinedLineSpacing = lineSpace * (linesCount - 1)
return CGSize(width: wrapLength, height: combinedTextHeight + combinedLineSpacing)
}
}