use prelude::*;
use core::{self, Layer, RenderContext, Color, Point2, Rect};
use core::builder::*;
use rusttype;
use backends::backend;
use font_loader::system_fonts;
static FONT_COUNTER: AtomicUsize = ATOMIC_USIZE_INIT;
#[derive(Clone)]
pub struct Font {
data : Vec<u8>,
font_id : usize,
size : f32,
context : RenderContext,
}
impl Debug for Font {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Font")
.field("data_len", &self.data.len())
.field("font_id", &self.font_id)
.field("size", &self.size)
.finish()
}
}
impl Font {
pub fn builder(context: &RenderContext) -> FontBuilder {
FontBuilder::new(context)
}
pub fn from_file(context: &RenderContext, file: &str) -> core::Result<Font> {
use std::io::Read;
let mut f = File::open(Path::new(file))?;
let mut font_data = Vec::new();
f.read_to_end(&mut font_data)?;
Ok(Self::create(context, font_data, 12.0))
}
pub fn query_all() -> Vec<String> {
system_fonts::query_all()
}
pub fn query() -> FontQueryBuilder {
FontQueryBuilder::new()
}
pub fn clone_with_size(self: &Self, size: f32) -> Font {
let mut font = (*self).clone();
font.size = size;
font
}
pub fn write<T>(self: &Self, layer: &Layer, text: &str, position: T, color: Color) -> &Font where Point2<f32>: From<T> {
let position = Point2::from(position);
self.write_paragraph(layer, text, position.0, position.1, 0.0, color, 0.0, 1.0, 1.0);
self
}
pub fn write_wrapped<T>(self: &Self, layer: &Layer, text: &str, position: T, color: Color, max_width: f32) -> &Font where Point2<f32>: From<T> {
let position = Point2::from(position);
self.write_paragraph(layer, text, position.0, position.1, max_width, color, 0.0, 1.0, 1.0);
self
}
pub fn write_transformed<T, U>(self: &Self, layer: &Layer, text: &str, position: T, color: Color, max_width: f32, rotation: f32, scale: U) -> &Font where Point2<f32>: From<T>+From<U> {
let position = Point2::from(position);
let scale = Point2::from(scale);
self.write_paragraph(layer, text, position.0, position.1, max_width, color, rotation, scale.0, scale.1);
self
}
pub fn arc(self: Self) -> Arc<Self> {
Arc::new(self)
}
pub(crate) fn query_specific(info: FontInfo) -> Vec<String> {
system_fonts::query_specific(&mut Self::build_property(&info))
}
pub(crate) fn from_info(context: &RenderContext, info: FontInfo) -> core::Result<Font> {
if let Some((font_data, _)) = system_fonts::get(&Self::build_property(&info)) {
Ok(Self::create(context, font_data, info.size))
} else {
Err(core::Error::FontError("Failed to get system font".to_string()))
}
}
fn create(context: &RenderContext, font_data: Vec<u8>, size: f32) -> Font {
Font {
data : font_data,
font_id : FONT_COUNTER.fetch_add(1, Ordering::Relaxed),
size : size,
context : context.clone(),
}
}
fn write_paragraph(self: &Self, layer: &Layer, text: &str, x: f32, y: f32, max_width: f32, color: Color, rotation: f32, scale_x: f32, scale_y: f32) {
let rt_font = rusttype::FontCollection::from_bytes(&self.data[..]).unwrap().into_font().unwrap();
let bucket_id = 0;
let glyphs = Self::layout_paragraph(&rt_font, rusttype::Scale::uniform(self.size), max_width, &text);
let context = self.context.lock();
context.font_cache.queue(self.font_id, &glyphs);
let anchor = (0., 0.);
let scale = (scale_x, scale_y);
let cos_rot = rotation.cos();
let sin_rot = rotation.sin();
for glyph in &glyphs {
if let Some((uv, pos, dim)) = context.font_cache.rect_for(self.font_id, glyph) {
let dist_x = pos.0 * scale_x;
let dist_y = pos.1 * scale_y;
let offset_x = x + dist_x * cos_rot - dist_y * sin_rot;
let offset_y = y + dist_x * sin_rot + dist_y * cos_rot;
layer.add_rect(None, bucket_id, 0, 1, uv, (offset_x, offset_y), anchor, dim, color, rotation, scale);
}
}
}
fn layout_paragraph<'a>(font: &'a rusttype::Font, scale: rusttype::Scale, width: f32, text: &str) -> Vec<rusttype::PositionedGlyph<'a>> {
use unicode_normalization::UnicodeNormalization;
let mut result = Vec::new();
let v_metrics = font.v_metrics(scale);
let advance_height = v_metrics.ascent - v_metrics.descent + v_metrics.line_gap;
let mut caret = rusttype::point(0.0, v_metrics.ascent);
let mut last_glyph_id = None;
for c in text.nfc() {
if c.is_control() {
match c {
'\n' => {
caret = rusttype::point(0.0, caret.y + advance_height);
},
_ => {}
}
continue;
}
let base_glyph = font.glyph(c);
if let Some(id) = last_glyph_id.take() {
caret.x += font.pair_kerning(scale, id, base_glyph.id());
}
last_glyph_id = Some(base_glyph.id());
let mut glyph = base_glyph.scaled(scale).positioned(caret);
if let Some(bb) = glyph.pixel_bounding_box() {
if width > 0.0 && bb.max.x > width as i32 {
caret = rusttype::point(0.0, caret.y + advance_height);
glyph = glyph.into_unpositioned().positioned(caret);
last_glyph_id = None;
}
}
caret.x += glyph.unpositioned().h_metrics().advance_width;
result.push(glyph);
}
result
}
fn build_property(info: &FontInfo) -> system_fonts::FontProperty {
let mut property = system_fonts::FontPropertyBuilder::new();
if info.family != "" {
property = property.family(&info.family);
}
if info.italic {
property = property.italic();
}
if info.oblique {
property = property.oblique();
}
if info.bold {
property = property.bold();
}
if info.monospace {
property = property.monospace();
}
property.build()
}
}
pub struct FontCache {
cache : Mutex<rusttype::gpu_cache::Cache<'static>>,
queue : Mutex<Vec<(Rect<u32>, Vec<u8>)>>,
dirty : AtomicBool,
}
impl FontCache {
pub fn new(width: u32, height: u32, scale_tolerance: f32, position_tolerance: f32) -> FontCache {
FontCache {
cache: Mutex::new(rusttype::gpu_cache::Cache::new(width, height, scale_tolerance, position_tolerance)),
queue: Mutex::new(Vec::new()),
dirty: AtomicBool::new(false),
}
}
pub fn queue(self: &Self, font_id: usize, glyphs: &[rusttype::PositionedGlyph]) {
let mut cache = self.cache.lock().unwrap();
let mut queue = self.queue.lock().unwrap();
let mut dirties = false;
for glyph in glyphs {
cache.queue_glyph(font_id, glyph.standalone());
}
cache.cache_queued(|rect, data| {
queue.push( ( ((rect.min.x, rect.min.y), (rect.max.x, rect.max.y)), data.to_vec() ) );
dirties = true;
}).unwrap();
if dirties {
self.dirty.store(dirties, Ordering::Relaxed);
}
}
pub fn update(self: &Self, texture: &backend::Texture2d) {
if self.dirty.load(Ordering::Relaxed) {
let mut queue = self.queue.lock().unwrap();
for &(ref rect, ref data) in queue.deref() {
texture.write(rect, data);
}
queue.clear();
self.dirty.store(false, Ordering::Relaxed);
}
}
pub fn rect_for(self: &Self, font_id: usize, glyph: &rusttype::PositionedGlyph) -> Option<(Rect, Point2, Point2)> {
let cache = self.cache.lock().unwrap();
if let Ok(Some((uv_rect, screen_rect))) = cache.rect_for(font_id, glyph) {
let uv = ((uv_rect.min.x, uv_rect.min.y), (uv_rect.max.x, uv_rect.max.y));
let pos = (screen_rect.min.x as f32, screen_rect.min.y as f32);
let dim = ((screen_rect.max.x - screen_rect.min.x) as f32, (screen_rect.max.y - screen_rect.min.y) as f32);
Some((uv, pos, dim))
} else {
None
}
}
}
#[derive(Clone)]
pub struct FontInfo {
pub italic : bool,
pub oblique : bool,
pub bold : bool,
pub monospace : bool,
pub family : String,
pub size : f32,
}
impl Default for FontInfo {
fn default() -> FontInfo {
FontInfo {
italic : false,
oblique : false,
bold : false,
monospace : false,
family : "".to_string(),
size : 10.0,
}
}
}