From 476a565ae5baaec04d46b40514ab63cd62cd7505 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Thu, 26 Nov 2020 16:12:01 +0800 Subject: [PATCH] rustybuzz: replace fontdue with rustybuzz This gets rustybuzz working, which gives us proper kerning for the Arabic test. Signed-off-by: Sean Cross --- Cargo.lock | 127 +++++++++++++++++++++++++--------- Cargo.toml | 3 +- src/main.rs | 195 +++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 267 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c564a2a..d2b4fbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,104 +4,169 @@ name = "approx" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" dependencies = [ - "num-traits 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits", ] [[package]] name = "arrayvec" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" [[package]] name = "autocfg" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "byteorder" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" [[package]] -name = "fontdue" -version = "0.0.1" +name = "cc" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15" dependencies = [ - "hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "jobserver", ] [[package]] -name = "hashbrown" -version = "0.5.0" +name = "jobserver" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" [[package]] name = "libm" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" [[package]] name = "num-traits" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443c53b3c3531dfcbfa499d8893944db78474ad7a1d87fa2d94d1a2231693ac6" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", ] [[package]] name = "ordered-float" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" dependencies = [ - "num-traits 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits", ] [[package]] name = "rust-font-test" version = "0.1.0" dependencies = [ - "fontdue 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rusttype 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "stats_alloc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "rusttype", + "rustybuzz", + "stats_alloc", ] [[package]] name = "rusttype" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa38506b5cbf2fb67f915e2725cb5012f1b9a785b0ab55c4733acda5f6554ef" dependencies = [ - "approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libm 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "stb_truetype 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "approx", + "arrayvec", + "libm", + "ordered-float", + "stb_truetype", ] +[[package]] +name = "rustybuzz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e91a1066320c9d45c87f2191f78a4a4402d8cc66b896abd696ec275515c0b8" +dependencies = [ + "bitflags", + "cc", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-general-category", + "unicode-script", +] + +[[package]] +name = "smallvec" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85" + [[package]] name = "stats_alloc" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a260c96bf26273969f360c2fc2e2c7732acc2ce49d939c7243c7230c2ad179d0" [[package]] name = "stb_truetype" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824210d6fb52cbc3ad2545270ead6860c7311aa5450642b078da4515937b6f7a" dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libm 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder", + "libm", ] -[metadata] -"checksum approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" -"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" -"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" -"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" -"checksum fontdue 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3ce5d972ee28d2830f868d3e1398721e0966a32113b888d8553712b50b1d03" -"checksum hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1de41fb8dba9714efd92241565cdff73f78508c95697dd56787d3cba27e2353" -"checksum libm 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" -"checksum num-traits 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "443c53b3c3531dfcbfa499d8893944db78474ad7a1d87fa2d94d1a2231693ac6" -"checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" -"checksum rusttype 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6fa38506b5cbf2fb67f915e2725cb5012f1b9a785b0ab55c4733acda5f6554ef" -"checksum stats_alloc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "a260c96bf26273969f360c2fc2e2c7732acc2ce49d939c7243c7230c2ad179d0" -"checksum stb_truetype 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "824210d6fb52cbc3ad2545270ead6860c7311aa5450642b078da4515937b6f7a" +[[package]] +name = "ttf-parser" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7622061403fd00f0820df288e5a580e87d3ce15a1c4313c59fd1ffb77129903f" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" + +[[package]] +name = "unicode-ccc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf4a1771ba99c4c8f6e5b1a37a208e970e77ed7297e29963dd1a2303601cd33" + +[[package]] +name = "unicode-general-category" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9af028e052a610d99e066b33304625dea9613170a2563314490a4e6ec5cf7f" + +[[package]] +name = "unicode-script" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79bf4d5fc96546fdb73f9827097810bbda93b11a6770ff3a54e1f445d4135787" diff --git a/Cargo.toml b/Cargo.toml index 66990aa..485467e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -fontdue = "0.0.1" +#fontdue = "0.2.4" stats_alloc = "0.1.8" rusttype = {version = "0.8.1", default-features = false, features = ["libm-math"] } +rustybuzz = "0.2.0" diff --git a/src/main.rs b/src/main.rs index 6e87c10..9044c97 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ -use rusttype::{point, FontCollection, PositionedGlyph, Scale}; +use rusttype::{point, Font, FontCollection, PositionedGlyph, Scale}; use std::io::Write; // Add an allocator that lets us keep track of the system extern crate stats_alloc; -use stats_alloc::{StatsAlloc, Region, INSTRUMENTED_SYSTEM}; +use stats_alloc::{Region, StatsAlloc, INSTRUMENTED_SYSTEM}; use std::alloc::System; #[global_allocator] static GLOBAL: &StatsAlloc = &INSTRUMENTED_SYSTEM; @@ -16,37 +16,174 @@ fn main() { println!(); println!("RUSTTYPE TEST:"); test_rusttype(font, message, rtl); - println!("FONTDUE TEST:"); - test_fontdue(font, message, rtl); + println!("RUSTTYPE + RUSTYBUZZ TEST:"); + test_rusthbuzz(font, message); // Arabic test let font = include_bytes!("../resources/LateefRegOT.ttf"); - let message = "مرحبا بالعالم"; + let message = "مرحبا بالعالم"; // "Hello, world!" + // let message = "مممممم"; // Kerning test nonsense string let rtl = true; + // let message = "agylixm"; + // let rtl = false; println!(); println!("RUSTTYPE TEST:"); test_rusttype(font, message, rtl); - println!("FONTDUE TEST:"); - test_fontdue(font, message, rtl); + + println!("RUSTTYPE + RUSTYBUZZ TEST:"); + test_rusthbuzz(font, message); } -fn test_fontdue(font: &[u8], message: &str, _rtl: bool) { +// fn test_fontdue(font: &[u8], message: &str, _rtl: bool) { +// let reg = Region::new(&GLOBAL); +// println!("start: {:#?}", reg.change()); + +// // Parse it into the font type. +// let font = fontdue::Font::from_bytes(font, fontdue::FontSettings::default()).unwrap(); +// println!("from_bytes: {:#?}", reg.change()); + +// // Rasterize and get the layout metrics for the letter 'g' at 17px. +// let (metrics, bitmap) = font.rasterize(message.chars().nth(0).unwrap(), 17.0); +// println!("font.rasterize: {:#?}", reg.change()); + +// // Used here to ensure that the value is not +// // dropped before we check the statistics +// ::std::mem::size_of_val(&metrics); +// ::std::mem::size_of_val(&bitmap); +// ::std::mem::size_of_val(&font); +// } + +fn test_rusthbuzz(font: &[u8], message: &str) { + const FB_WIDTH: usize = 520; + const FB_HEIGHT: usize = 200; + let mut buffer = [[0f32; FB_WIDTH]; FB_HEIGHT]; + let reg = Region::new(&GLOBAL); - println!("start: {:#?}", reg.change()); - // Parse it into the font type. - let mut font = fontdue::Font::from_bytes(font).unwrap(); - println!("from_bytes: {:#?}", reg.change()); + let mut buzz_font = rustybuzz::Font::from_slice(font, 0).unwrap(); + println!("buzz_font: {:#?}", reg.change()); + buzz_font.set_pixels_per_em(Some((196, 196))); - // Rasterize and get the layout metrics for the letter 'g' at 17px. - let (metrics, bitmap) = font.rasterize(message.chars().nth(0).unwrap(), 17.0); - println!("font.rasterize: {:#?}", reg.change()); + let mut buzz_buffer = rustybuzz::UnicodeBuffer::new(); + println!("buzz_buffer: {:#?}", reg.change()); - // Used here to ensure that the value is not - // dropped before we check the statistics - ::std::mem::size_of_val(&metrics); - ::std::mem::size_of_val(&bitmap); - ::std::mem::size_of_val(&font); + buzz_buffer.push_str(message); + println!("buzz_buffer_push: {:#?}", reg.change()); + + let buzz_features = vec![]; + println!("buzz_features: {:#?}", reg.change()); + + let glyph_buffer = rustybuzz::shape(&buzz_font, &buzz_features, buzz_buffer); + println!("buzz_shape: {:#?}", reg.change()); + + let rusttype_font = Font::from_bytes(font).unwrap_or_else(|e| { + panic!("error constructing a FontCollection from bytes: {}", e); + }); + println!("fonttype_collection: {:#?}", reg.change()); + + let mut cursor_x = 0.0; + let mut cursor_y = 0.0; + + let rusttype_height = 196.0; + let rusttype_scale = rusttype::Scale { + x: rusttype_height * 1.0, + y: rusttype_height, + }; + // Adapt the harfbuzz scale to rusttype's scale. + // I'm not sure where the "1.2" comes from, but the units only *just* match. + let harfbuzz_scale = rusttype_font.units_per_em() as f32 / rusttype_height * 1.2; + + let v_metrics = rusttype_font.v_metrics(rusttype_scale); + let offset = point(0.0, v_metrics.ascent); + + // println!("font is {} units-per-em", rusttype_font.units_per_em()); + for (buzz_info, buzz_position) in glyph_buffer + .glyph_infos() + .iter() + .zip(glyph_buffer.glyph_positions().iter()) + { + // let codepoint_rs = char::try_from(buzz_info.codepoint).expect("invalid codepoint"); + let font_glyph = rusttype_font.glyph(rusttype::GlyphId(buzz_info.codepoint)); + let glyph_scaled = font_glyph.scaled(rusttype_scale); + + let x_offset = buzz_position.x_offset as f32 / harfbuzz_scale; + let y_offset = buzz_position.y_offset as f32 / harfbuzz_scale; + let x_advance = buzz_position.x_advance as f32 / harfbuzz_scale; + let y_advance = buzz_position.y_advance as f32 / harfbuzz_scale; + + let glyph_positioned = glyph_scaled.positioned(rusttype::Point { + x: (x_offset as f32) + offset.x, + y: (y_offset as f32) + offset.y, + }); + + if let Some(bb) = glyph_positioned.pixel_bounding_box() { + // println!( + // "Offset: {} / {}, {} / {} {}/{} {}/{}", + // x_offset, y_offset, + // x_advance, y_advance, + // cursor_x, cursor_y, + // bb.min.x, bb.min.y, + // ); + glyph_positioned.draw(|x, y, weight| { + let x = (x as f32) + ((cursor_x + x_offset) as f32) + (bb.min.x as f32); + let y = (y as f32) + ((cursor_y + y_offset) as f32) + (bb.min.y as f32); + + if (x < 0.0) + || (x >= (buffer[0].len() as f32)) + || (y < 0.0) + || (y >= (buffer.len() as f32)) + { + // println!("(x,y) out of bounds: ({}, {})", x, y); + return; + } + let x = x as usize; + let y = y as usize; + // if y < buffer.len() && x < buffer[0].len() { + buffer[y][x] += weight; + // } + }); + } + cursor_x += x_advance; + cursor_y += y_advance; + } + + // println!("Buffer: {:?}", buffer); + for y in (0..buffer.len() - 4).step_by(4) { + for x in (0..buffer[y].len() - 2).step_by(2) { + let mut offset = 0; + let threshold = 0.5; + + if buffer[y + 0][x + 0] > threshold { + offset |= 1 << 0; + } + if buffer[y + 1][x + 0] > threshold { + offset |= 1 << 1; + } + if buffer[y + 2][x + 0] > threshold { + offset |= 1 << 2; + } + + if buffer[y + 0][x + 1] > threshold { + offset |= 1 << 3; + } + if buffer[y + 1][x + 1] > threshold { + offset |= 1 << 4; + } + if buffer[y + 2][x + 1] > threshold { + offset |= 1 << 5; + } + + if buffer[y + 3][x + 0] > threshold { + offset |= 1 << 6; + } + if buffer[y + 3][x + 1] > threshold { + offset |= 1 << 7; + } + let pixel_br = std::char::from_u32(0x2800 + offset).unwrap(); + print!("{}", pixel_br); + } + println!(); + } } fn test_rusttype(font: &[u8], message: &str, rtl: bool) { @@ -55,6 +192,7 @@ fn test_rusttype(font: &[u8], message: &str, rtl: bool) { panic!("error constructing a FontCollection from bytes: {}", e); }); println!("collection: {:#?}", reg.change()); + let font = collection .into_font() // only succeeds if collection consists of one font .unwrap_or_else(|e| { @@ -96,8 +234,8 @@ fn test_rusttype(font: &[u8], message: &str, rtl: bool) { println!("width: {}, height: {}", width, pixel_height); // Rasterise directly into ASCII art. - let mut pixel_data = vec![b'@'; width * pixel_height]; - let mapping = b"@%#x+=:-. "; // The approximation of greyscale + let mut pixel_data = vec!['@'; width * pixel_height]; + let mapping = [' ', '░', '▒', '▓', '█']; // The approximation of greyscale let mapping_scale = (mapping.len() - 1) as f32; for g in glyphs { if let Some(bb) = g.pixel_bounding_box() { @@ -105,7 +243,7 @@ fn test_rusttype(font: &[u8], message: &str, rtl: bool) { // v should be in the range 0.0 to 1.0 let i = (v * mapping_scale + 0.5) as usize; // so something's wrong if you get $ in the output. - let c = mapping.get(i).cloned().unwrap_or(b'$'); + let c = mapping.get(i).cloned().unwrap_or(' '); let x = x as i32 + bb.min.x; let y = y as i32 + bb.min.y; // There's still a possibility that the glyph clips the boundaries of the bitmap @@ -127,9 +265,14 @@ fn test_rusttype(font: &[u8], message: &str, rtl: bool) { let stdout = ::std::io::stdout(); let mut handle = stdout.lock(); for j in 0..pixel_height { - handle - .write_all(&pixel_data[j * width..(j + 1) * width]) - .unwrap(); + for px in pixel_data[j * width..(j + 1) * width].iter() { + let mut b = [0; 4]; + px.encode_utf8(&mut b); + handle.write_all(&b).unwrap(); + } + // handle + // .write_all(&b) + // .unwrap(); handle.write_all(b"\n").unwrap(); } }