Skip to content

Commit 12063e3

Browse files
committed
Enable loading fonts from file
1 parent 51a6cce commit 12063e3

File tree

5 files changed

+69
-22
lines changed

5 files changed

+69
-22
lines changed

Help/text.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,5 +140,23 @@ fill text "Hello World"
140140

141141
**Note:** Some fonts are inherently much more detailed than others, and may take a considerable time to generate. You may need to set the [detail](options.md#detail) option to a lower value for text than you would for other geometry.
142142

143+
The font name you provide must match a font that is already installed on your system. If no matching fonts are found then an error will be raised. If you wish to load a font file directly, without installing it, you can pass the filename or path to the `font` command instead of the font name:
144+
145+
```swift
146+
font "filename.ttf"
147+
```
148+
149+
Only fonts with a ".ttf", ".otf" or ".ttc" file extension are supported. The extension is required, or the `font` parameter will be treated as a system font rather than a file. If a relative path or filename is used, it should be specified relative to the ShapeScript file that references it.
150+
151+
The filename can be constructed dynamically by using the [text interpolation](text.md#interpolation) feature, which is sometimes useful if, for example, you have multiple font files with a common prefix or suffix:
152+
153+
```swift
154+
for n in 1 to 5 {
155+
texture "font" n ".ttf"
156+
text "Hello, World!"
157+
translate 0 -1 0
158+
}
159+
```
160+
143161
---
144162
[Index](index.md) | Next: [Builders](builders.md)

ShapeScript/EvaluationContext.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,45 @@ extension EvaluationContext {
214214
return url
215215
}
216216

217+
func resolveFont(_ name: String?) throws -> String? {
218+
guard let name = name?
219+
.trimmingCharacters(in: .whitespacesAndNewlines)
220+
.replacingOccurrences(of: "\t", with: " ")
221+
.replacingOccurrences(of: " ", with: " ")
222+
else {
223+
return nil
224+
}
225+
#if canImport(CoreGraphics)
226+
guard [".otf", ".ttf", ".ttc"].contains(where: {
227+
name.lowercased().hasSuffix($0)
228+
}) else {
229+
guard CGFont(name as CFString) != nil else {
230+
var options = [String]()
231+
#if canImport(CoreText)
232+
options += CTFontManagerCopyAvailablePostScriptNames() as? [String] ?? []
233+
options += CTFontManagerCopyAvailableFontFamilyNames() as? [String] ?? []
234+
#endif
235+
throw RuntimeErrorType.unknownFont(name, options: options)
236+
}
237+
return name
238+
}
239+
let url = try resolveURL(for: name)
240+
guard let dataProvider = CGDataProvider(url: url as CFURL) else {
241+
throw RuntimeErrorType.fileNotFound(for: name, at: url)
242+
}
243+
#if canImport(CoreText)
244+
guard let cgFont = CGFont(dataProvider),
245+
CTFontManagerRegisterGraphicsFont(cgFont, nil)
246+
else {
247+
throw RuntimeErrorType.fileParsingError(for: name, at: url, message: "")
248+
}
249+
return cgFont.fullName as String?
250+
#endif
251+
#else
252+
return name
253+
#endif
254+
}
255+
217256
func importModel(at path: String) throws {
218257
let url = try resolveURL(for: path)
219258
let program: Program

ShapeScript/Interpreter.swift

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,7 +1314,8 @@ extension Expression {
13141314
}
13151315
case .font where Value.tuple(values).isConvertible(to: .string):
13161316
let name = Value.tuple(values).stringValue
1317-
return try RuntimeError.wrap(.string(validateFont(name)), at: parameters[0].range)
1317+
let range = parameters.first!.range.lowerBound ..< parameters.last!.range.upperBound
1318+
return try RuntimeError.wrap(.string(context.resolveFont(name)), at: range)
13181319
case .paths:
13191320
return try .tuple(values.enumerated().flatMap { i, value -> [Value] in
13201321
switch value {
@@ -1381,24 +1382,3 @@ extension Expression {
13811382
}
13821383
}
13831384
}
1384-
1385-
private func validateFont(_ name: String?) throws -> String? {
1386-
guard let name = name?
1387-
.trimmingCharacters(in: .whitespacesAndNewlines)
1388-
.replacingOccurrences(of: "\t", with: " ")
1389-
.replacingOccurrences(of: " ", with: " ")
1390-
else {
1391-
return nil
1392-
}
1393-
#if canImport(CoreGraphics)
1394-
guard CGFont(name as CFString) != nil else {
1395-
var options = [String]()
1396-
#if canImport(CoreText)
1397-
options += CTFontManagerCopyAvailablePostScriptNames() as? [String] ?? []
1398-
options += CTFontManagerCopyAvailableFontFamilyNames() as? [String] ?? []
1399-
#endif
1400-
throw RuntimeErrorType.unknownFont(name, options: options)
1401-
}
1402-
#endif
1403-
return name
1404-
}
Binary file not shown.

ShapeScriptTests/InterpreterTests.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,16 @@ class InterpreterTests: XCTestCase {
10161016
#endif
10171017
}
10181018

1019+
func testSetFontWithFile() throws {
1020+
#if canImport(CoreGraphics)
1021+
let program = try parse("font \"EdgeOfTheGalaxyRegular-OVEa6.otf\"")
1022+
let delegate = TestDelegate()
1023+
let context = EvaluationContext(source: program.source, delegate: delegate)
1024+
XCTAssertNoThrow(try program.evaluate(in: context))
1025+
XCTAssertEqual(context.font, "Edge of the Galaxy Regular")
1026+
#endif
1027+
}
1028+
10191029
// MARK: Import
10201030

10211031
func testImport() throws {

0 commit comments

Comments
 (0)