feat: use harfbuzz for shaping ligatures

This commit is contained in:
tizee 2021-12-15 23:40:31 +08:00
parent 463bd23427
commit 4e71f7d138
No known key found for this signature in database
GPG Key ID: DBDCA222366710D2
5 changed files with 188 additions and 36 deletions

15
Cargo.lock generated
View File

@ -537,6 +537,20 @@ dependencies = [
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
name = "harfbuzz-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf8c27ca13930dc4ffe474880040fe9e0f03c2121600dc9c95423624cab3e467"
dependencies = [
"cc",
"core-graphics",
"core-text",
"foreign-types",
"freetype",
"pkg-config",
]
[[package]]
name = "hashbrown"
version = "0.9.1"
@ -1301,6 +1315,7 @@ dependencies = [
"dirs",
"env_logger",
"font-kit",
"harfbuzz-sys",
"image",
"imageproc",
"lazy_static",

View File

@ -12,7 +12,6 @@ edition = "2018"
[dependencies]
dirs = "3.0"
imageproc = "0.22.0"
font-kit = "0.10"
clipboard = "0.5.0"
tempfile = "3.1.0"
conv = "0.3.3"
@ -55,6 +54,13 @@ default-features = false
features = ["termcolor", "atty", "humantime"]
optional = true
[dependencies.font-kit]
version= "0.10"
features= ["loader-freetype-default", "source-fontconfig-default"]
[dependencies.harfbuzz-sys]
version="0.5.0"
[patch.crates-io]
pathfinder_simd = { version = "0.5.0", git = "https://github.com/servo/pathfinder" }

View File

@ -12,6 +12,8 @@
//! font.draw_text_mut(&mut image, Rgb([255, 0, 0]), 0, 0, FontStyle::REGULAR, "Hello, world");
//! ```
use crate::error::FontError;
use crate::hb_wrapper::{feature_from_tag, HBBuffer, HBFont};
use anyhow::Result;
use conv::ValueInto;
use font_kit::canvas::{Canvas, Format, RasterizationOptions};
use font_kit::font::Font;
@ -191,17 +193,6 @@ impl FontCollection {
Ok(Self(fonts))
}
fn glyph_for_char(&self, c: char, style: FontStyle) -> Option<(u32, &ImageFont, &Font)> {
for font in &self.0 {
let result = font.get_by_style(style);
if let Some(id) = result.glyph_for_char(c) {
return Some((id, font, result));
}
}
eprintln!("[warning] No font found for character `{}`", c);
None
}
/// get max height of all the fonts
pub fn get_font_height(&self) -> u32 {
self.0
@ -211,35 +202,57 @@ impl FontCollection {
.unwrap()
}
fn shape_text(&self, font: &mut HBFont, text: &str) -> Result<Vec<u32>> {
// feature tags
let features = vec![
feature_from_tag("kern")?,
feature_from_tag("clig")?,
feature_from_tag("liga")?,
];
let mut buf = HBBuffer::new()?;
buf.add_str(text);
buf.guess_segments_properties();
font.shape(&buf, features.as_slice());
let hb_infos = buf.get_glyph_infos();
let mut glyph_ids = Vec::new();
for info in hb_infos.iter() {
glyph_ids.push(info.codepoint);
}
Ok(glyph_ids)
}
fn layout(&self, text: &str, style: FontStyle) -> (Vec<PositionedGlyph>, u32) {
let mut delta_x = 0;
let height = self.get_font_height();
let glyphs = text
.chars()
.filter_map(|c| {
self.glyph_for_char(c, style).map(|(id, imfont, font)| {
let raster_rect = font
.raster_bounds(
id,
imfont.size,
Transform2F::default(),
HintingOptions::None,
RasterizationOptions::GrayscaleAa,
)
.unwrap();
let position =
Vector2I::new(delta_x as i32, height as i32) + raster_rect.origin();
delta_x += Self::get_glyph_width(font, id, imfont.size);
let imfont = self.0.get(0).unwrap();
let font = imfont.get_by_style(style);
let mut hb_font = HBFont::new(font);
// apply font features especially ligature with a shape engine
let shaped_glyphs = self.shape_text(&mut hb_font, text).unwrap();
PositionedGlyph {
id,
font: font.clone(),
size: imfont.size,
raster_rect,
position,
}
})
let glyphs = shaped_glyphs
.iter()
.map(|id| {
let raster_rect = font
.raster_bounds(
*id,
imfont.size,
Transform2F::default(),
HintingOptions::None,
RasterizationOptions::GrayscaleAa,
)
.unwrap();
let position = Vector2I::new(delta_x as i32, height as i32) + raster_rect.origin();
delta_x += Self::get_glyph_width(font, *id, imfont.size);
PositionedGlyph {
id: *id,
font: font.clone(),
size: imfont.size,
raster_rect,
position,
}
})
.collect();

117
src/hb_wrapper.rs Normal file
View File

@ -0,0 +1,117 @@
use anyhow::{ensure, Result};
use core::slice;
// font_kit already has a wrapper around freetype called Font so use it directly
use font_kit::font::Font;
use font_kit::loaders::freetype::NativeFont;
// use harfbuzz for shaping ligatures
pub use harfbuzz::*;
use harfbuzz_sys as harfbuzz;
use std::mem;
/// font feature tag
pub fn feature_from_tag(tag: &str) -> Result<hb_feature_t> {
unsafe {
let mut feature = mem::zeroed();
ensure!(
hb_feature_from_string(
tag.as_ptr() as *const i8,
tag.len() as i32,
&mut feature as *mut _
) != 0,
"hb_feature_from_string failed for {}",
tag
);
Ok(feature)
}
}
/// Harfbuzz font
pub struct HBFont {
font: *mut hb_font_t,
}
// harfbuzz freetype integration
extern "C" {
pub fn hb_ft_font_create_referenced(face: NativeFont) -> *mut hb_font_t; // the same as hb_face_t
}
impl Drop for HBFont {
fn drop(&mut self) {
unsafe { hb_font_destroy(self.font) }
}
}
impl HBFont {
pub fn new(face: &Font) -> HBFont {
HBFont {
font: unsafe { hb_ft_font_create_referenced(face.native_font() as _) },
}
}
pub fn shape(&mut self, buffer: &HBBuffer, features: &[hb_feature_t]) {
unsafe {
hb_shape(
self.font,
buffer.buffer,
features.as_ptr(),
features.len() as u32,
);
}
}
}
/// Harfbuzz buffer
pub struct HBBuffer {
buffer: *mut hb_buffer_t,
}
impl Drop for HBBuffer {
fn drop(&mut self) {
unsafe { hb_buffer_destroy(self.buffer) }
}
}
impl HBBuffer {
pub fn new() -> Result<HBBuffer> {
let hb_buf = unsafe { hb_buffer_create() };
ensure!(
unsafe { hb_buffer_allocation_successful(hb_buf) } != 0,
"hb_buffer_create failed!"
);
Ok(HBBuffer { buffer: hb_buf })
}
pub fn guess_segments_properties(&mut self) {
unsafe { hb_buffer_guess_segment_properties(self.buffer) };
}
pub fn add_utf8(&mut self, s: &[u8]) {
unsafe {
hb_buffer_add_utf8(
self.buffer,
s.as_ptr() as *const i8,
s.len() as i32,
0,
s.len() as i32,
);
}
}
pub fn add_str(&mut self, s: &str) {
self.add_utf8(s.as_bytes());
}
pub fn get_glyph_infos(&mut self) -> &[hb_glyph_info_t] {
unsafe {
let mut len: u32 = 0;
let info = hb_buffer_get_glyph_infos(self.buffer, &mut len as *mut u32);
slice::from_raw_parts(info, len as usize)
}
}
pub fn get_glyph_positions(&mut self) -> &[hb_glyph_position_t] {
unsafe {
let mut len: u32 = 0;
let info = hb_buffer_get_glyph_positions(self.buffer, &mut len as *mut u32);
slice::from_raw_parts(info, len as usize)
}
}
}

View File

@ -39,4 +39,5 @@ pub mod directories;
pub mod error;
pub mod font;
pub mod formatter;
pub mod hb_wrapper;
pub mod utils;