canary-rs/src/text/mod.rs

273 lines
7.7 KiB
Rust

// Copyright (c) 2022 Marceline Cramer
// SPDX-License-Identifier: LGPL-3.0-or-later
use super::{Color, DrawCommand, MeshIndex, MeshVertex, Rect, Vec2};
use allsorts::binary::read::ReadScope;
use allsorts::font::MatchingPresentation;
use allsorts::font_data::{DynamicFontTableProvider, FontData as AllsortsFontData};
use allsorts::glyph_position::{GlyphLayout, TextDirection};
use allsorts::{tag, Font as AllsortsFont};
use ouroboros::self_referencing;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
pub mod glyph;
use glyph::GlyphCache;
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct GlyphPosition {
pub index: u16,
pub hori_advance: i32,
pub vert_advance: i32,
pub xoff: i32,
pub yoff: i32,
}
pub struct TextLayout {
pub font_id: u32,
pub bounds: Rect,
pub glyphs: Vec<GlyphPosition>,
}
pub struct Font {
font_id: u32,
data: Mutex<FontData>,
glyph_cache: GlyphCache,
}
impl Font {
pub fn load(
font_id: u32,
script: u32,
direction: TextDirection,
vertical: bool,
file_buffer: Vec<u8>,
) -> Self {
let mut data = FontData::load(script, direction, vertical, file_buffer);
let glyph_cache = data.with_inner_mut(|font| GlyphCache::new(font));
let data = Mutex::new(data);
Self {
font_id,
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 pos = Vec2::new(xpos as f32, ypos as f32) / units_per_em;
let bb = glyphs.get(position.index as usize).unwrap().bounding_box;
bounds = bounds.union(&bb.offset(pos));
}
TextLayout {
font_id: self.font_id,
bounds,
glyphs: positions,
}
}
pub fn draw(
&self,
positions: &[GlyphPosition],
offset: Vec2,
scale: f32,
color: Color,
) -> 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 pos_scale = scale / 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 xpos = offset.x + xpos as f32 * pos_scale;
let ypos = offset.y - ypos as f32 * pos_scale;
for v in glyph.vertices.iter() {
let x = v.x * scale + xpos;
let y = v.y * scale + ypos;
vertices.push(MeshVertex {
position: Vec2::new(x, y),
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>,
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(
script: u32,
direction: TextDirection,
vertical: bool,
file_buffer: Vec<u8>,
) -> Self {
FontDataBuilder {
file_buffer,
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 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 {
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>>>,
current_index: AtomicU32,
}
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(),
current_index: AtomicU32::new(0),
}
}
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 = self.current_index.fetch_add(1, Ordering::Relaxed);
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
}
}