Skip to content

Commit 22c7902

Browse files
committed
Add system font support
1 parent 9da90a8 commit 22c7902

File tree

8 files changed

+104
-33
lines changed

8 files changed

+104
-33
lines changed

Sources/GateEngine/Resources/Text/Backends/ImageFont.swift

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ struct ImageFont: FontBackend {
2626
// fontData[.boldItalic] = boldItalic
2727
self.fontData = fontData
2828
}
29+
30+
func supportsStyle(_ style: Font.Style) -> Bool {
31+
return self.fontData.keys.contains(style)
32+
}
2933

3034
@MainActor
3135
mutating func characterData(forKey key: Font.Key, character: Character) -> CharacterData {
@@ -64,7 +68,6 @@ struct ImageFont: FontBackend {
6468
origin: GameMath.Position2,
6569
xAdvance: inout Float
6670
) -> AlignedCharacter {
67-
6871
let nativePointSize = nativePointSizes[key.style]!
6972
let scaledPointSize = Float(key.pointSize - (key.pointSize % nativePointSize))
7073

@@ -88,12 +91,15 @@ struct ImageFont: FontBackend {
8891
if let existing = textures[key.style] {
8992
return existing
9093
}
91-
populate(style: key.style)
92-
return textures[key.style]!
94+
if populate(style: key.style) {
95+
return textures[key.style]!
96+
}
97+
// return regular font as a fallback
98+
return texture(forKey: Font.Key(style: .regular, pointSize: key.pointSize))
9399
}
94100

95-
@MainActor private mutating func populate(style: Font.Style) {
96-
let fontData = fontData[style]!
101+
@MainActor private mutating func populate(style: Font.Style) -> Bool {
102+
guard let fontData = fontData[style] else {return false}
97103
let data = fontData.rawTexture.imageData
98104
let size = fontData.rawTexture.imageSize
99105

@@ -155,6 +161,7 @@ struct ImageFont: FontBackend {
155161

156162
self.textures[style] = texture
157163
self.nativePointSizes[style] = UInt(pointSize)
164+
return true
158165
}
159166

160167
var preferredSampleFilter: Text.SampleFilter { .nearest }

Sources/GateEngine/Resources/Text/Backends/TTFFont.swift

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,25 @@ private func getBakedQuad(
6666
internal var textureSizes: [Font.Key: Size2] = [:]
6767
internal var characterDatas: [Font.Key: [CharData]] = [:]
6868

69-
init(regular: String) async throws {
69+
init(regular: String, bold: String?, italic: String?, boldItalic: String?) async throws {
7070
let regular = try await Platform.current.loadResource(from: regular)
7171
assert(regular.isEmpty == false, "ttf file cannot be empty.")
72-
let fontData: [Font.Style: Data] = [.regular: regular]
73-
// fontData[.bold] = bold
74-
// fontData[.italic] = italic
75-
// fontData[.boldItalic] = boldItalic
72+
var fontData: [Font.Style: Data] = [.regular: regular]
73+
if let bold, let bold = try? await Platform.current.loadResource(from: bold) {
74+
fontData[.bold] = bold
75+
}
76+
if let italic, let italic = try? await Platform.current.loadResource(from: italic) {
77+
fontData[.italic] = italic
78+
}
79+
if let boldItalic, let boldItalic = try? await Platform.current.loadResource(from: boldItalic) {
80+
fontData[.boldItalic] = boldItalic
81+
}
7682
self.fontData = fontData
7783
}
84+
85+
func supportsStyle(_ style: Font.Style) -> Bool {
86+
return self.fontData.keys.contains(style)
87+
}
7888

7989
mutating func characterData(forKey key: Font.Key, character: Character) -> CharacterData {
8090
let characterData: [CharData] = {
@@ -144,7 +154,7 @@ private func getBakedQuad(
144154
}
145155

146156
private func textureSize(forPointSize pointSize: UInt, style: Font.Style) -> Size2 {
147-
let size = Float((pointSize + 1) * 10)
157+
let size = Float((pointSize * 2) * 10)
148158
return Size2(size, size)
149159
}
150160

@@ -179,7 +189,7 @@ private func getBakedQuad(
179189

180190
let texture = Texture(
181191
rawTexture: RawTexture(imageSize: .init(size), imageData: Data(image)),
182-
mipMapping: .none
192+
mipMapping: .auto(levels: .max)
183193
)
184194

185195
let key = Font.Key(style: style, pointSize: pointSize)
@@ -188,7 +198,7 @@ private func getBakedQuad(
188198
self.characterDatas[key] = charData
189199
}
190200

191-
nonisolated var preferredSampleFilter: Text.SampleFilter { .nearest }
201+
nonisolated var preferredSampleFilter: Text.SampleFilter { .linear }
192202
}
193203

194204
#endif

Sources/GateEngine/Resources/Text/Font.swift

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ struct AlignedCharacter {
2121

2222
protocol FontBackend {
2323
nonisolated var preferredSampleFilter: Text.SampleFilter { get }
24+
func supportsStyle(_ style: Font.Style) -> Bool
2425
@MainActor mutating func texture(forKey key: Font.Key) -> Texture
2526
@MainActor mutating func characterData(forKey key: Font.Key, character: Character)
2627
-> CharacterData
@@ -39,16 +40,23 @@ public final class Font: OldResource {
3940
internal var preferredSampleFilter: Text.SampleFilter {
4041
return backend.preferredSampleFilter
4142
}
43+
44+
func effectiveStyle(for requestedStyle: Style) -> Style {
45+
if backend.supportsStyle(requestedStyle) {
46+
return requestedStyle
47+
}
48+
return .regular
49+
}
4250

43-
public init(ttfRegular regular: String) {
51+
public init(ttfRegular regular: String, bold: String? = nil, italic: String? = nil, boldItalic: String? = nil) {
4452
super.init()
4553
#if DEBUG
4654
self._backend.configure(withOwner: self)
4755
#endif
4856
#if canImport(TrueType)
4957
Task.detached {
5058
do {
51-
let backend = try await TTFFont(regular: regular)
59+
let backend = try await TTFFont(regular: regular, bold: bold, italic: italic, boldItalic: boldItalic)
5260
Task { @MainActor in
5361
self.backend = backend
5462
self.state = .ready
@@ -140,8 +148,16 @@ public final class Font: OldResource {
140148
@inlinable
141149
public nonisolated static var `default`: Font { .tuffy }
142150

143-
public nonisolated static let tuffy: Font = Font(ttfRegular: "GateEngine/Fonts/Tuffy/Tuffy.ttf")
144-
public nonisolated static let tuffyBold: Font = Font(ttfRegular: "GateEngine/Fonts/Tuffy/Tuffy_Bold.ttf")
151+
public nonisolated static let tuffy: Font = Font(
152+
ttfRegular: "GateEngine/Fonts/Tuffy/Tuffy.ttf",
153+
bold: "GateEngine/Fonts/Tuffy/Tuffy_Bold.ttf",
154+
italic: "GateEngine/Fonts/Tuffy/Tuffy_Italic.ttf",
155+
boldItalic: "GateEngine/Fonts/Tuffy/Tuffy_Bold_Italic.ttf"
156+
)
145157
public nonisolated static let micro: Font = Font(pngRegular: "GateEngine/Fonts/Micro/micro.png")
146158
public nonisolated static let babel: Font = Font(pngRegular: "GateEngine/Fonts/Babel/Babel.png")
159+
160+
public static func named(_ name: String) -> Font {
161+
return Platform.current.font(named: name)
162+
}
147163
}

Sources/GateEngine/System/Platforms/Platform Implementations/Apple/AppKit/AppKitPlatform.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,37 @@ public struct AppKitPlatform: PlatformProtocol, InternalPlatformProtocol {
117117
#endif
118118
}
119119

120+
extension AppKitPlatform {
121+
public func font(named name: String) -> Font {
122+
var fonts: [URL] = []
123+
do {
124+
let userFonts = try self.synchronousFileSystem.contentsOfDirectory(at: "~/Library/Fonts")
125+
fonts.append(contentsOf: userFonts.map({"~/Library/Fonts/" + $0}).map({URL(fileURLWithPath: $0).resolvingSymlinksInPath().absoluteURL}).filter({$0.pathExtension.caseInsensitiveCompare("ttf") == .orderedSame}))
126+
}catch{
127+
Log.debug(error)
128+
}
129+
do {
130+
let systemFonts = try self.synchronousFileSystem.contentsOfDirectory(at: "/Library/Fonts")
131+
fonts.append(contentsOf: systemFonts.map({"/Library/Fonts/" + $0}).map({URL(fileURLWithPath: $0).resolvingSymlinksInPath().absoluteURL}).filter({$0.pathExtension.caseInsensitiveCompare("ttf") == .orderedSame}))
132+
}catch{
133+
Log.debug(error)
134+
}
135+
do {
136+
let systemFonts2 = try self.synchronousFileSystem.contentsOfDirectory(at: "/System/Library/Fonts")
137+
fonts.append(contentsOf: systemFonts2.map({"/System/Library/Fonts/" + $0}).map({URL(fileURLWithPath: $0).resolvingSymlinksInPath().absoluteURL}).filter({$0.pathExtension.caseInsensitiveCompare("ttf") == .orderedSame}))
138+
}catch{
139+
Log.debug(error)
140+
}
141+
for font in fonts {
142+
if font.deletingPathExtension().lastPathComponent.caseInsensitiveCompare(name) == .orderedSame {
143+
return Font(ttfRegular: font.path(percentEncoded: false))
144+
}
145+
}
146+
Log.warnOnce("No system font named \(name). Using default font instead.")
147+
return .default
148+
}
149+
}
150+
120151
extension AppKitPlatform {
121152
@MainActor func setupStatusBarMenu() {
122153
let appBundleOrCommandLineName =

Sources/GateEngine/System/Platforms/PlatformProtocol.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public extension Platform {
1515

1616
public protocol PlatformProtocol: Sendable {
1717
var supportsMultipleWindows: Bool { get }
18+
19+
func font(named name: String) -> Font
1820

1921
#if GATEENGINE_PLATFORM_HAS_FILESYSTEM
2022
#if GATEENGINE_PLATFORM_HAS_SynchronousFileSystem
@@ -34,6 +36,13 @@ public protocol PlatformProtocol: Sendable {
3436
#endif
3537
}
3638

39+
extension PlatformProtocol {
40+
func font(named name: String) -> Font {
41+
Log.infoOnce("Current platform does not support system fonts. Using default font.")
42+
return .default
43+
}
44+
}
45+
3746
#if GATEENGINE_PLATFORM_HAS_FILESYSTEM
3847
extension PlatformProtocol {
3948
#if GATEENGINE_PLATFORM_HAS_SynchronousFileSystem

Sources/GateEngine/UI/Button.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ open class Button: Control {
179179
private var labelCreated: Bool = false
180180
weak var _label: Label! = nil
181181
func createLabel() {
182-
let label = Label(text: "Button", font: .babel, fontSize: 14, style: .regular, textColor: self.textColors[.normal] ?? .white)
182+
let label = Label(text: "Button", font: .default, fontSize: 18, style: .bold, textColor: self.textColors[.normal] ?? .white)
183183
label.centerXAnchor.constrain(to: self.centerXAnchor)
184184
label.centerYAnchor.constrain(to: self.centerYAnchor)
185185
label.widthAnchor.constrain(to: self.widthAnchor)

Sources/GateEngine/UI/Label.swift

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public final class Label: View {
3535
internal var texture: Texture {
3636
if needsUpdateTexture {
3737
needsUpdateTexture = false
38-
_texture = font.texture(forPointSize: UInt(actualPointSize.rounded()), style: style)
38+
_texture = font.texture(forPointSize: actualPointSize, style: style)
3939
}
4040
return _texture
4141
}
@@ -76,7 +76,7 @@ public final class Label: View {
7676
fromString: text,
7777
font: font,
7878
pointSize: actualPointSize,
79-
style: style,
79+
style: font.effectiveStyle(for: style),
8080
paragraphWidth: paragraphWidth
8181
)
8282
_geometry.rawGeometry = values.0
@@ -110,8 +110,8 @@ public final class Label: View {
110110
}
111111
}
112112
}
113-
internal var actualPointSize: Float {
114-
return Float(fontSize) * interfaceScale
113+
internal var actualPointSize: UInt {
114+
return UInt((Float(fontSize) * interfaceScale).rounded(.awayFromZero))
115115
}
116116
public var style: Font.Style {
117117
didSet {
@@ -161,14 +161,14 @@ public final class Label: View {
161161
let yOffset: Float
162162
switch textAlignment {
163163
case .leading:
164-
xOffset = 4
165-
yOffset = 4
164+
xOffset = 4 * self.interfaceScale
165+
yOffset = (rect.height / 2) - ((size.height / 2) * self.interfaceScale)
166166
case .centered:
167167
xOffset = (rect.width / 2) - ((size.width / 2) * self.interfaceScale)
168168
yOffset = (rect.height / 2) - ((size.height / 2) * self.interfaceScale)
169169
case .trailing:
170-
xOffset = rect.width - (size.width * self.interfaceScale) - 4
171-
yOffset = rect.height - (size.height * self.interfaceScale) - 4
170+
xOffset = rect.width - (size.width * self.interfaceScale) - (4 * self.interfaceScale)
171+
yOffset = rect.height - (size.height * self.interfaceScale) - (4 * self.interfaceScale)
172172
}
173173

174174
canvas.insert(
@@ -187,7 +187,7 @@ public final class Label: View {
187187
private static func rawGeometry(
188188
fromString string: String,
189189
font: Font,
190-
pointSize: Float,
190+
pointSize: UInt,
191191
style: Font.Style,
192192
paragraphWidth: Float?
193193
) -> (RawGeometry, Size2) {
@@ -198,8 +198,6 @@ public final class Label: View {
198198
case wordComponent
199199
}
200200

201-
let roundedPointSize = UInt(pointSize.rounded())
202-
203201
var triangles: [Triangle] = []
204202
triangles.reserveCapacity(string.count)
205203

@@ -213,7 +211,7 @@ public final class Label: View {
213211
var currentWord: [Triangle] = []
214212

215213
func newLine() {
216-
yPosition += pointSize
214+
yPosition += Float(pointSize)
217215
lineCount += 1
218216
}
219217

@@ -261,7 +259,7 @@ public final class Label: View {
261259
func processCharacter(_ char: Character) {
262260
let quad = font.alignedCharacter(
263261
forCharacter: char,
264-
pointSize: roundedPointSize,
262+
pointSize: pointSize,
265263
style: style,
266264
origin: Position2(xPosition, yPosition),
267265
xAdvance: &xAdvance
@@ -292,7 +290,7 @@ public final class Label: View {
292290
var xAdvance: Float = .nan
293291
let quad = font.alignedCharacter(
294292
forCharacter: char,
295-
pointSize: roundedPointSize,
293+
pointSize: pointSize,
296294
style: style,
297295
origin: Position2(xPosition, yPosition),
298296
xAdvance: &xAdvance

Sources/GateEngine/UI/Toggle.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ open class Toggle: Control {
210210

211211
private var labelCreated: Bool = false
212212
public private(set) lazy var label: Label = {
213-
let label = Label(text: "Toggle", font: .babel, fontSize: 14, style: .regular, textColor: self.textColors[.normal] ?? .white)
213+
let label = Label(text: "Toggle", font: .default, fontSize: 18, style: .bold, textColor: self.textColors[.normal] ?? .white)
214214
label.centerXAnchor.constrain(to: self.centerXAnchor)
215215
label.centerYAnchor.constrain(to: self.centerYAnchor)
216216
label.widthAnchor.constrain(to: self.widthAnchor)

0 commit comments

Comments
 (0)