Hacky but functional scripted text rendering

This commit is contained in:
marceline-cramer 2022-07-18 16:49:33 -06:00
parent 96eb8c2daa
commit 0ead815fa1
5 changed files with 57 additions and 48 deletions

View File

@ -25,14 +25,14 @@ fn main() {
}
struct App {
script: canary::WasmtimeScript<canary::SimpleScriptAbi>,
script: canary::WasmtimeScript<canary::ScriptAbiImpl>,
last_update: Instant,
}
impl App {
pub fn new(module_path: &str) -> Self {
let runtime = canary::WasmtimeRuntime::new().unwrap();
let abi = canary::SimpleScriptAbi::default();
let abi = canary::ScriptAbiImpl::default();
let module = std::fs::read(module_path).unwrap();
let script = runtime.load_module(abi, &module).unwrap();

View File

@ -32,6 +32,7 @@ impl PanelImpl for DummyPanel {
}
fn draw(&mut self) {
self.panel.draw_text("Hello, world!");
let ctx = draw::DrawContext::new(self.panel);
self.menu.draw(&ctx);
}

View File

@ -84,6 +84,10 @@ impl Panel {
Self(id)
}
pub fn draw_text(&self, text: &str) {
unsafe { draw_text(text.as_ptr() as u32, text.len() as u32) }
}
pub fn draw_triangle(&self, v1: Vec2, v2: Vec2, v3: Vec2, color: Color) {
unsafe {
UiPanel_drawTriangle(
@ -107,4 +111,6 @@ extern "C" {
b: f32,
a: f32,
);
fn draw_text(text_ptr: u32, text_len: u32);
}

View File

@ -4,10 +4,14 @@ use parking_lot::Mutex;
pub mod text;
/// Low-level script API callbacks.
///
/// If you're a casual user of Canary the struct you're looking for is
/// [ScriptAbiImpl]. This trait exists to help with making mocks for testing.
pub trait ScriptAbi {
fn start_draw(&self);
fn draw_indexed(&self, vertices: &[MeshVertex], indices: &[MeshIndex]);
fn with_draw_commands(&self, f: impl FnOnce(&[DrawCommand]));
fn draw_text(&self, text: &str);
}
pub trait ScriptInstance {
@ -91,6 +95,15 @@ impl<T: ScriptAbi> WasmtimeScript<T> {
},
)?;
linker.func_wrap(
module,
"draw_text",
|mut caller: wasmtime::Caller<'_, T>, text_ptr: u32, text_len: u32| {
let text = Self::get_memory_slice_str(&mut caller, text_ptr, text_len);
caller.data().draw_text(text);
},
)?;
linker.func_wrap(
module,
"UiPanel_drawTriangle",
@ -145,6 +158,15 @@ impl<T: ScriptAbi> WasmtimeScript<T> {
bytemuck::cast_slice(bytes)
}
fn get_memory_slice_str(
caller: &mut wasmtime::Caller<'_, T>,
ptr: u32,
len: u32,
) -> &'static str {
let memory = Self::get_memory_slice_bytes(caller, ptr as usize, len as usize);
std::str::from_utf8(memory).unwrap()
}
fn get_memory_slice_bytes(
caller: &mut wasmtime::Caller<'_, T>,
ptr: usize,
@ -180,17 +202,17 @@ impl<T: ScriptAbi> ScriptInstance for WasmtimeScript<T> {
}
}
/// The standard [ScriptAbi] implementation to use.
#[derive(Default)]
pub struct SimpleScriptAbi {
pub struct ScriptAbiImpl {
draw_cmds: Mutex<Vec<DrawCommand>>,
text: text::DummyText,
font_store: text::FontStore,
}
impl ScriptAbi for SimpleScriptAbi {
impl ScriptAbi for ScriptAbiImpl {
fn start_draw(&self) {
let mut lock = self.draw_cmds.lock();
lock.clear();
self.text.draw(&mut lock);
}
fn draw_indexed(&self, vertices: &[MeshVertex], indices: &[MeshIndex]) {
@ -200,6 +222,13 @@ impl ScriptAbi for SimpleScriptAbi {
})
}
fn draw_text(&self, text: &str) {
let font = self.font_store.load_font("Liberation Sans");
let glyphs = font.shape(text);
let cmds = font.draw(&glyphs, 0.9);
self.draw_cmds.lock().extend(cmds.into_iter());
}
fn with_draw_commands(&self, f: impl FnOnce(&[DrawCommand])) {
f(self.draw_cmds.lock().as_slice());
}

View File

@ -141,7 +141,10 @@ impl Font {
self.data.lock().shape(text)
}
pub fn draw(&self, mesh: &mut DummyText, positions: &[GlyphPosition], yoff: f32) {
pub fn draw(&self, positions: &[GlyphPosition], yoff: f32) -> Vec<DrawCommand> {
let mut vertices = Vec::new();
let mut indices = Vec::new();
// TODO defer tessellation of missed glyph cache entries
let mut glyphs = self.glyph_cache.glyphs.write();
let units_per_em = self.glyph_cache.units_per_em;
@ -155,7 +158,7 @@ impl Font {
xcur += position.hori_advance;
ycur += position.vert_advance;
let voff = mesh.vertices.len() as MeshIndex;
let voff = vertices.len() as MeshIndex;
let scale = TEXT_SCALE / units_per_em;
let xpos = xpos as f32 * scale;
let ypos = ypos as f32 * scale;
@ -163,16 +166,18 @@ impl Font {
for v in glyph.vertices.iter() {
let x = v.position.x + xpos - 0.9;
let y = v.position.y + ypos + yoff;
mesh.vertices.push(MeshVertex {
vertices.push(MeshVertex {
position: canary_types::Vec2 { x, y },
color: v.color,
});
}
for i in glyph.indices.iter() {
mesh.indices.push(i + voff);
indices.push(i + voff);
}
}
vec![DrawCommand::Mesh { vertices, indices }]
}
}
@ -261,6 +266,12 @@ pub struct FontStore {
loaded: Mutex<HashMap<String, Arc<Font>>>,
}
impl Default for FontStore {
fn default() -> Self {
Self::new()
}
}
impl FontStore {
pub fn new() -> Self {
let source = font_kit::source::SystemSource::new();
@ -313,44 +324,6 @@ impl FontStore {
}
}
pub struct DummyText {
vertices: Vec<MeshVertex>,
indices: Vec<MeshIndex>,
}
impl Default for DummyText {
fn default() -> Self {
let store = FontStore::new();
let text = "toki o! nimi mi li jan [_mun_alasa_sona]. toki+pona li pona.";
let font_families = ["mononoki", "Liberation Sans", "Ubuntu"];
let mut mesh = Self {
vertices: Vec::new(),
indices: Vec::new(),
};
let line_height = -0.1;
let mut line_offset = 0.9;
for family in font_families.iter() {
let font = store.load_font(family);
let glyphs = font.shape(text);
font.draw(&mut mesh, &glyphs, line_offset);
line_offset += line_height;
}
mesh
}
}
impl DummyText {
pub fn draw(&self, cmds: &mut Vec<DrawCommand>) {
cmds.push(DrawCommand::Mesh {
vertices: self.vertices.clone(),
indices: self.indices.clone(),
});
}
}
pub struct OutlineSink {
units_per_em: f32,
builder: lyon::path::path::Builder,