WIP text rendering

This commit is contained in:
mars 2022-07-16 11:44:22 -06:00
parent bf8592b5fe
commit 50543d876a
3 changed files with 247 additions and 1 deletions

View File

@ -12,8 +12,10 @@ version = "0.1.0"
edition = "2021"
[dependencies]
allsorts = "0.10"
anyhow = "1"
bytemuck = "1"
canary_types = { path = "crates/types" }
lyon = "1"
parking_lot = "0.12"
wasmtime = "0.38"

View File

@ -1,6 +1,8 @@
pub use canary_types::*;
use parking_lot::Mutex;
pub mod text;
/// Low-level script API callbacks.
pub trait ScriptAbi {
fn start_draw(&self);
@ -181,11 +183,14 @@ impl<T: ScriptAbi> ScriptInstance for WasmtimeScript<T> {
#[derive(Default)]
pub struct SimpleScriptAbi {
draw_cmds: Mutex<Vec<DrawCommand>>,
text: text::DummyText,
}
impl ScriptAbi for SimpleScriptAbi {
fn start_draw(&self) {
self.draw_cmds.lock().clear();
let mut lock = self.draw_cmds.lock();
lock.clear();
self.text.draw(&mut lock);
}
fn draw_indexed(&self, vertices: &[MeshVertex], indices: &[MeshIndex]) {

239
src/text.rs Normal file
View File

@ -0,0 +1,239 @@
use super::{DrawCommand, MeshIndex, MeshVertex};
use allsorts::binary::read::ReadScope;
use allsorts::cff::CFF;
use allsorts::font::{GlyphTableFlags, MatchingPresentation};
use allsorts::font_data::FontData;
use allsorts::glyph_position::{GlyphLayout, TextDirection};
use allsorts::outline::OutlineBuilder;
use allsorts::pathfinder_geometry::{line_segment::LineSegment2F, vector::Vector2F};
use allsorts::tables::{glyf::GlyfTable, loca::LocaTable};
use allsorts::tables::{FontTableProvider, SfntVersion};
use allsorts::{tag, Font};
use std::collections::HashMap;
const TEXT_SCALE: f32 = 0.00005;
pub struct GlyphCache {
glyphs: HashMap<u16, CachedGlyph>,
}
impl GlyphCache {
pub fn new() -> Self {
Self {
glyphs: Default::default(),
}
}
pub fn write_glyph(&mut self, builder: &mut impl OutlineBuilder, index: u16) {
let mut sink = OutlineSink::new();
builder.visit(index, &mut sink).unwrap();
let path = sink.builder.build();
use lyon::tessellation::*;
let mut geometry: VertexBuffers<MeshVertex, MeshIndex> = VertexBuffers::new();
let mut tessellator = FillTessellator::new();
tessellator
.tessellate_path(
&path,
&FillOptions::default(),
&mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
let position = vertex.position() * TEXT_SCALE;
let position = canary_types::Vec2 {
x: position.x,
y: position.y,
};
MeshVertex {
position,
color: canary_types::Color::WHITE,
}
}),
)
.unwrap();
self.glyphs.insert(
index,
CachedGlyph {
vertices: geometry.vertices,
indices: geometry.indices,
},
);
}
}
pub struct CachedGlyph {
vertices: Vec<MeshVertex>,
indices: Vec<MeshIndex>,
}
pub struct DummyText {
vertices: Vec<MeshVertex>,
indices: Vec<MeshIndex>,
}
impl Default for DummyText {
fn default() -> Self {
// if you're looking at this for whatever reason, forgive the mess,
// Arabic rendering is misbehaving
let script = tag::ARAB;
let buffer = include_bytes!("/usr/share/fonts/TTF/Hack-Regular.ttf");
let buffer = include_bytes!("/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc");
let buffer = include_bytes!("/usr/share/fonts/misc/ter-u16n.otb");
let buffer = include_bytes!("/usr/share/fonts/noto/NotoNastaliqUrdu-Regular.ttf");
// let buffer = include_bytes!("/usr/share/fonts/noto/NotoSansArabic-Regular.ttf");
// let buffer = include_bytes!("/usr/share/fonts/noto/NotoKufiArabic-Regular.ttf");
// let buffer = include_bytes!("/usr/share/fonts/noto/NotoNaskhArabic-Regular.ttf");
let buffer = buffer.as_slice();
let scope = ReadScope::new(&buffer);
let font_file = scope.read::<FontData<'_>>().unwrap();
let provider = font_file.table_provider(0).unwrap();
let mut font = Font::new(provider).unwrap().unwrap();
let text = "نص حكيم له سر قاطع وذو شأن عظيم مكتوب على ثوب أخضر ومغلف بجلد أزرق";
let glyphs = font.map_glyphs(text, script, MatchingPresentation::NotRequired);
let mut cache = GlyphCache::new();
if font.glyph_table_flags.contains(GlyphTableFlags::CFF)
&& font.font_table_provider.sfnt_version() == tag::OTTO
{
let cff_data = font.font_table_provider.read_table_data(tag::CFF).unwrap();
let mut cff = ReadScope::new(&cff_data).read::<CFF<'_>>().unwrap();
println!("warming cache with {} glyphs", font.num_glyphs());
for index in 0..font.num_glyphs() {
cache.write_glyph(&mut cff, index);
}
} else if font.glyph_table_flags.contains(GlyphTableFlags::GLYF) {
let loca_data = font.font_table_provider.read_table_data(tag::LOCA).unwrap();
let loca = ReadScope::new(&loca_data)
.read_dep::<LocaTable<'_>>((
usize::from(font.maxp_table.num_glyphs),
font.head_table().unwrap().unwrap().index_to_loc_format,
))
.unwrap();
let glyf_data = font.font_table_provider.read_table_data(tag::GLYF).unwrap();
let mut glyf = ReadScope::new(&glyf_data)
.read_dep::<GlyfTable<'_>>(&loca)
.unwrap();
println!("warming cache with {} glyphs", font.num_glyphs());
for index in 0..font.num_glyphs() {
cache.write_glyph(&mut glyf, index);
}
} else {
panic!("no glyf or CFF table");
}
let lang_tag = Some(tag::URD);
let features = allsorts::gsub::Features::Mask(allsorts::gsub::FeatureMask::all());
let kerning = true;
let infos = font
.shape(glyphs, script, lang_tag, &features, kerning)
.unwrap();
let direction = TextDirection::RightToLeft;
let vertical = false;
let mut layout = GlyphLayout::new(&mut font, &infos, direction, vertical);
let positions = layout.glyph_positions().unwrap();
let mut mesh = Self {
vertices: Vec::new(),
indices: Vec::new(),
};
let mut xcur = 0;
let mut ycur = 0;
for (glyph, position) in infos.iter().zip(&positions) {
let xpos = xcur + position.x_offset;
let ypos = ycur + position.y_offset;
xcur += position.hori_advance;
ycur += position.vert_advance;
println!("({}, {}): {:#?}", xpos, ypos, glyph);
// TODO refactor
let glyph = cache.glyphs.get(&glyph.glyph.glyph_index).unwrap();
let voff = mesh.vertices.len() as MeshIndex;
let xoff = (xpos as f32) * TEXT_SCALE;
let yoff = (ypos as f32) * TEXT_SCALE;
for v in glyph.vertices.iter() {
let x = v.position.x + xoff + 0.5;
let y = v.position.y + yoff + 0.5;
mesh.vertices.push(MeshVertex {
position: canary_types::Vec2 { x, y },
color: v.color,
});
}
for i in glyph.indices.iter() {
mesh.indices.push(i + voff);
}
}
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 {
builder: lyon::path::path::Builder,
}
impl OutlineSink {
pub fn new() -> Self {
Self {
builder: lyon::path::Path::builder(),
}
}
fn pf_line_to_lyon(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(v: &Vector2F) -> lyon::geom::Point<f32> {
lyon::geom::Point::<f32>::new(v.x(), v.y())
}
}
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();
}
}