Hacky but functional scripted text rendering
This commit is contained in:
parent
96eb8c2daa
commit
0ead815fa1
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
37
src/lib.rs
37
src/lib.rs
|
@ -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());
|
||||
}
|
||||
|
|
57
src/text.rs
57
src/text.rs
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue