mirror of
https://github.com/wez/wezterm.git
synced 2024-12-23 13:21:38 +03:00
fonts: starting building out harfbuzz rasterizer
This seems like it might be a way to get COLR support without too much effort. This commit just adds bitmap image support as a first step
This commit is contained in:
parent
58a2a30623
commit
c7b689e369
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -6218,6 +6218,7 @@ dependencies = [
|
||||
"fontconfig",
|
||||
"freetype",
|
||||
"harfbuzz",
|
||||
"image",
|
||||
"k9",
|
||||
"lazy_static",
|
||||
"lfucache",
|
||||
|
@ -678,6 +678,7 @@ impl Default for FontLocatorSelection {
|
||||
pub enum FontRasterizerSelection {
|
||||
#[default]
|
||||
FreeType,
|
||||
Harfbuzz,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, FromDynamic, ToDynamic, Default)]
|
||||
|
@ -22,6 +22,7 @@ euclid = "0.22"
|
||||
finl_unicode = "1.2"
|
||||
freetype = { path = "../deps/freetype" }
|
||||
harfbuzz = { path = "../deps/harfbuzz" }
|
||||
image = "0.24.6"
|
||||
lazy_static = "1.4"
|
||||
lfucache = { path = "../lfucache" }
|
||||
log = "0.4"
|
||||
|
@ -281,20 +281,37 @@ impl Font {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_font_scale(&mut self, x_scale: c_int, y_scale: c_int) {
|
||||
pub fn set_synthetic_slant(&mut self, slant: f32) {
|
||||
unsafe {
|
||||
hb_font_set_synthetic_slant(self.font, slant);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_synthetic_bold(&mut self, x_embolden: f32, y_embolden: f32, in_place: bool) {
|
||||
unsafe {
|
||||
hb_font_set_synthetic_bold(
|
||||
self.font,
|
||||
x_embolden,
|
||||
y_embolden,
|
||||
if in_place { 1 } else { 0 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_font_scale(&self, x_scale: c_int, y_scale: c_int) {
|
||||
log::info!("setting x_scale={x_scale}, y_scale={y_scale}");
|
||||
unsafe {
|
||||
hb_font_set_scale(self.font, x_scale, y_scale);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ppem(&mut self, x_ppem: u32, y_ppem: u32) {
|
||||
pub fn set_ppem(&self, x_ppem: u32, y_ppem: u32) {
|
||||
unsafe {
|
||||
hb_font_set_ppem(self.font, x_ppem, y_ppem);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ptem(&mut self, ptem: f32) {
|
||||
pub fn set_ptem(&self, ptem: f32) {
|
||||
unsafe {
|
||||
hb_font_set_ptem(self.font, ptem);
|
||||
}
|
||||
@ -317,6 +334,26 @@ impl Font {
|
||||
pub fn shape(&mut self, buf: &mut Buffer, features: &[hb_feature_t]) {
|
||||
unsafe { hb_shape(self.font, buf.buf, features.as_ptr(), features.len() as u32) }
|
||||
}
|
||||
|
||||
pub fn paint_glyph(
|
||||
&self,
|
||||
glyph_pos: u32,
|
||||
funcs: &FontFuncs,
|
||||
paint_data: *mut c_void,
|
||||
palette_index: ::std::os::raw::c_uint,
|
||||
foreground: hb_color_t,
|
||||
) {
|
||||
unsafe {
|
||||
hb_font_paint_glyph(
|
||||
self.font,
|
||||
glyph_pos,
|
||||
funcs.funcs,
|
||||
paint_data,
|
||||
palette_index,
|
||||
foreground,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Buffer {
|
||||
@ -478,3 +515,130 @@ impl Buffer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FontFuncs {
|
||||
funcs: *mut hb_paint_funcs_t,
|
||||
}
|
||||
|
||||
impl Drop for FontFuncs {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
hb_paint_funcs_destroy(self.funcs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! func {
|
||||
($method:ident, $type:ty, $func:ident) => {
|
||||
pub fn $method(&mut self, func: $type) {
|
||||
unsafe {
|
||||
$func(self.funcs, func, std::ptr::null_mut(), None);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl FontFuncs {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let funcs = unsafe { hb_paint_funcs_create() };
|
||||
anyhow::ensure!(!funcs.is_null(), "hb_paint_funcs_create failed");
|
||||
Ok(Self { funcs })
|
||||
}
|
||||
|
||||
func!(
|
||||
set_push_transform_func,
|
||||
hb_paint_push_transform_func_t,
|
||||
hb_paint_funcs_set_push_transform_func
|
||||
);
|
||||
func!(
|
||||
set_pop_transform_func,
|
||||
hb_paint_pop_transform_func_t,
|
||||
hb_paint_funcs_set_pop_transform_func
|
||||
);
|
||||
func!(
|
||||
set_push_clip_glyph_func,
|
||||
hb_paint_push_clip_glyph_func_t,
|
||||
hb_paint_funcs_set_push_clip_glyph_func
|
||||
);
|
||||
func!(
|
||||
set_push_clip_rectangle_func,
|
||||
hb_paint_push_clip_rectangle_func_t,
|
||||
hb_paint_funcs_set_push_clip_rectangle_func
|
||||
);
|
||||
func!(
|
||||
set_pop_clip_func,
|
||||
hb_paint_pop_clip_func_t,
|
||||
hb_paint_funcs_set_pop_clip_func
|
||||
);
|
||||
func!(
|
||||
set_color_func,
|
||||
hb_paint_color_func_t,
|
||||
hb_paint_funcs_set_color_func
|
||||
);
|
||||
func!(
|
||||
set_image_func,
|
||||
hb_paint_image_func_t,
|
||||
hb_paint_funcs_set_image_func
|
||||
);
|
||||
func!(
|
||||
set_linear_gradient,
|
||||
hb_paint_linear_gradient_func_t,
|
||||
hb_paint_funcs_set_linear_gradient_func
|
||||
);
|
||||
func!(
|
||||
set_radial_gradient,
|
||||
hb_paint_radial_gradient_func_t,
|
||||
hb_paint_funcs_set_radial_gradient_func
|
||||
);
|
||||
func!(
|
||||
set_sweep_gradient,
|
||||
hb_paint_sweep_gradient_func_t,
|
||||
hb_paint_funcs_set_sweep_gradient_func
|
||||
);
|
||||
func!(
|
||||
set_push_group,
|
||||
hb_paint_push_group_func_t,
|
||||
hb_paint_funcs_set_push_group_func
|
||||
);
|
||||
func!(
|
||||
set_pop_group,
|
||||
hb_paint_pop_group_func_t,
|
||||
hb_paint_funcs_set_pop_group_func
|
||||
);
|
||||
func!(
|
||||
set_custom_palette_color,
|
||||
hb_paint_custom_palette_color_func_t,
|
||||
hb_paint_funcs_set_custom_palette_color_func
|
||||
);
|
||||
}
|
||||
|
||||
pub struct TagString([u8; 4]);
|
||||
|
||||
impl std::convert::AsRef<str> for TagString {
|
||||
fn as_ref(&self) -> &str {
|
||||
std::str::from_utf8(&self.0).expect("tag to be valid ascii")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for TagString {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &str {
|
||||
std::str::from_utf8(&self.0).expect("tag to be valid ascii")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TagString {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
self.as_ref().fmt(fmt)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hb_tag_to_string(tag: hb_tag_t) -> TagString {
|
||||
let mut buf = [0u8; 4];
|
||||
|
||||
// safety: hb_tag_to_string stores 4 bytes to the provided buffer
|
||||
unsafe {
|
||||
harfbuzz::hb_tag_to_string(tag, &mut buf as *mut u8 as *mut i8);
|
||||
}
|
||||
TagString(buf)
|
||||
}
|
||||
|
266
wezterm-font/src/rasterizer/harfbuzz.rs
Normal file
266
wezterm-font/src/rasterizer/harfbuzz.rs
Normal file
@ -0,0 +1,266 @@
|
||||
|
||||
use crate::hbwrap::{
|
||||
hb_blob_get_data, hb_blob_t, hb_bool_t, hb_glyph_extents_t, hb_paint_funcs_t, hb_tag_t,
|
||||
hb_tag_to_string, Face, Font, FontFuncs,
|
||||
};
|
||||
use crate::rasterizer::FAKE_ITALIC_SKEW;
|
||||
use crate::units::PixelLength;
|
||||
use crate::{FontRasterizer, ParsedFont, RasterizedGlyph};
|
||||
use anyhow::Context;
|
||||
use image::DynamicImage::{ImageLuma8, ImageLumaA8};
|
||||
|
||||
pub struct HarfbuzzRasterizer {
|
||||
face: Face,
|
||||
font: Font,
|
||||
funcs: FontFuncs,
|
||||
}
|
||||
|
||||
impl HarfbuzzRasterizer {
|
||||
pub fn from_locator(parsed: &ParsedFont) -> anyhow::Result<Self> {
|
||||
let mut font = Font::from_locator(&parsed.handle)?;
|
||||
font.set_ot_funcs();
|
||||
let face = font.get_face();
|
||||
|
||||
if parsed.synthesize_italic {
|
||||
font.set_synthetic_slant(FAKE_ITALIC_SKEW as f32);
|
||||
}
|
||||
if parsed.synthesize_bold {
|
||||
font.set_synthetic_bold(0.02, 0.02, false);
|
||||
}
|
||||
|
||||
let mut funcs = FontFuncs::new()?;
|
||||
funcs.set_push_transform_func(Some(PaintData::push_transform_trampoline));
|
||||
funcs.set_pop_transform_func(Some(PaintData::pop_transform_trampoline));
|
||||
funcs.set_image_func(Some(PaintData::image_trampoline));
|
||||
|
||||
Ok(Self { face, font, funcs })
|
||||
}
|
||||
}
|
||||
|
||||
impl FontRasterizer for HarfbuzzRasterizer {
|
||||
fn rasterize_glyph(
|
||||
&self,
|
||||
glyph_pos: u32,
|
||||
size: f64,
|
||||
dpi: u32,
|
||||
) -> anyhow::Result<RasterizedGlyph> {
|
||||
let mut data = PaintData {
|
||||
rasterizer: self,
|
||||
glyph_pos,
|
||||
size,
|
||||
dpi,
|
||||
result: RasterizedGlyph {
|
||||
data: vec![],
|
||||
height: 0,
|
||||
width: 0,
|
||||
bearing_x: PixelLength::new(0.),
|
||||
bearing_y: PixelLength::new(0.),
|
||||
has_color: false,
|
||||
},
|
||||
};
|
||||
|
||||
let pixel_size = (size * dpi as f64 / 72.) as u32;
|
||||
let upem = self.face.get_upem();
|
||||
|
||||
let scale = size as i32 * 64;
|
||||
let ppem = pixel_size;
|
||||
log::info!("computed scale={scale}, ppem={ppem}, upem={upem}");
|
||||
self.font.set_ppem(ppem, ppem);
|
||||
self.font.set_ptem(size as f32);
|
||||
self.font.set_font_scale(scale, scale);
|
||||
self.font.paint_glyph(
|
||||
glyph_pos,
|
||||
&self.funcs,
|
||||
&mut data as *mut _ as _,
|
||||
0, // palette index 0
|
||||
0xffffffff, // 100% white
|
||||
);
|
||||
|
||||
Ok(data.result)
|
||||
}
|
||||
}
|
||||
|
||||
struct PaintData<'a> {
|
||||
rasterizer: &'a HarfbuzzRasterizer,
|
||||
glyph_pos: u32,
|
||||
size: f64,
|
||||
dpi: u32,
|
||||
result: RasterizedGlyph,
|
||||
}
|
||||
|
||||
impl<'a> PaintData<'a> {
|
||||
extern "C" fn push_transform_trampoline(
|
||||
_funcs: *mut hb_paint_funcs_t,
|
||||
paint_data: *mut ::std::os::raw::c_void,
|
||||
xx: f32,
|
||||
yx: f32,
|
||||
xy: f32,
|
||||
yy: f32,
|
||||
dx: f32,
|
||||
dy: f32,
|
||||
_user_data: *mut ::std::os::raw::c_void,
|
||||
) {
|
||||
let this: &mut Self = unsafe { &mut *(paint_data as *mut Self) };
|
||||
this.push_transform(xx, yx, xy, yy, dx, dy);
|
||||
}
|
||||
|
||||
extern "C" fn pop_transform_trampoline(
|
||||
_funcs: *mut hb_paint_funcs_t,
|
||||
paint_data: *mut ::std::os::raw::c_void,
|
||||
_user_data: *mut ::std::os::raw::c_void,
|
||||
) {
|
||||
let this: &mut Self = unsafe { &mut *(paint_data as *mut Self) };
|
||||
this.pop_transform();
|
||||
}
|
||||
|
||||
extern "C" fn image_trampoline(
|
||||
_funcs: *mut hb_paint_funcs_t,
|
||||
paint_data: *mut ::std::os::raw::c_void,
|
||||
image: *mut hb_blob_t,
|
||||
width: ::std::os::raw::c_uint,
|
||||
height: ::std::os::raw::c_uint,
|
||||
format: hb_tag_t,
|
||||
slant: f32,
|
||||
extents: *mut hb_glyph_extents_t,
|
||||
_user_data: *mut ::std::os::raw::c_void,
|
||||
) -> hb_bool_t {
|
||||
let this: &mut Self = unsafe { &mut *(paint_data as *mut Self) };
|
||||
|
||||
let mut image_len = 0;
|
||||
let mut image_ptr = unsafe { hb_blob_get_data(image, &mut image_len) };
|
||||
let image =
|
||||
unsafe { std::slice::from_raw_parts(image_ptr as *const u8, image_len as usize) };
|
||||
|
||||
let result = this.image(image, width, height, format, slant, unsafe {
|
||||
if extents.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(&*extents)
|
||||
}
|
||||
});
|
||||
match result {
|
||||
Ok(()) => 1,
|
||||
Err(err) => {
|
||||
log::error!("image: {err:#}");
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push_transform(&mut self, xx: f32, yx: f32, xy: f32, yy: f32, dx: f32, dy: f32) {
|
||||
log::info!("push_transform: xx={xx} yx={yx} xy={xy} yy={yy} dx={dx} dy={dy}");
|
||||
}
|
||||
fn pop_transform(&mut self) {
|
||||
log::info!("pop_transform");
|
||||
}
|
||||
fn image(
|
||||
&mut self,
|
||||
image: &[u8],
|
||||
width: ::std::os::raw::c_uint,
|
||||
height: ::std::os::raw::c_uint,
|
||||
format: hb_tag_t,
|
||||
slant: f32,
|
||||
extents: Option<&hb_glyph_extents_t>,
|
||||
) -> anyhow::Result<()> {
|
||||
let format = hb_tag_to_string(format);
|
||||
log::info!("image {width}x{height} format={format} slant={slant} {extents:?}");
|
||||
|
||||
let decoded = image::io::Reader::new(std::io::Cursor::new(image))
|
||||
.with_guessed_format()?
|
||||
.decode()?;
|
||||
|
||||
match &decoded {
|
||||
ImageLuma8(_) | ImageLumaA8(_) => self.result.has_color = false,
|
||||
_ => self.result.has_color = true,
|
||||
}
|
||||
|
||||
let mut decoded = decoded.into_rgba8();
|
||||
|
||||
// Convert to premultiplied form
|
||||
fn multiply_alpha(alpha: u8, color: u8) -> u8 {
|
||||
let temp: u32 = alpha as u32 * (color as u32 + 0x80);
|
||||
|
||||
((temp + (temp >> 8)) >> 8) as u8
|
||||
}
|
||||
|
||||
for (_x, _y, pixel) in decoded.enumerate_pixels_mut() {
|
||||
let alpha = pixel[3];
|
||||
if alpha == 0 {
|
||||
pixel[0] = 0;
|
||||
pixel[1] = 0;
|
||||
pixel[2] = 0;
|
||||
} else {
|
||||
if alpha != 0xff {
|
||||
for n in 0..3 {
|
||||
pixel[n] = multiply_alpha(alpha, pixel[n]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Crop to the non-transparent portions of the image
|
||||
let mut first_line = None;
|
||||
let mut first_col = None;
|
||||
let mut last_col = None;
|
||||
let mut last_line = None;
|
||||
|
||||
for (y, row) in decoded.rows().enumerate() {
|
||||
for (x, pixel) in row.enumerate() {
|
||||
let alpha = pixel[3];
|
||||
if alpha != 0 {
|
||||
if first_line.is_none() {
|
||||
first_line = Some(y);
|
||||
}
|
||||
first_col = match first_col.take() {
|
||||
Some(other) if x < other => Some(x),
|
||||
Some(other) => Some(other),
|
||||
None => Some(x),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
for (y, row) in decoded.rows().enumerate().rev() {
|
||||
for (x, pixel) in row.enumerate().rev() {
|
||||
let alpha = pixel[3];
|
||||
if alpha != 0 {
|
||||
if last_line.is_none() {
|
||||
last_line = Some(y);
|
||||
}
|
||||
last_col = match last_col.take() {
|
||||
Some(other) if x > other => Some(x),
|
||||
Some(other) => Some(other),
|
||||
None => Some(x),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let first_col = first_col.unwrap_or(0) as u32;
|
||||
let first_line = first_line.unwrap_or(0) as u32;
|
||||
let last_col = last_col.unwrap_or(width as usize) as u32;
|
||||
let last_line = last_line.unwrap_or(height as usize) as u32;
|
||||
|
||||
let cropped = image::imageops::crop(
|
||||
&mut decoded,
|
||||
first_col,
|
||||
first_line,
|
||||
last_col - first_col,
|
||||
last_line - first_line,
|
||||
)
|
||||
.to_image();
|
||||
self.result.height = cropped.height() as usize;
|
||||
self.result.width = cropped.width() as usize;
|
||||
|
||||
log::info!("cropped -> {}x{}", self.result.width, self.result.height);
|
||||
|
||||
self.result.data = cropped.into_vec();
|
||||
|
||||
let (bearing_x, bearing_y) = extents
|
||||
.map(|ext| (ext.x_bearing as f64 / 64., ext.y_bearing as f64 / 64.))
|
||||
.unwrap_or((0., 0.));
|
||||
self.result.bearing_x = PixelLength::new(bearing_x);
|
||||
self.result.bearing_y = PixelLength::new(bearing_y);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ use config::FontRasterizerSelection;
|
||||
pub(crate) const FAKE_ITALIC_SKEW: f64 = 0.2;
|
||||
|
||||
pub mod freetype;
|
||||
pub mod harfbuzz;
|
||||
|
||||
/// A bitmap representation of a glyph.
|
||||
/// The data is stored as pre-multiplied RGBA 32bpp.
|
||||
@ -40,5 +41,8 @@ pub fn new_rasterizer(
|
||||
FontRasterizerSelection::FreeType => Ok(Box::new(
|
||||
freetype::FreeTypeRasterizer::from_locator(handle, pixel_geometry)?,
|
||||
)),
|
||||
FontRasterizerSelection::Harfbuzz => Ok(Box::new(
|
||||
harfbuzz::HarfbuzzRasterizer::from_locator(handle)?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user