2021-07-22 04:54:40 +00:00
|
|
|
use rusttype::{point, Font, PositionedGlyph, Scale};
|
|
|
|
use std::convert::TryInto;
|
2019-11-21 05:36:10 +00:00
|
|
|
use std::io::Write;
|
|
|
|
|
|
|
|
// Add an allocator that lets us keep track of the system
|
|
|
|
extern crate stats_alloc;
|
2020-11-26 08:12:01 +00:00
|
|
|
use stats_alloc::{Region, StatsAlloc, INSTRUMENTED_SYSTEM};
|
2019-11-21 05:36:10 +00:00
|
|
|
use std::alloc::System;
|
|
|
|
#[global_allocator]
|
|
|
|
static GLOBAL: &StatsAlloc<System> = &INSTRUMENTED_SYSTEM;
|
|
|
|
|
|
|
|
fn main() {
|
2019-11-21 07:16:52 +00:00
|
|
|
// Chinese test
|
2021-07-22 04:54:40 +00:00
|
|
|
// let font = include_bytes!("../resources/WenQuanYiMicroHei-01.ttf");
|
|
|
|
let font = include_bytes!("../resources/NotoSansSC-Regular.ttf");
|
2019-11-21 07:16:52 +00:00
|
|
|
let message = " 發啊你好";
|
|
|
|
let rtl = false;
|
2020-08-15 09:07:56 +00:00
|
|
|
println!();
|
|
|
|
println!("RUSTTYPE TEST:");
|
2019-11-21 07:16:52 +00:00
|
|
|
test_rusttype(font, message, rtl);
|
2020-11-26 08:12:01 +00:00
|
|
|
println!("RUSTTYPE + RUSTYBUZZ TEST:");
|
|
|
|
test_rusthbuzz(font, message);
|
2019-11-21 07:16:52 +00:00
|
|
|
|
|
|
|
// Arabic test
|
|
|
|
let font = include_bytes!("../resources/LateefRegOT.ttf");
|
2021-07-22 04:54:40 +00:00
|
|
|
let message = "مرحبا بالعالم "; // "Hello, world!"
|
|
|
|
// let message = "مممممم"; // Kerning test nonsense string
|
2019-11-21 07:16:52 +00:00
|
|
|
let rtl = true;
|
2020-08-15 09:07:56 +00:00
|
|
|
println!();
|
|
|
|
println!("RUSTTYPE TEST:");
|
2019-11-21 07:16:52 +00:00
|
|
|
test_rusttype(font, message, rtl);
|
2020-11-26 08:12:01 +00:00
|
|
|
|
|
|
|
println!("RUSTTYPE + RUSTYBUZZ TEST:");
|
|
|
|
test_rusthbuzz(font, message);
|
2021-07-22 04:54:40 +00:00
|
|
|
|
|
|
|
// 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);
|
2019-11-21 05:36:10 +00:00
|
|
|
}
|
|
|
|
|
2020-11-26 08:12:01 +00:00
|
|
|
// 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) {
|
2021-07-22 04:54:40 +00:00
|
|
|
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');
|
2020-11-26 08:12:01 +00:00
|
|
|
|
2019-11-21 05:36:10 +00:00
|
|
|
let reg = Region::new(&GLOBAL);
|
|
|
|
|
2021-07-22 04:58:27 +00:00
|
|
|
let mut buzz_font = rustybuzz::Face::from_slice(font, 0).unwrap();
|
2020-11-26 08:12:01 +00:00
|
|
|
println!("buzz_font: {:#?}", reg.change());
|
|
|
|
buzz_font.set_pixels_per_em(Some((196, 196)));
|
|
|
|
|
2021-07-22 04:54:40 +00:00
|
|
|
let buzz_features = vec![];
|
|
|
|
println!("buzz_features: {:#?}", reg.change());
|
|
|
|
|
2020-11-26 08:12:01 +00:00
|
|
|
let mut buzz_buffer = rustybuzz::UnicodeBuffer::new();
|
|
|
|
println!("buzz_buffer: {:#?}", reg.change());
|
|
|
|
|
2021-07-22 04:54:40 +00:00
|
|
|
for (line_number, line) in lines.enumerate() {
|
|
|
|
buzz_buffer.push_str(line);
|
|
|
|
println!("buzz_buffer_push: {:#?}", reg.change());
|
2020-11-26 08:12:01 +00:00
|
|
|
|
2021-07-22 04:54:40 +00:00
|
|
|
let glyph_buffer = rustybuzz::shape(&buzz_font, &buzz_features, buzz_buffer);
|
|
|
|
println!("buzz_shape: {:#?}", reg.change());
|
2020-11-26 08:12:01 +00:00
|
|
|
|
2021-07-22 04:54:40 +00:00
|
|
|
let rusttype_font =
|
|
|
|
Font::try_from_bytes(font).expect("error constructing a Font from bytes");
|
|
|
|
println!("fonttype_collection: {:#?}", reg.change());
|
2020-11-26 08:12:01 +00:00
|
|
|
|
2021-07-22 04:54:40 +00:00
|
|
|
let mut cursor_x = 0.0;
|
|
|
|
let mut cursor_y = (line_number as f32) * rusttype_height;
|
2020-11-26 08:12:01 +00:00
|
|
|
|
2021-07-22 04:54:40 +00:00
|
|
|
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 */;
|
2020-11-26 08:12:01 +00:00
|
|
|
|
2021-07-22 04:54:40 +00:00
|
|
|
let v_metrics = rusttype_font.v_metrics(rusttype_scale);
|
|
|
|
let offset = point(0.0, v_metrics.ascent);
|
2020-11-26 08:12:01 +00:00
|
|
|
|
2021-07-22 04:54:40 +00:00
|
|
|
// 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
|
2021-07-22 04:58:27 +00:00
|
|
|
.glyph_id
|
2021-07-22 04:54:40 +00:00
|
|
|
.try_into()
|
|
|
|
.expect("codepoint out of range"),
|
|
|
|
));
|
|
|
|
let glyph_scaled = font_glyph.scaled(rusttype_scale);
|
2020-11-26 08:12:01 +00:00
|
|
|
|
2021-07-22 04:54:40 +00:00
|
|
|
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;
|
|
|
|
// }
|
|
|
|
});
|
2020-11-26 08:12:01 +00:00
|
|
|
}
|
2021-07-22 04:54:40 +00:00
|
|
|
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;
|
2020-11-26 08:12:01 +00:00
|
|
|
}
|
|
|
|
}
|
2021-07-22 04:54:40 +00:00
|
|
|
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!();
|
|
|
|
// }
|
2019-11-21 05:36:10 +00:00
|
|
|
}
|
|
|
|
|
2019-11-21 07:16:52 +00:00
|
|
|
fn test_rusttype(font: &[u8], message: &str, rtl: bool) {
|
2019-11-21 05:36:10 +00:00
|
|
|
let reg = Region::new(&GLOBAL);
|
2020-11-26 08:12:01 +00:00
|
|
|
|
2021-07-22 04:54:40 +00:00
|
|
|
let font = Font::try_from_bytes(font).expect("couldn't read font file");
|
2019-11-21 05:36:10 +00:00
|
|
|
|
|
|
|
// 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.
|
2019-11-21 07:16:52 +00:00
|
|
|
let glyphs: Vec<PositionedGlyph<'_>> = font.layout(message, scale, offset).collect();
|
2019-11-21 05:36:10 +00:00
|
|
|
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.
|
2020-11-26 08:12:01 +00:00
|
|
|
let mut pixel_data = vec!['@'; width * pixel_height];
|
|
|
|
let mapping = [' ', '░', '▒', '▓', '█']; // The approximation of greyscale
|
2019-11-21 05:36:10 +00:00
|
|
|
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.
|
2020-11-26 08:12:01 +00:00
|
|
|
let c = mapping.get(i).cloned().unwrap_or(' ');
|
2019-11-21 05:36:10 +00:00
|
|
|
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 {
|
2019-11-21 07:16:52 +00:00
|
|
|
let x = if rtl {
|
|
|
|
width - (x as usize) - 1
|
|
|
|
} else {
|
|
|
|
x as usize
|
|
|
|
};
|
2019-11-21 05:36:10 +00:00
|
|
|
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 {
|
2020-11-26 08:12:01 +00:00
|
|
|
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();
|
2019-11-21 05:36:10 +00:00
|
|
|
handle.write_all(b"\n").unwrap();
|
|
|
|
}
|
|
|
|
}
|