use std::ffi::CString;
use std::mem::MaybeUninit;
use std::ptr;
use thiserror::Error;
use x11::xlib;
#[derive(Debug, Error)]
pub enum XError {
#[error("X Error: Could not open Display")]
DisplayOpen,
#[error("X Error: Could not load font with name {0:?}")]
CouldNotLoadFont(CString),
#[error("CStrings cannot hold NUL values")]
NulError(#[from] std::ffi::NulError),
}
static_assertions::assert_impl_all!(XError: Sync, Send);
enum Data {
FontSet {
display: *mut xlib::Display,
fontset: xlib::XFontSet,
},
XFont {
display: *mut xlib::Display,
xfont: *mut xlib::XFontStruct,
},
}
pub struct Context {
data: Data,
}
impl Context {
pub fn new(name: &str) -> Result<Self, XError> {
let name: CString = CString::new(name)?;
let dpy = unsafe { xlib::XOpenDisplay(ptr::null()) };
if dpy.is_null() {
return Err(XError::DisplayOpen);
}
let mut missing_ptr = MaybeUninit::uninit();
let mut missing_len = MaybeUninit::uninit();
let fontset = unsafe {
xlib::XCreateFontSet(
dpy,
name.as_ptr(),
missing_ptr.as_mut_ptr(),
missing_len.as_mut_ptr(),
ptr::null_mut(),
)
};
unsafe {
if !missing_ptr.assume_init().is_null() {
xlib::XFreeStringList(missing_ptr.assume_init());
}
}
if !fontset.is_null() {
Ok(Context {
data: Data::FontSet {
display: dpy,
fontset,
},
})
} else {
let xfont = unsafe { xlib::XLoadQueryFont(dpy, name.as_ptr()) };
if xfont.is_null() {
unsafe { xlib::XCloseDisplay(dpy) };
Err(XError::CouldNotLoadFont(name))
} else {
Ok(Context {
data: Data::XFont {
display: dpy,
xfont,
},
})
}
}
}
pub fn with_misc() -> Result<Self, XError> {
Self::new("-misc-fixed-*-*-*-*-*-*-*-*-*-*-*-*")
}
pub fn text_width<S: AsRef<str>>(&self, text: S) -> Result<u64, XError> {
get_text_width(&self, text)
}
}
impl Drop for Context {
fn drop(&mut self) {
unsafe {
match self.data {
Data::FontSet { display, fontset } => {
xlib::XFreeFontSet(display, fontset);
xlib::XCloseDisplay(display);
}
Data::XFont { display, xfont } => {
xlib::XFreeFont(display, xfont);
xlib::XCloseDisplay(display);
}
}
}
}
}
pub fn get_text_width<S: AsRef<str>>(ctx: &Context, text: S) -> Result<u64, XError> {
let text = CString::new(text.as_ref())?;
unsafe {
match ctx.data {
Data::FontSet { fontset, .. } => {
let mut rectangle = MaybeUninit::uninit();
xlib::XmbTextExtents(
fontset,
text.as_ptr(),
text.as_bytes().len() as i32,
ptr::null_mut(),
rectangle.as_mut_ptr(),
);
Ok(rectangle.assume_init().width as u64)
}
Data::XFont { xfont, .. } => {
Ok(xlib::XTextWidth(xfont, text.as_ptr(), text.as_bytes().len() as i32) as u64)
}
}
}
}
pub fn setup_multithreading() {
unsafe {
xlib::XInitThreads();
}
}
#[cfg(test)]
mod test {
use super::{get_text_width, Context};
use std::sync::Once;
use x11::xlib;
static SETUP: Once = Once::new();
fn setup() {
SETUP.call_once(|| unsafe {
xlib::XInitThreads();
})
}
#[test]
fn test_context_new() {
setup();
let ctx = Context::with_misc();
assert!(ctx.is_ok());
}
#[test]
fn test_context_drop() {
setup();
let ctx = Context::with_misc();
drop(ctx);
assert!(true);
}
#[test]
fn test_text_width() {
setup();
let ctx = Context::with_misc().unwrap();
assert!(get_text_width(&ctx, "Hello World").unwrap() > 0);
}
#[test]
fn test_text_alternate() {
setup();
let ctx = Context::new("?");
assert!(ctx.is_err());
}
}