Skip to content

Instantly share code, notes, and snippets.

@CryZe
Last active March 19, 2021 21:01
Show Gist options
  • Select an option

  • Save CryZe/d84854aef6adf13bd212079f28f01b08 to your computer and use it in GitHub Desktop.

Select an option

Save CryZe/d84854aef6adf13bd212079f28f01b08 to your computer and use it in GitHub Desktop.
fontemon.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::{
io::Cursor,
sync::{Arc, RwLock},
time::{Duration, Instant},
};
use minifb::{Key, KeyRepeat, ScaleMode, Window, WindowOptions};
use rustybuzz::UnicodeBuffer;
use tiny_skia::{Color, FillRule, Paint, PathBuilder, Pixmap, Shader, Transform};
use ttf_parser::{GlyphId, OutlineBuilder};
const FONT: &[u8] = include_bytes!("../fontemon_small.otf");
const WIDTH: u32 = 500;
const HEIGHT: u32 = 500;
const FONT_SCALE: f32 = HEIGHT as f32 / 2500.0;
const CHOICE_FRAMES: [u32; 55] = [
269, 527, 554, 649, 677, 883, 913, 1026, 1114, 1207, 1400, 1406, 1457, 1463, 1532, 1538, 1689,
1720, 1752, 1753, 1754, 1755, 1929, 1945, 1986, 2132, 2168, 2327, 2328, 2329, 2330, 2423, 2463,
2580, 3003, 3045, 3193, 3236, 3409, 3452, 3601, 3637, 4703, //
// Also game overs
370, 738, 1090, 1178, 1219, 4150, 4162, 4235, 4308, //
// Also cycle frames
4777, // First secret
2722, // Credits
// Also title screen
180,
];
struct SkiaBuilder(PathBuilder);
impl OutlineBuilder for SkiaBuilder {
fn move_to(&mut self, x: f32, y: f32) {
self.0.move_to(x, y)
}
fn line_to(&mut self, x: f32, y: f32) {
self.0.line_to(x, y)
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
self.0.quad_to(x1, y1, x, y)
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
self.0.cubic_to(x1, y1, x2, y2, x, y)
}
fn close(&mut self) {
self.0.close()
}
}
fn main() {
let font = ttf_parser::Face::from_slice(FONT, 0).unwrap();
let rb_font = rustybuzz::Face::from_slice(FONT, 0).unwrap();
let mut fb = Pixmap::new(WIDTH, HEIGHT).unwrap();
let mut text = UnicodeBuffer::new();
let mut window = Window::new(
"Fontemon",
WIDTH as _,
HEIGHT as _,
WindowOptions {
resize: true,
scale_mode: ScaleMode::AspectRatioStretch,
..Default::default()
},
)
.unwrap();
window.limit_update_rate(Some(Duration::from_secs(1) / 60));
window.set_key_repeat_delay(0.1);
window.set_key_repeat_rate(0.25);
let mut input = String::new();
let paint = Paint {
shader: Shader::SolidColor(Color::from_rgba8(15, 56, 15, 255)),
anti_alias: true,
..Default::default()
};
let transform = Transform::from_scale(FONT_SCALE, -FONT_SCALE)
.post_translate(HEIGHT as f32 / 250.0, 0.725 * HEIGHT as f32);
let mut first_frame = true;
let mut last_input = Instant::now();
let mut frame_id = 0;
while window.is_open() {
let mut pushed_any = first_frame;
first_frame = false;
for key in window.get_keys_pressed(KeyRepeat::Yes).iter().flatten() {
let ch = match key {
Key::A => 'a',
Key::B => 'b',
Key::C => 'c',
Key::D => 'd',
Key::Space => ' ',
Key::Backspace => {
pushed_any = true;
input.pop();
continue;
}
Key::Escape => {
pushed_any = true;
input.clear();
input.push_str(" ");
continue;
}
_ => continue,
};
pushed_any = true;
input.push(ch);
}
if !pushed_any
&& last_input.elapsed() > Duration::from_secs(1) / 2
&& !CHOICE_FRAMES.contains(&frame_id)
{
pushed_any = true;
input.push(' ');
}
if !pushed_any {
window.update();
continue;
}
last_input = Instant::now();
text.push_str(&input);
let glyphs = rustybuzz::shape(&rb_font, &[], text);
fb.fill(Color::from_rgba8(15, 188, 155, 255));
for glyph in glyphs.glyph_infos() {
if glyph.codepoint == 34 {
continue;
}
frame_id = glyph.codepoint;
let mut builder = SkiaBuilder(PathBuilder::new());
if font
.outline_glyph(GlyphId(frame_id as _), &mut builder)
.is_none()
{
continue;
}
if let Some(path) = builder.0.finish() {
fb.fill_path(&path, &paint, FillRule::Winding, transform, None);
}
}
text = glyphs.clear();
window
.update_with_buffer(bytemuck::cast_slice(fb.data()), WIDTH as _, HEIGHT as _)
.unwrap();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment