1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 05:12:40 +03:00

Add fontconfig support to locate fonts

Also restructure things a little bit to facilitate more robust
fallback implementation.
This commit is contained in:
Wez Furlong 2018-01-15 17:32:31 -08:00
parent d82860ef5a
commit 02bab4fcc7
6 changed files with 479 additions and 76 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/Cargo.lock
/target/
**/*.rs.bk
.*.sw*

View File

@ -6,7 +6,7 @@ version = "0.1.0"
[dependencies]
failure = "0.1.1"
hexdump = "0.1.0"
sdl2 = {version="0.31.0", features=["bundled"]}
sdl2 = {version="0.31.0", features=["bundled", "static-link"]}
vte = "0.3.2"
[dependencies.font]

266
font/src/ft/fcwrap.rs Normal file
View File

@ -0,0 +1,266 @@
//! Slightly higher level helper for fontconfig
use failure::{self, Error};
pub use fontconfig::fontconfig::*;
use std::ffi::{CStr, CString};
use std::mem;
use std::ptr;
static FC_MONO: i32 = 100;
pub struct FontSet {
fonts: *mut FcFontSet,
}
impl Drop for FontSet {
fn drop(&mut self) {
unsafe {
FcFontSetDestroy(self.fonts);
}
}
}
pub struct FontSetIter<'a> {
set: &'a FontSet,
position: isize,
}
impl<'a> Iterator for FontSetIter<'a> {
type Item = Pattern;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
if self.position == (*self.set.fonts).nfont as isize {
None
} else {
let pat = *(*self.set.fonts)
.fonts
.offset(self.position)
.as_mut()
.unwrap();
FcPatternReference(pat);
self.position += 1;
Some(Pattern { pat })
}
}
}
}
impl FontSet {
pub fn iter(&self) -> FontSetIter {
FontSetIter {
set: &self,
position: 0,
}
}
pub fn add(&mut self, pat: &Pattern) {
unsafe {
FcFontSetAdd(self.fonts, pat.pat);
}
}
}
#[repr(C)]
pub enum MatchKind {
Pattern = FcMatchPattern as isize,
Font = FcMatchFont as isize,
Scan = FcMatchScan as isize,
}
pub struct FcResultWrap(FcResult);
impl FcResultWrap {
pub fn succeeded(&self) -> bool {
self.0 == FcResultMatch
}
pub fn as_err(&self) -> Error {
// the compiler thinks we defined these globals, when all
// we did was import them from elsewhere
#[allow(non_upper_case_globals)]
match self.0 {
FcResultMatch => failure::err_msg("FcResultMatch"),
FcResultNoMatch => failure::err_msg("FcResultNoMatch"),
FcResultTypeMismatch => failure::err_msg("FcResultTypeMismatch"),
FcResultNoId => failure::err_msg("FcResultNoId"),
FcResultOutOfMemory => failure::err_msg("FcResultOutOfMemory"),
_ => format_err!("FcResult holds invalid value {}", self.0),
}
}
pub fn result<T>(&self, t: T) -> Result<T, Error> {
match self.0 {
FcResultMatch => Ok(t),
_ => Err(self.as_err()),
}
}
}
pub struct Pattern {
pat: *mut FcPattern,
}
impl Pattern {
pub fn new() -> Result<Pattern, Error> {
unsafe {
let p = FcPatternCreate();
ensure!(!p.is_null(), "FcPatternCreate failed");
Ok(Pattern { pat: p })
}
}
pub fn add_string(&mut self, key: &str, value: &str) -> Result<(), Error> {
let key = CString::new(key)?;
let value = CString::new(value)?;
unsafe {
ensure!(
FcPatternAddString(
self.pat,
key.as_ptr(),
value.as_ptr() as *const u8,
) != 0,
"failed to add string property {:?} -> {:?}",
key,
value
);
Ok(())
}
}
pub fn add_integer(&mut self, key: &str, value: i32) -> Result<(), Error> {
let key = CString::new(key)?;
unsafe {
ensure!(
FcPatternAddInteger(self.pat, key.as_ptr(), value) != 0,
"failed to set integer property {:?} -> {}",
key,
value
);
Ok(())
}
}
pub fn family(&mut self, family: &str) -> Result<(), Error> {
self.add_string("family", family)
}
pub fn monospace(&mut self) -> Result<(), Error> {
self.add_integer("spacing", FC_MONO)
}
pub fn print(&self) {
unsafe {
FcPatternPrint(self.pat);
}
}
pub fn format(&self, fmt: &str) -> Result<String, Error> {
let fmt = CString::new(fmt)?;
unsafe {
let s = FcPatternFormat(self.pat, fmt.as_ptr() as *const u8);
ensure!(!s.is_null(), "failed to format pattern");
let res = CStr::from_ptr(s as *const i8).to_string_lossy().into_owned();
FcStrFree(s);
Ok(res)
}
}
pub fn render_prepare(&self, pat: &Pattern) -> Result<Pattern, Error> {
unsafe {
let pat = FcFontRenderPrepare(ptr::null_mut(), self.pat, pat.pat);
ensure!(!pat.is_null(), "failed to prepare pattern");
Ok(Pattern { pat })
}
}
pub fn parse(pattern: &str) -> Result<Pattern, Error> {
let pattern = CString::new(pattern)?;
unsafe {
let p = FcNameParse(pattern.as_ptr() as *const u8);
ensure!(!p.is_null(), "failed to parse {:?}", pattern);
Ok(Pattern { pat: p })
}
}
pub fn config_substitute(
&mut self,
match_kind: MatchKind,
) -> Result<(), Error> {
unsafe {
ensure!(
FcConfigSubstitute(
ptr::null_mut(),
self.pat,
mem::transmute(match_kind),
) != 0,
"FcConfigSubstitute failed"
);
Ok(())
}
}
pub fn default_substitute(&mut self) {
unsafe {
FcDefaultSubstitute(self.pat);
}
}
pub fn find_match(&self) -> Result<Pattern, Error> {
unsafe {
let mut res = FcResultWrap(0);
let pat = FcFontMatch(
ptr::null_mut(),
self.pat,
&mut res.0 as *mut _,
);
res.result(Pattern{pat})
}
}
pub fn sort(&self, trim: bool) -> Result<FontSet, Error> {
unsafe {
let mut res = FcResultWrap(0);
let fonts = FcFontSort(
ptr::null_mut(),
self.pat,
if trim { 1 } else { 0 },
ptr::null_mut(),
&mut res.0 as *mut _,
);
res.result(FontSet{fonts})
}
}
pub fn get_file(&self) -> Result<String, Error> {
self.get_string("file")
}
pub fn get_string(&self, key: &str) -> Result<String, Error> {
unsafe {
let key = CString::new(key)?;
let mut ptr: *mut u8 = ptr::null_mut();
let res = FcResultWrap(FcPatternGetString(
self.pat,
key.as_ptr(),
0,
&mut ptr as *mut *mut u8,
));
if !res.succeeded() {
Err(res.as_err())
} else {
Ok(CStr::from_ptr(ptr as *const i8).to_string_lossy().into_owned())
}
}
}
}
impl Drop for Pattern {
fn drop(&mut self) {
unsafe {
FcPatternDestroy(self.pat);
}
}
}

View File

@ -47,6 +47,12 @@ impl Face {
)
}
pub fn has_codepoint(&self, cp: char) -> bool {
unsafe {
FT_Get_Char_Index(self.face, cp as u64) != 0
}
}
pub fn load_and_render_glyph(
&mut self,
glyph_index: FT_UInt,
@ -92,7 +98,7 @@ impl Library {
P: Into<Vec<u8>>,
{
let mut face = ptr::null_mut();
let path =CString::new(path.into())?;
let path = CString::new(path.into())?;
let res = unsafe {
FT_New_Face(self.lib, path.as_ptr(), face_index, &mut face as *mut _)

View File

@ -1,6 +1,7 @@
use failure::Error;
pub mod ftwrap;
pub mod hbwrap;
pub mod fcwrap;
use self::ftwrap::Library;
pub struct FTEngine {

View File

@ -11,19 +11,44 @@ use sdl2::pixels::{Color, PixelFormatEnum};
use sdl2::rect::Rect;
use sdl2::render::{BlendMode, Texture, TextureCreator};
use font::ft::fcwrap;
use font::ft::ftwrap;
use font::ft::hbwrap;
use std::mem;
use std::slice;
struct Glyph<'a> {
tex: Texture<'a>,
width: u32,
height: u32,
#[derive(Copy, Clone, Debug)]
struct GlyphInfo {
glyph_pos: u32,
cluster: u32,
x_advance: i32,
y_advance: i32,
x_offset: i32,
y_offset: i32,
}
impl GlyphInfo {
pub fn new(
info: &hbwrap::hb_glyph_info_t,
pos: &hbwrap::hb_glyph_position_t,
) -> GlyphInfo {
GlyphInfo {
glyph_pos: info.codepoint,
cluster: info.cluster,
x_advance: pos.x_advance / 64,
y_advance: pos.y_advance / 64,
x_offset: pos.x_offset / 64,
y_offset: pos.y_offset / 64,
}
}
}
struct Glyph<'a> {
tex: Texture<'a>,
width: u32,
height: u32,
info: GlyphInfo,
bearing_x: i32,
bearing_y: i32,
}
@ -32,7 +57,7 @@ impl<'a> Glyph<'a> {
fn new<T>(
texture_creator: &'a TextureCreator<T>,
glyph: &ftwrap::FT_GlyphSlotRec_,
pos: &hbwrap::hb_glyph_position_t,
info: &GlyphInfo,
) -> Result<Glyph<'a>, Error> {
let mode: ftwrap::FT_Pixel_Mode =
unsafe { mem::transmute(glyph.bitmap.pixel_mode as u32) };
@ -60,10 +85,7 @@ impl<'a> Glyph<'a> {
tex,
width: width as u32,
height: height as u32,
x_advance: pos.x_advance / 64,
y_advance: pos.y_advance / 64,
x_offset: pos.x_offset / 64,
y_offset: pos.y_offset / 64,
info: *info,
bearing_x: glyph.bitmap_left,
bearing_y: glyph.bitmap_top,
})
@ -73,24 +95,88 @@ impl<'a> Glyph<'a> {
}
}
fn glyphs_for_text<'a, T>(
texture_creator: &'a TextureCreator<T>,
s: &str,
) -> Result<Vec<Glyph<'a>>, Error> {
struct FontInfo {
face: ftwrap::Face,
font: hbwrap::Font,
}
struct FontHolder {
lib: ftwrap::Library,
size: i64,
pattern: fcwrap::Pattern,
font_list: fcwrap::FontSet,
fonts: Vec<FontInfo>,
}
#[derive(Debug)]
struct ShapedCluster {
/// index into FontHolder.fonts
font_idx: usize,
/// holds shaped results
info: Vec<GlyphInfo>,
}
impl Drop for FontHolder {
fn drop(&mut self) {
// Ensure that we drop the fonts before we drop the
// library, otherwise we will end up faulting
self.fonts.clear();
}
}
impl FontHolder {
fn new(size: i64) -> Result<FontHolder, Error> {
let mut lib = ftwrap::Library::new()?;
lib.set_lcd_filter(
ftwrap::FT_LcdFilter::FT_LCD_FILTER_DEFAULT,
)?;
let mut face =
lib.new_face("/home/wez/.fonts/OperatorMonoLig-Book.otf", 0)?;
face.set_char_size(0, 36 * 64, 96, 96)?;
let mut font = hbwrap::Font::new(&face);
let lang = hbwrap::language_from_string("en")?;
let mut buf = hbwrap::Buffer::new()?;
buf.set_script(hbwrap::HB_SCRIPT_LATIN);
buf.set_direction(hbwrap::HB_DIRECTION_LTR);
buf.set_language(lang);
buf.add_str(s);
let mut pattern = fcwrap::Pattern::new()?;
pattern.family("Operator Mono SSm Lig")?;
pattern.family("Emoji One")?;
pattern.monospace()?;
pattern.config_substitute(fcwrap::MatchKind::Pattern)?;
pattern.default_substitute();
let font_list = pattern.sort(true)?;
Ok(FontHolder {
lib,
size,
font_list,
pattern,
fonts: Vec::new(),
})
}
fn load_next_fallback(&mut self) -> Result<(), Error> {
let idx = self.fonts.len();
let pat = self.font_list.iter().nth(idx).ok_or(failure::err_msg(
"no more fallbacks",
))?;
let pat = self.pattern.render_prepare(&pat)?;
let file = pat.get_file()?;
let mut face = self.lib.new_face(file, 0)?;
face.set_char_size(0, self.size * 64, 96, 96)?;
let font = hbwrap::Font::new(&face);
self.fonts.push(FontInfo { face, font });
Ok(())
}
fn get_font(&mut self, idx: usize) -> Result<&mut FontInfo, Error> {
if idx >= self.fonts.len() {
self.load_next_fallback()?;
ensure!(
idx < self.fonts.len(),
"should not ask for a font later than the next prepared font"
);
}
Ok(&mut self.fonts[idx])
}
fn shape(&mut self, s: &str) -> Result<Vec<ShapedCluster>, Error> {
let features = vec![
// kerning
hbwrap::feature_from_string("kern")?,
@ -99,53 +185,95 @@ fn glyphs_for_text<'a, T>(
// contextual ligatures
hbwrap::feature_from_string("clig")?,
];
font.shape(&mut buf, Some(features.as_slice()));
let mut buf = hbwrap::Buffer::new()?;
buf.set_script(hbwrap::HB_SCRIPT_LATIN);
buf.set_direction(hbwrap::HB_DIRECTION_LTR);
buf.set_language(hbwrap::language_from_string("en")?);
buf.add_str(s);
let font_idx = 0;
self.shape_with_font(font_idx, &mut buf, &features)?;
let infos = buf.glyph_infos();
let positions = buf.glyph_positions();
let mut result = Vec::new();
let mut cluster = Vec::new();
for (i, info) in infos.iter().enumerate() {
let pos = &positions[i];
println!(
"info {} glyph_pos={}, cluster={} x_adv={} y_adv={} x_off={} y_off={}",
i,
info.codepoint,
info.cluster,
pos.x_advance,
pos.y_advance,
pos.x_offset,
pos.y_offset
);
let glyph = face.load_and_render_glyph(
info.codepoint,
(ftwrap::FT_LOAD_COLOR) as i32,
ftwrap::FT_Render_Mode::FT_RENDER_MODE_LCD,
)?;
let g = Glyph::new(texture_creator, glyph, pos)?;
/*
println!(
"width={} height={} advx={} advy={} bearing={},{}",
g.width,
g.height,
g.x_advance,
g.y_advance,
g.bearing_x,
g.bearing_y
); */
result.push(g);
// TODO: if info.codepoint == 0 here then we should
// rebuild that portion of the string and reshape it
// with the next fallback font
cluster.push(GlyphInfo::new(info, pos));
}
println!("shaped: {:?}", cluster);
Ok(vec![
ShapedCluster {
font_idx,
info: cluster,
},
])
}
fn shape_with_font(
&mut self,
idx: usize,
buf: &mut hbwrap::Buffer,
features: &Vec<hbwrap::hb_feature_t>,
) -> Result<(), Error> {
let info = self.get_font(idx)?;
info.font.shape(buf, Some(features.as_slice()));
Ok(())
}
fn load_glyph(
&mut self,
font_idx: usize,
glyph_pos: u32,
) -> Result<&ftwrap::FT_GlyphSlotRec_, Error> {
let info = &mut self.fonts[font_idx];
info.face.load_and_render_glyph(
glyph_pos,
(ftwrap::FT_LOAD_COLOR) as i32,
ftwrap::FT_Render_Mode::FT_RENDER_MODE_LCD,
)
}
}
fn glyphs_for_text<'a, T>(
texture_creator: &'a TextureCreator<T>,
s: &str,
) -> Result<Vec<Glyph<'a>>, Error> {
let mut font_holder = FontHolder::new(36)?;
let mut result = Vec::new();
for cluster in font_holder.shape(s)? {
for info in cluster.info.iter() {
if info.glyph_pos == 0 {
println!("skip: no codepoint for this one");
continue;
}
let glyph =
font_holder.load_glyph(cluster.font_idx, info.glyph_pos)?;
if glyph.bitmap.width == 0 || glyph.bitmap.rows == 0 {
println!("skip: bitmap for this has 0 dimensions {:?}", glyph);
continue;
}
let g = Glyph::new(texture_creator, glyph, info)?;
result.push(g);
}
}
Ok(result)
}
fn run() -> Result<(), Error> {
let sdl_context = sdl2::init().map_err(failure::err_msg)?;
let video_subsys = sdl_context.video().map_err(failure::err_msg)?;
let window = video_subsys
@ -155,7 +283,7 @@ fn run() -> Result<(), Error> {
.build()?;
let mut canvas = window.into_canvas().build()?;
let texture_creator = canvas.texture_creator();
let glyphs = glyphs_for_text(&texture_creator, "foo->bar();")?;
let mut glyphs = glyphs_for_text(&texture_creator, "foo->bar();")?;
for event in sdl_context
.event_pump()
@ -176,21 +304,22 @@ fn run() -> Result<(), Error> {
let mut x = 10i32;
let mut y = 100i32;
for g in glyphs.iter() {
for g in glyphs.iter_mut() {
g.tex.set_color_mod(0xb3, 0xb3, 0xb3);
canvas
.copy(
&g.tex,
Some(Rect::new(0, 0, g.width, g.height)),
Some(Rect::new(
x + g.x_offset - g.bearing_x,
y - (g.y_offset + g.bearing_y as i32) as i32,
x + g.info.x_offset - g.bearing_x,
y - (g.info.y_offset + g.bearing_y as i32) as i32,
g.width,
g.height,
)),
)
.map_err(failure::err_msg)?;
x += g.x_advance;
y += g.y_advance;
x += g.info.x_advance;
y += g.info.y_advance;
}
canvas.present();