WIP text layout API

This commit is contained in:
marceline-cramer 2022-07-18 17:44:42 -06:00
parent f53c7ac4cb
commit 1feb3e9ef2
6 changed files with 218 additions and 85 deletions

View File

@ -19,6 +19,7 @@ canary_types = { path = "crates/types" }
lyon = "1"
ouroboros = "^0.15"
parking_lot = "0.12"
slab = "0.4"
wasmtime = "0.38"
[dependencies.font-kit]

View File

@ -32,9 +32,18 @@ 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);
let layout = TextLayout::new("Hello, world!");
let bounds = layout.get_bounds();
let bounds = draw::Rect {
bl: bounds.bl.into(),
tr: bounds.tr.into(),
};
ctx.draw_rect(bounds, Color::MAGENTA);
}
fn on_cursor_event(&mut self, kind: CursorEventKind, at: canary_script::Vec2) {

View File

@ -95,10 +95,6 @@ impl Panel {
}
}
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) {
let vertices = [
MeshVertex {
@ -121,7 +117,46 @@ impl Panel {
}
}
#[repr(transparent)]
pub struct TextLayout(u32);
impl TextLayout {
pub fn new(text: &str) -> Self {
unsafe { Self(text_layout_new(text.as_ptr() as u32, text.len() as u32)) }
}
pub fn get_bounds(&self) -> Rect {
unsafe {
let mut bounds = Rect::default();
let bounds_ptr: *mut Rect = &mut bounds;
text_layout_get_bounds(self.0, bounds_ptr as u32);
bounds
}
}
pub fn get_glyphs(&self) -> Vec<GlyphPosition> {
unsafe {
let num = text_layout_get_glyphs_num(self.0) as usize;
let mut glyphs = Vec::with_capacity(num);
glyphs.set_len(num);
text_layout_get_glyphs_data(self.0, glyphs.as_ptr() as u32);
glyphs
}
}
}
impl Drop for TextLayout {
fn drop(&mut self) {
unsafe { text_layout_delete(self.0) }
}
}
extern "C" {
fn draw_indexed(vertices_ptr: u32, vertices_num: u32, indices_ptr: u32, indices_num: u32);
fn draw_text(text_ptr: u32, text_len: u32);
fn text_layout_new(text_ptr: u32, text_len: u32) -> u32;
fn text_layout_delete(id: u32);
fn text_layout_get_bounds(id: u32, rect_ptr: u32);
fn text_layout_get_glyphs_num(id: u32) -> u32;
fn text_layout_get_glyphs_data(id: u32, glyphs_ptr: u32);
}

View File

@ -5,7 +5,7 @@ pub use num_traits;
use bytemuck::{Pod, Zeroable};
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)]
pub struct Vec2 {
pub x: f32,
pub y: f32,
@ -58,6 +58,13 @@ pub enum CursorEventKind {
Deselect = 3,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)]
pub struct Rect {
pub bl: Vec2,
pub tr: Vec2,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
pub struct GlyphPosition {

View File

@ -1,5 +1,6 @@
pub use canary_types::*;
use parking_lot::Mutex;
use parking_lot::{Mutex, RwLock};
use slab::Slab;
pub mod text;
@ -11,7 +12,12 @@ 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);
fn text_layout_new(&self, text: &str) -> u32;
fn text_layout_delete(&self, id: u32);
fn text_layout_get_bounds(&self, id: u32, rect: &mut Rect);
fn text_layout_get_glyphs_num(&self, id: u32) -> u32;
fn text_layout_get_glyphs_data(&self, id: u32, glyphs: &mut [GlyphPosition]);
}
pub trait ScriptInstance {
@ -97,40 +103,82 @@ impl<T: ScriptAbi> WasmtimeScript<T> {
linker.func_wrap(
module,
"draw_text",
"text_layout_new",
|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);
caller.data().text_layout_new(text)
},
)?;
linker.func_wrap(
module,
"text_layout_delete",
|mut caller: wasmtime::Caller<'_, T>, id: u32| caller.data().text_layout_delete(id),
)?;
linker.func_wrap(
module,
"text_layout_get_bounds",
|mut caller: wasmtime::Caller<'_, T>, id: u32, rect_ptr: u32| {
let rect: &mut Rect = Self::get_memory_ref(&mut caller, rect_ptr);
caller.data().text_layout_get_bounds(id, rect);
},
)?;
linker.func_wrap(
module,
"text_layout_get_glyphs_num",
|mut caller: wasmtime::Caller<'_, T>, id: u32| {
caller.data().text_layout_get_glyphs_num(id)
},
)?;
linker.func_wrap(
module,
"text_layout_get_glyphs_data",
|mut caller: wasmtime::Caller<'_, T>, id: u32, glyphs_ptr: u32, glyphs_num: u32| {
let glyphs: &mut [GlyphPosition] =
Self::get_memory_slice(&mut caller, glyphs_ptr, glyphs_num);
caller.data().text_layout_get_glyphs_data(id, glyphs);
},
)?;
Ok(())
}
fn get_memory_ref<D: bytemuck::Pod>(
caller: &mut wasmtime::Caller<'_, T>,
ptr: u32,
) -> &'static mut D {
let len = std::mem::size_of::<D>();
let bytes = Self::get_memory_slice_bytes(caller, ptr as usize, len);
bytemuck::from_bytes_mut(bytes)
}
fn get_memory_slice<D: bytemuck::Pod>(
caller: &mut wasmtime::Caller<'_, T>,
ptr: u32,
num: u32,
) -> &'static [D] {
) -> &'static mut [D] {
let len = num as usize * std::mem::size_of::<D>();
let bytes = Self::get_memory_slice_bytes(caller, ptr as usize, len);
bytemuck::cast_slice(bytes)
bytemuck::cast_slice_mut(bytes)
}
fn get_memory_slice_str(
caller: &mut wasmtime::Caller<'_, T>,
ptr: u32,
len: u32,
) -> &'static str {
) -> &'static mut str {
let memory = Self::get_memory_slice_bytes(caller, ptr as usize, len as usize);
std::str::from_utf8(memory).unwrap()
std::str::from_utf8_mut(memory).unwrap()
}
fn get_memory_slice_bytes(
caller: &mut wasmtime::Caller<'_, T>,
ptr: usize,
len: usize,
) -> &'static [u8] {
) -> &'static mut [u8] {
let memory = caller.get_export("memory").unwrap().into_memory().unwrap();
if ptr + len > memory.data_size(&caller) {
panic!("Attempted wasm memory read is out-of-bounds!");
@ -138,7 +186,7 @@ impl<T: ScriptAbi> WasmtimeScript<T> {
unsafe {
let ptr = memory.data_ptr(caller).add(ptr);
std::slice::from_raw_parts(ptr, len)
std::slice::from_raw_parts_mut(ptr, len)
}
}
}
@ -166,6 +214,7 @@ impl<T: ScriptAbi> ScriptInstance for WasmtimeScript<T> {
pub struct ScriptAbiImpl {
draw_cmds: Mutex<Vec<DrawCommand>>,
font_store: text::FontStore,
text_layouts: RwLock<Slab<text::TextLayout>>,
}
impl ScriptAbi for ScriptAbiImpl {
@ -181,14 +230,37 @@ impl ScriptAbi for ScriptAbiImpl {
})
}
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());
}
fn text_layout_new(&self, text: &str) -> u32 {
let font = self.font_store.load_font("Liberation Sans");
let layout = font.shape(text);
self.text_layouts.write().insert(layout) as u32
}
fn text_layout_delete(&self, id: u32) {
self.text_layouts.write().remove(id as usize);
}
fn text_layout_get_bounds(&self, id: u32, dst: &mut Rect) {
let src = self.text_layouts.read().get(id as usize).unwrap().bounds;
let _ = std::mem::replace(dst, src);
}
fn text_layout_get_glyphs_num(&self, id: u32) -> u32 {
self.text_layouts
.read()
.get(id as usize)
.unwrap()
.glyphs
.len() as u32
}
fn text_layout_get_glyphs_data(&self, id: u32, dst: &mut [GlyphPosition]) {
let layouts = self.text_layouts.read();
let src = layouts.get(id as usize).unwrap().glyphs.as_slice();
dst.copy_from_slice(src);
}
}

View File

@ -10,7 +10,7 @@ use allsorts::pathfinder_geometry::{line_segment::LineSegment2F, vector::Vector2
use allsorts::tables::{glyf::GlyfTable, loca::LocaTable};
use allsorts::tables::{FontTableProvider, SfntVersion};
use allsorts::{tag, Font as AllsortsFont};
use canary_types::GlyphPosition;
use canary_types::{GlyphPosition, Rect};
use lyon::path::Path;
use ouroboros::self_referencing;
use parking_lot::{Mutex, RwLock};
@ -113,11 +113,72 @@ impl CachedGlyph {
}
}
pub struct OutlineSink {
units_per_em: f32,
builder: lyon::path::path::Builder,
}
impl OutlineSink {
pub fn new(units_per_em: f32) -> Self {
Self {
units_per_em,
builder: lyon::path::Path::builder(),
}
}
fn pf_line_to_lyon(
&self,
line: &LineSegment2F,
) -> (lyon::geom::Point<f32>, lyon::geom::Point<f32>) {
(
self.pf_vector_to_lyon(&line.from()),
self.pf_vector_to_lyon(&line.to()),
)
}
fn pf_vector_to_lyon(&self, v: &Vector2F) -> lyon::geom::Point<f32> {
lyon::geom::Point::<f32>::new(v.x(), v.y()) / self.units_per_em
}
}
impl allsorts::outline::OutlineSink for OutlineSink {
fn move_to(&mut self, to: Vector2F) {
let to = self.pf_vector_to_lyon(&to);
self.builder.begin(to);
}
fn line_to(&mut self, to: Vector2F) {
let to = self.pf_vector_to_lyon(&to);
self.builder.line_to(to);
}
fn quadratic_curve_to(&mut self, control: Vector2F, to: Vector2F) {
let control = self.pf_vector_to_lyon(&control);
let to = self.pf_vector_to_lyon(&to);
self.builder.quadratic_bezier_to(control, to);
}
fn cubic_curve_to(&mut self, control: LineSegment2F, to: Vector2F) {
let (ctrl1, ctrl2) = self.pf_line_to_lyon(&control);
let to = self.pf_vector_to_lyon(&to);
self.builder.cubic_bezier_to(ctrl1, ctrl2, to);
}
fn close(&mut self) {
self.builder.close();
}
}
pub struct GlyphMesh {
vertices: Vec<MeshVertex>,
indices: Vec<MeshIndex>,
}
pub struct TextLayout {
pub bounds: Rect,
pub glyphs: Vec<GlyphPosition>,
}
pub struct Font {
data: Mutex<FontData>,
glyph_cache: GlyphCache,
@ -137,7 +198,7 @@ impl Font {
Self { data, glyph_cache }
}
pub fn shape(&self, text: &str) -> Vec<GlyphPosition> {
pub fn shape(&self, text: &str) -> TextLayout {
self.data.lock().shape(text)
}
@ -227,7 +288,7 @@ impl FontData {
.build()
}
pub fn shape(&mut self, text: &str) -> Vec<GlyphPosition> {
pub fn shape(&mut self, text: &str) -> TextLayout {
let face_index = *self.borrow_face_index() as u16;
let presentation = MatchingPresentation::Required;
let script = *self.borrow_script();
@ -237,7 +298,7 @@ impl FontData {
let direction = *self.borrow_direction();
let vertical = *self.borrow_vertical();
self.with_inner_mut(|font| {
let glyphs = self.with_inner_mut(|font| {
let mapped = font.map_glyphs(text, script, presentation);
let infos = font
.shape(mapped, script, lang_tag, &features, kerning)
@ -257,7 +318,11 @@ impl FontData {
}
glyphs
})
});
let bounds = Rect::default();
TextLayout { bounds, glyphs }
}
}
@ -323,59 +388,3 @@ impl FontStore {
font
}
}
pub struct OutlineSink {
units_per_em: f32,
builder: lyon::path::path::Builder,
}
impl OutlineSink {
pub fn new(units_per_em: f32) -> Self {
Self {
units_per_em,
builder: lyon::path::Path::builder(),
}
}
fn pf_line_to_lyon(
&self,
line: &LineSegment2F,
) -> (lyon::geom::Point<f32>, lyon::geom::Point<f32>) {
(
self.pf_vector_to_lyon(&line.from()),
self.pf_vector_to_lyon(&line.to()),
)
}
fn pf_vector_to_lyon(&self, v: &Vector2F) -> lyon::geom::Point<f32> {
lyon::geom::Point::<f32>::new(v.x(), v.y()) / self.units_per_em
}
}
impl allsorts::outline::OutlineSink for OutlineSink {
fn move_to(&mut self, to: Vector2F) {
let to = self.pf_vector_to_lyon(&to);
self.builder.begin(to);
}
fn line_to(&mut self, to: Vector2F) {
let to = self.pf_vector_to_lyon(&to);
self.builder.line_to(to);
}
fn quadratic_curve_to(&mut self, control: Vector2F, to: Vector2F) {
let control = self.pf_vector_to_lyon(&control);
let to = self.pf_vector_to_lyon(&to);
self.builder.quadratic_bezier_to(control, to);
}
fn cubic_curve_to(&mut self, control: LineSegment2F, to: Vector2F) {
let (ctrl1, ctrl2) = self.pf_line_to_lyon(&control);
let to = self.pf_vector_to_lyon(&to);
self.builder.cubic_bezier_to(ctrl1, ctrl2, to);
}
fn close(&mut self) {
self.builder.close();
}
}