mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
Merge pull request #1653 from mitchellh/coretext
Enable CoreText font shaper for macOS by default
This commit is contained in:
143
.github/workflows/release-pr.yml
vendored
143
.github/workflows/release-pr.yml
vendored
@ -149,3 +149,146 @@ jobs:
|
||||
r2-bucket: ghostty-pr
|
||||
source-dir: blob
|
||||
destination-dir: ./
|
||||
|
||||
build-macos-debug:
|
||||
runs-on: namespace-profile-ghostty-macos
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v26
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v14
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.5.1
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
curl -L https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-for-Swift-Package-Manager.zip > sparkle.zip
|
||||
unzip sparkle.zip
|
||||
echo "$(pwd)/bin" >> $GITHUB_PATH
|
||||
|
||||
# Load Build Number
|
||||
- name: Build Number
|
||||
run: |
|
||||
echo "GHOSTTY_BUILD=$(git rev-list --count head)" >> $GITHUB_ENV
|
||||
echo "GHOSTTY_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
|
||||
# GhosttyKit is the framework that is built from Zig for our native
|
||||
# Mac app to access. Build this in release mode.
|
||||
- name: Build GhosttyKit
|
||||
run: nix develop -c zig build -Dstatic=true -Doptimize=Debug
|
||||
|
||||
# The native app is built with native XCode tooling. This also does
|
||||
# codesigning. IMPORTANT: this must NOT run in a Nix environment.
|
||||
# Nix breaks xcodebuild so this has to be run outside.
|
||||
- name: Build Ghostty.app
|
||||
run: cd macos && xcodebuild -target Ghostty -configuration Release
|
||||
|
||||
# We inject the "build number" as simply the number of commits since HEAD.
|
||||
# This will be a monotonically always increasing build number that we use.
|
||||
- name: Update Info.plist
|
||||
env:
|
||||
SPARKLE_KEY_PUB: ${{ secrets.PROD_MACOS_SPARKLE_KEY_PUB }}
|
||||
run: |
|
||||
# Version Info
|
||||
/usr/libexec/PlistBuddy -c "Set :GhosttyCommit $GHOSTTY_COMMIT" "macos/build/Release/Ghostty.app/Contents/Info.plist"
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $GHOSTTY_BUILD" "macos/build/Release/Ghostty.app/Contents/Info.plist"
|
||||
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $GHOSTTY_COMMIT" "macos/build/Release/Ghostty.app/Contents/Info.plist"
|
||||
|
||||
# Updater
|
||||
/usr/libexec/PlistBuddy -c "Set :SUPublicEDKey $SPARKLE_KEY_PUB" "macos/build/Release/Ghostty.app/Contents/Info.plist"
|
||||
|
||||
- name: Codesign app bundle
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
|
||||
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
|
||||
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
|
||||
run: |
|
||||
# Turn our base64-encoded certificate back to a regular .p12 file
|
||||
echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
|
||||
|
||||
# We need to create a new keychain, otherwise using the certificate will prompt
|
||||
# with a UI dialog asking for the certificate password, which we can't
|
||||
# use in a headless CI environment
|
||||
security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
|
||||
security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain
|
||||
|
||||
# Codesign Sparkle. Some notes here:
|
||||
# - The XPC services aren't used since we don't sandbox Ghostty,
|
||||
# but since they're part of the build, they still need to be
|
||||
# codesigned.
|
||||
# - The binaries in the "Versions" folders need to NOT be symlinks.
|
||||
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Downloader.xpc"
|
||||
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Installer.xpc"
|
||||
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/Autoupdate"
|
||||
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/Updater.app"
|
||||
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework"
|
||||
|
||||
# Codesign the app bundle
|
||||
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime --entitlements "macos/Ghostty.entitlements" macos/build/Release/Ghostty.app
|
||||
|
||||
- name: "Notarize app bundle"
|
||||
env:
|
||||
PROD_MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
|
||||
PROD_MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
|
||||
PROD_MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
|
||||
run: |
|
||||
# Store the notarization credentials so that we can prevent a UI password dialog
|
||||
# from blocking the CI
|
||||
echo "Create keychain profile"
|
||||
xcrun notarytool store-credentials "notarytool-profile" --apple-id "$PROD_MACOS_NOTARIZATION_APPLE_ID" --team-id "$PROD_MACOS_NOTARIZATION_TEAM_ID" --password "$PROD_MACOS_NOTARIZATION_PWD"
|
||||
|
||||
# We can't notarize an app bundle directly, but we need to compress it as an archive.
|
||||
# Therefore, we create a zip file containing our app bundle, so that we can send it to the
|
||||
# notarization service
|
||||
echo "Creating temp notarization archive"
|
||||
ditto -c -k --keepParent "macos/build/Release/Ghostty.app" "notarization.zip"
|
||||
|
||||
# Here we send the notarization request to the Apple's Notarization service, waiting for the result.
|
||||
# This typically takes a few seconds inside a CI environment, but it might take more depending on the App
|
||||
# characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if
|
||||
# you're curious
|
||||
echo "Notarize app"
|
||||
xcrun notarytool submit "notarization.zip" --keychain-profile "notarytool-profile" --wait
|
||||
|
||||
# Finally, we need to "attach the staple" to our executable, which will allow our app to be
|
||||
# validated by macOS even when an internet connection is not available.
|
||||
echo "Attach staple"
|
||||
xcrun stapler staple "macos/build/Release/Ghostty.app"
|
||||
|
||||
# Zip up the app
|
||||
- name: Zip App
|
||||
run: cd macos/build/Release && zip -9 -r --symlinks ../../../ghostty-macos-universal-debug.zip Ghostty.app
|
||||
|
||||
# Update Blob Storage
|
||||
- name: Prep R2 Storage
|
||||
run: |
|
||||
mkdir blob
|
||||
mkdir -p blob/${GHOSTTY_BUILD}
|
||||
cp ghostty-macos-universal-debug.zip blob/${GHOSTTY_BUILD}/ghostty-macos-universal-debug.zip
|
||||
- name: Upload to R2
|
||||
uses: ryand56/r2-upload-action@latest
|
||||
with:
|
||||
r2-account-id: ${{ secrets.CF_R2_PR_ACCOUNT_ID }}
|
||||
r2-access-key-id: ${{ secrets.CF_R2_PR_AWS_KEY }}
|
||||
r2-secret-access-key: ${{ secrets.CF_R2_PR_SECRET_KEY }}
|
||||
r2-bucket: ghostty-pr
|
||||
source-dir: blob
|
||||
destination-dir: ./
|
||||
|
19
.github/workflows/test.yml
vendored
19
.github/workflows/test.yml
vendored
@ -277,6 +277,25 @@ jobs:
|
||||
- name: Test Dynamic Build
|
||||
run: nix develop -c zig build -Dstatic=false
|
||||
|
||||
test-macos:
|
||||
runs-on: namespace-profile-ghostty-macos
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v26
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v14
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
|
||||
- name: test
|
||||
run: nix develop -c zig build test
|
||||
|
||||
prettier:
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
timeout-minutes: 60
|
||||
|
19
build.zig
19
build.zig
@ -1055,11 +1055,14 @@ fn addDeps(
|
||||
"fontconfig",
|
||||
fontconfig_dep.module("fontconfig"),
|
||||
);
|
||||
if (config.font_backend.hasHarfbuzz()) step.root_module.addImport(
|
||||
"harfbuzz",
|
||||
harfbuzz_dep.module("harfbuzz"),
|
||||
);
|
||||
step.root_module.addImport("oniguruma", oniguruma_dep.module("oniguruma"));
|
||||
step.root_module.addImport("freetype", freetype_dep.module("freetype"));
|
||||
step.root_module.addImport("glslang", glslang_dep.module("glslang"));
|
||||
step.root_module.addImport("spirv_cross", spirv_cross_dep.module("spirv_cross"));
|
||||
step.root_module.addImport("harfbuzz", harfbuzz_dep.module("harfbuzz"));
|
||||
step.root_module.addImport("xev", libxev_dep.module("xev"));
|
||||
step.root_module.addImport("opengl", opengl_dep.module("opengl"));
|
||||
step.root_module.addImport("pixman", pixman_dep.module("pixman"));
|
||||
@ -1110,7 +1113,6 @@ fn addDeps(
|
||||
step.addIncludePath(freetype_dep.path(""));
|
||||
step.linkSystemLibrary2("bzip2", dynamic_link_opts);
|
||||
step.linkSystemLibrary2("freetype2", dynamic_link_opts);
|
||||
step.linkSystemLibrary2("harfbuzz", dynamic_link_opts);
|
||||
step.linkSystemLibrary2("libpng", dynamic_link_opts);
|
||||
step.linkSystemLibrary2("oniguruma", dynamic_link_opts);
|
||||
step.linkSystemLibrary2("pixman-1", dynamic_link_opts);
|
||||
@ -1119,6 +1121,9 @@ fn addDeps(
|
||||
if (config.font_backend.hasFontconfig()) {
|
||||
step.linkSystemLibrary2("fontconfig", dynamic_link_opts);
|
||||
}
|
||||
if (config.font_backend.hasHarfbuzz()) {
|
||||
step.linkSystemLibrary2("harfbuzz", dynamic_link_opts);
|
||||
}
|
||||
}
|
||||
|
||||
// Other dependencies, we may dynamically link
|
||||
@ -1136,14 +1141,16 @@ fn addDeps(
|
||||
step.linkLibrary(freetype_dep.artifact("freetype"));
|
||||
try static_libs.append(freetype_dep.artifact("freetype").getEmittedBin());
|
||||
|
||||
// Harfbuzz
|
||||
step.linkLibrary(harfbuzz_dep.artifact("harfbuzz"));
|
||||
try static_libs.append(harfbuzz_dep.artifact("harfbuzz").getEmittedBin());
|
||||
|
||||
// Pixman
|
||||
step.linkLibrary(pixman_dep.artifact("pixman"));
|
||||
try static_libs.append(pixman_dep.artifact("pixman").getEmittedBin());
|
||||
|
||||
// Harfbuzz
|
||||
if (config.font_backend.hasHarfbuzz()) {
|
||||
step.linkLibrary(harfbuzz_dep.artifact("harfbuzz"));
|
||||
try static_libs.append(harfbuzz_dep.artifact("harfbuzz").getEmittedBin());
|
||||
}
|
||||
|
||||
// Only Linux gets fontconfig
|
||||
if (config.font_backend.hasFontconfig()) {
|
||||
// Fontconfig
|
||||
|
@ -1,6 +1,7 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const base = @import("base.zig");
|
||||
const c = @import("c.zig");
|
||||
const cftype = @import("type.zig");
|
||||
const ComparisonResult = base.ComparisonResult;
|
||||
const Range = base.Range;
|
||||
@ -42,6 +43,14 @@ pub const Array = opaque {
|
||||
};
|
||||
|
||||
pub const MutableArray = opaque {
|
||||
pub fn create() Allocator.Error!*MutableArray {
|
||||
return CFArrayCreateMutable(
|
||||
null,
|
||||
0,
|
||||
&c.kCFTypeArrayCallBacks,
|
||||
) orelse error.OutOfMemory;
|
||||
}
|
||||
|
||||
pub fn createCopy(array: *Array) Allocator.Error!*MutableArray {
|
||||
return CFArrayCreateMutableCopy(
|
||||
null,
|
||||
@ -54,6 +63,18 @@ pub const MutableArray = opaque {
|
||||
cftype.CFRelease(self);
|
||||
}
|
||||
|
||||
pub fn appendValue(
|
||||
self: *MutableArray,
|
||||
comptime Elem: type,
|
||||
value: *const Elem,
|
||||
) void {
|
||||
CFArrayAppendValue(self, @constCast(@ptrCast(value)));
|
||||
}
|
||||
|
||||
pub fn removeValue(self: *MutableArray, idx: usize) void {
|
||||
CFArrayRemoveValueAtIndex(self, idx);
|
||||
}
|
||||
|
||||
pub fn sortValues(
|
||||
self: *MutableArray,
|
||||
comptime Elem: type,
|
||||
@ -73,12 +94,24 @@ pub const MutableArray = opaque {
|
||||
);
|
||||
}
|
||||
|
||||
extern "c" fn CFArrayCreateMutable(
|
||||
allocator: ?*anyopaque,
|
||||
capacity: usize,
|
||||
callbacks: ?*const anyopaque,
|
||||
) ?*MutableArray;
|
||||
extern "c" fn CFArrayCreateMutableCopy(
|
||||
allocator: ?*anyopaque,
|
||||
capacity: usize,
|
||||
array: *Array,
|
||||
) ?*MutableArray;
|
||||
|
||||
extern "c" fn CFArrayAppendValue(
|
||||
*MutableArray,
|
||||
*anyopaque,
|
||||
) void;
|
||||
extern "c" fn CFArrayRemoveValueAtIndex(
|
||||
*MutableArray,
|
||||
usize,
|
||||
) void;
|
||||
extern "c" fn CFArraySortValues(
|
||||
array: *MutableArray,
|
||||
range: Range,
|
||||
|
@ -6,8 +6,8 @@ const c = @import("c.zig");
|
||||
|
||||
pub const Dictionary = opaque {
|
||||
pub fn create(
|
||||
keys: ?[]?*const anyopaque,
|
||||
values: ?[]?*const anyopaque,
|
||||
keys: ?[]const ?*const anyopaque,
|
||||
values: ?[]const ?*const anyopaque,
|
||||
) Allocator.Error!*Dictionary {
|
||||
if (keys != null or values != null) {
|
||||
assert(keys != null);
|
||||
@ -17,8 +17,8 @@ pub const Dictionary = opaque {
|
||||
|
||||
return @as(?*Dictionary, @ptrFromInt(@intFromPtr(c.CFDictionaryCreate(
|
||||
null,
|
||||
@ptrCast(if (keys) |slice| slice.ptr else null),
|
||||
@ptrCast(if (values) |slice| slice.ptr else null),
|
||||
@constCast(@ptrCast(if (keys) |slice| slice.ptr else null)),
|
||||
@constCast(@ptrCast(if (values) |slice| slice.ptr else null)),
|
||||
@intCast(if (keys) |slice| slice.len else 0),
|
||||
&c.kCFTypeDictionaryKeyCallBacks,
|
||||
&c.kCFTypeDictionaryValueCallBacks,
|
||||
|
@ -13,8 +13,9 @@ pub const Face = struct {
|
||||
/// Our font face
|
||||
font: *macos.text.Font,
|
||||
|
||||
/// Harfbuzz font corresponding to this face.
|
||||
hb_font: harfbuzz.Font,
|
||||
/// Harfbuzz font corresponding to this face. We only use this
|
||||
/// if we're using Harfbuzz.
|
||||
hb_font: if (harfbuzz_shaper) harfbuzz.Font else void,
|
||||
|
||||
/// The presentation for this font.
|
||||
presentation: font.Presentation,
|
||||
@ -25,6 +26,10 @@ pub const Face = struct {
|
||||
/// Set quirks.disableDefaultFontFeatures
|
||||
quirks_disable_default_font_features: bool = false,
|
||||
|
||||
/// True if our build is using Harfbuzz. If we're not, we can avoid
|
||||
/// some Harfbuzz-specific code paths.
|
||||
const harfbuzz_shaper = font.options.backend.hasHarfbuzz();
|
||||
|
||||
/// The matrix applied to a regular font to auto-italicize it.
|
||||
pub const italic_skew = macos.graphics.AffineTransform{
|
||||
.a = 1,
|
||||
@ -75,10 +80,6 @@ pub const Face = struct {
|
||||
/// Initialize a face with a CTFont. This will take ownership over
|
||||
/// the CTFont. This does NOT copy or retain the CTFont.
|
||||
pub fn initFont(ct_font: *macos.text.Font, opts: font.face.Options) !Face {
|
||||
var hb_font = try harfbuzz.coretext.createFont(ct_font);
|
||||
errdefer hb_font.destroy();
|
||||
hb_font.setScale(opts.size.pixels(), opts.size.pixels());
|
||||
|
||||
const traits = ct_font.getSymbolicTraits();
|
||||
const metrics = metrics: {
|
||||
var metrics = try calcMetrics(ct_font);
|
||||
@ -86,6 +87,13 @@ pub const Face = struct {
|
||||
break :metrics metrics;
|
||||
};
|
||||
|
||||
var hb_font = if (comptime harfbuzz_shaper) font: {
|
||||
var hb_font = try harfbuzz.coretext.createFont(ct_font);
|
||||
hb_font.setScale(opts.size.pixels(), opts.size.pixels());
|
||||
break :font hb_font;
|
||||
} else {};
|
||||
errdefer if (comptime harfbuzz_shaper) hb_font.destroy();
|
||||
|
||||
var result: Face = .{
|
||||
.font = ct_font,
|
||||
.hb_font = hb_font,
|
||||
@ -144,7 +152,7 @@ pub const Face = struct {
|
||||
|
||||
pub fn deinit(self: *Face) void {
|
||||
self.font.release();
|
||||
self.hb_font.destroy();
|
||||
if (comptime harfbuzz_shaper) self.hb_font.destroy();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
|
@ -115,6 +115,19 @@ pub const Backend = enum {
|
||||
=> false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn hasHarfbuzz(self: Backend) bool {
|
||||
return switch (self) {
|
||||
.freetype,
|
||||
.fontconfig_freetype,
|
||||
.coretext_freetype,
|
||||
=> true,
|
||||
|
||||
.coretext,
|
||||
.web_canvas,
|
||||
=> false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// The styles that a family can take.
|
||||
|
BIN
src/font/res/JetBrainsMonoNerdFont-Regular.ttf
Normal file
BIN
src/font/res/JetBrainsMonoNerdFont-Regular.ttf
Normal file
Binary file not shown.
@ -1,7 +1,7 @@
|
||||
const builtin = @import("builtin");
|
||||
const options = @import("main.zig").options;
|
||||
const harfbuzz = @import("shaper/harfbuzz.zig");
|
||||
const coretext = @import("shaper/coretext.zig");
|
||||
pub const harfbuzz = @import("shaper/harfbuzz.zig");
|
||||
pub const coretext = @import("shaper/coretext.zig");
|
||||
pub const web_canvas = @import("shaper/web_canvas.zig");
|
||||
pub usingnamespace @import("shaper/run.zig");
|
||||
|
||||
@ -10,12 +10,12 @@ pub const Shaper = switch (options.backend) {
|
||||
.freetype,
|
||||
.fontconfig_freetype,
|
||||
.coretext_freetype,
|
||||
.coretext,
|
||||
=> harfbuzz.Shaper,
|
||||
|
||||
// Has missing features, can't be used yet. See the comments in
|
||||
// the coretext.zig file for more details.
|
||||
//.coretext => coretext.Shaper,
|
||||
// Note that coretext_freetype cannot use the coretext
|
||||
// shaper because the coretext shaper requests CoreText
|
||||
// font faces.
|
||||
.coretext => coretext.Shaper,
|
||||
|
||||
.web_canvas => web_canvas.Shaper,
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,9 @@ pub const fontEmoji = @embedFile("res/NotoColorEmoji.ttf");
|
||||
pub const fontEmojiText = @embedFile("res/NotoEmoji-Regular.ttf");
|
||||
pub const fontVariable = @embedFile("res/Lilex-VF.ttf");
|
||||
|
||||
/// Font with nerd fonts embedded.
|
||||
pub const fontNerdFont = @embedFile("res/JetBrainsMonoNerdFont-Regular.ttf");
|
||||
|
||||
/// Cozette is a unique font because it embeds some emoji characters
|
||||
/// but has a text presentation.
|
||||
pub const fontCozette = @embedFile("res/CozetteVector.ttf");
|
||||
|
@ -256,7 +256,9 @@ pub const GlobalState = struct {
|
||||
std.log.info("ghostty build optimize={s}", .{build_config.mode_string});
|
||||
std.log.info("runtime={}", .{build_config.app_runtime});
|
||||
std.log.info("font_backend={}", .{build_config.font_backend});
|
||||
if (comptime build_config.font_backend.hasHarfbuzz()) {
|
||||
std.log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()});
|
||||
}
|
||||
if (comptime build_config.font_backend.hasFontconfig()) {
|
||||
std.log.info("dependency fontconfig={d}", .{fontconfig.version()});
|
||||
}
|
||||
|
Reference in New Issue
Block a user