mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
start adding fontconfig conditional compilation
This commit is contained in:
21
build.zig
21
build.zig
@ -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
37
src/font/DeferredFace.zig
Normal 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;
|
||||
}
|
@ -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 {
|
||||
|
@ -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
77
src/font/discovery.zig
Normal 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" });
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
@ -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()});
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user