mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00

Most emoji fonts are massive glyphs (128x128, 256x256, etc.). This means the texture we need to store emoji is also massive. For a 128x128 emoji font (both Apple and Noto), we can only store 12 emoji before resizing prior to this commit. This commit now threads through a max height through to the font face and resizes the bitmap in memory before putting it in the atlas. This results in significant savings. The max height is the cell height. We allow the glyphs to be as wide as necessary due to double (and more) wide glyphs. For the unicode emoji test file, the atlas size before and after: Before: 262 MB After: 16 MB
380 lines
12 KiB
Zig
380 lines
12 KiB
Zig
const std = @import("std");
|
||
const fs = std.fs;
|
||
const Builder = std.build.Builder;
|
||
const LibExeObjStep = std.build.LibExeObjStep;
|
||
const glfw = @import("vendor/mach/libs/glfw/build.zig");
|
||
const fontconfig = @import("pkg/fontconfig/build.zig");
|
||
const freetype = @import("pkg/freetype/build.zig");
|
||
const harfbuzz = @import("pkg/harfbuzz/build.zig");
|
||
const imgui = @import("pkg/imgui/build.zig");
|
||
const libxml2 = @import("vendor/zig-libxml2/libxml2.zig");
|
||
const libuv = @import("pkg/libuv/build.zig");
|
||
const libpng = @import("pkg/libpng/build.zig");
|
||
const macos = @import("pkg/macos/build.zig");
|
||
const stb_image_resize = @import("pkg/stb_image_resize/build.zig");
|
||
const utf8proc = @import("pkg/utf8proc/build.zig");
|
||
const zlib = @import("pkg/zlib/build.zig");
|
||
const tracylib = @import("pkg/tracy/build.zig");
|
||
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_coretext: bool = false;
|
||
var enable_fontconfig: bool = false;
|
||
|
||
pub fn build(b: *std.build.Builder) !void {
|
||
const mode = b.standardReleaseOptions();
|
||
const target = target: {
|
||
var result = b.standardTargetOptions(.{});
|
||
|
||
if (result.isLinux() and result.isGnuLibC()) {
|
||
// https://github.com/ziglang/zig/issues/9485
|
||
result.glibc_version = .{ .major = 2, .minor = 28 };
|
||
}
|
||
|
||
break :target result;
|
||
};
|
||
|
||
tracy = b.option(
|
||
bool,
|
||
"tracy",
|
||
"Enable Tracy integration (default true in Debug on Linux)",
|
||
) orelse (mode == .Debug and target.isLinux());
|
||
|
||
enable_coretext = b.option(
|
||
bool,
|
||
"coretext",
|
||
"Enable coretext for font discovery (default true on macOS)",
|
||
) orelse target.isDarwin();
|
||
|
||
enable_fontconfig = b.option(
|
||
bool,
|
||
"fontconfig",
|
||
"Enable fontconfig for font discovery (default true on Linux)",
|
||
) orelse target.isLinux();
|
||
|
||
const static = b.option(
|
||
bool,
|
||
"static",
|
||
"Statically build as much as possible for the exe",
|
||
) orelse true;
|
||
|
||
const conformance = b.option(
|
||
[]const u8,
|
||
"conformance",
|
||
"Name of the conformance app to run with 'run' option.",
|
||
);
|
||
|
||
const emit_test_exe = b.option(
|
||
bool,
|
||
"test-exe",
|
||
"Build and install test executables with 'build'",
|
||
) orelse false;
|
||
|
||
const exe = b.addExecutable("ghostty", "src/main.zig");
|
||
const exe_options = b.addOptions();
|
||
exe_options.addOption(bool, "tracy_enabled", tracy);
|
||
exe_options.addOption(bool, "coretext", enable_coretext);
|
||
exe_options.addOption(bool, "fontconfig", enable_fontconfig);
|
||
|
||
// Exe
|
||
{
|
||
exe.setTarget(target);
|
||
exe.setBuildMode(mode);
|
||
exe.addOptions("build_options", exe_options);
|
||
exe.install();
|
||
|
||
// Add the shared dependencies
|
||
try addDeps(b, exe, static);
|
||
}
|
||
|
||
// term.wasm
|
||
{
|
||
const wasm = b.addSharedLibrary(
|
||
"ghostty-term",
|
||
"src/terminal/main_wasm.zig",
|
||
.{ .unversioned = {} },
|
||
);
|
||
wasm.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
|
||
wasm.setBuildMode(mode);
|
||
wasm.setOutputDir("zig-out");
|
||
|
||
// Wasm-specific deps
|
||
wasm.addPackage(tracylib.pkg);
|
||
wasm.addPackage(utf8proc.pkg);
|
||
_ = try utf8proc.link(b, wasm);
|
||
|
||
const step = b.step("term-wasm", "Build the terminal.wasm library");
|
||
step.dependOn(&wasm.step);
|
||
}
|
||
|
||
// Run
|
||
{
|
||
// Build our run step, which runs the main app by default, but will
|
||
// run a conformance app if `-Dconformance` is set.
|
||
const run_exe = if (conformance) |name| blk: {
|
||
var conformance_exes = try conformanceSteps(b, target, mode);
|
||
defer conformance_exes.deinit();
|
||
break :blk conformance_exes.get(name) orelse return error.InvalidConformance;
|
||
} else exe;
|
||
const run_cmd = run_exe.run();
|
||
run_cmd.step.dependOn(&run_exe.step);
|
||
if (b.args) |args| {
|
||
run_cmd.addArgs(args);
|
||
}
|
||
|
||
const run_step = b.step("run", "Run the app");
|
||
run_step.dependOn(&run_cmd.step);
|
||
}
|
||
|
||
// Tests
|
||
{
|
||
const test_step = b.step("test", "Run all tests");
|
||
var test_filter = b.option([]const u8, "test-filter", "Filter for test");
|
||
|
||
const main_test = b.addTestExe("ghostty-test", "src/main.zig");
|
||
{
|
||
if (emit_test_exe) main_test.install();
|
||
const main_test_run = main_test.run();
|
||
main_test.setFilter(test_filter);
|
||
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", .{});
|
||
test_step.dependOn(&before.step);
|
||
test_step.dependOn(&main_test_run.step);
|
||
test_step.dependOn(&after.step);
|
||
}
|
||
|
||
// Named package dependencies don't have their tests run by reference,
|
||
// so we iterate through them here. We're only interested in dependencies
|
||
// 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 buf: [256]u8 = undefined;
|
||
var test_ = b.addTestExeSource(
|
||
try std.fmt.bufPrint(&buf, "{s}-test", .{pkg.name}),
|
||
pkg.source,
|
||
);
|
||
const test_run = test_.run();
|
||
|
||
test_.setTarget(target);
|
||
try addDeps(b, test_, true);
|
||
if (pkg.dependencies) |children| {
|
||
test_.packages = std.ArrayList(std.build.Pkg).init(b.allocator);
|
||
try test_.packages.appendSlice(children);
|
||
}
|
||
|
||
var before = b.addLog("\x1b[" ++ color_map.get("cyan").? ++ "\x1b[" ++ color_map.get("b").? ++ "[{s} tests]" ++ "\x1b[" ++ color_map.get("d").? ++ " ----" ++ "\x1b[0m", .{pkg.name});
|
||
var after = b.addLog("\x1b[" ++ color_map.get("d").? ++ "–––---\n\n" ++ "\x1b[0m", .{});
|
||
test_step.dependOn(&before.step);
|
||
test_step.dependOn(&test_run.step);
|
||
test_step.dependOn(&after.step);
|
||
|
||
if (emit_test_exe) test_.install();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Adds and links all of the primary dependencies for the exe.
|
||
fn addDeps(
|
||
b: *std.build.Builder,
|
||
step: *std.build.LibExeObjStep,
|
||
static: bool,
|
||
) !void {
|
||
// We always need the Zig packages
|
||
if (enable_fontconfig) step.addPackage(fontconfig.pkg);
|
||
step.addPackage(freetype.pkg);
|
||
step.addPackage(harfbuzz.pkg);
|
||
step.addPackage(imgui.pkg);
|
||
step.addPackage(glfw.pkg);
|
||
step.addPackage(libuv.pkg);
|
||
step.addPackage(stb_image_resize.pkg);
|
||
step.addPackage(utf8proc.pkg);
|
||
|
||
// Mac Stuff
|
||
if (step.target.isDarwin()) {
|
||
step.addPackage(macos.pkg);
|
||
_ = try macos.link(b, step, .{});
|
||
}
|
||
|
||
// We always statically compile glad
|
||
step.addIncludePath("vendor/glad/include/");
|
||
step.addCSourceFile("vendor/glad/src/gl.c", &.{});
|
||
|
||
// Tracy
|
||
step.addPackage(tracylib.pkg);
|
||
if (tracy) {
|
||
var tracy_step = try tracylib.link(b, step);
|
||
system_sdk.include(b, tracy_step, .{});
|
||
}
|
||
|
||
// stb_image_resize
|
||
_ = try stb_image_resize.link(b, step, .{});
|
||
|
||
// utf8proc
|
||
_ = try utf8proc.link(b, step);
|
||
|
||
// Glfw
|
||
const glfw_opts: glfw.Options = .{ .metal = false, .opengl = false };
|
||
try glfw.link(b, step, glfw_opts);
|
||
|
||
// Imgui, we have to do this later since we need some information
|
||
const imgui_backends = [_][]const u8{ "glfw", "opengl3" };
|
||
var imgui_opts: imgui.Options = .{
|
||
.backends = &imgui_backends,
|
||
.freetype = .{ .enabled = true },
|
||
};
|
||
|
||
// Dynamic link
|
||
if (!static) {
|
||
step.addIncludePath(freetype.include_path_self);
|
||
step.linkSystemLibrary("bzip2");
|
||
step.linkSystemLibrary("freetype2");
|
||
step.linkSystemLibrary("harfbuzz");
|
||
step.linkSystemLibrary("libpng");
|
||
step.linkSystemLibrary("libuv");
|
||
step.linkSystemLibrary("zlib");
|
||
|
||
if (enable_fontconfig) step.linkSystemLibrary("fontconfig");
|
||
}
|
||
|
||
// Other dependencies, we may dynamically link
|
||
if (static) {
|
||
const zlib_step = try zlib.link(b, step);
|
||
const libpng_step = try libpng.link(b, step, .{
|
||
.zlib = .{
|
||
.step = zlib_step,
|
||
.include = &zlib.include_paths,
|
||
},
|
||
});
|
||
|
||
// Freetype
|
||
const freetype_step = try freetype.link(b, step, .{
|
||
.libpng = freetype.Options.Libpng{
|
||
.enabled = true,
|
||
.step = libpng_step,
|
||
.include = &libpng.include_paths,
|
||
},
|
||
|
||
.zlib = .{
|
||
.enabled = true,
|
||
.step = zlib_step,
|
||
.include = &zlib.include_paths,
|
||
},
|
||
});
|
||
|
||
// Harfbuzz
|
||
const harfbuzz_step = try harfbuzz.link(b, step, .{
|
||
.freetype = .{
|
||
.enabled = true,
|
||
.step = freetype_step,
|
||
.include = &freetype.include_paths,
|
||
},
|
||
|
||
.coretext = .{
|
||
.enabled = enable_coretext,
|
||
},
|
||
});
|
||
system_sdk.include(b, harfbuzz_step, .{});
|
||
|
||
// Libuv
|
||
const libuv_step = try libuv.link(b, step);
|
||
system_sdk.include(b, libuv_step, .{});
|
||
|
||
// Only Linux gets fontconfig
|
||
if (enable_fontconfig) {
|
||
// Libxml2
|
||
const libxml2_lib = try libxml2.create(
|
||
b,
|
||
step.target,
|
||
step.build_mode,
|
||
.{ .lzma = false, .zlib = false },
|
||
);
|
||
libxml2_lib.link(step);
|
||
|
||
// Fontconfig
|
||
const fontconfig_step = try fontconfig.link(b, step, .{
|
||
.freetype = .{
|
||
.enabled = true,
|
||
.step = freetype_step,
|
||
.include = &freetype.include_paths,
|
||
},
|
||
|
||
.libxml2 = true,
|
||
});
|
||
libxml2_lib.link(fontconfig_step);
|
||
}
|
||
|
||
// Imgui
|
||
imgui_opts.freetype.step = freetype_step;
|
||
imgui_opts.freetype.include = &freetype.include_paths;
|
||
}
|
||
|
||
// Imgui
|
||
const imgui_step = try imgui.link(b, step, imgui_opts);
|
||
try glfw.link(b, imgui_step, glfw_opts);
|
||
}
|
||
|
||
fn conformanceSteps(
|
||
b: *std.build.Builder,
|
||
target: std.zig.CrossTarget,
|
||
mode: std.builtin.Mode,
|
||
) !std.StringHashMap(*LibExeObjStep) {
|
||
var map = std.StringHashMap(*LibExeObjStep).init(b.allocator);
|
||
|
||
// Open the directory ./conformance
|
||
const c_dir_path = (comptime root()) ++ "/conformance";
|
||
var c_dir = try fs.openIterableDirAbsolute(c_dir_path, .{});
|
||
defer c_dir.close();
|
||
|
||
// Go through and add each as a step
|
||
var c_dir_it = c_dir.iterate();
|
||
while (try c_dir_it.next()) |entry| {
|
||
// Get the index of the last '.' so we can strip the extension.
|
||
const index = std.mem.lastIndexOfScalar(u8, entry.name, '.') orelse continue;
|
||
if (index == 0) continue;
|
||
|
||
// Name of the conformance app and full path to the entrypoint.
|
||
const name = entry.name[0..index];
|
||
const path = try fs.path.join(b.allocator, &[_][]const u8{
|
||
c_dir_path,
|
||
entry.name,
|
||
});
|
||
|
||
// Executable builder.
|
||
const c_exe = b.addExecutable(name, path);
|
||
c_exe.setTarget(target);
|
||
c_exe.setBuildMode(mode);
|
||
|
||
// Store the mapping
|
||
try map.put(name, c_exe);
|
||
}
|
||
|
||
return map;
|
||
}
|
||
|
||
/// Path to the directory with the build.zig.
|
||
fn root() []const u8 {
|
||
return std.fs.path.dirname(@src().file) orelse unreachable;
|
||
}
|
||
|
||
/// ANSI escape codes for colored log output
|
||
const color_map = std.ComptimeStringMap([]const u8, .{
|
||
&.{ "black", "30m" },
|
||
&.{ "blue", "34m" },
|
||
&.{ "b", "1m" },
|
||
&.{ "d", "2m" },
|
||
&.{ "cyan", "36m" },
|
||
&.{ "green", "32m" },
|
||
&.{ "magenta", "35m" },
|
||
&.{ "red", "31m" },
|
||
&.{ "white", "37m" },
|
||
&.{ "yellow", "33m" },
|
||
});
|