use rusttype::{point, Font, PositionedGlyph, Scale}; use std::convert::TryInto; use std::io::Write; // Add an allocator that lets us keep track of the system extern crate stats_alloc; use stats_alloc::{Region, StatsAlloc, INSTRUMENTED_SYSTEM}; use std::alloc::System; #[global_allocator] static GLOBAL: &StatsAlloc = &INSTRUMENTED_SYSTEM; fn main() { // Chinese test // let font = include_bytes!("../resources/WenQuanYiMicroHei-01.ttf"); let font = include_bytes!("../resources/NotoSansSC-Regular.ttf"); let message = " 發啊你好"; let rtl = false; println!(); println!("RUSTTYPE TEST:"); test_rusttype(font, message, rtl); println!("RUSTTYPE + RUSTYBUZZ TEST:"); test_rusthbuzz(font, message); // Arabic test let font = include_bytes!("../resources/LateefRegOT.ttf"); let message = "مرحبا بالعالم "; // "Hello, world!" // let message = "مممممم"; // Kerning test nonsense string let rtl = true; println!(); println!("RUSTTYPE TEST:"); test_rusttype(font, message, rtl); println!("RUSTTYPE + RUSTYBUZZ TEST:"); test_rusthbuzz(font, message); // Emoji Test // let font = include_bytes!("../resources/TwitterColorEmoji-SVGinOT.ttf"); // let font = include_bytes!("../resources/Symbola-AjYx.ttf"); let font = include_bytes!("../resources/NotoEmoji-Regular.ttf"); let message = concat!( "🐶🐱🦁🐎🦄🐷🐘🐰\n", "🐼🐓🐧🐢🐟🐙🦋🌷\n", "🌳🌵🍄🌏🌙☁️🔥🍌\n", "🍎🍓🌽🍕🎂❤️😀🤖\n", "🎩👓🔧🎅👍☂️⌛⏰\n", "🎁💡📕✏️📎✂️🔒🔑\n", "🔨☎️🏁🚂🚲✈️🚀🏆\n", "⚽🎸🎺🔔⚓🎧📁📌\n", "Hello, world! ää 🀄🃏\u{1F170}\u{1F170}\u{FE0F}\n", // "🐶🐱🦁🐎🦄🐷🐘🐰🐼🐓🐧🐢🐟🐙🦋🌷🌳🌵🍄🌏🌙☁️🔥🍌🍎🍓🌽🍕🎂❤️😀🤖🎩👓🔧🎅👍☂️⌛⏰🎁💡📕✏️📎✂️🔒🔑🔨☎️🏁🚂🚲✈️🚀🏆⚽🎸🎺🔔⚓🎧📁📌" ); println!(); println!("RUSTTYPE + RUSTYBUZZ TEST:"); test_rusthbuzz(font, message); let font = include_bytes!("../resources/TwitterColorEmoji-SVGinOT.ttf"); println!(); println!("RUSTTYPE + RUSTYBUZZ TEST:"); test_rusthbuzz(font, message); let font = include_bytes!("../resources/Symbola-AjYx.ttf"); println!(); println!("RUSTTYPE + RUSTYBUZZ TEST:"); test_rusthbuzz(font, message); } // 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 = 336; const FB_HEIGHT: usize = 536; let rusttype_height = 32.0; let mut buffer = vec![]; for _ in 0..FB_HEIGHT { buffer.push(vec![0f32; FB_WIDTH]); } use minifb::{Key, Window, WindowOptions}; let mut window = Window::new( "Betrusted", FB_WIDTH, FB_HEIGHT, WindowOptions { scale_mode: minifb::ScaleMode::AspectRatioStretch, resize: true, ..WindowOptions::default() }, ) .unwrap_or_else(|e| { panic!("{}", e); }); const DARK_COLOUR: u32 = 0xB5B5AD; const LIGHT_COLOUR: u32 = 0x1B1B19; let mut native_buffer = vec![LIGHT_COLOUR; FB_WIDTH * FB_HEIGHT]; let lines = message.split('\n'); let reg = Region::new(&GLOBAL); let mut buzz_font = rustybuzz::Face::from_slice(font, 0).unwrap(); println!("buzz_font: {:#?}", reg.change()); buzz_font.set_pixels_per_em(Some((196, 196))); let buzz_features = vec![]; println!("buzz_features: {:#?}", reg.change()); let mut buzz_buffer = rustybuzz::UnicodeBuffer::new(); println!("buzz_buffer: {:#?}", reg.change()); for (line_number, line) in lines.enumerate() { buzz_buffer.push_str(line); println!("buzz_buffer_push: {:#?}", reg.change()); let glyph_buffer = rustybuzz::shape(&buzz_font, &buzz_features, buzz_buffer); println!("buzz_shape: {:#?}", reg.change()); let rusttype_font = Font::try_from_bytes(font).expect("error constructing a Font from bytes"); println!("fonttype_collection: {:#?}", reg.change()); let mut cursor_x = 0.0; let mut cursor_y = (line_number as f32) * rusttype_height; 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.0 /* * 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 .glyph_id .try_into() .expect("codepoint out of range"), )); 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; } buzz_buffer = glyph_buffer.clear(); buzz_buffer.clear(); } let mut offset = 0; for line in &*buffer { for pixel in line { native_buffer[offset] = if pixel > &0.5 { LIGHT_COLOUR } else { DARK_COLOUR }; offset += 1; } } window .update_with_buffer(&native_buffer, FB_WIDTH, FB_HEIGHT) .unwrap(); while window.is_open() && !window.is_key_down(Key::Escape) { window.update(); } // 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) { let reg = Region::new(&GLOBAL); let font = Font::try_from_bytes(font).expect("couldn't read font file"); // Desired font pixel height let height: f32 = 20.0; // to get 80 chars across (fits most terminals); adjust as desired let pixel_height = height.ceil() as usize; // 2x scale in x direction to counter the aspect ratio of monospace characters. let scale = Scale { x: height * 2.0, y: height, }; // The origin of a line of text is at the baseline (roughly where // non-descending letters sit). We don't want to clip the text, so we shift // it down with an offset when laying it out. v_metrics.ascent is the // distance between the baseline and the highest edge of any glyph in // the font. That's enough to guarantee that there's no clipping. let v_metrics = font.v_metrics(scale); let offset = point(0.0, v_metrics.ascent); println!("metrics: {:#?}", reg.change()); // Glyphs to draw for "RustType". Feel free to try other strings. let glyphs: Vec> = font.layout(message, scale, offset).collect(); println!("glyphs: {:#?}", reg.change()); // Find the most visually pleasing width to display let width = glyphs .iter() .rev() .map(|g| g.position().x as f32 + g.unpositioned().h_metrics().advance_width) .next() .unwrap_or(0.0) .ceil() as usize; println!("width: {}, height: {}", width, pixel_height); // Rasterise directly into ASCII art. 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() { g.draw(|x, y, v| { // 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(' '); 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 if x >= 0 && x < width as i32 && y >= 0 && y < pixel_height as i32 { let x = if rtl { width - (x as usize) - 1 } else { x as usize }; let y = y as usize; pixel_data[(x + y * width)] = c; } }) } } println!("rasterization: {:#?}", reg.change()); // Print it out let stdout = ::std::io::stdout(); let mut handle = stdout.lock(); for j in 0..pixel_height { 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(); } }