Files
playdate-kit/Sources/PlayDate/GraphicsFont.swift
T
javierandClaude Fable 5 6485997edc Promote subsystem namespaces to the top level of the module
System, Display, Graphics, Sprite, Sound, File, JSON, Lua, Scoreboards,
Network, Rect, SystemEvent, and AccessReply now live at module scope
instead of being nested in the Playdate enum; consumers write
System.log(...) instead of Playdate.System.log(...), and can qualify with
the module name (PlayDate.System) on collision. The Playdate enum remains
only as the raw C API bootstrap (initialize(with:), api, apiPointer), and
Playdate.Error is renamed PlaydateError so no top-level type shadows
Swift.Error.

Breaking change for any existing consumers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 11:10:25 +02:00

126 lines
5.3 KiB
Swift

//
// GraphicsFont.swift
// Font, FontPage, and Glyph wrappers around LCDFont / LCDFontPage / LCDFontGlyph.
//
internal import CPlaydate
extension Graphics {
/// A font loaded from a .pft file. Wraps `LCDFont`.
public final class Font {
let pointer: OpaquePointer
/// Fonts created from in-memory data reference that data; it is kept
/// alive here.
private let retainedData: UnsafeRawPointer?
init(pointer: OpaquePointer, retainedData: UnsafeRawPointer? = nil) {
self.pointer = pointer
self.retainedData = retainedData
}
/// Loads a font from a file.
public convenience init(path: String) throws(PlaydateError) {
var error: UnsafePointer<CChar>?
let pointer = path.withPlaydateCString { gfx.loadFont.unsafelyUnwrapped($0, &error) }
guard let pointer else { throw PlaydateError(cString: error) }
self.init(pointer: pointer)
}
/// Creates a font from the contents of a .pft file already in memory.
/// The bytes are copied and retained for the font's lifetime.
public convenience init?(data: UnsafeRawBufferPointer, wide: Bool = false) {
let copy = UnsafeMutableRawPointer.allocate(byteCount: data.count, alignment: 4)
copy.copyMemory(from: data.baseAddress.unsafelyUnwrapped, byteCount: data.count)
let fontData = OpaquePointer(copy)
guard let pointer = gfx.makeFontFromData.unsafelyUnwrapped(
fontData, wide ? 1 : 0, Int32(data.count)) else {
copy.deallocate()
return nil
}
self.init(pointer: pointer, retainedData: UnsafeRawPointer(copy))
}
deinit {
// Per the C API docs, fonts are freed with the system allocator.
System.systemFree(UnsafeMutableRawPointer(pointer))
retainedData?.deallocate()
}
/// The font's glyph height in pixels.
public var height: Int {
Int(gfx.getFontHeight.unsafelyUnwrapped(pointer))
}
/// The width of `text` when drawn with this font.
public func textWidth(_ text: String, tracking: Int = 0) -> Int {
let utf8 = ContiguousArray(text.utf8)
return utf8.withUnsafeBufferPointer { buffer in
Int(gfx.getTextWidth.unsafelyUnwrapped(pointer, buffer.baseAddress, buffer.count,
kUTF8Encoding, Int32(tracking)))
}
}
/// The height of `text` when wrapped to `maxWidth` with this font.
public func textHeight(_ text: String, maxWidth: Int, wrap: TextWrappingMode = .word,
tracking: Int = 0, extraLeading: Int = 0) -> Int {
let utf8 = ContiguousArray(text.utf8)
return utf8.withUnsafeBufferPointer { buffer in
Int(gfx.getTextHeightForMaxWidth.unsafelyUnwrapped(
pointer, buffer.baseAddress, buffer.count, Int32(maxWidth), kUTF8Encoding,
wrap.cValue, Int32(tracking), Int32(extraLeading)))
}
}
/// The page containing glyph data for the character `codepoint`
/// belongs to. The page references data owned by the font.
public func page(for codepoint: UInt32) -> FontPage? {
guard let page = gfx.getFontPage.unsafelyUnwrapped(pointer, codepoint) else { return nil }
return FontPage(pointer: page, font: self)
}
/// The glyph for `codepoint`, with its bitmap and advance.
/// The bitmap references data owned by the font.
public func glyph(for codepoint: UInt32) -> (glyph: Glyph, bitmap: Bitmap?, advance: Int)? {
var bitmap: OpaquePointer?
var advance: Int32 = 0
guard let glyph = gfx.getFontGlyph.unsafelyUnwrapped(pointer, codepoint, &bitmap, &advance) else {
return nil
}
return (Glyph(pointer: glyph, font: self),
bitmap.map { Bitmap(pointer: $0, isOwned: false) },
Int(advance))
}
}
/// A page of glyphs within a font. Wraps `LCDFontPage`.
/// Keep the font alive while using its pages.
public struct FontPage {
let pointer: OpaquePointer
let font: Font
/// The glyph for `codepoint` within this page, with its bitmap and advance.
public func glyph(for codepoint: UInt32) -> (glyph: Glyph, bitmap: Bitmap?, advance: Int)? {
var bitmap: OpaquePointer?
var advance: Int32 = 0
guard let glyph = gfx.getPageGlyph.unsafelyUnwrapped(pointer, codepoint, &bitmap, &advance) else {
return nil
}
return (Glyph(pointer: glyph, font: font),
bitmap.map { Bitmap(pointer: $0, isOwned: false) },
Int(advance))
}
}
/// A single glyph within a font. Wraps `LCDFontGlyph`.
/// Keep the font alive while using its glyphs.
public struct Glyph {
let pointer: OpaquePointer
let font: Font
/// The kerning adjustment between this glyph and the next character.
public func kerning(glyphCode: UInt32, nextCode: UInt32) -> Int {
Int(gfx.getGlyphKerning.unsafelyUnwrapped(pointer, glyphCode, nextCode))
}
}
}