2018-01-15 10:34:59 +03:00
|
|
|
//! Higher level freetype bindings
|
2018-01-17 10:02:32 +03:00
|
|
|
|
2020-10-25 02:40:15 +03:00
|
|
|
use crate::locator::FontDataHandle;
|
2019-12-19 20:13:29 +03:00
|
|
|
use anyhow::{anyhow, Context};
|
2021-02-21 00:54:56 +03:00
|
|
|
use config::{configuration, FreeTypeLoadTarget};
|
2019-03-23 19:28:40 +03:00
|
|
|
pub use freetype::*;
|
2021-03-21 09:15:33 +03:00
|
|
|
use std::borrow::Cow;
|
2018-01-15 10:34:59 +03:00
|
|
|
use std::ptr;
|
|
|
|
|
2019-03-23 19:28:40 +03:00
|
|
|
#[inline]
|
|
|
|
pub fn succeeded(error: FT_Error) -> bool {
|
|
|
|
error == freetype::FT_Err_Ok as FT_Error
|
|
|
|
}
|
|
|
|
|
2018-01-15 10:34:59 +03:00
|
|
|
/// Translate an error and value into a result
|
2019-12-15 08:43:05 +03:00
|
|
|
fn ft_result<T>(err: FT_Error, t: T) -> anyhow::Result<T> {
|
2018-09-20 03:52:34 +03:00
|
|
|
if succeeded(err) {
|
2018-01-15 10:34:59 +03:00
|
|
|
Ok(t)
|
|
|
|
} else {
|
2020-11-26 02:05:18 +03:00
|
|
|
unsafe {
|
|
|
|
let reason = FT_Error_String(err);
|
|
|
|
if reason.is_null() {
|
|
|
|
Err(anyhow!("FreeType error {:?} 0x{:x}", err, err))
|
|
|
|
} else {
|
|
|
|
let reason = std::ffi::CStr::from_ptr(reason);
|
|
|
|
Err(anyhow!(
|
|
|
|
"FreeType error {:?} 0x{:x}: {}",
|
|
|
|
err,
|
|
|
|
err,
|
|
|
|
reason.to_string_lossy()
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
2018-01-15 10:34:59 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-19 20:13:29 +03:00
|
|
|
fn render_mode_to_load_target(render_mode: FT_Render_Mode) -> u32 {
|
|
|
|
// enable FT_LOAD_TARGET bits. There are no flags defined
|
|
|
|
// for these in the bindings so we do some bit magic for
|
|
|
|
// ourselves. This is how the FT_LOAD_TARGET_() macro
|
|
|
|
// assembles these bits.
|
|
|
|
(render_mode as u32) & 15 << 16
|
|
|
|
}
|
|
|
|
|
2020-11-09 00:20:42 +03:00
|
|
|
pub fn compute_load_flags_from_config() -> (i32, FT_Render_Mode) {
|
2019-12-19 20:13:29 +03:00
|
|
|
let config = configuration();
|
2020-11-09 00:20:42 +03:00
|
|
|
|
2021-02-21 00:54:56 +03:00
|
|
|
let load_flags = config.freetype_load_flags.bits() | FT_LOAD_COLOR;
|
|
|
|
let render = match config.freetype_load_target {
|
|
|
|
FreeTypeLoadTarget::Mono => FT_Render_Mode::FT_RENDER_MODE_MONO,
|
|
|
|
FreeTypeLoadTarget::Normal => FT_Render_Mode::FT_RENDER_MODE_NORMAL,
|
|
|
|
FreeTypeLoadTarget::Light => FT_Render_Mode::FT_RENDER_MODE_LIGHT,
|
|
|
|
FreeTypeLoadTarget::HorizontalLcd => FT_Render_Mode::FT_RENDER_MODE_LCD,
|
|
|
|
FreeTypeLoadTarget::VerticalLcd => FT_Render_Mode::FT_RENDER_MODE_LCD_V,
|
2020-11-09 00:20:42 +03:00
|
|
|
};
|
|
|
|
|
2021-02-21 00:54:56 +03:00
|
|
|
let load_flags = load_flags | render_mode_to_load_target(render);
|
2019-12-19 20:13:29 +03:00
|
|
|
|
2021-02-21 00:54:56 +03:00
|
|
|
(load_flags as i32, render)
|
2019-12-09 23:12:54 +03:00
|
|
|
}
|
|
|
|
|
2021-03-21 09:15:33 +03:00
|
|
|
type CowVecU8 = Cow<'static, [u8]>;
|
|
|
|
|
2018-01-15 10:34:59 +03:00
|
|
|
pub struct Face {
|
|
|
|
pub face: FT_Face,
|
2021-03-21 09:15:33 +03:00
|
|
|
_bytes: CowVecU8,
|
2020-11-25 20:20:14 +03:00
|
|
|
size: Option<FaceSize>,
|
2018-01-15 10:34:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for Face {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
|
|
|
FT_Done_Face(self.face);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-25 20:20:14 +03:00
|
|
|
struct FaceSize {
|
|
|
|
size: f64,
|
|
|
|
dpi: u32,
|
|
|
|
cell_width: f64,
|
|
|
|
cell_height: f64,
|
|
|
|
}
|
|
|
|
|
2018-01-15 10:34:59 +03:00
|
|
|
impl Face {
|
2019-12-09 19:00:55 +03:00
|
|
|
/// This is a wrapper around set_char_size and select_size
|
|
|
|
/// that accounts for some weirdness with eg: color emoji
|
2020-11-25 20:20:14 +03:00
|
|
|
pub fn set_font_size(&mut self, point_size: f64, dpi: u32) -> anyhow::Result<(f64, f64)> {
|
|
|
|
if let Some(face_size) = self.size.as_ref() {
|
|
|
|
if face_size.size == point_size && face_size.dpi == dpi {
|
|
|
|
return Ok((face_size.cell_width, face_size.cell_height));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-20 22:52:20 +03:00
|
|
|
let pixel_height = point_size * dpi as f64 / 72.0;
|
|
|
|
log::debug!(
|
|
|
|
"set_char_size computing {} dpi={} (pixel height={})",
|
|
|
|
point_size,
|
|
|
|
dpi,
|
|
|
|
pixel_height
|
|
|
|
);
|
|
|
|
|
2019-12-09 19:00:55 +03:00
|
|
|
// Scaling before truncating to integer minimizes the chances of hitting
|
|
|
|
// the fallback code for set_pixel_sizes below.
|
2020-11-25 20:20:14 +03:00
|
|
|
let size = (point_size * 64.0) as FT_F26Dot6;
|
2019-12-09 19:00:55 +03:00
|
|
|
|
|
|
|
let (cell_width, cell_height) = match self.set_char_size(size, size, dpi, dpi) {
|
|
|
|
Ok(_) => {
|
|
|
|
// Compute metrics for the nominal monospace cell
|
|
|
|
self.cell_metrics()
|
|
|
|
}
|
|
|
|
Err(err) => {
|
2021-03-20 22:52:20 +03:00
|
|
|
log::debug!("set_char_size: {:?}, will inspect strikes", err);
|
|
|
|
|
2019-12-09 19:00:55 +03:00
|
|
|
let sizes = unsafe {
|
|
|
|
let rec = &(*self.face);
|
|
|
|
std::slice::from_raw_parts(rec.available_sizes, rec.num_fixed_sizes as usize)
|
|
|
|
};
|
|
|
|
if sizes.is_empty() {
|
|
|
|
return Err(err);
|
|
|
|
}
|
2021-03-20 22:52:20 +03:00
|
|
|
// Find the best matching size; we look for the strike whose height
|
|
|
|
// is closest to the desired size.
|
|
|
|
struct Best {
|
|
|
|
idx: usize,
|
|
|
|
distance: usize,
|
|
|
|
height: i16,
|
|
|
|
width: i16,
|
|
|
|
}
|
|
|
|
let mut best: Option<Best> = None;
|
2019-12-09 19:00:55 +03:00
|
|
|
|
|
|
|
for (idx, info) in sizes.iter().enumerate() {
|
2021-03-20 22:52:20 +03:00
|
|
|
log::debug!("idx={} info={:?}", idx, info);
|
|
|
|
let distance = (info.height - (pixel_height as i16)).abs() as usize;
|
|
|
|
let candidate = Best {
|
|
|
|
idx,
|
|
|
|
distance,
|
|
|
|
height: info.height,
|
|
|
|
width: info.width,
|
|
|
|
};
|
|
|
|
|
|
|
|
match best.take() {
|
|
|
|
Some(existing) => {
|
|
|
|
best.replace(if candidate.distance < existing.distance {
|
|
|
|
candidate
|
|
|
|
} else {
|
|
|
|
existing
|
|
|
|
});
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
best.replace(candidate);
|
|
|
|
}
|
2019-12-09 19:00:55 +03:00
|
|
|
}
|
|
|
|
}
|
2021-03-20 22:52:20 +03:00
|
|
|
let best = best.unwrap();
|
|
|
|
self.select_size(best.idx)?;
|
|
|
|
(f64::from(best.width), f64::from(best.height))
|
2019-12-09 19:00:55 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-11-25 20:20:14 +03:00
|
|
|
self.size.replace(FaceSize {
|
|
|
|
size: point_size,
|
|
|
|
dpi,
|
|
|
|
cell_width,
|
|
|
|
cell_height,
|
|
|
|
});
|
|
|
|
|
2019-12-09 19:00:55 +03:00
|
|
|
Ok((cell_width, cell_height))
|
|
|
|
}
|
|
|
|
|
2019-12-15 19:38:52 +03:00
|
|
|
fn set_char_size(
|
2018-01-15 10:34:59 +03:00
|
|
|
&mut self,
|
|
|
|
char_width: FT_F26Dot6,
|
|
|
|
char_height: FT_F26Dot6,
|
|
|
|
horz_resolution: FT_UInt,
|
|
|
|
vert_resolution: FT_UInt,
|
2019-12-15 08:43:05 +03:00
|
|
|
) -> anyhow::Result<()> {
|
2018-01-15 10:34:59 +03:00
|
|
|
ft_result(
|
|
|
|
unsafe {
|
|
|
|
FT_Set_Char_Size(
|
|
|
|
self.face,
|
|
|
|
char_width,
|
|
|
|
char_height,
|
|
|
|
horz_resolution,
|
|
|
|
vert_resolution,
|
|
|
|
)
|
|
|
|
},
|
|
|
|
(),
|
|
|
|
)
|
2021-03-20 22:52:20 +03:00
|
|
|
.context("FT_Set_Char_Size")?;
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
if (*self.face).height == 0 {
|
|
|
|
anyhow::bail!("font has 0 height, fallback to bitmaps");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
2018-01-15 10:34:59 +03:00
|
|
|
}
|
|
|
|
|
2019-12-15 19:38:52 +03:00
|
|
|
fn select_size(&mut self, idx: usize) -> anyhow::Result<()> {
|
2020-11-26 03:13:11 +03:00
|
|
|
ft_result(unsafe { FT_Select_Size(self.face, idx as i32) }, ()).context("FT_Select_Size")
|
2018-09-20 03:52:34 +03:00
|
|
|
}
|
|
|
|
|
2018-01-15 10:34:59 +03:00
|
|
|
pub fn load_and_render_glyph(
|
|
|
|
&mut self,
|
|
|
|
glyph_index: FT_UInt,
|
|
|
|
load_flags: FT_Int32,
|
2020-11-09 00:20:42 +03:00
|
|
|
render_mode: FT_Render_Mode,
|
2019-12-15 08:43:05 +03:00
|
|
|
) -> anyhow::Result<&FT_GlyphSlotRec_> {
|
2018-01-15 10:34:59 +03:00
|
|
|
unsafe {
|
2020-11-26 03:13:11 +03:00
|
|
|
ft_result(FT_Load_Glyph(self.face, glyph_index, load_flags), ()).with_context(
|
|
|
|
|| {
|
|
|
|
anyhow!(
|
|
|
|
"load_and_render_glyph: FT_Load_Glyph glyph_index:{}",
|
|
|
|
glyph_index
|
|
|
|
)
|
|
|
|
},
|
|
|
|
)?;
|
|
|
|
let slot = &mut *(*self.face).glyph;
|
|
|
|
ft_result(FT_Render_Glyph(slot, render_mode), ())
|
|
|
|
.context("load_and_render_glyph: FT_Render_Glyph")?;
|
|
|
|
Ok(slot)
|
2018-01-15 10:34:59 +03:00
|
|
|
}
|
|
|
|
}
|
2018-01-16 11:24:49 +03:00
|
|
|
|
2018-01-21 12:32:59 +03:00
|
|
|
pub fn cell_metrics(&mut self) -> (f64, f64) {
|
2018-01-16 11:24:49 +03:00
|
|
|
unsafe {
|
|
|
|
let metrics = &(*(*self.face).size).metrics;
|
2018-03-04 00:00:56 +03:00
|
|
|
let height = (metrics.y_scale as f64 * f64::from((*self.face).height))
|
|
|
|
/ (f64::from(0x1_0000) * 64.0);
|
2018-01-16 11:24:49 +03:00
|
|
|
|
2018-01-21 12:32:59 +03:00
|
|
|
let mut width = 0.0;
|
2018-01-16 11:24:49 +03:00
|
|
|
for i in 32..128 {
|
|
|
|
let glyph_pos = FT_Get_Char_Index(self.face, i);
|
2019-12-15 00:16:57 +03:00
|
|
|
if glyph_pos == 0 {
|
|
|
|
continue;
|
|
|
|
}
|
2018-01-17 10:02:32 +03:00
|
|
|
let res = FT_Load_Glyph(self.face, glyph_pos, FT_LOAD_COLOR as i32);
|
2018-09-20 03:52:34 +03:00
|
|
|
if succeeded(res) {
|
2018-01-16 11:24:49 +03:00
|
|
|
let glyph = &(*(*self.face).glyph);
|
2018-01-21 12:32:59 +03:00
|
|
|
if glyph.metrics.horiAdvance as f64 > width {
|
|
|
|
width = glyph.metrics.horiAdvance as f64;
|
|
|
|
}
|
2018-01-16 11:24:49 +03:00
|
|
|
}
|
|
|
|
}
|
2020-12-28 20:02:30 +03:00
|
|
|
if width == 0.0 {
|
|
|
|
// Most likely we're looking at a symbol font with no latin
|
|
|
|
// glyphs at all. Let's just pick a selection of glyphs
|
|
|
|
for glyph_pos in 1..8 {
|
|
|
|
let res = FT_Load_Glyph(self.face, glyph_pos, FT_LOAD_COLOR as i32);
|
|
|
|
if succeeded(res) {
|
|
|
|
let glyph = &(*(*self.face).glyph);
|
|
|
|
if glyph.metrics.horiAdvance as f64 > width {
|
|
|
|
width = glyph.metrics.horiAdvance as f64;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if width == 0.0 {
|
|
|
|
log::error!(
|
|
|
|
"Couldn't find any glyphs for metrics, so guessing width == height"
|
|
|
|
);
|
|
|
|
width = height * 64.;
|
|
|
|
}
|
|
|
|
}
|
2018-01-21 12:32:59 +03:00
|
|
|
(width / 64.0, height)
|
2018-01-16 11:24:49 +03:00
|
|
|
}
|
|
|
|
}
|
2018-01-15 10:34:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Library {
|
|
|
|
lib: FT_Library,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for Library {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
|
|
|
FT_Done_FreeType(self.lib);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Library {
|
2019-12-15 08:43:05 +03:00
|
|
|
pub fn new() -> anyhow::Result<Library> {
|
2018-01-15 10:34:59 +03:00
|
|
|
let mut lib = ptr::null_mut();
|
|
|
|
let res = unsafe { FT_Init_FreeType(&mut lib as *mut _) };
|
2019-11-02 19:13:51 +03:00
|
|
|
let lib = ft_result(res, lib).context("FT_Init_FreeType")?;
|
2019-12-10 00:42:07 +03:00
|
|
|
let mut lib = Library { lib };
|
|
|
|
|
2020-12-21 20:15:13 +03:00
|
|
|
let config = configuration();
|
|
|
|
if let Some(vers) = config.freetype_interpreter_version {
|
|
|
|
let interpreter_version: FT_UInt = vers;
|
|
|
|
unsafe {
|
|
|
|
FT_Property_Set(
|
|
|
|
lib.lib,
|
|
|
|
b"truetype\0" as *const u8 as *const FT_String,
|
|
|
|
b"interpreter-version\0" as *const u8 as *const FT_String,
|
|
|
|
&interpreter_version as *const FT_UInt as *const _,
|
|
|
|
);
|
|
|
|
}
|
2020-11-09 00:20:42 +03:00
|
|
|
}
|
|
|
|
|
2019-12-19 20:13:29 +03:00
|
|
|
// Due to patent concerns, the freetype library disables the LCD
|
|
|
|
// filtering feature by default, and since we always build our
|
|
|
|
// own copy of freetype, it is likewise disabled by default for
|
|
|
|
// us too. As a result, this call will generally fail.
|
|
|
|
// Freetype is still able to render a decent result without it!
|
2019-12-10 00:42:07 +03:00
|
|
|
lib.set_lcd_filter(FT_LcdFilter::FT_LCD_FILTER_DEFAULT).ok();
|
|
|
|
|
|
|
|
Ok(lib)
|
2018-01-15 10:34:59 +03:00
|
|
|
}
|
|
|
|
|
2019-12-15 08:43:05 +03:00
|
|
|
pub fn face_from_locator(&self, handle: &FontDataHandle) -> anyhow::Result<Face> {
|
2019-12-09 23:12:54 +03:00
|
|
|
match handle {
|
|
|
|
FontDataHandle::OnDisk { path, index } => {
|
|
|
|
self.new_face(path.to_str().unwrap(), *index as _)
|
|
|
|
}
|
2020-10-13 03:05:53 +03:00
|
|
|
FontDataHandle::Memory { data, index, .. } => {
|
2021-03-21 09:15:33 +03:00
|
|
|
self.new_face_from_slice(data.clone(), *index as _)
|
2020-10-13 03:05:53 +03:00
|
|
|
}
|
2019-12-09 23:12:54 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-15 08:43:05 +03:00
|
|
|
pub fn new_face<P>(&self, path: P, face_index: FT_Long) -> anyhow::Result<Face>
|
2018-01-15 10:34:59 +03:00
|
|
|
where
|
2019-12-15 02:22:47 +03:00
|
|
|
P: AsRef<std::path::Path>,
|
2018-01-15 10:34:59 +03:00
|
|
|
{
|
|
|
|
let mut face = ptr::null_mut();
|
2021-03-21 09:00:53 +03:00
|
|
|
|
|
|
|
if let Some(path_str) = path.as_ref().to_str() {
|
|
|
|
if let Ok(path_cstr) = std::ffi::CString::new(path_str) {
|
|
|
|
let res = unsafe {
|
|
|
|
FT_New_Face(
|
|
|
|
self.lib,
|
|
|
|
path_cstr.as_ptr(),
|
|
|
|
face_index,
|
|
|
|
&mut face as *mut _,
|
|
|
|
)
|
|
|
|
};
|
|
|
|
return Ok(Face {
|
|
|
|
face: ft_result(res, face).with_context(|| {
|
|
|
|
format!(
|
|
|
|
"FT_New_Face for {} index {}",
|
|
|
|
path.as_ref().display(),
|
|
|
|
face_index
|
|
|
|
)
|
|
|
|
})?,
|
2021-03-21 09:15:33 +03:00
|
|
|
_bytes: CowVecU8::Borrowed(b""),
|
2021-03-21 09:00:53 +03:00
|
|
|
size: None,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-15 02:22:47 +03:00
|
|
|
let path = path.as_ref();
|
2018-01-15 10:34:59 +03:00
|
|
|
|
2019-12-15 02:22:47 +03:00
|
|
|
let data = std::fs::read(path)?;
|
2021-03-21 08:49:27 +03:00
|
|
|
log::trace!(
|
|
|
|
"Loading {} ({} bytes) for freetype!",
|
|
|
|
path.display(),
|
|
|
|
data.len()
|
|
|
|
);
|
2021-03-21 09:15:33 +03:00
|
|
|
let data = CowVecU8::Owned(data);
|
2019-12-15 02:22:47 +03:00
|
|
|
|
|
|
|
let res = unsafe {
|
|
|
|
FT_New_Memory_Face(
|
|
|
|
self.lib,
|
|
|
|
data.as_ptr(),
|
|
|
|
data.len() as _,
|
|
|
|
face_index,
|
|
|
|
&mut face as *mut _,
|
|
|
|
)
|
|
|
|
};
|
2018-02-21 09:08:19 +03:00
|
|
|
Ok(Face {
|
2019-12-15 08:43:05 +03:00
|
|
|
face: ft_result(res, face).with_context(|| {
|
|
|
|
format!(
|
2019-12-15 02:22:47 +03:00
|
|
|
"FT_New_Memory_Face for {} index {}",
|
|
|
|
path.display(),
|
|
|
|
face_index
|
2019-12-15 08:43:05 +03:00
|
|
|
)
|
2019-12-15 02:22:47 +03:00
|
|
|
})?,
|
|
|
|
_bytes: data,
|
2020-11-25 20:20:14 +03:00
|
|
|
size: None,
|
2018-02-21 09:08:19 +03:00
|
|
|
})
|
2018-01-15 10:34:59 +03:00
|
|
|
}
|
|
|
|
|
2021-03-21 09:15:33 +03:00
|
|
|
pub fn new_face_from_slice(&self, data: CowVecU8, face_index: FT_Long) -> anyhow::Result<Face> {
|
2019-02-18 23:56:41 +03:00
|
|
|
let mut face = ptr::null_mut();
|
|
|
|
|
|
|
|
let res = unsafe {
|
|
|
|
FT_New_Memory_Face(
|
|
|
|
self.lib,
|
|
|
|
data.as_ptr(),
|
2019-02-19 00:13:36 +03:00
|
|
|
data.len() as _,
|
2019-02-18 23:56:41 +03:00
|
|
|
face_index,
|
|
|
|
&mut face as *mut _,
|
|
|
|
)
|
|
|
|
};
|
|
|
|
Ok(Face {
|
2019-11-02 19:13:51 +03:00
|
|
|
face: ft_result(res, face)
|
2019-12-15 08:43:05 +03:00
|
|
|
.with_context(|| format!("FT_New_Memory_Face for index {}", face_index))?,
|
2019-12-09 03:02:04 +03:00
|
|
|
_bytes: data,
|
2020-11-25 20:20:14 +03:00
|
|
|
size: None,
|
2019-02-18 23:56:41 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-12-15 08:43:05 +03:00
|
|
|
pub fn set_lcd_filter(&mut self, filter: FT_LcdFilter) -> anyhow::Result<()> {
|
2020-11-26 03:13:11 +03:00
|
|
|
unsafe {
|
|
|
|
ft_result(FT_Library_SetLcdFilter(self.lib, filter), ())
|
|
|
|
.context("FT_Library_SetLcdFilter")
|
|
|
|
}
|
2018-01-15 10:34:59 +03:00
|
|
|
}
|
|
|
|
}
|