From e01b385c89c71cabedb3e5c348a3d2007dcf1265 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 9 Mar 2026 18:08:26 +0100 Subject: [PATCH] fontconfig: add bindings --- build/enums.rs | 7 ++ docs/setup.md | 1 + src/fontconfig.rs | 138 +++++++++++++++++++++++++++++++++++++++ src/fontconfig/consts.rs | 20 ++++++ src/main.rs | 1 + 5 files changed, 167 insertions(+) create mode 100644 src/fontconfig.rs create mode 100644 src/fontconfig/consts.rs diff --git a/build/enums.rs b/build/enums.rs index 4a4382b3..8be06bf1 100644 --- a/build/enums.rs +++ b/build/enums.rs @@ -15,6 +15,9 @@ mod libinput; #[path = "../src/pango/consts.rs"] mod pango; +#[path = "../src/fontconfig/consts.rs"] +mod fontconfig; + fn get_target() -> repc::Target { let rustc_target = env::var("TARGET").unwrap(); repc::TARGET_MAP @@ -148,5 +151,9 @@ pub fn main() -> anyhow::Result<()> { write_ty(&mut f, pango::CAIRO_OPERATORS, "cairo_operator_t")?; write_ty(&mut f, pango::PANGO_ELLIPSIZE_MODES, "PangoEllipsizeMode_")?; + let mut f = open("fontconfig_tys.rs")?; + write_ty(&mut f, fontconfig::FC_MATCH_KINDS, "FcMatchKind")?; + write_ty(&mut f, fontconfig::FC_RESULTS, "FcResult")?; + Ok(()) } diff --git a/docs/setup.md b/docs/setup.md index 6cff6112..7089cf3a 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -8,6 +8,7 @@ The following libraries must be installed before compiling Jay: - libgbm.so - libudev.so - libpangocairo-1.0.so +- libfontconfig.so You must also have a C compiler (GCC or Clang) and the latest version of rust installed. You can install rust with [rustup](https://rustup.rs/). diff --git a/src/fontconfig.rs b/src/fontconfig.rs new file mode 100644 index 00000000..413557a0 --- /dev/null +++ b/src/fontconfig.rs @@ -0,0 +1,138 @@ +use { + crate::fontconfig::consts::{FC_MATCH_PATTERN, FC_RESULT_MATCH}, + run_on_drop::on_drop, + std::{ + borrow::Cow, + ffi::{CStr, OsStr, c_char}, + os::{ + raw::{c_int, c_uchar}, + unix::ffi::OsStrExt, + }, + path::PathBuf, + ptr, + }, + thiserror::Error, + uapi::IntoUstr, +}; + +mod consts; + +include!(concat!(env!("OUT_DIR"), "/fontconfig_tys.rs")); + +#[derive(Debug, Error)] +pub enum FontconfigError { + #[error("FcConfigGetCurrent returned NULL")] + Init, + #[error("Could not create a pattern")] + CreatePattern, + #[error("Could not find a match")] + NoMatch, + #[error("Match has no name")] + NoName, + #[error("Match has no file")] + NoFile, +} + +#[derive(Debug)] +#[expect(dead_code)] +pub struct Font { + pub fullname: String, + pub file: PathBuf, + pub index: Option, +} + +#[expect(dead_code)] +pub fn match_font(family: &str) -> Result { + thread_local! { + static CONFIG: *mut FcConfig = FcConfigGetCurrent(); + } + let config = CONFIG.with(|c| *c); + if config.is_null() { + return Err(FontconfigError::Init); + } + let family = family.into_ustr(); + let p = FcPatternCreate(); + if p.is_null() { + return Err(FontconfigError::CreatePattern); + } + let _destroy_pattern = on_drop(|| unsafe { FcPatternDestroy(p) }); + let mut result = 0; + let p = unsafe { + FcPatternAddString(p, FC_FAMILY.as_ptr(), family.as_ptr() as _); + FcConfigSubstitute(config, p, FC_MATCH_PATTERN.0 as _); + FcDefaultSubstitute(p); + FcFontMatch(config, p, &mut result) + }; + if p.is_null() { + return Err(FontconfigError::NoMatch); + } + let _destroy_pattern = on_drop(|| unsafe { FcPatternDestroy(p) }); + if result != FC_RESULT_MATCH.0 as FcResult { + return Err(FontconfigError::NoMatch); + } + let get_cstr = |name: &CStr| { + let mut out = ptr::null_mut(); + let res = unsafe { FcPatternGetString(p, name.as_ptr(), 0, &mut out) }; + if res != FC_RESULT_MATCH.0 as FcResult || out.is_null() { + return None; + } + let cstr = unsafe { CStr::from_ptr(out.cast()) }; + Some(cstr) + }; + let get_int = |name: &CStr| { + let mut out = 0; + let res = unsafe { FcPatternGetInteger(p, name.as_ptr(), 0, &mut out) }; + if res != FC_RESULT_MATCH.0 as FcResult { + return None; + } + Some(out as i32) + }; + Ok(Font { + fullname: get_cstr(FC_FULLNAME) + .map(CStr::to_string_lossy) + .map(Cow::into_owned) + .ok_or(FontconfigError::NoName)?, + file: get_cstr(FC_FILE) + .map(CStr::to_bytes) + .map(OsStr::from_bytes) + .map(Into::into) + .ok_or(FontconfigError::NoFile)?, + index: get_int(FC_INDEX), + }) +} + +type FcBool = c_int; +type FcPattern = u8; +type FcConfig = u8; +type FcChar8 = c_uchar; +const FC_FAMILY: &CStr = c"family"; +const FC_FULLNAME: &CStr = c"fullname"; +const FC_FILE: &CStr = c"file"; +const FC_INDEX: &CStr = c"index"; + +#[link(name = "fontconfig")] +unsafe extern "C" { + safe fn FcConfigGetCurrent() -> *mut FcConfig; + safe fn FcPatternCreate() -> *mut FcPattern; + fn FcPatternDestroy(p: *mut FcPattern); + fn FcPatternAddString(p: *mut FcPattern, object: *const c_char, s: *const FcChar8) -> FcBool; + fn FcConfigSubstitute(config: *mut FcConfig, p: *mut FcPattern, kind: FcMatchKind) -> FcBool; + fn FcDefaultSubstitute(p: *mut FcPattern); + fn FcFontMatch( + config: *mut FcConfig, + p: *mut FcPattern, + result: *mut FcResult, + ) -> *mut FcPattern; + fn FcPatternGetString( + p: *mut FcPattern, + object: *const c_char, + id: c_int, + s: *mut *mut FcChar8, + ) -> FcResult; + fn FcPatternGetInteger( + p: *mut FcPattern, + object: *const c_char, + id: c_int, + i: *mut c_int, + ) -> FcResult; +} diff --git a/src/fontconfig/consts.rs b/src/fontconfig/consts.rs new file mode 100644 index 00000000..9e716f2f --- /dev/null +++ b/src/fontconfig/consts.rs @@ -0,0 +1,20 @@ +#![allow(dead_code)] + +cenum! { + _FcMatchKind, FC_MATCH_KINDS; + + FC_MATCH_PATTERN = 0, + FC_MATCH_FONT = 1, + FC_MATCH_SCAN = 2, + FC_MATCH_KIND_END = 3, +} + +cenum! { + _FcResult, FC_RESULTS; + + FC_RESULT_MATCH = 0, + FC_RESULT_NO_MATCH = 1, + FC_RESULT_TYPE_MISMATCH = 2, + FC_RESULT_NO_ID = 3, + FC_RESULT_OUT_OF_MEMORY = 4, +} diff --git a/src/main.rs b/src/main.rs index 129978a5..ca0a0d56 100644 --- a/src/main.rs +++ b/src/main.rs @@ -70,6 +70,7 @@ mod edid; mod ei; mod eventfd_cache; mod fixed; +mod fontconfig; mod forker; mod format; mod gfx_api;