mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-05-24 05:40:15 +00:00
font: add Windows font discovery backend (#12386)
Adds a FreeType-based `Discover` implementation for Windows. It walks the system font directory (`%SYSTEMROOT%\Fonts`) and the per-user directory (`%LOCALAPPDATA%\Microsoft\Windows\Fonts`), matches descriptors by FreeType `family_name` (falling back to the SFNT name table), and, when a codepoint is in the descriptor, filters on CMap coverage. Wired up as a new `.freetype_windows` backend which `Backend.default()` now returns on Windows. Existing freetype-only paths are untouched and no other platform is affected; cross-platform switches were extended to handle the new enum value the same way they handle `.freetype`. With this in place, the standard code paths (`+list-fonts`, `SharedGridSet` font-family lookup, `CodepointResolver` fallback) work on Windows without any `os.tag == .windows` branches in the caller. Verified by the `build-libghostty-windows-gnu` CI job. No runtime binary ships yet on Windows (no apprt), but this is a drop-in for the discovery API that the Win32 apprt (and the revisited `+list-fonts` PR #12384) will use. Once this lands, #12384 can be closed and `+list-fonts` will work on Windows through the ordinary discovery code path, which addresses the review feedback that `+list-fonts` should only show fonts the internal discovery can find. --- AI usage disclosure: developed with Claude Code (Claude Opus 4.7). Claude drafted the implementation based on my design direction -- I picked the "add a Discover backend" shape over the ad-hoc approach in the earlier `+list-fonts` PR. I reviewed each diff and validated it with a Windows GNU-ABI smoke build before pushing. Part of the Win32 apprt upstreaming series (see discussion #2563 / mattn/ghostty#1).
This commit is contained in:
@@ -100,8 +100,12 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
|
||||
var families: std.ArrayList([]const u8) = .empty;
|
||||
var map: std.StringHashMap(std.ArrayListUnmanaged([]const u8)) = .init(alloc);
|
||||
|
||||
// Look up all available fonts
|
||||
var disco = font.Discover.init();
|
||||
// Look up all available fonts. The library is only used by backends
|
||||
// that need it (the Windows backend opens candidate font files with
|
||||
// FreeType); other backends ignore it.
|
||||
var font_lib = try font.Library.init(alloc);
|
||||
defer font_lib.deinit();
|
||||
var disco = font.Discover.init(font_lib);
|
||||
defer disco.deinit();
|
||||
var disco_it = try disco.discover(alloc, .{
|
||||
.family = config.family,
|
||||
|
||||
@@ -26,6 +26,10 @@ fc: if (options.backend == .fontconfig_freetype) ?Fontconfig else void =
|
||||
ct: if (font.Discover == font.discovery.CoreText) ?CoreText else void =
|
||||
if (font.Discover == font.discovery.CoreText) null else {},
|
||||
|
||||
/// Windows (FreeType directory scan)
|
||||
win: if (options.backend == .freetype_windows) ?Windows else void =
|
||||
if (options.backend == .freetype_windows) null else {},
|
||||
|
||||
/// Canvas
|
||||
wc: if (options.backend == .web_canvas) ?WebCanvas else void =
|
||||
if (options.backend == .web_canvas) null else {},
|
||||
@@ -51,6 +55,42 @@ pub const Fontconfig = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// Windows specific data. Only present with the freetype_windows backend.
|
||||
///
|
||||
/// Unlike Fontconfig/CoreText which carry lightweight descriptor handles,
|
||||
/// the Windows backend has no external descriptor service — the "deferred"
|
||||
/// metadata is the FreeType face itself. We keep a pre-loaded face (loaded
|
||||
/// at discovery time) to answer `hasCodepoint` cheaply without re-opening
|
||||
/// the file on every query, and remember the path so `load()` can open a
|
||||
/// fresh face at the caller's requested size/options.
|
||||
pub const Windows = struct {
|
||||
/// Path to the font file. Owned here.
|
||||
path: [:0]const u8,
|
||||
|
||||
/// Face index within the file (for .ttc collections).
|
||||
face_index: i32,
|
||||
|
||||
/// Variations to apply on load.
|
||||
variations: []const font.face.Variation,
|
||||
|
||||
/// Pre-loaded face used for cheap metadata queries (glyphIndex,
|
||||
/// hasColor). The size it was opened at is irrelevant for these
|
||||
/// queries since the CMap is size-independent. Deinit'd with us.
|
||||
peek: Face,
|
||||
|
||||
/// Whether the face presents as emoji (has color glyphs) or text.
|
||||
presentation: Presentation,
|
||||
|
||||
/// Allocator that owns `path`.
|
||||
alloc: Allocator,
|
||||
|
||||
pub fn deinit(self: *Windows) void {
|
||||
self.peek.deinit();
|
||||
self.alloc.free(self.path);
|
||||
self.* = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/// CoreText specific data. This is only present when building with CoreText.
|
||||
pub const CoreText = struct {
|
||||
/// The initialized font
|
||||
@@ -88,6 +128,7 @@ pub fn deinit(self: *DeferredFace) void {
|
||||
switch (options.backend) {
|
||||
.fontconfig_freetype => if (self.fc) |*fc| fc.deinit(),
|
||||
.freetype => {},
|
||||
.freetype_windows => if (self.win) |*w| w.deinit(),
|
||||
.web_canvas => if (self.wc) |*wc| wc.deinit(),
|
||||
.coretext,
|
||||
.coretext_freetype,
|
||||
@@ -103,6 +144,8 @@ pub fn familyName(self: DeferredFace, buf: []u8) ![]const u8 {
|
||||
switch (options.backend) {
|
||||
.freetype => {},
|
||||
|
||||
.freetype_windows => if (self.win) |w| return try w.peek.name(buf),
|
||||
|
||||
.fontconfig_freetype => if (self.fc) |fc|
|
||||
return (try fc.pattern.get(.family, 0)).string,
|
||||
|
||||
@@ -131,6 +174,8 @@ pub fn name(self: DeferredFace, buf: []u8) ![]const u8 {
|
||||
switch (options.backend) {
|
||||
.freetype => {},
|
||||
|
||||
.freetype_windows => if (self.win) |w| return try w.peek.name(buf),
|
||||
|
||||
.fontconfig_freetype => if (self.fc) |fc|
|
||||
return (try fc.pattern.get(.fullname, 0)).string,
|
||||
|
||||
@@ -164,6 +209,7 @@ pub fn load(
|
||||
) !Face {
|
||||
return switch (options.backend) {
|
||||
.fontconfig_freetype => try self.loadFontconfig(lib, opts),
|
||||
.freetype_windows => try self.loadWindows(lib, opts),
|
||||
.coretext, .coretext_harfbuzz, .coretext_noshape => try self.loadCoreText(lib, opts),
|
||||
.coretext_freetype => try self.loadCoreTextFreetype(lib, opts),
|
||||
.web_canvas => try self.loadWebCanvas(opts),
|
||||
@@ -191,6 +237,19 @@ fn loadFontconfig(
|
||||
return face;
|
||||
}
|
||||
|
||||
fn loadWindows(
|
||||
self: *DeferredFace,
|
||||
lib: Library,
|
||||
opts: font.face.Options,
|
||||
) !Face {
|
||||
const w = self.win.?;
|
||||
|
||||
var face = try Face.initFile(lib, w.path, w.face_index, opts);
|
||||
errdefer face.deinit();
|
||||
try face.setVariations(w.variations, opts);
|
||||
return face;
|
||||
}
|
||||
|
||||
fn loadCoreText(
|
||||
self: *DeferredFace,
|
||||
lib: Library,
|
||||
@@ -287,6 +346,14 @@ pub fn hasCodepoint(self: DeferredFace, cp: u32, p: ?Presentation) bool {
|
||||
}
|
||||
},
|
||||
|
||||
.freetype_windows => {
|
||||
// Use the pre-loaded peek face for a cheap CMap lookup.
|
||||
if (self.win) |w| {
|
||||
if (p) |desired| if (w.presentation != desired) return false;
|
||||
return w.peek.glyphIndex(cp) != null;
|
||||
}
|
||||
},
|
||||
|
||||
.coretext,
|
||||
.coretext_freetype,
|
||||
.coretext_harfbuzz,
|
||||
@@ -411,7 +478,7 @@ test "fontconfig" {
|
||||
|
||||
// Get a deferred face from fontconfig
|
||||
var def = def: {
|
||||
var fc = discovery.Fontconfig.init();
|
||||
var fc = discovery.Fontconfig.init(lib);
|
||||
defer fc.deinit();
|
||||
var it = try fc.discover(alloc, .{ .family = "monospace", .size = 12 });
|
||||
defer it.deinit();
|
||||
@@ -443,7 +510,7 @@ test "coretext" {
|
||||
|
||||
// Get a deferred face from fontconfig
|
||||
var def = def: {
|
||||
var fc = discovery.CoreText.init();
|
||||
var fc = discovery.CoreText.init(lib);
|
||||
var it = try fc.discover(alloc, .{ .family = "Monaco", .size = 12 });
|
||||
defer it.deinit();
|
||||
break :def (try it.next()).?;
|
||||
|
||||
@@ -442,7 +442,7 @@ fn discover(self: *SharedGridSet) !?*Discover {
|
||||
// If we initialized, use it
|
||||
if (self.font_discover) |*v| return v;
|
||||
|
||||
self.font_discover = .init();
|
||||
self.font_discover = .init(self.font_lib);
|
||||
return &self.font_discover.?;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,12 @@ pub const Backend = enum {
|
||||
/// FreeType for font rendering with no font discovery enabled.
|
||||
freetype,
|
||||
|
||||
/// FreeType for font rendering with a built-in Windows font directory
|
||||
/// scanner (C:\Windows\Fonts + %LOCALAPPDATA%\Microsoft\Windows\Fonts).
|
||||
/// Used when DirectWrite is not available; matches by family_name and
|
||||
/// SFNT name table without any external index.
|
||||
freetype_windows,
|
||||
|
||||
/// Fontconfig for font discovery and FreeType for font rendering.
|
||||
fontconfig_freetype,
|
||||
|
||||
@@ -42,10 +48,10 @@ pub const Backend = enum {
|
||||
|
||||
if (target.os.tag == .windows) {
|
||||
// Avoid fontconfig on Windows because its libxml2 dependency
|
||||
// may not unpack due to symlinks. Use plain freetype for now
|
||||
// which means no font discovery. Full solution would likely use
|
||||
// DirectWrite which has its own discovery API.
|
||||
return .freetype;
|
||||
// may not unpack due to symlinks. Use the FreeType-based
|
||||
// Windows font-directory scanner for discovery. A future
|
||||
// DirectWrite backend can replace this if needed.
|
||||
return .freetype_windows;
|
||||
}
|
||||
|
||||
// macOS also supports "coretext_freetype" but there is no scenario
|
||||
@@ -60,6 +66,7 @@ pub const Backend = enum {
|
||||
pub fn hasFreetype(self: Backend) bool {
|
||||
return switch (self) {
|
||||
.freetype,
|
||||
.freetype_windows,
|
||||
.fontconfig_freetype,
|
||||
.coretext_freetype,
|
||||
=> true,
|
||||
@@ -81,6 +88,7 @@ pub const Backend = enum {
|
||||
=> true,
|
||||
|
||||
.freetype,
|
||||
.freetype_windows,
|
||||
.fontconfig_freetype,
|
||||
.web_canvas,
|
||||
=> false,
|
||||
@@ -92,6 +100,7 @@ pub const Backend = enum {
|
||||
.fontconfig_freetype => true,
|
||||
|
||||
.freetype,
|
||||
.freetype_windows,
|
||||
.coretext,
|
||||
.coretext_freetype,
|
||||
.coretext_harfbuzz,
|
||||
@@ -104,6 +113,7 @@ pub const Backend = enum {
|
||||
pub fn hasHarfbuzz(self: Backend) bool {
|
||||
return switch (self) {
|
||||
.freetype,
|
||||
.freetype_windows,
|
||||
.fontconfig_freetype,
|
||||
.coretext_freetype,
|
||||
.coretext_harfbuzz,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = @import("../quirks.zig").inlineAssert;
|
||||
const fontconfig = @import("fontconfig");
|
||||
@@ -7,6 +8,9 @@ const opentype = @import("opentype.zig");
|
||||
const options = @import("main.zig").options;
|
||||
const Collection = @import("main.zig").Collection;
|
||||
const DeferredFace = @import("main.zig").DeferredFace;
|
||||
const Face = @import("main.zig").Face;
|
||||
const Library = @import("main.zig").Library;
|
||||
const Presentation = @import("main.zig").Presentation;
|
||||
const Variation = @import("main.zig").face.Variation;
|
||||
|
||||
const log = std.log.scoped(.discovery);
|
||||
@@ -14,6 +18,7 @@ const log = std.log.scoped(.discovery);
|
||||
/// Discover implementation for the compile options.
|
||||
pub const Discover = switch (options.backend) {
|
||||
.freetype => void, // no discovery
|
||||
.freetype_windows => Windows,
|
||||
.fontconfig_freetype => Fontconfig,
|
||||
.web_canvas => void, // no discovery
|
||||
.coretext,
|
||||
@@ -242,7 +247,8 @@ pub const Descriptor = struct {
|
||||
pub const Fontconfig = struct {
|
||||
fc_config: *fontconfig.Config,
|
||||
|
||||
pub fn init() Fontconfig {
|
||||
pub fn init(lib: Library) Fontconfig {
|
||||
_ = lib;
|
||||
// safe to call multiple times and concurrently
|
||||
_ = fontconfig.init();
|
||||
return .{ .fc_config = fontconfig.initLoadConfigAndFonts() };
|
||||
@@ -333,7 +339,8 @@ pub const Fontconfig = struct {
|
||||
};
|
||||
|
||||
pub const CoreText = struct {
|
||||
pub fn init() CoreText {
|
||||
pub fn init(lib: Library) CoreText {
|
||||
_ = lib;
|
||||
// Required for the "interface" but does nothing for CoreText.
|
||||
return .{};
|
||||
}
|
||||
@@ -879,6 +886,263 @@ pub const CoreText = struct {
|
||||
};
|
||||
};
|
||||
|
||||
/// Windows font discovery. Enumerates font files in the system and
|
||||
/// per-user font directories and matches them to a descriptor via
|
||||
/// FreeType's family_name field (with a fallback to the SFNT name
|
||||
/// table when family_name is missing).
|
||||
///
|
||||
/// No external service is used; each discover() call walks the
|
||||
/// directories, opening candidate files with FreeType only as needed.
|
||||
/// For typical Windows installations (~300 fonts) a name query is in
|
||||
/// the tens of milliseconds. A codepoint fallback query may be
|
||||
/// noticeably slower because every candidate has to be opened to
|
||||
/// probe its CMap.
|
||||
pub const Windows = struct {
|
||||
lib: Library,
|
||||
|
||||
pub fn init(lib: Library) Windows {
|
||||
return .{ .lib = lib };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Windows) void {
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn discover(
|
||||
self: *const Windows,
|
||||
alloc: Allocator,
|
||||
desc: Descriptor,
|
||||
) !DiscoverIterator {
|
||||
return .{
|
||||
.alloc = alloc,
|
||||
.lib = self.lib,
|
||||
.desc = desc,
|
||||
.variations = desc.variations,
|
||||
.state = .system,
|
||||
.dir = null,
|
||||
.iter = null,
|
||||
.system_path = null,
|
||||
.user_path = null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn discoverFallback(
|
||||
self: *const Windows,
|
||||
alloc: Allocator,
|
||||
collection: *Collection,
|
||||
desc: Descriptor,
|
||||
) !DiscoverIterator {
|
||||
_ = collection;
|
||||
return self.discover(alloc, desc);
|
||||
}
|
||||
|
||||
pub const DiscoverIterator = struct {
|
||||
alloc: Allocator,
|
||||
lib: Library,
|
||||
desc: Descriptor,
|
||||
variations: []const Variation,
|
||||
state: State,
|
||||
dir: ?std.fs.Dir,
|
||||
iter: ?std.fs.Dir.Iterator,
|
||||
system_path: ?[:0]const u8,
|
||||
user_path: ?[:0]const u8,
|
||||
|
||||
const State = enum { system, user, done };
|
||||
|
||||
pub fn deinit(self: *DiscoverIterator) void {
|
||||
if (self.dir) |*d| d.close();
|
||||
if (self.system_path) |p| self.alloc.free(p);
|
||||
if (self.user_path) |p| self.alloc.free(p);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn next(self: *DiscoverIterator) !?DeferredFace {
|
||||
while (true) {
|
||||
// Ensure we have a directory iterator for the current state.
|
||||
if (self.iter == null) {
|
||||
switch (self.state) {
|
||||
.system => {
|
||||
const path = self.systemFontsPath() orelse {
|
||||
self.state = .user;
|
||||
continue;
|
||||
};
|
||||
self.system_path = path;
|
||||
self.dir = std.fs.openDirAbsoluteZ(
|
||||
path,
|
||||
.{ .iterate = true },
|
||||
) catch {
|
||||
self.state = .user;
|
||||
continue;
|
||||
};
|
||||
self.iter = self.dir.?.iterate();
|
||||
},
|
||||
.user => {
|
||||
const path = self.userFontsPath() orelse {
|
||||
self.state = .done;
|
||||
continue;
|
||||
};
|
||||
self.user_path = path;
|
||||
self.dir = std.fs.openDirAbsoluteZ(
|
||||
path,
|
||||
.{ .iterate = true },
|
||||
) catch {
|
||||
self.state = .done;
|
||||
continue;
|
||||
};
|
||||
self.iter = self.dir.?.iterate();
|
||||
},
|
||||
.done => return null,
|
||||
}
|
||||
}
|
||||
|
||||
const entry = (self.iter.?.next() catch null) orelse {
|
||||
// Finished this directory; advance state.
|
||||
if (self.dir) |*d| d.close();
|
||||
self.dir = null;
|
||||
self.iter = null;
|
||||
self.state = switch (self.state) {
|
||||
.system => .user,
|
||||
.user => .done,
|
||||
.done => .done,
|
||||
};
|
||||
continue;
|
||||
};
|
||||
|
||||
if (entry.kind != .file) continue;
|
||||
if (!isFontFile(entry.name)) continue;
|
||||
|
||||
if (try self.tryMatch(entry.name)) |face| return face;
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the system fonts directory from %SYSTEMROOT%. Returns null
|
||||
/// if SYSTEMROOT is unset, which shouldn't happen on a healthy
|
||||
/// Windows install but we just skip the directory rather than
|
||||
/// falling back to a hardcoded drive letter.
|
||||
fn systemFontsPath(self: *DiscoverIterator) ?[:0]const u8 {
|
||||
const systemroot = std.process.getEnvVarOwned(
|
||||
self.alloc,
|
||||
"SYSTEMROOT",
|
||||
) catch return null;
|
||||
defer self.alloc.free(systemroot);
|
||||
return std.fmt.allocPrintSentinel(
|
||||
self.alloc,
|
||||
"{s}\\Fonts",
|
||||
.{systemroot},
|
||||
0,
|
||||
) catch null;
|
||||
}
|
||||
|
||||
fn userFontsPath(self: *DiscoverIterator) ?[:0]const u8 {
|
||||
const local_appdata = std.process.getEnvVarOwned(
|
||||
self.alloc,
|
||||
"LOCALAPPDATA",
|
||||
) catch return null;
|
||||
defer self.alloc.free(local_appdata);
|
||||
return std.fmt.allocPrintSentinel(
|
||||
self.alloc,
|
||||
"{s}\\Microsoft\\Windows\\Fonts",
|
||||
.{local_appdata},
|
||||
0,
|
||||
) catch null;
|
||||
}
|
||||
|
||||
fn tryMatch(
|
||||
self: *DiscoverIterator,
|
||||
name: []const u8,
|
||||
) !?DeferredFace {
|
||||
const dir_path = switch (self.state) {
|
||||
.system => self.system_path.?,
|
||||
.user => self.user_path.?,
|
||||
.done => return null,
|
||||
};
|
||||
|
||||
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
const full_path = std.fmt.bufPrintZ(
|
||||
&path_buf,
|
||||
"{s}\\{s}",
|
||||
.{ dir_path, name },
|
||||
) catch return null;
|
||||
|
||||
const is_ttc = std.ascii.endsWithIgnoreCase(name, ".ttc");
|
||||
const max_faces: i32 = if (is_ttc) 16 else 1;
|
||||
|
||||
// Probe each face in the file.
|
||||
var face_index: i32 = 0;
|
||||
while (face_index < max_faces) : (face_index += 1) {
|
||||
var face = Face.initFile(
|
||||
self.lib,
|
||||
full_path,
|
||||
face_index,
|
||||
.{ .size = .{ .points = 12 } },
|
||||
) catch break;
|
||||
|
||||
if (self.matches(&face)) {
|
||||
return try self.makeDeferred(face, full_path, face_index);
|
||||
}
|
||||
|
||||
face.deinit();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Check whether the given face matches the descriptor.
|
||||
fn matches(self: *const DiscoverIterator, face: *Face) bool {
|
||||
if (self.desc.family) |family| {
|
||||
if (!familyMatches(face, family)) return false;
|
||||
}
|
||||
if (self.desc.codepoint != 0) {
|
||||
if (face.glyphIndex(self.desc.codepoint) == null) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn makeDeferred(
|
||||
self: *DiscoverIterator,
|
||||
face: Face,
|
||||
full_path: []const u8,
|
||||
face_index: i32,
|
||||
) !DeferredFace {
|
||||
const path_owned = try self.alloc.dupeZ(u8, full_path);
|
||||
errdefer self.alloc.free(path_owned);
|
||||
|
||||
const presentation: Presentation =
|
||||
if (face.hasColor()) .emoji else .text;
|
||||
|
||||
return DeferredFace{
|
||||
.win = .{
|
||||
.path = path_owned,
|
||||
.face_index = face_index,
|
||||
.variations = self.variations,
|
||||
.peek = face,
|
||||
.presentation = presentation,
|
||||
.alloc = self.alloc,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fn isFontFile(name: []const u8) bool {
|
||||
return std.ascii.endsWithIgnoreCase(name, ".ttf") or
|
||||
std.ascii.endsWithIgnoreCase(name, ".ttc") or
|
||||
std.ascii.endsWithIgnoreCase(name, ".otf");
|
||||
}
|
||||
|
||||
/// Compare a face's family against a requested family name. Checks
|
||||
/// FreeType's family_name first, then falls back to the SFNT name
|
||||
/// table entry.
|
||||
fn familyMatches(face: *Face, family: [:0]const u8) bool {
|
||||
const ft_family: ?[*:0]const u8 = face.face.handle.*.family_name;
|
||||
if (ft_family) |f| {
|
||||
if (std.ascii.eqlIgnoreCase(std.mem.span(f), family)) return true;
|
||||
}
|
||||
var buf: [256]u8 = undefined;
|
||||
const sfnt = face.name(&buf) catch "";
|
||||
return sfnt.len > 0 and std.ascii.eqlIgnoreCase(sfnt, family);
|
||||
}
|
||||
};
|
||||
|
||||
test "descriptor hash" {
|
||||
const testing = std.testing;
|
||||
|
||||
@@ -900,7 +1164,10 @@ test "fontconfig" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var fc = Fontconfig.init();
|
||||
var lib = try Library.init(alloc);
|
||||
defer lib.deinit();
|
||||
|
||||
var fc = Fontconfig.init(lib);
|
||||
defer fc.deinit();
|
||||
var it = try fc.discover(alloc, .{ .family = "monospace", .size = 12 });
|
||||
defer it.deinit();
|
||||
@@ -912,7 +1179,10 @@ test "fontconfig codepoint" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var fc = Fontconfig.init();
|
||||
var lib = try Library.init(alloc);
|
||||
defer lib.deinit();
|
||||
|
||||
var fc = Fontconfig.init(lib);
|
||||
defer fc.deinit();
|
||||
var it = try fc.discover(alloc, .{ .codepoint = 'A', .size = 12 });
|
||||
defer it.deinit();
|
||||
@@ -934,7 +1204,10 @@ test "coretext" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var ct = CoreText.init();
|
||||
var lib = try Library.init(alloc);
|
||||
defer lib.deinit();
|
||||
|
||||
var ct = CoreText.init(lib);
|
||||
defer ct.deinit();
|
||||
var it = try ct.discover(alloc, .{ .family = "Monaco", .size = 12 });
|
||||
defer it.deinit();
|
||||
@@ -952,7 +1225,10 @@ test "coretext codepoint" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var ct = CoreText.init();
|
||||
var lib = try Library.init(alloc);
|
||||
defer lib.deinit();
|
||||
|
||||
var ct = CoreText.init(lib);
|
||||
defer ct.deinit();
|
||||
var it = try ct.discover(alloc, .{ .codepoint = 'A', .size = 12 });
|
||||
defer it.deinit();
|
||||
@@ -981,7 +1257,10 @@ test "coretext sorting" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var ct = CoreText.init();
|
||||
var lib = try Library.init(alloc);
|
||||
defer lib.deinit();
|
||||
|
||||
var ct = CoreText.init(lib);
|
||||
defer ct.deinit();
|
||||
|
||||
// We try to get a Regular, Italic, Bold, & Bold Italic version of SF Pro,
|
||||
@@ -1047,3 +1326,24 @@ test "coretext sorting" {
|
||||
try testing.expectEqualStrings("SF Pro Bold Italic", name);
|
||||
}
|
||||
}
|
||||
|
||||
test "windows" {
|
||||
if (options.backend != .freetype_windows) return error.SkipZigTest;
|
||||
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var lib = try Library.init(alloc);
|
||||
defer lib.deinit();
|
||||
|
||||
var win = Windows.init(lib);
|
||||
defer win.deinit();
|
||||
|
||||
// Arial ships on every stock Windows install.
|
||||
var it = try win.discover(alloc, .{ .family = "Arial", .size = 12 });
|
||||
defer it.deinit();
|
||||
|
||||
var face = (try it.next()) orelse return error.TestFontNotFound;
|
||||
defer face.deinit();
|
||||
try testing.expect(face.hasCodepoint('A', null));
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ pub const web_canvas = @import("face/web_canvas.zig");
|
||||
/// Face implementation for the compile options.
|
||||
pub const Face = switch (options.backend) {
|
||||
.freetype,
|
||||
.freetype_windows,
|
||||
.fontconfig_freetype,
|
||||
.coretext_freetype,
|
||||
=> freetype.Face,
|
||||
|
||||
@@ -10,6 +10,7 @@ const font = @import("main.zig");
|
||||
pub const Library = switch (options.backend) {
|
||||
// Freetype requires a state library
|
||||
.freetype,
|
||||
.freetype_windows,
|
||||
.fontconfig_freetype,
|
||||
.coretext_freetype,
|
||||
=> FreetypeLibrary,
|
||||
|
||||
@@ -19,6 +19,7 @@ pub const default_features = feature.default_features;
|
||||
/// Shaper implementation for our compile options.
|
||||
pub const Shaper = switch (options.backend) {
|
||||
.freetype,
|
||||
.freetype_windows,
|
||||
.fontconfig_freetype,
|
||||
.coretext_freetype,
|
||||
.coretext_harfbuzz,
|
||||
|
||||
@@ -2585,7 +2585,7 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper {
|
||||
});
|
||||
} else {
|
||||
// On CoreText we want to load Apple Emoji, we should have it.
|
||||
var disco = font.Discover.init();
|
||||
var disco = font.Discover.init(lib);
|
||||
defer disco.deinit();
|
||||
var disco_it = try disco.discover(alloc, .{
|
||||
.family = "Apple Color Emoji",
|
||||
@@ -2640,7 +2640,7 @@ fn testShaperWithDiscoveredFont(alloc: Allocator, font_req: [:0]const u8) !TestS
|
||||
|
||||
// Discover and add our font to the collection.
|
||||
{
|
||||
var disco = font.Discover.init();
|
||||
var disco = font.Discover.init(lib);
|
||||
defer disco.deinit();
|
||||
var disco_it = try disco.discover(alloc, .{
|
||||
.family = font_req,
|
||||
|
||||
@@ -2071,7 +2071,7 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper {
|
||||
});
|
||||
} else {
|
||||
// On CoreText we want to load Apple Emoji, we should have it.
|
||||
var disco = font.Discover.init();
|
||||
var disco = font.Discover.init(lib);
|
||||
defer disco.deinit();
|
||||
var disco_it = try disco.discover(alloc, .{
|
||||
.family = "Apple Color Emoji",
|
||||
@@ -2126,7 +2126,7 @@ fn testShaperWithDiscoveredFont(alloc: Allocator, font_req: [:0]const u8) !TestS
|
||||
|
||||
// Discover and add our font to the collection.
|
||||
{
|
||||
var disco = font.Discover.init();
|
||||
var disco = font.Discover.init(lib);
|
||||
defer disco.deinit();
|
||||
var disco_it = try disco.discover(alloc, .{
|
||||
.family = font_req,
|
||||
|
||||
Reference in New Issue
Block a user