From ac26c20e94c8098df90f126ab28069a390bcb30a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 17 Sep 2022 09:21:23 -0700 Subject: [PATCH] font discovery builds up a set of deferred faces --- src/font/DeferredFace.zig | 39 ++++++++++++--------- src/font/discovery.zig | 72 +++++++++++++++++++++++++++++++++++---- 2 files changed, 88 insertions(+), 23 deletions(-) diff --git a/src/font/DeferredFace.zig b/src/font/DeferredFace.zig index dc3c46d50..e5e4cc135 100644 --- a/src/font/DeferredFace.zig +++ b/src/font/DeferredFace.zig @@ -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,21 +59,23 @@ 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) { - // Check if char exists - if (!self.fc.charset.hasChar(cp)) return false; + if (self.fc) |fc| { + // Check if char exists + 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)) - .emoji - else - .text; + // If we have a presentation, check it matches + if (p) |desired| { + const emoji_lang = "und-zsye"; + const actual: Presentation = if (fc.langset.hasLang(emoji_lang)) + .emoji + else + .text; - return desired == actual; + return desired == actual; + } + + return true; } - - return true; } // This is unreachable because discovery mechanisms terminate, and diff --git a/src/font/discovery.zig b/src/font/discovery.zig index e352fa923..06790e01f 100644 --- a/src/font/discovery.zig +++ b/src/font/discovery.zig @@ -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()); + } }