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.
|
// Build options, see the build options help for more info.
|
||||||
var tracy: bool = false;
|
var tracy: bool = false;
|
||||||
|
var enable_fontconfig: bool = false;
|
||||||
|
|
||||||
pub fn build(b: *std.build.Builder) !void {
|
pub fn build(b: *std.build.Builder) !void {
|
||||||
const mode = b.standardReleaseOptions();
|
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)",
|
"Enable Tracy integration (default true in Debug on Linux)",
|
||||||
) orelse (mode == .Debug and target.isLinux());
|
) 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(
|
const static = b.option(
|
||||||
bool,
|
bool,
|
||||||
"static",
|
"static",
|
||||||
@ -49,12 +56,12 @@ pub fn build(b: *std.build.Builder) !void {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const exe = b.addExecutable("ghostty", "src/main.zig");
|
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
|
// Exe
|
||||||
{
|
{
|
||||||
const exe_options = b.addOptions();
|
|
||||||
exe_options.addOption(bool, "tracy_enabled", tracy);
|
|
||||||
|
|
||||||
exe.setTarget(target);
|
exe.setTarget(target);
|
||||||
exe.setBuildMode(mode);
|
exe.setBuildMode(mode);
|
||||||
exe.addOptions("build_options", exe_options);
|
exe.addOptions("build_options", exe_options);
|
||||||
@ -119,6 +126,7 @@ pub fn build(b: *std.build.Builder) !void {
|
|||||||
|
|
||||||
main_test.setTarget(target);
|
main_test.setTarget(target);
|
||||||
try addDeps(b, main_test, true);
|
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 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", .{});
|
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).
|
// we wrote (are in the "pkg/" directory).
|
||||||
for (main_test.packages.items) |pkg_| {
|
for (main_test.packages.items) |pkg_| {
|
||||||
const pkg: std.build.Pkg = 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;
|
if (std.mem.eql(u8, pkg.name, "glfw")) continue;
|
||||||
var test_ = b.addTestSource(pkg.source);
|
var test_ = b.addTestSource(pkg.source);
|
||||||
|
|
||||||
@ -158,7 +167,7 @@ fn addDeps(
|
|||||||
static: bool,
|
static: bool,
|
||||||
) !void {
|
) !void {
|
||||||
// We always need the Zig packages
|
// We always need the Zig packages
|
||||||
step.addPackage(fontconfig.pkg);
|
if (enable_fontconfig) step.addPackage(fontconfig.pkg);
|
||||||
step.addPackage(freetype.pkg);
|
step.addPackage(freetype.pkg);
|
||||||
step.addPackage(harfbuzz.pkg);
|
step.addPackage(harfbuzz.pkg);
|
||||||
step.addPackage(glfw.pkg);
|
step.addPackage(glfw.pkg);
|
||||||
@ -195,7 +204,7 @@ fn addDeps(
|
|||||||
step.linkSystemLibrary("libuv");
|
step.linkSystemLibrary("libuv");
|
||||||
step.linkSystemLibrary("zlib");
|
step.linkSystemLibrary("zlib");
|
||||||
|
|
||||||
if (step.target.isLinux()) step.linkSystemLibrary("fontconfig");
|
if (enable_fontconfig) step.linkSystemLibrary("fontconfig");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other dependencies, we may dynamically link
|
// Other dependencies, we may dynamically link
|
||||||
@ -237,7 +246,7 @@ fn addDeps(
|
|||||||
system_sdk.include(b, libuv_step, .{});
|
system_sdk.include(b, libuv_step, .{});
|
||||||
|
|
||||||
// Only Linux gets fontconfig
|
// Only Linux gets fontconfig
|
||||||
if (step.target.isLinux()) {
|
if (enable_fontconfig) {
|
||||||
// Libxml2
|
// Libxml2
|
||||||
const libxml2_lib = try libxml2.create(
|
const libxml2_lib = try libxml2.create(
|
||||||
b,
|
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 Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const Atlas = @import("../Atlas.zig");
|
const Atlas = @import("../Atlas.zig");
|
||||||
|
const DeferredFace = @import("main.zig").DeferredFace;
|
||||||
const Face = @import("main.zig").Face;
|
const Face = @import("main.zig").Face;
|
||||||
const Library = @import("main.zig").Library;
|
const Library = @import("main.zig").Library;
|
||||||
const Glyph = @import("main.zig").Glyph;
|
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
|
// 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
|
// most important memory efficiency we can look for. This is totally opaque
|
||||||
// to the user so we can change this later.
|
// 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.
|
/// The available faces we have. This shouldn't be modified manually.
|
||||||
/// Instead, use the functions available on Group.
|
/// 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 takes ownership of the face. The face will be deallocated when
|
||||||
/// the group is deallocated.
|
/// the group is deallocated.
|
||||||
pub fn addFace(self: *Group, alloc: Allocator, style: Style, face: Face) !void {
|
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.
|
/// 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 {
|
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
|
// If the presentation is null, we allow the first presentation we
|
||||||
// can find. Otherwise, we check for the specific one requested.
|
// can find. Otherwise, we check for the specific one requested.
|
||||||
if (p != null and face.presentation != p.?) continue;
|
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.
|
/// Return the Face represented by a given FontIndex.
|
||||||
pub fn faceFromIndex(self: Group, index: FontIndex) Face {
|
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
|
/// Render a glyph by glyph index into the given font atlas and return
|
||||||
@ -151,7 +155,8 @@ pub fn renderGlyph(
|
|||||||
glyph_index: u32,
|
glyph_index: u32,
|
||||||
) !Glyph {
|
) !Glyph {
|
||||||
const face = self.faces.get(index.style).items[@intCast(usize, index.idx)];
|
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 {
|
test {
|
||||||
|
@ -129,7 +129,7 @@ pub fn metrics(self: *GroupCache, alloc: Allocator) !Metrics {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const cell_baseline = cell_baseline: {
|
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(
|
break :cell_baseline cell_height - @intToFloat(
|
||||||
f32,
|
f32,
|
||||||
face.unitsToPxY(face.face.handle.*.ascender),
|
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 std = @import("std");
|
||||||
|
const build_options = @import("build_options");
|
||||||
|
|
||||||
|
pub const DeferredFace = @import("DeferredFace.zig");
|
||||||
pub const Face = @import("Face.zig");
|
pub const Face = @import("Face.zig");
|
||||||
pub const Group = @import("Group.zig");
|
pub const Group = @import("Group.zig");
|
||||||
pub const GroupCache = @import("GroupCache.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 Library = @import("Library.zig");
|
||||||
pub const Shaper = @import("Shaper.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.
|
/// The styles that a family can take.
|
||||||
pub const Style = enum(u3) {
|
pub const Style = enum(u3) {
|
||||||
regular = 0,
|
regular = 0,
|
||||||
@ -33,4 +42,7 @@ pub const Metrics = struct {
|
|||||||
|
|
||||||
test {
|
test {
|
||||||
@import("std").testing.refAllDecls(@This());
|
@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 {
|
pub fn main() !void {
|
||||||
// Output some debug information right away
|
// Output some debug information right away
|
||||||
log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()});
|
log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()});
|
||||||
if (builtin.os.tag == .linux) {
|
if (options.fontconfig) {
|
||||||
log.info("dependency fontconfig={d}", .{fontconfig.version()});
|
log.info("dependency fontconfig={d}", .{fontconfig.version()});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user