mirror of
https://github.com/wez/wezterm.git
synced 2024-11-24 07:46:59 +03:00
fonts: extract the shaper concept
This commit is contained in:
parent
41d10811a6
commit
b5a59a18fa
@ -11,6 +11,7 @@ use std::rc::Rc;
|
||||
|
||||
pub mod loader;
|
||||
pub mod rasterizer;
|
||||
pub mod shaper;
|
||||
|
||||
pub mod system;
|
||||
pub use self::system::*;
|
||||
|
181
src/font/shaper/harfbuzz.rs
Normal file
181
src/font/shaper/harfbuzz.rs
Normal file
@ -0,0 +1,181 @@
|
||||
use crate::font::ftwrap;
|
||||
use crate::font::hbwrap as harfbuzz;
|
||||
use crate::font::shaper::FontShaper;
|
||||
use crate::font::system::GlyphInfo;
|
||||
use failure::{bail, Fallible};
|
||||
use log::{debug, error};
|
||||
use std::cell::RefCell;
|
||||
|
||||
struct FontPair {
|
||||
face: ftwrap::Face,
|
||||
font: harfbuzz::Font,
|
||||
}
|
||||
|
||||
pub struct HarfbuzzShaper {
|
||||
fonts: Vec<RefCell<FontPair>>,
|
||||
}
|
||||
|
||||
impl HarfbuzzShaper {
|
||||
fn do_shape(
|
||||
&self,
|
||||
font_idx: crate::font::system::FallbackIdx,
|
||||
s: &str,
|
||||
) -> Fallible<Vec<GlyphInfo>> {
|
||||
let features = vec![
|
||||
// kerning
|
||||
harfbuzz::feature_from_string("kern")?,
|
||||
// ligatures
|
||||
harfbuzz::feature_from_string("liga")?,
|
||||
// contextual ligatures
|
||||
harfbuzz::feature_from_string("clig")?,
|
||||
];
|
||||
|
||||
let mut buf = harfbuzz::Buffer::new()?;
|
||||
buf.set_script(harfbuzz::hb_script_t::HB_SCRIPT_LATIN);
|
||||
buf.set_direction(harfbuzz::hb_direction_t::HB_DIRECTION_LTR);
|
||||
buf.set_language(harfbuzz::language_from_string("en")?);
|
||||
buf.add_str(s);
|
||||
|
||||
{
|
||||
match self.fonts.get(font_idx) {
|
||||
Some(pair) => {
|
||||
pair.borrow_mut()
|
||||
.font
|
||||
.shape(&mut buf, Some(features.as_slice()));
|
||||
}
|
||||
None => {
|
||||
let chars: Vec<u32> = s.chars().map(|c| c as u32).collect();
|
||||
bail!("No more fallbacks while shaping {:x?}", chars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let infos = buf.glyph_infos();
|
||||
let positions = buf.glyph_positions();
|
||||
|
||||
let mut cluster = Vec::new();
|
||||
|
||||
let mut last_text_pos = None;
|
||||
let mut first_fallback_pos = None;
|
||||
|
||||
// Compute the lengths of the text clusters.
|
||||
// Ligatures and combining characters mean
|
||||
// that a single glyph can take the place of
|
||||
// multiple characters. The 'cluster' member
|
||||
// of the glyph info is set to the position
|
||||
// in the input utf8 text, so we make a pass
|
||||
// over the set of clusters to look for differences
|
||||
// greater than 1 and backfill the length of
|
||||
// the corresponding text fragment. We need
|
||||
// the fragments to properly handle fallback,
|
||||
// and they're handy to have for debugging
|
||||
// purposes too.
|
||||
let mut sizes = Vec::with_capacity(s.len());
|
||||
for (i, info) in infos.iter().enumerate() {
|
||||
let pos = info.cluster as usize;
|
||||
let mut size = 1;
|
||||
if let Some(last_pos) = last_text_pos {
|
||||
let diff = pos - last_pos;
|
||||
if diff > 1 {
|
||||
sizes[i - 1] = diff;
|
||||
}
|
||||
} else if pos != 0 {
|
||||
size = pos;
|
||||
}
|
||||
last_text_pos = Some(pos);
|
||||
sizes.push(size);
|
||||
}
|
||||
if let Some(last_pos) = last_text_pos {
|
||||
let diff = s.len() - last_pos;
|
||||
if diff > 1 {
|
||||
let last = sizes.len() - 1;
|
||||
sizes[last] = diff;
|
||||
}
|
||||
}
|
||||
//debug!("sizes: {:?}", sizes);
|
||||
|
||||
// Now make a second pass to determine if we need
|
||||
// to perform fallback to a later font.
|
||||
// We can determine this by looking at the codepoint.
|
||||
for (i, info) in infos.iter().enumerate() {
|
||||
let pos = info.cluster as usize;
|
||||
if info.codepoint == 0 {
|
||||
if first_fallback_pos.is_none() {
|
||||
// Start of a run that needs fallback
|
||||
first_fallback_pos = Some(pos);
|
||||
}
|
||||
} else if let Some(start_pos) = first_fallback_pos {
|
||||
// End of a fallback run
|
||||
//debug!("range: {:?}-{:?} needs fallback", start, pos);
|
||||
|
||||
let substr = &s[start_pos..pos];
|
||||
let mut shape = match self.do_shape(font_idx + 1, substr) {
|
||||
Ok(shape) => Ok(shape),
|
||||
Err(e) => {
|
||||
error!("{:?} for {:?}", e, substr);
|
||||
if font_idx == 0 && s == "?" {
|
||||
bail!("unable to find any usable glyphs for `?` in font_idx 0");
|
||||
}
|
||||
self.do_shape(0, "?")
|
||||
}
|
||||
}?;
|
||||
|
||||
// Fixup the cluster member to match our current offset
|
||||
for mut info in &mut shape {
|
||||
info.cluster += start_pos as u32;
|
||||
}
|
||||
cluster.append(&mut shape);
|
||||
|
||||
first_fallback_pos = None;
|
||||
}
|
||||
if info.codepoint != 0 {
|
||||
if s.is_char_boundary(pos) && s.is_char_boundary(pos + sizes[i]) {
|
||||
let text = &s[pos..pos + sizes[i]];
|
||||
//debug!("glyph from `{}`", text);
|
||||
cluster.push(GlyphInfo::new(text, font_idx, info, &positions[i]));
|
||||
} else {
|
||||
cluster.append(&mut self.do_shape(0, "?")?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if we started and didn't finish a
|
||||
// fallback run.
|
||||
if let Some(start_pos) = first_fallback_pos {
|
||||
let substr = &s[start_pos..];
|
||||
if false {
|
||||
debug!(
|
||||
"at end {:?}-{:?} needs fallback {}",
|
||||
start_pos,
|
||||
s.len() - 1,
|
||||
substr,
|
||||
);
|
||||
}
|
||||
let mut shape = match self.do_shape(font_idx + 1, substr) {
|
||||
Ok(shape) => Ok(shape),
|
||||
Err(e) => {
|
||||
error!("{:?} for {:?}", e, substr);
|
||||
if font_idx == 0 && s == "?" {
|
||||
bail!("unable to find any usable glyphs for `?` in font_idx 0");
|
||||
}
|
||||
self.do_shape(0, "?")
|
||||
}
|
||||
}?;
|
||||
// Fixup the cluster member to match our current offset
|
||||
for mut info in &mut shape {
|
||||
info.cluster += start_pos as u32;
|
||||
}
|
||||
cluster.append(&mut shape);
|
||||
}
|
||||
|
||||
//debug!("shaped: {:#?}", cluster);
|
||||
|
||||
Ok(cluster)
|
||||
}
|
||||
}
|
||||
|
||||
impl FontShaper for HarfbuzzShaper {
|
||||
fn shape(&self, text: &str) -> Fallible<Vec<GlyphInfo>> {
|
||||
self.do_shape(0, text)
|
||||
}
|
||||
}
|
9
src/font/shaper/mod.rs
Normal file
9
src/font/shaper/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use crate::font::system::GlyphInfo;
|
||||
use failure::Fallible;
|
||||
|
||||
pub mod harfbuzz;
|
||||
|
||||
pub trait FontShaper {
|
||||
/// Shape text and return a vector of GlyphInfo
|
||||
fn shape(&self, text: &str) -> Fallible<Vec<GlyphInfo>>;
|
||||
}
|
Loading…
Reference in New Issue
Block a user