feat(font): add the FreeType-only font backend (font-backend-freetype)
Ghostty can be built with -Dfont-backend=freetype, which uses FreeType without any font discovery. The Rust port only had fontconfig+freetype (default on Linux) and the CoreText variants. The new cargo feature `font-backend-freetype` on ghostty-font (forwarded by ghostty-cli and the root crate; non-Apple, non-wasm targets) behaves like Zig's mode: font-family discovery, fallback search and codepoint-map overrides are skipped (embedded fonts only); +list-fonts and +show-face print Zig's "built without a font discovery mechanism" message and exit 1; +version reports `.freetype`. The default backend is unchanged. Also registers the Rust-only benchmark tooling extensions (--time, --handler=full, ghostty-synthetic +sgr) in DEVIANT_FIXES.md. Note: `--all-features` now selects this backend for the workspace. Test Plan: - cargo test -p ghostty-font --features font-backend-freetype (198) - cargo build --features font-backend-freetype && ghostty +list-fonts
This commit is contained in:
1 parent
ebd603f3da
commit
ff1169abed
18 files changed
+327
-12
No files matched your search
@@ -14,6 +14,9 @@ path = "src/main.rs"
|
|||||||
sentry = ["ghostty-crash/sentry"]
|
sentry = ["ghostty-crash/sentry"]
|
||||||
# Mirrors upstream's `-Dsnap` build option (off by default).
|
# Mirrors upstream's `-Dsnap` build option (off by default).
|
||||||
snap = ["ghostty-os/snap"]
|
snap = ["ghostty-os/snap"]
|
||||||
|
# Mirrors upstream's `-Dfont-backend=freetype`: FreeType rendering with no
|
||||||
|
# font discovery (fontconfig is not used). Non-Apple, non-wasm targets only.
|
||||||
|
font-backend-freetype = ["ghostty-cli/font-backend-freetype"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ghostty-core.workspace = true
|
ghostty-core.workspace = true
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ version.workspace = true
|
|||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# Select the FreeType-only font backend (no font discovery), like
|
||||||
|
# `-Dfont-backend=freetype`.
|
||||||
|
font-backend-freetype = ["ghostty-font/font-backend-freetype"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
flate2 = "1.1.10"
|
flate2 = "1.1.10"
|
||||||
ghostty-build-info.workspace = true
|
ghostty-build-info.workspace = true
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ impl Default for BuildInfo {
|
|||||||
release_channel: ghostty_build_info::RELEASE_CHANNEL.to_string(),
|
release_channel: ghostty_build_info::RELEASE_CHANNEL.to_string(),
|
||||||
build_mode: ghostty_build_info::BUILD_MODE.to_string(),
|
build_mode: ghostty_build_info::BUILD_MODE.to_string(),
|
||||||
app_runtime: ".gtk".to_string(),
|
app_runtime: ".gtk".to_string(),
|
||||||
font_backend: ".fontconfig_freetype".to_string(),
|
font_backend: format!(".{}", ghostty_font::options.backend.name()),
|
||||||
renderer: "renderer.generic.Renderer(renderer.OpenGL)".to_string(),
|
renderer: "renderer.generic.Renderer(renderer.OpenGL)".to_string(),
|
||||||
gtk_build: None,
|
gtk_build: None,
|
||||||
gtk_runtime: None,
|
gtk_runtime: None,
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ use ghostty_config::config_struct;
|
|||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
|
/// Printed (without a trailing newline) by `+list-fonts` and `+show-face`
|
||||||
|
/// when the build has no font discovery mechanism (Zig `Discover == void`,
|
||||||
|
/// the `freetype` font backend).
|
||||||
|
pub(crate) const NO_DISCOVERY_MESSAGE: &str = "Ghostty was built without a font discovery mechanism. This is a compile-time\noption. Please review how Ghostty was built from source, contact the\nmaintainer to enable a font discovery mechanism, and try again.";
|
||||||
|
|
||||||
config_struct! {
|
config_struct! {
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
/// The font family to search for. If this is set, then only fonts
|
/// The font family to search for. If this is set, then only fonts
|
||||||
@@ -51,6 +56,14 @@ pub fn run(args: &[impl AsRef<[u8]>]) -> Result<u8, Error> {
|
|||||||
|
|
||||||
use std::{collections::HashMap, io::Write};
|
use std::{collections::HashMap, io::Write};
|
||||||
|
|
||||||
|
// Its possible to build Ghostty without font discovery!
|
||||||
|
if !ghostty_font::HAS_DISCOVERY {
|
||||||
|
let mut stderr = crate::zig_writer::ZigWriter::new(crate::zig_writer::stderr_sink(), 1024);
|
||||||
|
write!(stderr, "{NO_DISCOVERY_MESSAGE}")?;
|
||||||
|
stderr.flush()?;
|
||||||
|
return Ok(1);
|
||||||
|
}
|
||||||
|
|
||||||
// We'll be putting our fonts into a list categorized by family
|
// We'll be putting our fonts into a list categorized by family
|
||||||
// so it is easier to read the output.
|
// so it is easier to read the output.
|
||||||
let mut families: Vec<String> = Vec::new();
|
let mut families: Vec<String> = Vec::new();
|
||||||
@@ -118,3 +131,23 @@ pub fn run(args: &[impl AsRef<[u8]>]) -> Result<u8, Error> {
|
|||||||
stdout.flush()?;
|
stdout.flush()?;
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
/// Without a discovery mechanism (the `freetype` font backend, Zig
|
||||||
|
/// `Discover == void`) `+list-fonts` prints the compile-time-option
|
||||||
|
/// message and exits with 1, after parsing the arguments.
|
||||||
|
#[cfg(feature = "font-backend-freetype")]
|
||||||
|
#[test]
|
||||||
|
fn freetype_backend_reports_no_discovery() {
|
||||||
|
const { assert!(!ghostty_font::HAS_DISCOVERY) };
|
||||||
|
assert_eq!(super::run(&[] as &[&[u8]]).expect("run"), 1);
|
||||||
|
assert!(super::NO_DISCOVERY_MESSAGE.ends_with("try again."));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "font-backend-freetype"))]
|
||||||
|
#[test]
|
||||||
|
fn default_backend_has_discovery() {
|
||||||
|
const { assert!(ghostty_font::HAS_DISCOVERY) };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -86,6 +86,16 @@ cli_options!(Options, help, diagnostics);
|
|||||||
/// style. Valid options are `text` and `emoji`. If unset, the presentation
|
/// style. Valid options are `text` and `emoji`. If unset, the presentation
|
||||||
/// style of a codepoint will be inferred from the Unicode standard.
|
/// style of a codepoint will be inferred from the Unicode standard.
|
||||||
pub fn run(args: &[impl AsRef<[u8]>]) -> Result<u8, Error> {
|
pub fn run(args: &[impl AsRef<[u8]>]) -> Result<u8, Error> {
|
||||||
|
// Its possible to build Ghostty without font discovery! (This is checked
|
||||||
|
// before the arguments are parsed, as in Zig; the stderr writer is on
|
||||||
|
// stdout, like the other show-face writers.)
|
||||||
|
if !ghostty_font::HAS_DISCOVERY {
|
||||||
|
let mut stderr = ZigWriter::new(stdout_sink(), 1024);
|
||||||
|
write!(stderr, "{}", crate::list_fonts::NO_DISCOVERY_MESSAGE)?;
|
||||||
|
stderr.flush()?;
|
||||||
|
return Ok(1);
|
||||||
|
}
|
||||||
|
|
||||||
let mut opts = Options::default();
|
let mut opts = Options::default();
|
||||||
crate::parse_options(&mut opts, args)?;
|
crate::parse_options(&mut opts, args)?;
|
||||||
|
|
||||||
|
|||||||
@@ -146,9 +146,12 @@ pub fn default_crash_options() -> ghostty_crash::Options {
|
|||||||
.to_owned(),
|
.to_owned(),
|
||||||
..ghostty_crash::Options::default()
|
..ghostty_crash::Options::default()
|
||||||
};
|
};
|
||||||
|
ghostty_font::options
|
||||||
|
.backend
|
||||||
|
.name()
|
||||||
|
.clone_into(&mut o.font_backend);
|
||||||
if cfg!(target_vendor = "apple") {
|
if cfg!(target_vendor = "apple") {
|
||||||
"none".clone_into(&mut o.app_runtime);
|
"none".clone_into(&mut o.app_runtime);
|
||||||
"coretext".clone_into(&mut o.font_backend);
|
|
||||||
"metal".clone_into(&mut o.renderer);
|
"metal".clone_into(&mut o.renderer);
|
||||||
}
|
}
|
||||||
o
|
o
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ coretext-harfbuzz = ["dep:harfbuzz-sys"]
|
|||||||
# CoreText for discovery and rendering, no shaping
|
# CoreText for discovery and rendering, no shaping
|
||||||
# (Zig `-Dfont-backend=coretext_noshape`; Apple targets only).
|
# (Zig `-Dfont-backend=coretext_noshape`; Apple targets only).
|
||||||
coretext-noshape = []
|
coretext-noshape = []
|
||||||
|
# FreeType for rendering and HarfBuzz for shaping with no font discovery
|
||||||
|
# (Zig `-Dfont-backend=freetype`; non-Apple, non-wasm targets only; the
|
||||||
|
# default there is `fontconfig_freetype`). Not enabled by `--all-features`
|
||||||
|
# in any meaningful sense: it switches the whole build to the backend.
|
||||||
|
font-backend-freetype = []
|
||||||
|
|
||||||
# This crate wraps the freetype / harfbuzz / fontconfig C libraries through
|
# This crate wraps the freetype / harfbuzz / fontconfig C libraries through
|
||||||
# their `-sys` crates; unsafe is denied by default and allowed on minimal,
|
# their `-sys` crates; unsafe is denied by default and allowed on minimal,
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
//!
|
//!
|
||||||
//! Backends (see `src/backend.rs`):
|
//! Backends (see `src/backend.rs`):
|
||||||
//! * Linux & other non-Apple, non-wasm targets: `fontconfig_freetype`.
|
//! * Linux & other non-Apple, non-wasm targets: `fontconfig_freetype`.
|
||||||
|
//! * Linux & other non-Apple, non-wasm targets with the cargo feature
|
||||||
|
//! `font-backend-freetype`: `freetype` (FreeType rendering, HarfBuzz
|
||||||
|
//! shaping, no font discovery; Zig `-Dfont-backend=freetype`).
|
||||||
//! * wasm32: `web_canvas`.
|
//! * wasm32: `web_canvas`.
|
||||||
//! * Apple targets: `coretext` (default), or via cargo features
|
//! * Apple targets: `coretext` (default), or via cargo features
|
||||||
//! `coretext-freetype`, `coretext-harfbuzz`, `coretext-noshape`.
|
//! `coretext-freetype`, `coretext-harfbuzz`, `coretext-noshape`.
|
||||||
@@ -10,6 +13,8 @@
|
|||||||
//! Emitted cfgs:
|
//! Emitted cfgs:
|
||||||
//! * `font_freetype` - FreeType face / library are compiled.
|
//! * `font_freetype` - FreeType face / library are compiled.
|
||||||
//! * `font_fontconfig` - fontconfig discovery is compiled.
|
//! * `font_fontconfig` - fontconfig discovery is compiled.
|
||||||
|
//! * `font_discovery` - a discovery mechanism (fontconfig or CoreText) exists
|
||||||
|
//! (Zig: `font.Discover != void`).
|
||||||
//! * `font_harfbuzz` - the HarfBuzz shaper is compiled.
|
//! * `font_harfbuzz` - the HarfBuzz shaper is compiled.
|
||||||
//! * `font_coretext` - CoreText discovery/deferred face (Apple targets).
|
//! * `font_coretext` - CoreText discovery/deferred face (Apple targets).
|
||||||
//! * `font_coretext_face` - the CoreText `Face` is the font face.
|
//! * `font_coretext_face` - the CoreText `Face` is the font face.
|
||||||
@@ -21,6 +26,7 @@ fn main() {
|
|||||||
for c in [
|
for c in [
|
||||||
"font_freetype",
|
"font_freetype",
|
||||||
"font_fontconfig",
|
"font_fontconfig",
|
||||||
|
"font_discovery",
|
||||||
"font_harfbuzz",
|
"font_harfbuzz",
|
||||||
"font_coretext",
|
"font_coretext",
|
||||||
"font_coretext_face",
|
"font_coretext_face",
|
||||||
@@ -40,6 +46,15 @@ fn main() {
|
|||||||
let ct_hb = feat("CORETEXT_HARFBUZZ");
|
let ct_hb = feat("CORETEXT_HARFBUZZ");
|
||||||
let ct_noshape = feat("CORETEXT_NOSHAPE");
|
let ct_noshape = feat("CORETEXT_NOSHAPE");
|
||||||
|
|
||||||
|
let ft_only = feat("FONT_BACKEND_FREETYPE");
|
||||||
|
if ft_only && (wasm || apple) {
|
||||||
|
println!(
|
||||||
|
"cargo:warning=feature `font-backend-freetype` only applies to non-Apple, \
|
||||||
|
non-wasm targets; ignored"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let ft_only = ft_only && !wasm && !apple;
|
||||||
|
|
||||||
let on = |c: &str, v: bool| {
|
let on = |c: &str, v: bool| {
|
||||||
if v {
|
if v {
|
||||||
println!("cargo:rustc-cfg={c}");
|
println!("cargo:rustc-cfg={c}");
|
||||||
@@ -49,7 +64,9 @@ fn main() {
|
|||||||
let freetype = !wasm && (!apple || ct_ft);
|
let freetype = !wasm && (!apple || ct_ft);
|
||||||
let harfbuzz = !wasm && (!apple || ct_ft || ct_hb);
|
let harfbuzz = !wasm && (!apple || ct_ft || ct_hb);
|
||||||
on("font_freetype", freetype);
|
on("font_freetype", freetype);
|
||||||
on("font_fontconfig", !wasm && !apple);
|
let fontconfig = !wasm && !apple && !ft_only;
|
||||||
|
on("font_fontconfig", fontconfig);
|
||||||
|
on("font_discovery", fontconfig || apple);
|
||||||
on("font_harfbuzz", harfbuzz);
|
on("font_harfbuzz", harfbuzz);
|
||||||
on("font_coretext", apple);
|
on("font_coretext", apple);
|
||||||
on("font_coretext_face", apple && !ct_ft);
|
on("font_coretext_face", apple && !ct_ft);
|
||||||
|
|||||||
@@ -13,6 +13,9 @@
|
|||||||
//! (Zig: `-Dfont-backend=...`). "There is no scenario that is the default"
|
//! (Zig: `-Dfont-backend=...`). "There is no scenario that is the default"
|
||||||
//! for those; they're only used by people who want to self-compile Ghostty
|
//! for those; they're only used by people who want to self-compile Ghostty
|
||||||
//! and prefer the freetype aesthetic.
|
//! and prefer the freetype aesthetic.
|
||||||
|
//! * Non-Apple, non-wasm targets with the cargo feature
|
||||||
|
//! `font-backend-freetype`: [`Backend::Freetype`] (Zig
|
||||||
|
//! `-Dfont-backend=freetype`: no font discovery).
|
||||||
//! * wasm32: [`Backend::WebCanvas`].
|
//! * wasm32: [`Backend::WebCanvas`].
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
@@ -50,6 +53,19 @@ impl Backend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The Zig tag name (`@tagName`) of the backend.
|
||||||
|
pub fn name(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Backend::Freetype => "freetype",
|
||||||
|
Backend::FontconfigFreetype => "fontconfig_freetype",
|
||||||
|
Backend::Coretext => "coretext",
|
||||||
|
Backend::CoretextFreetype => "coretext_freetype",
|
||||||
|
Backend::CoretextHarfbuzz => "coretext_harfbuzz",
|
||||||
|
Backend::CoretextNoshape => "coretext_noshape",
|
||||||
|
Backend::WebCanvas => "web_canvas",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn has_freetype(self) -> bool {
|
pub fn has_freetype(self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self,
|
self,
|
||||||
@@ -88,7 +104,20 @@ pub struct Options {
|
|||||||
pub backend: Backend,
|
pub backend: Backend,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(not(target_arch = "wasm32"), not(target_vendor = "apple")))]
|
#[cfg(all(
|
||||||
|
not(target_arch = "wasm32"),
|
||||||
|
not(target_vendor = "apple"),
|
||||||
|
feature = "font-backend-freetype"
|
||||||
|
))]
|
||||||
|
pub const OPTIONS: Options = Options {
|
||||||
|
backend: Backend::Freetype,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(all(
|
||||||
|
not(target_arch = "wasm32"),
|
||||||
|
not(target_vendor = "apple"),
|
||||||
|
not(feature = "font-backend-freetype")
|
||||||
|
))]
|
||||||
pub const OPTIONS: Options = Options {
|
pub const OPTIONS: Options = Options {
|
||||||
backend: Backend::FontconfigFreetype,
|
backend: Backend::FontconfigFreetype,
|
||||||
};
|
};
|
||||||
@@ -182,13 +211,45 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(not(target_arch = "wasm32"), not(target_vendor = "apple")))]
|
#[test]
|
||||||
|
fn freetype_capabilities() {
|
||||||
|
let b = Backend::Freetype;
|
||||||
|
assert!(b.has_freetype());
|
||||||
|
assert!(!b.has_fontconfig());
|
||||||
|
assert!(b.has_harfbuzz());
|
||||||
|
assert!(!b.has_coretext());
|
||||||
|
assert_eq!(b.name(), "freetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(
|
||||||
|
not(target_arch = "wasm32"),
|
||||||
|
not(target_vendor = "apple"),
|
||||||
|
not(feature = "font-backend-freetype")
|
||||||
|
))]
|
||||||
#[test]
|
#[test]
|
||||||
fn linux_backend_capabilities() {
|
fn linux_backend_capabilities() {
|
||||||
let b = OPTIONS.backend;
|
let b = OPTIONS.backend;
|
||||||
|
assert_eq!(b, Backend::FontconfigFreetype);
|
||||||
assert!(b.has_freetype());
|
assert!(b.has_freetype());
|
||||||
assert!(b.has_fontconfig());
|
assert!(b.has_fontconfig());
|
||||||
assert!(b.has_harfbuzz());
|
assert!(b.has_harfbuzz());
|
||||||
assert!(!b.has_coretext());
|
assert!(!b.has_coretext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(
|
||||||
|
not(target_arch = "wasm32"),
|
||||||
|
not(target_vendor = "apple"),
|
||||||
|
feature = "font-backend-freetype"
|
||||||
|
))]
|
||||||
|
#[test]
|
||||||
|
fn freetype_only_backend_selected() {
|
||||||
|
let b = OPTIONS.backend;
|
||||||
|
assert_eq!(b, Backend::Freetype);
|
||||||
|
assert!(b.has_freetype());
|
||||||
|
assert!(!b.has_fontconfig());
|
||||||
|
assert!(b.has_harfbuzz());
|
||||||
|
assert!(!b.has_coretext());
|
||||||
|
// No discovery mechanism, as in Zig (`Discover == void`).
|
||||||
|
const { assert!(!crate::HAS_DISCOVERY) };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[cfg(font_discovery)]
|
||||||
|
use crate::collection::{AddOptions, Entry};
|
||||||
use crate::{
|
use crate::{
|
||||||
Atlas,
|
Atlas,
|
||||||
CodepointMap,
|
CodepointMap,
|
||||||
@@ -17,7 +19,7 @@ use crate::{
|
|||||||
Presentation,
|
Presentation,
|
||||||
SpriteFace,
|
SpriteFace,
|
||||||
Style,
|
Style,
|
||||||
collection::{AddOptions, Collection, Entry, GetFaceError, Index, PresentationMode, Special},
|
collection::{Collection, GetFaceError, Index, PresentationMode, Special},
|
||||||
discovery::Discover,
|
discovery::Discover,
|
||||||
face::{RenderError as FaceRenderError, RenderOptions},
|
face::{RenderError as FaceRenderError, RenderOptions},
|
||||||
sprite,
|
sprite,
|
||||||
@@ -88,6 +90,7 @@ pub struct CodepointResolver {
|
|||||||
|
|
||||||
/// The descriptor cache is used to cache the descriptor to font face
|
/// The descriptor cache is used to cache the descriptor to font face
|
||||||
/// mapping for codepoint maps.
|
/// mapping for codepoint maps.
|
||||||
|
#[cfg_attr(not(font_discovery), allow(dead_code))]
|
||||||
descriptor_cache: DescriptorCache,
|
descriptor_cache: DescriptorCache,
|
||||||
|
|
||||||
/// Set this to a non-null value to enable sprite glyph drawing. If this
|
/// Set this to a non-null value to enable sprite glyph drawing. If this
|
||||||
@@ -108,6 +111,7 @@ pub struct CodepointResolver {
|
|||||||
///
|
///
|
||||||
/// As in upstream, descriptors are compared by their `hashcode()` only.
|
/// As in upstream, descriptors are compared by their `hashcode()` only.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
#[cfg_attr(not(font_discovery), allow(dead_code))]
|
||||||
struct DescriptorCache(HashMap<u64, Option<Index>>);
|
struct DescriptorCache(HashMap<u64, Option<Index>>);
|
||||||
|
|
||||||
impl CodepointResolver {
|
impl CodepointResolver {
|
||||||
@@ -240,6 +244,27 @@ impl CodepointResolver {
|
|||||||
.get_index(cp, Style::Regular, PresentationMode::Any)
|
.get_index(cp, Style::Regular, PresentationMode::Any)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Without a discovery mechanism (the `freetype` backend, Zig
|
||||||
|
/// `Discover == void`) there is no fallback search.
|
||||||
|
#[cfg(not(font_discovery))]
|
||||||
|
#[allow(clippy::unused_self)]
|
||||||
|
fn discover_fallback(
|
||||||
|
&mut self,
|
||||||
|
_cp: u32,
|
||||||
|
_style: Style,
|
||||||
|
_p: PresentationMode,
|
||||||
|
) -> Option<Index> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Without a discovery mechanism, codepoint overrides cannot find fonts.
|
||||||
|
#[cfg(not(font_discovery))]
|
||||||
|
#[allow(clippy::unused_self)]
|
||||||
|
fn get_index_codepoint_override(&mut self, _cp: u32) -> Option<Index> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(font_discovery)]
|
||||||
fn discover_fallback(
|
fn discover_fallback(
|
||||||
&mut self,
|
&mut self,
|
||||||
cp: u32,
|
cp: u32,
|
||||||
@@ -309,6 +334,7 @@ impl CodepointResolver {
|
|||||||
|
|
||||||
/// Checks if the codepoint is in the map of codepoint overrides,
|
/// Checks if the codepoint is in the map of codepoint overrides,
|
||||||
/// finds the override font, and returns it.
|
/// finds the override font, and returns it.
|
||||||
|
#[cfg(font_discovery)]
|
||||||
fn get_index_codepoint_override(&mut self, cp: u32) -> Option<Index> {
|
fn get_index_codepoint_override(&mut self, cp: u32) -> Option<Index> {
|
||||||
// Get our codepoint map. If we have no map set then we have no
|
// Get our codepoint map. If we have no map set then we have no
|
||||||
// codepoint overrides and we're done.
|
// codepoint overrides and we're done.
|
||||||
@@ -459,7 +485,7 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
Face,
|
Face,
|
||||||
Library,
|
Library,
|
||||||
collection::{LoadOptions, SizeAdjustment},
|
collection::{AddOptions, LoadOptions, SizeAdjustment},
|
||||||
embedded,
|
embedded,
|
||||||
face::{DesiredSize, Options},
|
face::{DesiredSize, Options},
|
||||||
};
|
};
|
||||||
@@ -534,6 +560,27 @@ mod tests {
|
|||||||
assert!(r.get_index(0x1FB00, Style::Regular, None).is_none());
|
assert!(r.get_index(0x1FB00, Style::Regular, None).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `freetype` backend (no discovery): codepoint overrides and fallback
|
||||||
|
/// searches are disabled, so an unmapped codepoint is simply not found.
|
||||||
|
#[cfg(not(font_discovery))]
|
||||||
|
#[test]
|
||||||
|
fn freetype_backend_has_no_discovery_fallback_or_override() {
|
||||||
|
const { assert!(!crate::HAS_DISCOVERY) };
|
||||||
|
let lib = Library::init().unwrap();
|
||||||
|
let mut c = Collection::new();
|
||||||
|
c.load_options = Some(LoadOptions::new(lib.clone()));
|
||||||
|
add(&mut c, &lib, embedded::REGULAR, Style::Regular, 12.0);
|
||||||
|
|
||||||
|
let mut r = CodepointResolver::new(c);
|
||||||
|
assert!(r.discover.is_none());
|
||||||
|
assert!(r.get_index_codepoint_override('A' as u32).is_none());
|
||||||
|
// Not in the embedded font, and there is no discovery to ask.
|
||||||
|
assert!(r.get_index(0x4E2D, Style::Regular, None).is_none());
|
||||||
|
assert!(r.get_index(0x4E2D, Style::Bold, None).is_none());
|
||||||
|
// Embedded codepoints still resolve.
|
||||||
|
assert!(r.get_index('A' as u32, Style::Regular, None).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_index_disabled_font_style() {
|
fn get_index_disabled_font_style() {
|
||||||
let lib = Library::init().unwrap();
|
let lib = Library::init().unwrap();
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
//! 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.
|
||||||
|
//!
|
||||||
|
//! Port of `font/DeferredFace.zig` (`freetype` backend: no discovery, so
|
||||||
|
//! there is no deferred data and every face must already be loaded).
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Face,
|
||||||
|
Library,
|
||||||
|
Presentation,
|
||||||
|
face::{self, Options},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum LoadError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Face(#[from] face::FaceError),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// With no discovery mechanism there is no data (all fields are `void` in
|
||||||
|
/// Zig for the `freetype` backend).
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DeferredFace {}
|
||||||
|
|
||||||
|
impl DeferredFace {
|
||||||
|
/// Returns the family name of the font.
|
||||||
|
pub fn family_name(&self) -> Result<String, LoadError> {
|
||||||
|
Ok(String::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the name of this face.
|
||||||
|
pub fn name(&self) -> Result<String, LoadError> {
|
||||||
|
Ok(String::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the deferred font face.
|
||||||
|
pub fn load(&mut self, _lib: &Library, _opts: Options) -> Result<Face, LoadError> {
|
||||||
|
// Unreachable because we must be already loaded or have the
|
||||||
|
// proper configuration for one of the other deferred mechanisms.
|
||||||
|
unreachable!("deferred face without a discovery mechanism");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this face can satisfy the given codepoint and
|
||||||
|
/// presentation.
|
||||||
|
pub fn has_codepoint(&self, _cp: u32, _p: Option<Presentation>) -> bool {
|
||||||
|
// This is unreachable because discovery mechanisms terminate, and
|
||||||
|
// if we're not using a discovery mechanism, the face MUST be loaded.
|
||||||
|
unreachable!("deferred face without a discovery mechanism");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,42 @@ pub use fc_impl::*;
|
|||||||
mod coretext_impl;
|
mod coretext_impl;
|
||||||
#[cfg(font_coretext)]
|
#[cfg(font_coretext)]
|
||||||
pub use coretext_impl::*;
|
pub use coretext_impl::*;
|
||||||
|
|
||||||
|
/// The `freetype` backend has no discovery mechanism: Zig's
|
||||||
|
/// `Discover = void`. This is an uninhabited stand-in so that generic code
|
||||||
|
/// type-checks; it can never be constructed, and the callers check
|
||||||
|
/// [`crate::HAS_DISCOVERY`] (or `cfg(font_discovery)`) first.
|
||||||
|
#[cfg(not(any(font_fontconfig, font_coretext)))]
|
||||||
|
pub struct Discover(std::convert::Infallible);
|
||||||
|
|
||||||
|
#[cfg(not(any(font_fontconfig, font_coretext)))]
|
||||||
|
pub type DiscoverError = std::convert::Infallible;
|
||||||
|
#[cfg(not(any(font_fontconfig, font_coretext)))]
|
||||||
|
pub type NextError = std::convert::Infallible;
|
||||||
|
|
||||||
|
#[cfg(not(any(font_fontconfig, font_coretext)))]
|
||||||
|
pub struct DiscoverIterator(std::convert::Infallible);
|
||||||
|
|
||||||
|
#[cfg(not(any(font_fontconfig, font_coretext)))]
|
||||||
|
impl Discover {
|
||||||
|
#[must_use]
|
||||||
|
pub fn init() -> Discover {
|
||||||
|
unreachable!("no font discovery mechanism in this build")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discover(&self, _desc: &Descriptor) -> Result<DiscoverIterator, DiscoverError> {
|
||||||
|
match self.0 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(font_fontconfig, font_coretext)))]
|
||||||
|
impl DiscoverIterator {
|
||||||
|
#[allow(clippy::should_implement_trait)]
|
||||||
|
pub fn next(&mut self) -> Result<Option<crate::deferred_face::DeferredFace>, NextError> {
|
||||||
|
match self.0 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Descriptor is used to search for fonts (canonical definition in `ghostty-config`).
|
/// Descriptor is used to search for fonts (canonical definition in `ghostty-config`).
|
||||||
pub use ghostty_config::font_types::Descriptor;
|
pub use ghostty_config::font_types::Descriptor;
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ pub mod config_types;
|
|||||||
pub mod coretext_util;
|
pub mod coretext_util;
|
||||||
#[cfg(font_fontconfig)]
|
#[cfg(font_fontconfig)]
|
||||||
pub mod deferred_face;
|
pub mod deferred_face;
|
||||||
|
#[cfg(all(not(target_arch = "wasm32"), not(font_fontconfig), not(font_coretext)))]
|
||||||
|
#[path = "deferred_face_freetype.rs"]
|
||||||
|
pub mod deferred_face;
|
||||||
#[cfg(font_coretext)]
|
#[cfg(font_coretext)]
|
||||||
#[path = "deferred_face_coretext.rs"]
|
#[path = "deferred_face_coretext.rs"]
|
||||||
pub mod deferred_face;
|
pub mod deferred_face;
|
||||||
@@ -48,6 +51,11 @@ pub mod sprite;
|
|||||||
|
|
||||||
pub use atlas::Atlas;
|
pub use atlas::Atlas;
|
||||||
pub use backend::{Backend, OPTIONS as options};
|
pub use backend::{Backend, OPTIONS as options};
|
||||||
|
|
||||||
|
/// Whether a font discovery mechanism exists in this build (Zig:
|
||||||
|
/// `font.Discover != void`; false for the `freetype` and `web_canvas`
|
||||||
|
/// backends).
|
||||||
|
pub const HAS_DISCOVERY: bool = cfg!(font_discovery);
|
||||||
pub use codepoint_map::CodepointMap;
|
pub use codepoint_map::CodepointMap;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub use codepoint_resolver::CodepointResolver;
|
pub use codepoint_resolver::CodepointResolver;
|
||||||
|
|||||||
@@ -19,5 +19,5 @@ pub mod run;
|
|||||||
|
|
||||||
pub mod web_canvas;
|
pub mod web_canvas;
|
||||||
|
|
||||||
#[cfg(all(test, font_harfbuzz, font_fontconfig))]
|
#[cfg(all(test, font_harfbuzz))]
|
||||||
pub(crate) mod test_util;
|
pub(crate) mod test_util;
|
||||||
@@ -23,7 +23,6 @@ use crate::{
|
|||||||
Style,
|
Style,
|
||||||
codepoint_resolver::CodepointResolver,
|
codepoint_resolver::CodepointResolver,
|
||||||
collection::{AddOptions, Collection, LoadOptions, SizeAdjustment},
|
collection::{AddOptions, Collection, LoadOptions, SizeAdjustment},
|
||||||
discovery::{Descriptor, Fontconfig},
|
|
||||||
embedded,
|
embedded,
|
||||||
face::{DesiredSize, FontData, Options as FaceOptions},
|
face::{DesiredSize, FontData, Options as FaceOptions},
|
||||||
shape,
|
shape,
|
||||||
@@ -102,7 +101,18 @@ pub(crate) fn test_shaper_with_font(font_req: TestFont) -> TestShaper {
|
|||||||
|
|
||||||
/// Returns None (the test is skipped, like `error.SkipZigTest`) if the font
|
/// Returns None (the test is skipped, like `error.SkipZigTest`) if the font
|
||||||
/// can't be discovered.
|
/// can't be discovered.
|
||||||
|
#[cfg(not(font_fontconfig))]
|
||||||
|
pub(crate) fn test_shaper_with_discovered_font(_font_req: &str) -> Option<TestShaper> {
|
||||||
|
// No fontconfig discovery in this backend (for example `freetype`).
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns None (the test is skipped, like `error.SkipZigTest`) if the font
|
||||||
|
/// can't be discovered.
|
||||||
|
#[cfg(font_fontconfig)]
|
||||||
pub(crate) fn test_shaper_with_discovered_font(font_req: &str) -> Option<TestShaper> {
|
pub(crate) fn test_shaper_with_discovered_font(font_req: &str) -> Option<TestShaper> {
|
||||||
|
use crate::discovery::{Descriptor, Fontconfig};
|
||||||
|
|
||||||
let lib = Library::init().unwrap();
|
let lib = Library::init().unwrap();
|
||||||
let mut c = Collection::new();
|
let mut c = Collection::new();
|
||||||
c.load_options = Some(LoadOptions::new(lib.clone()));
|
c.load_options = Some(LoadOptions::new(lib.clone()));
|
||||||
|
|||||||
@@ -27,13 +27,15 @@ use ghostty_config::{
|
|||||||
types_b::FreetypeLoadFlags,
|
types_b::FreetypeLoadFlags,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(font_discovery)]
|
||||||
|
use crate::discovery::Discover;
|
||||||
use crate::{
|
use crate::{
|
||||||
Face,
|
Face,
|
||||||
Style,
|
Style,
|
||||||
codepoint_map::CodepointMap,
|
codepoint_map::CodepointMap,
|
||||||
codepoint_resolver::{CodepointResolver, StyleStatus},
|
codepoint_resolver::{CodepointResolver, StyleStatus},
|
||||||
collection::{self, AddOptions, Collection, LoadOptions, SizeAdjustment},
|
collection::{self, AddOptions, Collection, LoadOptions, SizeAdjustment},
|
||||||
discovery::{Descriptor, Discover},
|
discovery::Descriptor,
|
||||||
embedded,
|
embedded,
|
||||||
face::{self, DesiredSize, FontData},
|
face::{self, DesiredSize, FontData},
|
||||||
library::{Library, LibraryError},
|
library::{Library, LibraryError},
|
||||||
@@ -45,7 +47,9 @@ use crate::{
|
|||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum RefError {
|
pub enum RefError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
#[cfg(font_discovery)]
|
||||||
Discover(#[from] crate::discovery::DiscoverError),
|
Discover(#[from] crate::discovery::DiscoverError),
|
||||||
|
#[cfg(font_discovery)]
|
||||||
#[error("font discovery failed: {0}")]
|
#[error("font discovery failed: {0}")]
|
||||||
DiscoverNext(#[from] crate::discovery::NextError),
|
DiscoverNext(#[from] crate::discovery::NextError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
@@ -80,6 +84,7 @@ struct Inner {
|
|||||||
/// Font discovery mechanism. Initialized once and kept for the
|
/// Font discovery mechanism. Initialized once and kept for the
|
||||||
/// lifetime of the set because some font discovery mechanisms
|
/// lifetime of the set because some font discovery mechanisms
|
||||||
/// (i.e. fontconfig) are unsafe to reinit.
|
/// (i.e. fontconfig) are unsafe to reinit.
|
||||||
|
#[cfg(font_discovery)]
|
||||||
font_discover: Option<Discover>,
|
font_discover: Option<Discover>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +103,7 @@ impl SharedGridSet {
|
|||||||
font_lib: Library::init()?,
|
font_lib: Library::init()?,
|
||||||
inner: Mutex::new(Inner {
|
inner: Mutex::new(Inner {
|
||||||
map: HashMap::new(),
|
map: HashMap::new(),
|
||||||
|
#[cfg(font_discovery)]
|
||||||
font_discover: None,
|
font_discover: None,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -158,7 +164,10 @@ impl SharedGridSet {
|
|||||||
// Init our resolver which just requires setting fields.
|
// Init our resolver which just requires setting fields.
|
||||||
let mut resolver = CodepointResolver::new(c);
|
let mut resolver = CodepointResolver::new(c);
|
||||||
resolver.styles = styles;
|
resolver.styles = styles;
|
||||||
resolver.discover = Some(Self::discover(&mut inner).clone());
|
#[cfg(font_discovery)]
|
||||||
|
{
|
||||||
|
resolver.discover = Some(Self::discover(&mut inner).clone());
|
||||||
|
}
|
||||||
resolver.codepoint_map = Some(key.codepoint_map.clone());
|
resolver.codepoint_map = Some(key.codepoint_map.clone());
|
||||||
|
|
||||||
let grid = Arc::new(SharedGrid::new(resolver)?);
|
let grid = Arc::new(SharedGrid::new(resolver)?);
|
||||||
@@ -174,6 +183,7 @@ impl SharedGridSet {
|
|||||||
|
|
||||||
/// Builds the Collection for the given configuration key and
|
/// Builds the Collection for the given configuration key and
|
||||||
/// initial font size.
|
/// initial font size.
|
||||||
|
#[cfg_attr(not(font_discovery), allow(unused_variables))]
|
||||||
fn collection(
|
fn collection(
|
||||||
&self,
|
&self,
|
||||||
inner: &mut Inner,
|
inner: &mut Inner,
|
||||||
@@ -191,7 +201,9 @@ impl SharedGridSet {
|
|||||||
c.load_options = Some(load_options.clone());
|
c.load_options = Some(load_options.clone());
|
||||||
c.metric_modifiers.clone_from(&key.metric_modifiers);
|
c.metric_modifiers.clone_from(&key.metric_modifiers);
|
||||||
|
|
||||||
// Search for fonts
|
// Search for fonts. Without a discovery mechanism (the `freetype`
|
||||||
|
// backend) there is nothing to search with.
|
||||||
|
#[cfg(font_discovery)]
|
||||||
{
|
{
|
||||||
let disco = Self::discover(inner);
|
let disco = Self::discover(inner);
|
||||||
for style in Style::ALL {
|
for style in Style::ALL {
|
||||||
@@ -392,6 +404,7 @@ impl SharedGridSet {
|
|||||||
/// Initialize once and return the font discovery mechanism. This remains
|
/// Initialize once and return the font discovery mechanism. This remains
|
||||||
/// initialized throughout the lifetime of the application because some
|
/// initialized throughout the lifetime of the application because some
|
||||||
/// font discovery mechanisms (i.e. fontconfig) are unsafe to reinit.
|
/// font discovery mechanisms (i.e. fontconfig) are unsafe to reinit.
|
||||||
|
#[cfg(font_discovery)]
|
||||||
fn discover(inner: &mut Inner) -> &Discover {
|
fn discover(inner: &mut Inner) -> &Discover {
|
||||||
inner.font_discover.get_or_insert_with(Discover::init)
|
inner.font_discover.get_or_insert_with(Discover::init)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ Only entries with an observable behavioural difference are listed.
|
|||||||
| Config strings | UTF-8 not required in many places | Escaped-bytes representation (`EncodedOs`) so non-UTF-8 survives; behaviour and error names matched to Zig (for example `theme=/etc/passwd/x` gives `error.NotDir`, checked against the oracle) | `notes/review-rv11-codex.md` (item 3) | 672e8a4, 4bbe0d7 |
|
| Config strings | UTF-8 not required in many places | Escaped-bytes representation (`EncodedOs`) so non-UTF-8 survives; behaviour and error names matched to Zig (for example `theme=/etc/passwd/x` gives `error.NotDir`, checked against the oracle) | `notes/review-rv11-codex.md` (item 3) | 672e8a4, 4bbe0d7 |
|
||||||
| `+version` / build info | Real build metadata | Placeholder `build_info` values in places; `gtk-opengl-debug` follows `cfg!(debug_assertions)` (the `release` profile has them on, `production` off) | `notes/ghostty-cli.md`, `notes/ghostty-config.md` | 6286e02 |
|
| `+version` / build info | Real build metadata | Placeholder `build_info` values in places; `gtk-opengl-debug` follows `cfg!(debug_assertions)` (the `release` profile has them on, `production` off) | `notes/ghostty-cli.md`, `notes/ghostty-config.md` | 6286e02 |
|
||||||
| OSC 52 base64 acceptance | Surface.zig:2149,2163 rules | Was different in both directions (Codex core audit); fixed to match Zig | `notes/review-core-codex.md` | 2f58f02 |
|
| OSC 52 base64 acceptance | Surface.zig:2149,2163 rules | Was different in both directions (Codex core audit); fixed to match Zig | `notes/review-core-codex.md` | 2f58f02 |
|
||||||
|
| Benchmark / synthetic tooling extensions | `ghostty-bench` has no `--time` option and only the simplified terminal handler; `ghostty-synthetic` has the actions `ascii`, `osc`, `utf8` | `ghostty-bench --time` prints the measured step time to stderr; `ghostty-bench --handler=full` runs the full `stream_readonly` handler instead of the print-only one; `ghostty-synthetic +sgr` generates SGR-heavy output. Dev/benchmark tooling only; otherwise upstream behaviour is unchanged | `crates/ghostty-bench/src/main.rs`, `src/benches.rs`, `src/bin/ghostty-synthetic.rs`; `notes/review-rv16-codex.md` | 0c4f0ee |
|
||||||
|
|
||||||
## Maintenance
|
## Maintenance
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Z19: font-backend=freetype feature, DEVIANT_FIXES additions (RV16)
|
||||||
|
|
||||||
|
## Font backend `freetype` (Zig `-Dfont-backend=freetype`)
|
||||||
|
- Cargo feature `font-backend-freetype` on `ghostty-font`, forwarded by `ghostty-cli` and the root crate (`cargo build --features font-backend-freetype`). Default (fontconfig_freetype / coretext / web_canvas) unchanged. Non-Apple, non-wasm targets only (build.rs warns and ignores it elsewhere).
|
||||||
|
- `build.rs` emits `font_discovery` (fontconfig or CoreText exists; Zig `Discover != void`); `font_fontconfig` is off in this mode. `Backend::name()` added; `OPTIONS` = `Backend::Freetype`; `ghostty_font::HAS_DISCOVERY`.
|
||||||
|
- Zig behaviour matched: `Discover = void` (Rust: uninhabited stub `Discover`, never constructed); `DeferredFace` is empty (family/name "", load/has_codepoint unreachable); SharedGridSet skips the font-family search (primary fonts come from the embedded fallback only) and still adds the embedded emoji fonts; CodepointResolver has no discovery fallback and codepoint-map overrides return None; `+list-fonts` (after arg parsing) and `+show-face` (before parsing, written to stdout like Zig's writers) print the "built without a font discovery mechanism" message (no trailing newline) and exit 1. `+version` shows `.freetype`; crash options use `Backend::name()`.
|
||||||
|
- Fontconfig sys crate is still linked (target dependency), just unused.
|
||||||
|
- Tests: `cargo test -p ghostty-font --features font-backend-freetype` (198 pass), `cargo test -p ghostty-cli --features font-backend-freetype list_fonts`. shaper `test_util` now compiles without fontconfig (discovered-font tests skip). Note `--all-features` (just recipes) selects this backend workspace-wide.
|
||||||
|
|
||||||
|
## DEVIANT_FIXES.md
|
||||||
|
- Added row 33 (maximum-length NOTIFY_SOCKET: Zig `systemd.zig:116` writes `path[108]` after the `108 < len` check; Rust uses rustix `SocketAddrUnix::new`). Introduced in f8c5ce0. New regression test `notify_maximum_length_socket_path_is_rejected_safely` in `crates/ghostty-os/src/systemd.rs` (own hunk, for the orchestrator's fix-deviant commit).
|
||||||
|
- Added "Benchmark / synthetic tooling extensions" row (`--time`, `--handler=full`, `+sgr`; commit 0c4f0ee) under Other deliberate deviations.
|
||||||
Reference in new issue
Block a user