System font loading + proper scaling + FontStore
This commit is contained in:
parent
feadf8fa15
commit
dc71971c39
|
@ -20,3 +20,6 @@ lyon = "1"
|
||||||
ouroboros = "^0.15"
|
ouroboros = "^0.15"
|
||||||
parking_lot = "0.12"
|
parking_lot = "0.12"
|
||||||
wasmtime = "0.38"
|
wasmtime = "0.38"
|
||||||
|
|
||||||
|
[dependencies.font-kit]
|
||||||
|
version = "0.11"
|
||||||
|
|
164
src/text.rs
164
src/text.rs
|
@ -14,16 +14,27 @@ use canary_types::GlyphPosition;
|
||||||
use lyon::path::Path;
|
use lyon::path::Path;
|
||||||
use ouroboros::self_referencing;
|
use ouroboros::self_referencing;
|
||||||
|
|
||||||
const TEXT_SCALE: f32 = 0.0001;
|
const TEXT_SCALE: f32 = 0.075;
|
||||||
|
|
||||||
pub struct GlyphCache {
|
pub struct GlyphCache {
|
||||||
|
units_per_em: f32,
|
||||||
glyphs: Vec<CachedGlyph>,
|
glyphs: Vec<CachedGlyph>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlyphCache {
|
impl GlyphCache {
|
||||||
pub fn new(font: &AllsortsFont<DynamicFontTableProvider>) -> Self {
|
pub fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
units_per_em: 0.0,
|
||||||
|
glyphs: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_paths(&mut self, font: &AllsortsFont<DynamicFontTableProvider>) {
|
||||||
let glyph_num = font.num_glyphs();
|
let glyph_num = font.num_glyphs();
|
||||||
let mut glyphs = Vec::with_capacity(glyph_num as usize);
|
self.glyphs.clear();
|
||||||
|
self.glyphs.reserve(glyph_num as usize);
|
||||||
|
|
||||||
|
self.units_per_em = font.head_table().unwrap().unwrap().units_per_em as f32;
|
||||||
|
|
||||||
if font.glyph_table_flags.contains(GlyphTableFlags::CFF)
|
if font.glyph_table_flags.contains(GlyphTableFlags::CFF)
|
||||||
&& font.font_table_provider.sfnt_version() == tag::OTTO
|
&& font.font_table_provider.sfnt_version() == tag::OTTO
|
||||||
|
@ -31,7 +42,8 @@ impl GlyphCache {
|
||||||
let cff_data = font.font_table_provider.read_table_data(tag::CFF).unwrap();
|
let cff_data = font.font_table_provider.read_table_data(tag::CFF).unwrap();
|
||||||
let mut cff = ReadScope::new(&cff_data).read::<CFF<'_>>().unwrap();
|
let mut cff = ReadScope::new(&cff_data).read::<CFF<'_>>().unwrap();
|
||||||
for index in 0..glyph_num {
|
for index in 0..glyph_num {
|
||||||
glyphs.push(CachedGlyph::new(&mut cff, index));
|
self.glyphs
|
||||||
|
.push(CachedGlyph::new(self.units_per_em, &mut cff, index));
|
||||||
}
|
}
|
||||||
} else if font.glyph_table_flags.contains(GlyphTableFlags::GLYF) {
|
} else if font.glyph_table_flags.contains(GlyphTableFlags::GLYF) {
|
||||||
let loca_data = font.font_table_provider.read_table_data(tag::LOCA).unwrap();
|
let loca_data = font.font_table_provider.read_table_data(tag::LOCA).unwrap();
|
||||||
|
@ -47,24 +59,24 @@ impl GlyphCache {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for index in 0..glyph_num {
|
for index in 0..glyph_num {
|
||||||
glyphs.push(CachedGlyph::new(&mut glyf, index));
|
self.glyphs
|
||||||
|
.push(CachedGlyph::new(self.units_per_em, &mut glyf, index));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("no glyf or CFF table");
|
panic!("no glyf or CFF table {:?}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self { glyphs }
|
pub fn draw(&mut self, mesh: &mut DummyText, index: u16, xpos: i32, ypos: i32, yoff: f32) {
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw(&mut self, mesh: &mut DummyText, index: u16, xpos: i32, ypos: i32) {
|
|
||||||
let glyph = self.glyphs.get_mut(index as usize).unwrap().get_mesh();
|
let glyph = self.glyphs.get_mut(index as usize).unwrap().get_mesh();
|
||||||
let voff = mesh.vertices.len() as MeshIndex;
|
let voff = mesh.vertices.len() as MeshIndex;
|
||||||
let xoff = (xpos as f32) * TEXT_SCALE;
|
let scale = TEXT_SCALE / self.units_per_em;
|
||||||
let yoff = (ypos as f32) * TEXT_SCALE;
|
let xpos = xpos as f32 * scale;
|
||||||
|
let ypos = ypos as f32 * scale;
|
||||||
|
|
||||||
for v in glyph.vertices.iter() {
|
for v in glyph.vertices.iter() {
|
||||||
let x = v.position.x + xoff - 0.9;
|
let x = v.position.x + xpos - 0.9;
|
||||||
let y = v.position.y + yoff + 0.5;
|
let y = v.position.y + ypos + yoff;
|
||||||
mesh.vertices.push(MeshVertex {
|
mesh.vertices.push(MeshVertex {
|
||||||
position: canary_types::Vec2 { x, y },
|
position: canary_types::Vec2 { x, y },
|
||||||
color: v.color,
|
color: v.color,
|
||||||
|
@ -83,8 +95,8 @@ pub struct CachedGlyph {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CachedGlyph {
|
impl CachedGlyph {
|
||||||
pub fn new(builder: &mut impl OutlineBuilder, index: u16) -> Self {
|
pub fn new(units_per_em: f32, builder: &mut impl OutlineBuilder, index: u16) -> Self {
|
||||||
let mut sink = OutlineSink::new();
|
let mut sink = OutlineSink::new(units_per_em);
|
||||||
builder.visit(index, &mut sink).unwrap();
|
builder.visit(index, &mut sink).unwrap();
|
||||||
let path = sink.builder.build();
|
let path = sink.builder.build();
|
||||||
|
|
||||||
|
@ -96,11 +108,12 @@ impl CachedGlyph {
|
||||||
use lyon::tessellation::*;
|
use lyon::tessellation::*;
|
||||||
|
|
||||||
let mut geometry: VertexBuffers<MeshVertex, MeshIndex> = VertexBuffers::new();
|
let mut geometry: VertexBuffers<MeshVertex, MeshIndex> = VertexBuffers::new();
|
||||||
|
let fill_options = FillOptions::default().with_tolerance(0.0005);
|
||||||
let mut tessellator = FillTessellator::new();
|
let mut tessellator = FillTessellator::new();
|
||||||
tessellator
|
tessellator
|
||||||
.tessellate_path(
|
.tessellate_path(
|
||||||
&self.path.clone(),
|
&self.path.clone(),
|
||||||
&FillOptions::default(),
|
&fill_options,
|
||||||
&mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
|
&mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| {
|
||||||
let position = vertex.position() * TEXT_SCALE;
|
let position = vertex.position() * TEXT_SCALE;
|
||||||
let position = canary_types::Vec2 {
|
let position = canary_types::Vec2 {
|
||||||
|
@ -136,6 +149,7 @@ pub struct Font {
|
||||||
script: u32,
|
script: u32,
|
||||||
direction: TextDirection,
|
direction: TextDirection,
|
||||||
vertical: bool,
|
vertical: bool,
|
||||||
|
glyph_cache: GlyphCache,
|
||||||
|
|
||||||
#[borrows(file_buffer)]
|
#[borrows(file_buffer)]
|
||||||
#[covariant]
|
#[covariant]
|
||||||
|
@ -158,12 +172,13 @@ impl Font {
|
||||||
vertical: bool,
|
vertical: bool,
|
||||||
file_buffer: Vec<u8>,
|
file_buffer: Vec<u8>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
FontBuilder {
|
let mut built = FontBuilder {
|
||||||
file_buffer,
|
file_buffer,
|
||||||
face_index,
|
face_index,
|
||||||
script,
|
script,
|
||||||
direction,
|
direction,
|
||||||
vertical,
|
vertical,
|
||||||
|
glyph_cache: GlyphCache::empty(),
|
||||||
read_scope_builder: |buffer| ReadScope::new(buffer),
|
read_scope_builder: |buffer| ReadScope::new(buffer),
|
||||||
font_data_builder: |scope| scope.read::<FontData<'_>>().unwrap(),
|
font_data_builder: |scope| scope.read::<FontData<'_>>().unwrap(),
|
||||||
inner_builder: |font_data| {
|
inner_builder: |font_data| {
|
||||||
|
@ -172,11 +187,16 @@ impl Font {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
.build()
|
.build();
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_glyph_cache(&self) -> GlyphCache {
|
let mut glyph_cache = GlyphCache::empty();
|
||||||
GlyphCache::new(self.borrow_inner())
|
built.with_inner_mut(|font| {
|
||||||
|
glyph_cache.load_paths(font);
|
||||||
|
});
|
||||||
|
|
||||||
|
built.with_glyph_cache_mut(|old| std::mem::replace(old, glyph_cache));
|
||||||
|
|
||||||
|
built
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shape(&mut self, text: &str) -> Vec<GlyphPosition> {
|
pub fn shape(&mut self, text: &str) -> Vec<GlyphPosition> {
|
||||||
|
@ -213,6 +233,49 @@ impl Font {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct FontStore {
|
||||||
|
source: Box<dyn font_kit::source::Source>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontStore {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let source = font_kit::source::SystemSource::new();
|
||||||
|
let source = Box::new(source);
|
||||||
|
Self { source }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_font(&self, title: &str) -> Font {
|
||||||
|
use font_kit::family_name::FamilyName;
|
||||||
|
use font_kit::handle::Handle;
|
||||||
|
use font_kit::properties::{Properties, Style};
|
||||||
|
|
||||||
|
println!("loading font family {}", title);
|
||||||
|
|
||||||
|
let font_handle = self
|
||||||
|
.source
|
||||||
|
.select_best_match(&[FamilyName::Title(title.to_string())], &Properties::new())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("loading font file: {:?}", font_handle);
|
||||||
|
|
||||||
|
let font_data = if let Handle::Path {
|
||||||
|
path,
|
||||||
|
font_index: _,
|
||||||
|
} = font_handle
|
||||||
|
{
|
||||||
|
std::fs::read(path).unwrap()
|
||||||
|
} else {
|
||||||
|
panic!("font is not a file for some reason");
|
||||||
|
};
|
||||||
|
|
||||||
|
let script = tag::LATN;
|
||||||
|
let face_index = 0;
|
||||||
|
let direction = TextDirection::LeftToRight;
|
||||||
|
let vertical = false;
|
||||||
|
Font::load(face_index, script, direction, vertical, font_data.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DummyText {
|
pub struct DummyText {
|
||||||
vertices: Vec<MeshVertex>,
|
vertices: Vec<MeshVertex>,
|
||||||
indices: Vec<MeshIndex>,
|
indices: Vec<MeshIndex>,
|
||||||
|
@ -220,24 +283,22 @@ pub struct DummyText {
|
||||||
|
|
||||||
impl Default for DummyText {
|
impl Default for DummyText {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let script = tag::LATN;
|
let store = FontStore::new();
|
||||||
let face_index = 0;
|
let text = "The quick brown fox jumps over the lazy dog. 🍑";
|
||||||
let direction = TextDirection::LeftToRight;
|
let font_families = ["mononoki", "Liberation Sans", "Ubuntu", "Noto Color Emoji"];
|
||||||
let vertical = false;
|
|
||||||
let buffer = include_bytes!("linja-pona-4.9.otf");
|
|
||||||
let mut font = Font::load(face_index, script, direction, vertical, buffer.to_vec());
|
|
||||||
|
|
||||||
let mut cache = font.create_glyph_cache();
|
|
||||||
|
|
||||||
let text = "toki o! nimi mi li [_mun_alasa_sona]. toki+pona li pona.";
|
|
||||||
|
|
||||||
let glyphs = font.shape(text);
|
|
||||||
|
|
||||||
let mut mesh = Self {
|
let mut mesh = Self {
|
||||||
vertices: Vec::new(),
|
vertices: Vec::new(),
|
||||||
indices: Vec::new(),
|
indices: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let line_height = -0.1;
|
||||||
|
let mut line_offset = 0.9;
|
||||||
|
for family in font_families.iter() {
|
||||||
|
let mut font = store.load_font(family);
|
||||||
|
let glyphs = font.shape(text);
|
||||||
|
|
||||||
|
font.with_glyph_cache_mut(|cache| {
|
||||||
let mut xcur = 0;
|
let mut xcur = 0;
|
||||||
let mut ycur = 0;
|
let mut ycur = 0;
|
||||||
for glyph in glyphs.iter() {
|
for glyph in glyphs.iter() {
|
||||||
|
@ -245,7 +306,11 @@ impl Default for DummyText {
|
||||||
let ypos = ycur + glyph.yoff;
|
let ypos = ycur + glyph.yoff;
|
||||||
xcur += glyph.hori_advance;
|
xcur += glyph.hori_advance;
|
||||||
ycur += glyph.vert_advance;
|
ycur += glyph.vert_advance;
|
||||||
cache.draw(&mut mesh, glyph.index, xpos, ypos);
|
cache.draw(&mut mesh, glyph.index, xpos, ypos, line_offset);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
line_offset += line_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
mesh
|
mesh
|
||||||
|
@ -262,48 +327,53 @@ impl DummyText {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OutlineSink {
|
pub struct OutlineSink {
|
||||||
|
units_per_em: f32,
|
||||||
builder: lyon::path::path::Builder,
|
builder: lyon::path::path::Builder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutlineSink {
|
impl OutlineSink {
|
||||||
pub fn new() -> Self {
|
pub fn new(units_per_em: f32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
units_per_em,
|
||||||
builder: lyon::path::Path::builder(),
|
builder: lyon::path::Path::builder(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pf_line_to_lyon(line: &LineSegment2F) -> (lyon::geom::Point<f32>, lyon::geom::Point<f32>) {
|
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.from()),
|
||||||
Self::pf_vector_to_lyon(&line.to()),
|
self.pf_vector_to_lyon(&line.to()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pf_vector_to_lyon(v: &Vector2F) -> lyon::geom::Point<f32> {
|
fn pf_vector_to_lyon(&self, v: &Vector2F) -> lyon::geom::Point<f32> {
|
||||||
lyon::geom::Point::<f32>::new(v.x(), v.y())
|
lyon::geom::Point::<f32>::new(v.x(), v.y()) / self.units_per_em
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl allsorts::outline::OutlineSink for OutlineSink {
|
impl allsorts::outline::OutlineSink for OutlineSink {
|
||||||
fn move_to(&mut self, to: Vector2F) {
|
fn move_to(&mut self, to: Vector2F) {
|
||||||
let to = Self::pf_vector_to_lyon(&to);
|
let to = self.pf_vector_to_lyon(&to);
|
||||||
self.builder.begin(to);
|
self.builder.begin(to);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn line_to(&mut self, to: Vector2F) {
|
fn line_to(&mut self, to: Vector2F) {
|
||||||
let to = Self::pf_vector_to_lyon(&to);
|
let to = self.pf_vector_to_lyon(&to);
|
||||||
self.builder.line_to(to);
|
self.builder.line_to(to);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quadratic_curve_to(&mut self, control: Vector2F, to: Vector2F) {
|
fn quadratic_curve_to(&mut self, control: Vector2F, to: Vector2F) {
|
||||||
let control = Self::pf_vector_to_lyon(&control);
|
let control = self.pf_vector_to_lyon(&control);
|
||||||
let to = Self::pf_vector_to_lyon(&to);
|
let to = self.pf_vector_to_lyon(&to);
|
||||||
self.builder.quadratic_bezier_to(control, to);
|
self.builder.quadratic_bezier_to(control, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cubic_curve_to(&mut self, control: LineSegment2F, to: Vector2F) {
|
fn cubic_curve_to(&mut self, control: LineSegment2F, to: Vector2F) {
|
||||||
let (ctrl1, ctrl2) = Self::pf_line_to_lyon(&control);
|
let (ctrl1, ctrl2) = self.pf_line_to_lyon(&control);
|
||||||
let to = Self::pf_vector_to_lyon(&to);
|
let to = self.pf_vector_to_lyon(&to);
|
||||||
self.builder.cubic_bezier_to(ctrl1, ctrl2, to);
|
self.builder.cubic_bezier_to(ctrl1, ctrl2, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue