460 lines
14 KiB
Rust
460 lines
14 KiB
Rust
use super::{DrawCommand, MeshIndex, MeshVertex};
|
|
|
|
use allsorts::binary::read::ReadScope;
|
|
use allsorts::cff::CFF;
|
|
use allsorts::font::{GlyphTableFlags, MatchingPresentation};
|
|
use allsorts::font_data::{DynamicFontTableProvider, FontData as AllsortsFontData};
|
|
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 as AllsortsFont};
|
|
use canary_types::{GlyphPosition, Rect};
|
|
use lyon::path::Path;
|
|
use ouroboros::self_referencing;
|
|
use parking_lot::{Mutex, RwLock};
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
|
|
const TEXT_SCALE: f32 = 0.075;
|
|
|
|
pub struct GlyphCache {
|
|
units_per_em: f32,
|
|
glyphs: RwLock<Vec<CachedGlyph>>,
|
|
}
|
|
|
|
impl GlyphCache {
|
|
pub fn new(font: &AllsortsFont<DynamicFontTableProvider>) -> Self {
|
|
let glyph_num = font.num_glyphs();
|
|
let mut glyphs = Vec::with_capacity(glyph_num as usize);
|
|
let units_per_em = font.head_table().unwrap().unwrap().units_per_em as f32;
|
|
|
|
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();
|
|
for index in 0..glyph_num {
|
|
glyphs.push(CachedGlyph::new(units_per_em, &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();
|
|
|
|
for index in 0..glyph_num {
|
|
glyphs.push(CachedGlyph::new(units_per_em, &mut glyf, index));
|
|
}
|
|
} else {
|
|
panic!("no glyf or CFF table");
|
|
}
|
|
|
|
Self {
|
|
units_per_em,
|
|
glyphs: RwLock::new(glyphs),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct CachedGlyph {
|
|
path: Path,
|
|
bounding_box: Rect,
|
|
mesh: Option<GlyphMesh>,
|
|
}
|
|
|
|
impl CachedGlyph {
|
|
pub fn new(units_per_em: f32, builder: &mut impl OutlineBuilder, index: u16) -> Self {
|
|
let mut sink = OutlineSink::new(units_per_em);
|
|
builder.visit(index, &mut sink).unwrap();
|
|
let bounding_box = sink.bounding_box;
|
|
let path = sink.builder.build();
|
|
|
|
Self {
|
|
bounding_box,
|
|
path,
|
|
mesh: None,
|
|
}
|
|
}
|
|
|
|
pub fn get_mesh(&mut self) -> &GlyphMesh {
|
|
self.mesh.get_or_insert_with(|| {
|
|
use lyon::tessellation::*;
|
|
|
|
let mut geometry: VertexBuffers<MeshVertex, MeshIndex> = VertexBuffers::new();
|
|
let fill_options = FillOptions::default().with_tolerance(0.0005);
|
|
let mut tessellator = FillTessellator::new();
|
|
tessellator
|
|
.tessellate_path(
|
|
&self.path.clone(),
|
|
&fill_options,
|
|
&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();
|
|
|
|
GlyphMesh {
|
|
vertices: geometry.vertices,
|
|
indices: geometry.indices,
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
pub struct OutlineSink {
|
|
units_per_em: f32,
|
|
bounding_box: Rect,
|
|
builder: lyon::path::path::Builder,
|
|
}
|
|
|
|
impl OutlineSink {
|
|
pub fn new(units_per_em: f32) -> Self {
|
|
Self {
|
|
units_per_em,
|
|
bounding_box: Rect::NEG_INFINITY,
|
|
builder: lyon::path::Path::builder(),
|
|
}
|
|
}
|
|
|
|
fn pf_line_to_lyon(
|
|
&mut 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(&mut self, v: &Vector2F) -> lyon::geom::Point<f32> {
|
|
let point = lyon::geom::Point::<f32>::new(v.x(), v.y()) / self.units_per_em;
|
|
|
|
// TODO clean this up with helper math methods?
|
|
let bb = &mut self.bounding_box;
|
|
|
|
if point.x < bb.bl.x {
|
|
bb.bl.x = point.x;
|
|
}
|
|
|
|
if point.x > bb.tr.x {
|
|
bb.tr.x = point.x;
|
|
}
|
|
|
|
if point.y < bb.bl.y {
|
|
bb.bl.y = point.y;
|
|
}
|
|
|
|
if point.y > bb.tr.y {
|
|
bb.tr.y = point.y;
|
|
}
|
|
|
|
point
|
|
}
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
impl Font {
|
|
pub fn load(
|
|
face_index: u16,
|
|
script: u32,
|
|
direction: TextDirection,
|
|
vertical: bool,
|
|
file_buffer: Vec<u8>,
|
|
) -> Self {
|
|
let mut data = FontData::load(face_index, script, direction, vertical, file_buffer);
|
|
let glyph_cache = data.with_inner_mut(|font| GlyphCache::new(font));
|
|
let data = Mutex::new(data);
|
|
Self { data, glyph_cache }
|
|
}
|
|
|
|
pub fn shape(&self, text: &str) -> TextLayout {
|
|
let positions = self.data.lock().shape(text);
|
|
let glyphs = self.glyph_cache.glyphs.read();
|
|
let mut bounds = Rect::NEG_INFINITY;
|
|
|
|
let units_per_em = self.glyph_cache.units_per_em;
|
|
let mut xcur = 0;
|
|
let mut ycur = 0;
|
|
for position in positions.iter() {
|
|
let xpos = xcur + position.xoff;
|
|
let ypos = ycur + position.yoff;
|
|
xcur += position.hori_advance;
|
|
ycur += position.vert_advance;
|
|
|
|
let scale = TEXT_SCALE / units_per_em;
|
|
let xpos = xpos as f32 * scale - 0.9;
|
|
let ypos = ypos as f32 * scale + 0.9;
|
|
|
|
let mut bb = glyphs.get(position.index as usize).unwrap().bounding_box;
|
|
bb.bl.x = bb.bl.x * TEXT_SCALE + xpos;
|
|
bb.bl.y = bb.bl.y * TEXT_SCALE + ypos;
|
|
bb.tr.x = bb.tr.x * TEXT_SCALE + xpos;
|
|
bb.tr.y = bb.tr.y * TEXT_SCALE + ypos;
|
|
|
|
// TODO use euclid instead
|
|
if bounds.bl.x > bb.bl.x {
|
|
bounds.bl.x = bb.bl.x;
|
|
}
|
|
|
|
if bounds.bl.y > bb.bl.y {
|
|
bounds.bl.y = bb.bl.y;
|
|
}
|
|
|
|
if bounds.tr.x < bb.tr.x {
|
|
bounds.tr.x = bb.tr.x;
|
|
}
|
|
|
|
if bounds.tr.y < bb.tr.y {
|
|
bounds.tr.y = bb.tr.y;
|
|
}
|
|
}
|
|
|
|
TextLayout {
|
|
bounds,
|
|
glyphs: positions,
|
|
}
|
|
}
|
|
|
|
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;
|
|
let mut xcur = 0;
|
|
let mut ycur = 0;
|
|
for position in positions.iter() {
|
|
let glyph = glyphs.get_mut(position.index as usize).unwrap().get_mesh();
|
|
|
|
let xpos = xcur + position.xoff;
|
|
let ypos = ycur + position.yoff;
|
|
xcur += position.hori_advance;
|
|
ycur += position.vert_advance;
|
|
|
|
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;
|
|
|
|
for v in glyph.vertices.iter() {
|
|
let x = v.position.x + xpos - 0.9;
|
|
let y = v.position.y + ypos + yoff;
|
|
vertices.push(MeshVertex {
|
|
position: canary_types::Vec2 { x, y },
|
|
color: v.color,
|
|
});
|
|
}
|
|
|
|
for i in glyph.indices.iter() {
|
|
indices.push(i + voff);
|
|
}
|
|
}
|
|
|
|
vec![DrawCommand::Mesh { vertices, indices }]
|
|
}
|
|
}
|
|
|
|
#[self_referencing]
|
|
pub struct FontData {
|
|
file_buffer: Vec<u8>,
|
|
face_index: u16,
|
|
script: u32,
|
|
direction: TextDirection,
|
|
vertical: bool,
|
|
|
|
#[borrows(file_buffer)]
|
|
#[covariant]
|
|
read_scope: ReadScope<'this>,
|
|
|
|
#[borrows(read_scope)]
|
|
#[not_covariant]
|
|
font_data: AllsortsFontData<'this>,
|
|
|
|
#[borrows(font_data)]
|
|
#[covariant]
|
|
inner: AllsortsFont<DynamicFontTableProvider<'this>>,
|
|
}
|
|
|
|
impl FontData {
|
|
pub fn load(
|
|
face_index: u16,
|
|
script: u32,
|
|
direction: TextDirection,
|
|
vertical: bool,
|
|
file_buffer: Vec<u8>,
|
|
) -> Self {
|
|
FontDataBuilder {
|
|
file_buffer,
|
|
face_index,
|
|
script,
|
|
direction,
|
|
vertical,
|
|
read_scope_builder: |buffer| ReadScope::new(buffer),
|
|
font_data_builder: |scope| scope.read::<AllsortsFontData<'_>>().unwrap(),
|
|
inner_builder: |font_data| {
|
|
AllsortsFont::new(font_data.table_provider(0).unwrap())
|
|
.unwrap()
|
|
.unwrap()
|
|
},
|
|
}
|
|
.build()
|
|
}
|
|
|
|
pub fn shape(&mut self, text: &str) -> Vec<GlyphPosition> {
|
|
let face_index = *self.borrow_face_index() as u16;
|
|
let presentation = MatchingPresentation::Required;
|
|
let script = *self.borrow_script();
|
|
let lang_tag = None;
|
|
let features = allsorts::gsub::Features::default();
|
|
let kerning = true;
|
|
let direction = *self.borrow_direction();
|
|
let vertical = *self.borrow_vertical();
|
|
|
|
self.with_inner_mut(|font| {
|
|
let mapped = font.map_glyphs(text, script, presentation);
|
|
let infos = font
|
|
.shape(mapped, script, lang_tag, &features, kerning)
|
|
.unwrap();
|
|
let mut layout = GlyphLayout::new(font, &infos, direction, vertical);
|
|
let positions = layout.glyph_positions().unwrap();
|
|
let mut glyphs = Vec::with_capacity(positions.len());
|
|
for (glyph, position) in infos.iter().zip(&positions) {
|
|
glyphs.push(GlyphPosition {
|
|
face: face_index,
|
|
index: glyph.glyph.glyph_index,
|
|
hori_advance: position.hori_advance,
|
|
vert_advance: position.vert_advance,
|
|
xoff: position.x_offset,
|
|
yoff: position.y_offset,
|
|
});
|
|
}
|
|
|
|
glyphs
|
|
})
|
|
}
|
|
}
|
|
|
|
pub struct FontStore {
|
|
source: Box<dyn font_kit::source::Source>,
|
|
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();
|
|
let source = Box::new(source);
|
|
|
|
Self {
|
|
source,
|
|
loaded: Default::default(),
|
|
}
|
|
}
|
|
|
|
pub fn load_font(&self, title: &str) -> Arc<Font> {
|
|
let mut loaded = self.loaded.lock();
|
|
|
|
if let Some(loaded) = loaded.get(title) {
|
|
return loaded.to_owned();
|
|
}
|
|
|
|
use font_kit::family_name::FamilyName;
|
|
use font_kit::handle::Handle;
|
|
use font_kit::properties::Properties;
|
|
|
|
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;
|
|
let font = Font::load(face_index, script, direction, vertical, font_data.clone());
|
|
let font = Arc::new(font);
|
|
loaded.insert(title.to_string(), font.to_owned());
|
|
font
|
|
}
|
|
}
|