mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
font discovery builds up a set of deferred faces
This commit is contained in:
@ -17,18 +17,23 @@ const Presentation = @import("main.zig").Presentation;
|
|||||||
face: ?Face = null,
|
face: ?Face = null,
|
||||||
|
|
||||||
/// Fontconfig
|
/// 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.
|
/// Fontconfig specific data. This is only present if building with fontconfig.
|
||||||
pub const Fontconfig = struct {
|
pub const Fontconfig = struct {
|
||||||
pattern: *fontconfig.Pattern,
|
pattern: *fontconfig.Pattern,
|
||||||
charset: *fontconfig.CharSet,
|
charset: *const fontconfig.CharSet,
|
||||||
langset: *fontconfig.LangSet,
|
langset: *const fontconfig.LangSet,
|
||||||
|
|
||||||
|
pub fn deinit(self: *Fontconfig) void {
|
||||||
|
self.pattern.destroy();
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn deinit(self: *DeferredFace) void {
|
pub fn deinit(self: *DeferredFace) void {
|
||||||
if (self.face) |*face| face.deinit();
|
if (self.face) |*face| face.deinit();
|
||||||
|
if (options.fontconfig) if (self.fc) |*fc| fc.deinit();
|
||||||
self.* = undefined;
|
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
|
// If we are using fontconfig, use the fontconfig metadata to
|
||||||
// avoid loading the face.
|
// avoid loading the face.
|
||||||
if (options.fontconfig) {
|
if (options.fontconfig) {
|
||||||
// Check if char exists
|
if (self.fc) |fc| {
|
||||||
if (!self.fc.charset.hasChar(cp)) return false;
|
// Check if char exists
|
||||||
|
if (!fc.charset.hasChar(cp)) return false;
|
||||||
|
|
||||||
// If we have a presentation, check it matches
|
// If we have a presentation, check it matches
|
||||||
if (p) |desired| {
|
if (p) |desired| {
|
||||||
const emoji_lang = "und-zsye";
|
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
|
.emoji
|
||||||
else
|
else
|
||||||
.text;
|
.text;
|
||||||
|
|
||||||
return desired == actual;
|
return desired == actual;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is unreachable because discovery mechanisms terminate, and
|
// This is unreachable because discovery mechanisms terminate, and
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const fontconfig = @import("fontconfig");
|
const fontconfig = @import("fontconfig");
|
||||||
|
const DeferredFace = @import("main.zig").DeferredFace;
|
||||||
|
|
||||||
const log = std.log.named(.discovery);
|
const log = std.log.named(.discovery);
|
||||||
|
|
||||||
|
pub const Error = error{
|
||||||
|
FontConfigFailed,
|
||||||
|
};
|
||||||
|
|
||||||
/// Descriptor is used to search for fonts. The only required field
|
/// 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.
|
/// is "family". The rest are ignored unless they're set to a non-zero value.
|
||||||
pub const Descriptor = struct {
|
pub const Descriptor = struct {
|
||||||
@ -53,25 +58,78 @@ pub const Fontconfig = struct {
|
|||||||
pub fn init() Fontconfig {
|
pub fn init() Fontconfig {
|
||||||
// safe to call multiple times and concurrently
|
// safe to call multiple times and concurrently
|
||||||
_ = fontconfig.init();
|
_ = 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
|
// Build our pattern that we'll search for
|
||||||
const pat = desc.toFcPattern();
|
const pat = desc.toFcPattern();
|
||||||
defer pat.destroy();
|
errdefer pat.destroy();
|
||||||
assert(self.fc_config.substituteWithPat(pat, .pattern));
|
assert(self.fc_config.substituteWithPat(pat, .pattern));
|
||||||
pat.defaultSubstitute();
|
pat.defaultSubstitute();
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
const res = self.fc_config.fontSort(pat, true, null);
|
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 {
|
test {
|
||||||
defer fontconfig.fini();
|
const testing = std.testing;
|
||||||
var fc = Fontconfig.init();
|
|
||||||
|
|
||||||
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user