start adding fontconfig conditional compilation

This commit is contained in:
Mitchell Hashimoto
2022-09-16 15:06:00 -07:00
parent 51d70fc74e
commit 141182aa13
7 changed files with 153 additions and 13 deletions

View File

@ -16,6 +16,7 @@ const system_sdk = @import("vendor/mach/libs/glfw/system_sdk.zig");
// Build options, see the build options help for more info.
var tracy: bool = false;
var enable_fontconfig: bool = false;
pub fn build(b: *std.build.Builder) !void {
const mode = b.standardReleaseOptions();
@ -36,6 +37,12 @@ pub fn build(b: *std.build.Builder) !void {
"Enable Tracy integration (default true in Debug on Linux)",
) orelse (mode == .Debug and target.isLinux());
enable_fontconfig = b.option(
bool,
"fontconfig",
"Enable fontconfig for font discovery (default true on Linux)",
) orelse target.isLinux();
const static = b.option(
bool,
"static",
@ -49,12 +56,12 @@ pub fn build(b: *std.build.Builder) !void {
);
const exe = b.addExecutable("ghostty", "src/main.zig");
const exe_options = b.addOptions();
exe_options.addOption(bool, "tracy_enabled", tracy);
exe_options.addOption(bool, "fontconfig", enable_fontconfig);
// Exe
{
const exe_options = b.addOptions();
exe_options.addOption(bool, "tracy_enabled", tracy);
exe.setTarget(target);
exe.setBuildMode(mode);
exe.addOptions("build_options", exe_options);
@ -119,6 +126,7 @@ pub fn build(b: *std.build.Builder) !void {
main_test.setTarget(target);
try addDeps(b, main_test, true);
main_test.addOptions("build_options", exe_options);
var before = b.addLog("\x1b[" ++ color_map.get("cyan").? ++ "\x1b[" ++ color_map.get("b").? ++ "[{s} tests]" ++ "\x1b[" ++ color_map.get("d").? ++ " ----" ++ "\x1b[0m", .{"ghostty"});
var after = b.addLog("\x1b[" ++ color_map.get("d").? ++ "---\n\n" ++ "\x1b[0m", .{});
@ -132,6 +140,7 @@ pub fn build(b: *std.build.Builder) !void {
// we wrote (are in the "pkg/" directory).
for (main_test.packages.items) |pkg_| {
const pkg: std.build.Pkg = pkg_;
if (std.mem.eql(u8, pkg.name, "build_options")) continue;
if (std.mem.eql(u8, pkg.name, "glfw")) continue;
var test_ = b.addTestSource(pkg.source);
@ -158,7 +167,7 @@ fn addDeps(
static: bool,
) !void {
// We always need the Zig packages
step.addPackage(fontconfig.pkg);
if (enable_fontconfig) step.addPackage(fontconfig.pkg);
step.addPackage(freetype.pkg);
step.addPackage(harfbuzz.pkg);
step.addPackage(glfw.pkg);
@ -195,7 +204,7 @@ fn addDeps(
step.linkSystemLibrary("libuv");
step.linkSystemLibrary("zlib");
if (step.target.isLinux()) step.linkSystemLibrary("fontconfig");
if (enable_fontconfig) step.linkSystemLibrary("fontconfig");
}
// Other dependencies, we may dynamically link
@ -237,7 +246,7 @@ fn addDeps(
system_sdk.include(b, libuv_step, .{});
// Only Linux gets fontconfig
if (step.target.isLinux()) {
if (enable_fontconfig) {
// Libxml2
const libxml2_lib = try libxml2.create(
b,

37
src/font/DeferredFace.zig Normal file
View File

@ -0,0 +1,37 @@
//! A deferred face represents a single font face with all the information
//! necessary to load it, but defers loading the full face until it is
//! needed.
//!
//! This allows us to have many fallback fonts to look for glyphs, but
//! only load them if they're really needed.
const DeferredFace = @This();
const std = @import("std");
const assert = std.debug.assert;
const fontconfig = @import("fontconfig");
const options = @import("main.zig").options;
const Face = @import("main.zig").Face;
/// The loaded face (once loaded).
face: ?Face = null,
/// Fontconfig
fc: if (options.fontconfig) Fontconfig else void = undefined,
/// Fontconfig specific data. This is only present if building with fontconfig.
pub const Fontconfig = struct {
pattern: *fontconfig.Pattern,
charset: *fontconfig.CharSet,
langset: *fontconfig.LangSet,
};
pub fn deinit(self: *DeferredFace) void {
if (self.face) |*face| face.deinit();
self.* = undefined;
}
/// Returns true if the face has been loaded.
pub inline fn loaded(self: DeferredFace) bool {
return self.face != null;
}

View File

@ -11,6 +11,7 @@ const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const Atlas = @import("../Atlas.zig");
const DeferredFace = @import("main.zig").DeferredFace;
const Face = @import("main.zig").Face;
const Library = @import("main.zig").Library;
const Glyph = @import("main.zig").Glyph;
@ -24,7 +25,7 @@ const log = std.log.scoped(.font_group);
// usually only one font group for the entire process so this isn't the
// most important memory efficiency we can look for. This is totally opaque
// to the user so we can change this later.
const StyleArray = std.EnumArray(Style, std.ArrayListUnmanaged(Face));
const StyleArray = std.EnumArray(Style, std.ArrayListUnmanaged(DeferredFace));
/// The available faces we have. This shouldn't be modified manually.
/// Instead, use the functions available on Group.
@ -58,7 +59,7 @@ pub fn deinit(self: *Group, alloc: Allocator) void {
/// The group takes ownership of the face. The face will be deallocated when
/// the group is deallocated.
pub fn addFace(self: *Group, alloc: Allocator, style: Style, face: Face) !void {
try self.faces.getPtr(style).append(alloc, face);
try self.faces.getPtr(style).append(alloc, .{ .face = face });
}
/// This represents a specific font in the group.
@ -110,7 +111,9 @@ pub fn indexForCodepoint(
}
fn indexForCodepointExact(self: Group, cp: u32, style: Style, p: ?Presentation) ?FontIndex {
for (self.faces.get(style).items) |face, i| {
for (self.faces.get(style).items) |deferred, i| {
const face = deferred.face.?;
// If the presentation is null, we allow the first presentation we
// can find. Otherwise, we check for the specific one requested.
if (p != null and face.presentation != p.?) continue;
@ -129,7 +132,8 @@ fn indexForCodepointExact(self: Group, cp: u32, style: Style, p: ?Presentation)
/// Return the Face represented by a given FontIndex.
pub fn faceFromIndex(self: Group, index: FontIndex) Face {
return self.faces.get(index.style).items[@intCast(usize, index.idx)];
const deferred = self.faces.get(index.style).items[@intCast(usize, index.idx)];
return deferred.face.?;
}
/// Render a glyph by glyph index into the given font atlas and return
@ -151,7 +155,8 @@ pub fn renderGlyph(
glyph_index: u32,
) !Glyph {
const face = self.faces.get(index.style).items[@intCast(usize, index.idx)];
return try face.renderGlyph(alloc, atlas, glyph_index);
assert(face.loaded());
return try face.face.?.renderGlyph(alloc, atlas, glyph_index);
}
test {

View File

@ -129,7 +129,7 @@ pub fn metrics(self: *GroupCache, alloc: Allocator) !Metrics {
};
const cell_baseline = cell_baseline: {
const face = self.group.faces.get(.regular).items[0];
const face = self.group.faces.get(.regular).items[0].face.?;
break :cell_baseline cell_height - @intToFloat(
f32,
face.unitsToPxY(face.face.handle.*.ascender),

77
src/font/discovery.zig Normal file
View File

@ -0,0 +1,77 @@
const std = @import("std");
const assert = std.debug.assert;
const fontconfig = @import("fontconfig");
const log = std.log.named(.discovery);
/// 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 {
/// Font family to search for. This can be a fully qualified font
/// name such as "Fira Code", "monospace", "serif", etc. Memory is
/// owned by the caller and should be freed when this descriptor
/// is no longer in use. The discovery structs will never store the
/// descriptor.
///
/// On systems that use fontconfig (Linux), this can be a full
/// fontconfig pattern, such as "Fira Code-14:bold".
family: [:0]const u8,
/// Font size in points that the font should support.
size: u16 = 0,
/// True if we want to search specifically for a font that supports
/// bold, italic, or both.
bold: bool = false,
italic: bool = false,
/// Convert to Fontconfig pattern to use for lookup. The pattern does
/// not have defaults filled/substituted (Fontconfig thing) so callers
/// must still do this.
pub fn toFcPattern(self: Descriptor) *fontconfig.Pattern {
const pat = fontconfig.Pattern.create();
assert(pat.add(.family, .{ .string = self.family }, false));
if (self.size > 0) assert(pat.add(.size, .{ .integer = self.size }, false));
if (self.bold) assert(pat.add(
.weight,
.{ .integer = @enumToInt(fontconfig.Weight.bold) },
false,
));
if (self.italic) assert(pat.add(
.slant,
.{ .integer = @enumToInt(fontconfig.Slant.italic) },
false,
));
return pat;
}
};
pub const Fontconfig = struct {
fc_config: *fontconfig.Config,
pub fn init() Fontconfig {
// safe to call multiple times and concurrently
_ = fontconfig.init();
return .{ .fc_config = fontconfig.initLoadConfig() };
}
pub fn discover(self: *Fontconfig, desc: Descriptor) void {
// Build our pattern that we'll search for
const pat = desc.toFcPattern();
defer 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();
}
};
test {
defer fontconfig.fini();
var fc = Fontconfig.init();
fc.discover(.{ .family = "monospace" });
}

View File

@ -1,5 +1,7 @@
const std = @import("std");
const build_options = @import("build_options");
pub const DeferredFace = @import("DeferredFace.zig");
pub const Face = @import("Face.zig");
pub const Group = @import("Group.zig");
pub const GroupCache = @import("GroupCache.zig");
@ -7,6 +9,13 @@ pub const Glyph = @import("Glyph.zig");
pub const Library = @import("Library.zig");
pub const Shaper = @import("Shaper.zig");
/// Build options
pub const options: struct {
fontconfig: bool = false,
} = .{
.fontconfig = build_options.fontconfig,
};
/// The styles that a family can take.
pub const Style = enum(u3) {
regular = 0,
@ -33,4 +42,7 @@ pub const Metrics = struct {
test {
@import("std").testing.refAllDecls(@This());
// TODO
if (options.fontconfig) _ = @import("discovery.zig");
}

View File

@ -16,7 +16,7 @@ const log = std.log.scoped(.main);
pub fn main() !void {
// Output some debug information right away
log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()});
if (builtin.os.tag == .linux) {
if (options.fontconfig) {
log.info("dependency fontconfig={d}", .{fontconfig.version()});
}