font discovery builds up a set of deferred faces

This commit is contained in:
Mitchell Hashimoto
2022-09-17 09:21:23 -07:00
parent 1c1da63c7e
commit ac26c20e94
2 changed files with 88 additions and 23 deletions

View File

@ -17,18 +17,23 @@ const Presentation = @import("main.zig").Presentation;
face: ?Face = null,
/// Fontconfig
fc: if (options.fontconfig) Fontconfig else void = undefined,
fc: if (options.fontconfig) ?Fontconfig else void = if (options.fontconfig) null else {},
/// Fontconfig specific data. This is only present if building with fontconfig.
pub const Fontconfig = struct {
pattern: *fontconfig.Pattern,
charset: *fontconfig.CharSet,
langset: *fontconfig.LangSet,
charset: *const fontconfig.CharSet,
langset: *const fontconfig.LangSet,
pub fn deinit(self: *Fontconfig) void {
self.pattern.destroy();
self.* = undefined;
}
};
pub fn deinit(self: *DeferredFace) void {
if (self.face) |*face| face.deinit();
if (options.fontconfig) if (self.fc) |*fc| fc.deinit();
self.* = undefined;
}
@ -54,13 +59,14 @@ pub fn hasCodepoint(self: DeferredFace, cp: u32, p: ?Presentation) bool {
// If we are using fontconfig, use the fontconfig metadata to
// avoid loading the face.
if (options.fontconfig) {
if (self.fc) |fc| {
// Check if char exists
if (!self.fc.charset.hasChar(cp)) return false;
if (!fc.charset.hasChar(cp)) return false;
// If we have a presentation, check it matches
if (p) |desired| {
const emoji_lang = "und-zsye";
const actual: Presentation = if (self.fc.langset.hasLang(emoji_lang))
const actual: Presentation = if (fc.langset.hasLang(emoji_lang))
.emoji
else
.text;
@ -70,6 +76,7 @@ pub fn hasCodepoint(self: DeferredFace, cp: u32, p: ?Presentation) bool {
return true;
}
}
// This is unreachable because discovery mechanisms terminate, and
// if we're not using a discovery mechanism, the face MUST be loaded.

View File

@ -1,9 +1,14 @@
const std = @import("std");
const assert = std.debug.assert;
const fontconfig = @import("fontconfig");
const DeferredFace = @import("main.zig").DeferredFace;
const log = std.log.named(.discovery);
pub const Error = error{
FontConfigFailed,
};
/// Descriptor is used to search for fonts. The only required field
/// is "family". The rest are ignored unless they're set to a non-zero value.
pub const Descriptor = struct {
@ -53,25 +58,78 @@ pub const Fontconfig = struct {
pub fn init() Fontconfig {
// safe to call multiple times and concurrently
_ = fontconfig.init();
return .{ .fc_config = fontconfig.initLoadConfig() };
return .{ .fc_config = fontconfig.initLoadConfigAndFonts() };
}
pub fn discover(self: *Fontconfig, desc: Descriptor) void {
/// Discover fonts from a descriptor. This returns an iterator that can
/// be used to build up the deferred fonts.
pub fn discover(self: *Fontconfig, desc: Descriptor) !DiscoverIterator {
// Build our pattern that we'll search for
const pat = desc.toFcPattern();
defer pat.destroy();
errdefer pat.destroy();
assert(self.fc_config.substituteWithPat(pat, .pattern));
pat.defaultSubstitute();
// Search
const res = self.fc_config.fontSort(pat, true, null);
defer res.fs.destroy();
if (res.result != .match) return Error.FontConfigFailed;
errdefer res.fs.destroy();
return DiscoverIterator{
.config = self.fc_config,
.pattern = pat,
.set = res.fs,
.fonts = res.fs.fonts(),
.i = 0,
};
}
pub const DiscoverIterator = struct {
config: *fontconfig.Config,
pattern: *fontconfig.Pattern,
set: *fontconfig.FontSet,
fonts: []*fontconfig.Pattern,
i: usize,
pub fn deinit(self: *DiscoverIterator) void {
self.set.destroy();
self.pattern.destroy();
self.* = undefined;
}
pub fn next(self: *DiscoverIterator) fontconfig.Error!?DeferredFace {
if (self.i >= self.fonts.len) return null;
// Get the copied pattern from our fontset that has the
// attributes configured for rendering.
const font_pattern = try self.config.fontRenderPrepare(
self.pattern,
self.fonts[self.i],
);
errdefer font_pattern.destroy();
// Increment after we return
defer self.i += 1;
return DeferredFace{
.face = null,
.fc = .{
.pattern = font_pattern,
.charset = (try font_pattern.get(.charset, 0)).char_set,
.langset = (try font_pattern.get(.lang, 0)).lang_set,
},
};
}
};
};
test {
defer fontconfig.fini();
var fc = Fontconfig.init();
const testing = std.testing;
fc.discover(.{ .family = "monospace" });
var fc = Fontconfig.init();
var it = try fc.discover(.{ .family = "monospace" });
defer it.deinit();
while (try it.next()) |face| {
try testing.expect(!face.loaded());
}
}