1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-11 14:25:57 +03:00

Render text on sdl using harfbuzz and freetype

This commit is contained in:
Wez Furlong 2018-01-14 23:34:59 -08:00
parent c53ca64c33
commit d82860ef5a
9 changed files with 592 additions and 0 deletions

3
.gitignore vendored Normal file
View File

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

17
.rustfmt.toml Normal file
View File

@ -0,0 +1,17 @@
# Run `rustfmt --config-help` to see available config options.
# Please keep these in alphabetical order.
closure_block_indent_threshold = 1
condense_wildcard_suffixes = true
error_on_line_overflow = false
error_on_line_overflow_comments = false
# Too many derives can cause line length limits to be exceeded. rustfmt-nightly
# 0.2.5 doesn't appear to pay attention to this.
merge_derives = false
reorder_imported_names = true
reorder_imports = true
reorder_imports_in_group = true
# Override the Nuclide/Atom default tab spacing.
tab_spaces = 4
use_try_shorthand = true
write_mode = "replace"
max_width = 80

13
Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
authors = ["Wez Furlong <wez@wezfurlong.org>"]
name = "wterm"
version = "0.1.0"
[dependencies]
failure = "0.1.1"
hexdump = "0.1.0"
sdl2 = {version="0.31.0", features=["bundled"]}
vte = "0.3.2"
[dependencies.font]
path = "font"

10
font/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
authors = ["Wez Furlong <wez@wezfurlong.org>"]
name = "font"
version = "0.1.0"
[dependencies]
failure = "0.1.1"
freetype = "0.3.0"
harfbuzz-sys = "0.1.15"
servo-fontconfig = "0.4.0"

111
font/src/ft/ftwrap.rs Normal file
View File

@ -0,0 +1,111 @@
//! Higher level freetype bindings
use failure::Error;
pub use freetype::freetype::*;
use std::ffi::CString;
use std::ptr;
/// Translate an error and value into a result
fn ft_result<T>(err: FT_Error, t: T) -> Result<T, Error> {
if err.succeeded() {
Ok(t)
} else {
Err(format_err!("FreeType error {:?}", err))
}
}
pub struct Face {
pub face: FT_Face,
}
impl Drop for Face {
fn drop(&mut self) {
unsafe {
FT_Done_Face(self.face);
}
}
}
impl Face {
pub fn set_char_size(
&mut self,
char_width: FT_F26Dot6,
char_height: FT_F26Dot6,
horz_resolution: FT_UInt,
vert_resolution: FT_UInt,
) -> Result<(), Error> {
ft_result(
unsafe {
FT_Set_Char_Size(
self.face,
char_width,
char_height,
horz_resolution,
vert_resolution,
)
},
(),
)
}
pub fn load_and_render_glyph(
&mut self,
glyph_index: FT_UInt,
load_flags: FT_Int32,
render_mode: FT_Render_Mode,
) -> Result<&FT_GlyphSlotRec_, Error> {
unsafe {
let res = FT_Load_Glyph(self.face, glyph_index, load_flags);
if res.succeeded() {
FT_Render_Glyph((*self.face).glyph, render_mode);
}
ft_result(res, &*(*self.face).glyph)
}
}
}
pub struct Library {
lib: FT_Library,
}
impl Drop for Library {
fn drop(&mut self) {
unsafe {
FT_Done_FreeType(self.lib);
}
}
}
impl Library {
pub fn new() -> Result<Library, Error> {
let mut lib = ptr::null_mut();
let res = unsafe { FT_Init_FreeType(&mut lib as *mut _) };
let lib = ft_result(res, lib)?;
Ok(Library { lib })
}
pub fn new_face<P>(
&self,
path: P,
face_index: FT_Long,
) -> Result<Face, Error>
where
P: Into<Vec<u8>>,
{
let mut face = ptr::null_mut();
let path =CString::new(path.into())?;
let res = unsafe {
FT_New_Face(self.lib, path.as_ptr(), face_index, &mut face as *mut _)
};
Ok(Face { face: ft_result(res, face)? })
}
pub fn set_lcd_filter(
&mut self,
filter: FT_LcdFilter,
) -> Result<(), Error> {
unsafe {
ft_result(FT_Library_SetLcdFilter(self.lib, filter), ())
}
}
}

167
font/src/ft/hbwrap.rs Normal file
View File

@ -0,0 +1,167 @@
//! Higher level harfbuzz bindings
use failure::Error;
pub use harfbuzz_sys::*;
use std::mem;
use std::ptr;
use std::slice;
pub fn language_from_string(s: &str) -> Result<hb_language_t, Error> {
unsafe {
let lang = hb_language_from_string(s.as_ptr() as *const i8, s.len() as i32);
ensure!(
!lang.is_null(),
"failed to convert {} to language"
);
Ok(lang)
}
}
pub fn feature_from_string(s: &str) -> Result<hb_feature_t, Error> {
unsafe {
let mut feature = mem::zeroed();
ensure!(
hb_feature_from_string(
s.as_ptr() as *const i8,
s.len() as i32,
&mut feature as *mut _,
) != 0,
"failed to create feature from {}",
s
);
Ok(feature)
}
}
pub struct Font {
font: *mut hb_font_t,
}
impl Drop for Font {
fn drop(&mut self) {
unsafe {
hb_font_destroy(self.font);
}
}
}
impl Font {
/// Create a harfbuzz face from a freetype font
pub fn new(face: &::ft::ftwrap::Face) -> Font {
// hb_ft_font_create_referenced always returns a
// pointer to something, or derefs a nullptr internally
// if everything fails, so there's nothing for us to
// test here.
Font { font: unsafe { hb_ft_font_create_referenced(face.face) } }
}
/// Perform shaping. On entry, Buffer holds the text to shape.
/// Once done, Buffer holds the output glyph and position info
pub fn shape(&mut self, buf: &mut Buffer, features: Option<&[hb_feature_t]>) {
unsafe {
if let Some(features) = features {
hb_shape(self.font, buf.buf, features.as_ptr(), features.len() as u32)
} else {
hb_shape(self.font, buf.buf, ptr::null(), 0)
}
}
}
}
pub struct Buffer {
buf: *mut hb_buffer_t,
}
impl Drop for Buffer {
fn drop(&mut self) {
unsafe {
hb_buffer_destroy(self.buf);
}
}
}
impl Buffer {
/// Create a new buffer
pub fn new() -> Result<Buffer, Error> {
let buf = unsafe { hb_buffer_create() };
ensure!(
unsafe { hb_buffer_allocation_successful(buf) } != 0,
"hb_buffer_create failed"
);
Ok(Buffer { buf })
}
/// Reset the buffer back to its initial post-creation state
pub fn reset(&mut self) {
unsafe {
hb_buffer_reset(self.buf);
}
}
pub fn set_direction(&mut self, direction: hb_direction_t) {
unsafe {
hb_buffer_set_direction(self.buf, direction);
}
}
pub fn set_script(&mut self, script: hb_script_t) {
unsafe {
hb_buffer_set_script(self.buf, script);
}
}
pub fn set_language(&mut self, lang: hb_language_t) {
unsafe {
hb_buffer_set_language(self.buf, lang);
}
}
pub fn add(&mut self, codepoint: hb_codepoint_t, cluster: u32) {
unsafe {
hb_buffer_add(self.buf, codepoint, cluster);
}
}
pub fn add_utf8(&mut self, buf: &[u8]) {
unsafe {
hb_buffer_add_utf8(
self.buf,
buf.as_ptr() as *const i8,
buf.len() as i32,
0,
buf.len() as i32,
);
}
}
pub fn add_str(&mut self, s: &str) {
unsafe {
hb_buffer_add_utf8(
self.buf,
s.as_ptr() as *const i8,
s.len() as i32,
0,
s.len() as i32,
);
}
}
/// Returns glyph information. This is only valid after calling
/// font->shape() on this buffer instance.
pub fn glyph_infos(&self) -> &[hb_glyph_info_t] {
unsafe {
let mut len : u32 = 0;
let info = hb_buffer_get_glyph_infos(self.buf, &mut len as *mut _);
slice::from_raw_parts(info, len as usize)
}
}
/// Returns glyph positions. This is only valid after calling
/// font->shape() on this buffer instance.
pub fn glyph_positions(&self) -> &[hb_glyph_position_t] {
unsafe {
let mut len : u32 = 0;
let pos = hb_buffer_get_glyph_positions(self.buf, &mut len as *mut _);
slice::from_raw_parts(pos, len as usize)
}
}
}

17
font/src/ft/mod.rs Normal file
View File

@ -0,0 +1,17 @@
use failure::Error;
pub mod ftwrap;
pub mod hbwrap;
use self::ftwrap::Library;
pub struct FTEngine {
lib: Library,
}
impl ::FontEngine for FTEngine {
fn new() -> Result<FTEngine, Error> {
Ok(FTEngine {
lib: Library::new()?,
})
}
}

47
font/src/lib.rs Normal file
View File

@ -0,0 +1,47 @@
#[macro_use]
extern crate failure;
extern crate harfbuzz_sys;
#[cfg(not(target_os = "macos"))]
extern crate fontconfig; // from servo-fontconfig
#[cfg(not(target_os = "macos"))]
extern crate freetype;
use failure::Error;
#[cfg(not(target_os = "macos"))]
pub mod ft;
#[cfg(not(target_os = "macos"))]
pub use ft::FTEngine as Engine;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FontDescription {
name: String,
}
/// A user provided font description that can be used
/// to lookup a font
impl FontDescription {
pub fn new<S>(name: S) -> FontDescription
where
S: Into<String>,
{
FontDescription {
name: name.into()
}
}
}
trait FontEngine {
fn new() -> Result<Self, Error>
where
Self: Sized;
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

207
src/main.rs Normal file
View File

@ -0,0 +1,207 @@
#[macro_use]
extern crate failure;
extern crate hexdump;
use failure::Error;
extern crate font;
extern crate sdl2;
use sdl2::event::{Event, WindowEvent};
use sdl2::keyboard::Keycode;
use sdl2::pixels::{Color, PixelFormatEnum};
use sdl2::rect::Rect;
use sdl2::render::{BlendMode, Texture, TextureCreator};
use font::ft::ftwrap;
use font::ft::hbwrap;
use std::mem;
use std::slice;
struct Glyph<'a> {
tex: Texture<'a>,
width: u32,
height: u32,
x_advance: i32,
y_advance: i32,
x_offset: i32,
y_offset: i32,
bearing_x: i32,
bearing_y: i32,
}
impl<'a> Glyph<'a> {
fn new<T>(
texture_creator: &'a TextureCreator<T>,
glyph: &ftwrap::FT_GlyphSlotRec_,
pos: &hbwrap::hb_glyph_position_t,
) -> Result<Glyph<'a>, Error> {
let mode: ftwrap::FT_Pixel_Mode =
unsafe { mem::transmute(glyph.bitmap.pixel_mode as u32) };
// pitch is the number of bytes per source row
let pitch = glyph.bitmap.pitch.abs() as usize;
match mode {
ftwrap::FT_Pixel_Mode::FT_PIXEL_MODE_LCD => {
let width = (glyph.bitmap.width as usize) / 3;
let height = glyph.bitmap.rows as usize;
let mut tex = texture_creator
.create_texture_static(
Some(PixelFormatEnum::BGR24),
width as u32,
height as u32,
)
.map_err(failure::err_msg)?;
let data = unsafe {
slice::from_raw_parts(glyph.bitmap.buffer, height * pitch)
};
tex.update(None, data, pitch)?;
Ok(Glyph {
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,
bearing_x: glyph.bitmap_left,
bearing_y: glyph.bitmap_top,
})
}
mode @ _ => bail!("unhandled pixel mode: {:?}", mode),
}
}
}
fn glyphs_for_text<'a, T>(
texture_creator: &'a TextureCreator<T>,
s: &str,
) -> Result<Vec<Glyph<'a>>, 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 features = vec![
// kerning
hbwrap::feature_from_string("kern")?,
// ligatures
hbwrap::feature_from_string("liga")?,
// contextual ligatures
hbwrap::feature_from_string("clig")?,
];
font.shape(&mut buf, Some(features.as_slice()));
let infos = buf.glyph_infos();
let positions = buf.glyph_positions();
let mut result = 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);
}
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
.window("wterm", 1024, 768)
.resizable()
.opengl()
.build()?;
let mut canvas = window.into_canvas().build()?;
let texture_creator = canvas.texture_creator();
let glyphs = glyphs_for_text(&texture_creator, "foo->bar();")?;
for event in sdl_context
.event_pump()
.map_err(failure::err_msg)?
.wait_iter()
{
match event {
Event::KeyDown { keycode: Some(Keycode::Escape), .. } |
Event::Quit { .. } => break,
Event::Window { win_event: WindowEvent::Resized(..), .. } => {
println!("resize");
}
Event::Window { win_event: WindowEvent::Exposed, .. } => {
println!("exposed");
canvas.set_draw_color(Color::RGBA(0, 0, 0, 255));
canvas.clear();
canvas.set_blend_mode(BlendMode::Blend);
let mut x = 10i32;
let mut y = 100i32;
for g in glyphs.iter() {
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,
g.width,
g.height,
)),
)
.map_err(failure::err_msg)?;
x += g.x_advance;
y += g.y_advance;
}
canvas.present();
}
_ => {}
}
}
Ok(())
}
fn main() {
run().unwrap();
}