WIP text layout API
This commit is contained in:
parent
f53c7ac4cb
commit
1feb3e9ef2
|
@ -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]
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
106
src/lib.rs
106
src/lib.rs
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
131
src/text.rs
131
src/text.rs
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue