Support bold/italic font rendering on macOS
This patch adds support for rendering italic fonts and bold fonts. The `font` crate has a couple of new paradigms to support this: font keys and glyph keys. `FontKey` is a lightweight (4 byte) identifier for a font loaded out of the rasterizer. This replaces `FontDesc` for rasterizing glyphs from a loaded font. `FontDesc` had the problem that it contained two strings, and the glyph cache needs to store a copy of the font key for every loaded glyph. `GlyphKey` is now passed to the glyph rasterization method instead of a simple `char`. `GlyphKey` contains information including font, size, and the character. The rasterizer APIs do not define what happens when loading the same font from a `FontDesc` more than once. It is assumed that the application will track the resulting `FontKey` instead of asking the font to be loaded multiple times.
This commit is contained in:
parent
874719580c
commit
b70394170c
|
@ -43,7 +43,7 @@ use euclid::point::Point2D;
|
||||||
use euclid::rect::Rect;
|
use euclid::rect::Rect;
|
||||||
use euclid::size::Size2D;
|
use euclid::size::Size2D;
|
||||||
|
|
||||||
use super::{FontDesc, RasterizedGlyph, Metrics};
|
use super::{FontDesc, RasterizedGlyph, Metrics, FontKey, GlyphKey};
|
||||||
|
|
||||||
pub mod cg_color;
|
pub mod cg_color;
|
||||||
use self::cg_color::{CGColorRef, CGColor};
|
use self::cg_color::{CGColorRef, CGColor};
|
||||||
|
@ -52,6 +52,10 @@ pub mod byte_order;
|
||||||
use self::byte_order::kCGBitmapByteOrder32Host;
|
use self::byte_order::kCGBitmapByteOrder32Host;
|
||||||
use self::byte_order::extract_rgb;
|
use self::byte_order::extract_rgb;
|
||||||
|
|
||||||
|
use super::Size;
|
||||||
|
|
||||||
|
static FONT_LOAD_ERROR: &'static str = "font specified by FontKey has been loaded";
|
||||||
|
|
||||||
/// Font descriptor
|
/// Font descriptor
|
||||||
///
|
///
|
||||||
/// The descriptor provides data about a font and supports creating a font.
|
/// The descriptor provides data about a font and supports creating a font.
|
||||||
|
@ -70,7 +74,7 @@ pub struct Descriptor {
|
||||||
///
|
///
|
||||||
/// Given a fontdesc, can rasterize fonts.
|
/// Given a fontdesc, can rasterize fonts.
|
||||||
pub struct Rasterizer {
|
pub struct Rasterizer {
|
||||||
fonts: HashMap<FontDesc, Font>,
|
fonts: HashMap<FontKey, Font>,
|
||||||
device_pixel_ratio: f32,
|
device_pixel_ratio: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,22 +87,35 @@ impl Rasterizer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn metrics(&mut self, desc: &FontDesc, size: f32) -> Metrics {
|
/// Get metrics for font specified by FontKey
|
||||||
let scaled_size = self.device_pixel_ratio * size;
|
///
|
||||||
self.get_font(desc, scaled_size).unwrap().metrics()
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If FontKey was not generated by `load_font`, this method will panic.
|
||||||
|
pub fn metrics(&self, key: FontKey, _size: Size) -> Metrics {
|
||||||
|
// NOTE size is not needed here since the font loaded already contains
|
||||||
|
// it. It's part of the API due to platform differences.
|
||||||
|
let font = self.fonts.get(&key).expect(FONT_LOAD_ERROR);
|
||||||
|
font.metrics()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_font(&mut self, desc: &FontDesc, size: f32) -> Option<Font> {
|
pub fn load_font(&mut self, desc: &FontDesc, size: Size) -> Option<FontKey> {
|
||||||
if let Some(font) = self.fonts.get(desc) {
|
self.get_font(desc, size)
|
||||||
return Some(font.clone());
|
.map(|font| {
|
||||||
|
let key = FontKey::next();
|
||||||
|
self.fonts.insert(key, font);
|
||||||
|
|
||||||
|
key
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_font(&mut self, desc: &FontDesc, size: Size) -> Option<Font> {
|
||||||
let descriptors = descriptors_for_family(&desc.name[..]);
|
let descriptors = descriptors_for_family(&desc.name[..]);
|
||||||
for descriptor in descriptors {
|
for descriptor in descriptors {
|
||||||
if descriptor.style_name == desc.style {
|
if descriptor.style_name == desc.style {
|
||||||
// Found the font we want
|
// Found the font we want
|
||||||
let font = descriptor.to_font(size as _);
|
let scaled_size = size.as_f32_pts() as f64 * self.device_pixel_ratio as f64;
|
||||||
self.fonts.insert(desc.to_owned(), font.clone());
|
let font = descriptor.to_font(scaled_size);
|
||||||
return Some(font);
|
return Some(font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,11 +123,18 @@ impl Rasterizer {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_glyph(&mut self, desc: &FontDesc, size: f32, c: char) -> RasterizedGlyph {
|
/// Get rasterized glyph for given glyph key
|
||||||
let scaled_size = self.device_pixel_ratio * size;
|
///
|
||||||
let glyph = self.get_font(desc, scaled_size).unwrap().get_glyph(c, scaled_size as _);
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the FontKey specified in GlyphKey was not generated from `load_font`
|
||||||
|
pub fn get_glyph(&mut self, glyph: &GlyphKey) -> RasterizedGlyph {
|
||||||
|
let scaled_size = self.device_pixel_ratio * glyph.size.as_f32_pts();
|
||||||
|
|
||||||
glyph
|
self.fonts
|
||||||
|
.get(&glyph.font_key)
|
||||||
|
.expect(FONT_LOAD_ERROR)
|
||||||
|
.get_glyph(glyph.c, scaled_size as _)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,8 +205,8 @@ pub fn descriptors_for_family(family: &str) -> Vec<Descriptor> {
|
||||||
|
|
||||||
impl Descriptor {
|
impl Descriptor {
|
||||||
/// Create a Font from this descriptor
|
/// Create a Font from this descriptor
|
||||||
pub fn to_font(&self, pt_size: f64) -> Font {
|
pub fn to_font(&self, size: f64) -> Font {
|
||||||
let ct_font = ct_new_from_descriptor(&self.ct_descriptor, pt_size);
|
let ct_font = ct_new_from_descriptor(&self.ct_descriptor, size);
|
||||||
let cg_font = ct_font.copy_to_CGFont();
|
let cg_font = ct_font.copy_to_CGFont();
|
||||||
Font {
|
Font {
|
||||||
ct_font: ct_font,
|
ct_font: ct_font,
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
//!
|
//!
|
||||||
//! CoreText is used on Mac OS.
|
//! CoreText is used on Mac OS.
|
||||||
//! FreeType is used on everything that's not Mac OS.
|
//! FreeType is used on everything that's not Mac OS.
|
||||||
|
#![feature(integer_atomics)]
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
extern crate fontconfig;
|
extern crate fontconfig;
|
||||||
|
@ -38,6 +39,7 @@ extern crate euclid;
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::sync::atomic::{AtomicU32, ATOMIC_U32_INIT, Ordering};
|
||||||
|
|
||||||
// If target isn't macos, reexport everything from ft
|
// If target isn't macos, reexport everything from ft
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
@ -51,7 +53,7 @@ mod darwin;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub use darwin::*;
|
pub use darwin::*;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct FontDesc {
|
pub struct FontDesc {
|
||||||
name: String,
|
name: String,
|
||||||
style: String,
|
style: String,
|
||||||
|
@ -68,6 +70,54 @@ impl FontDesc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Identifier for a Font for use in maps/etc
|
||||||
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
|
pub struct FontKey {
|
||||||
|
token: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontKey {
|
||||||
|
/// Get next font key for given size
|
||||||
|
///
|
||||||
|
/// The generated key will be globally unique
|
||||||
|
pub fn next() -> FontKey {
|
||||||
|
static TOKEN: AtomicU32 = ATOMIC_U32_INIT;
|
||||||
|
|
||||||
|
FontKey {
|
||||||
|
token: TOKEN.fetch_add(1, Ordering::SeqCst),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||||
|
pub struct GlyphKey {
|
||||||
|
pub c: char,
|
||||||
|
pub font_key: FontKey,
|
||||||
|
pub size: Size,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Font size stored as integer
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
||||||
|
pub struct Size(i32);
|
||||||
|
|
||||||
|
impl Size {
|
||||||
|
/// Scale factor between font "Size" type and point size
|
||||||
|
#[inline]
|
||||||
|
pub fn factor() -> f32 {
|
||||||
|
2.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `Size` from a f32 size in points
|
||||||
|
pub fn new(size: f32) -> Size {
|
||||||
|
Size((size * Size::factor()) as i32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the f32 size in points
|
||||||
|
pub fn as_f32_pts(self) -> f32 {
|
||||||
|
self.0 as f32 / Size::factor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RasterizedGlyph {
|
pub struct RasterizedGlyph {
|
||||||
pub c: char,
|
pub c: char,
|
||||||
pub width: i32,
|
pub width: i32,
|
||||||
|
|
|
@ -8,7 +8,9 @@ use std::fs;
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use font::Size;
|
||||||
use serde_yaml;
|
use serde_yaml;
|
||||||
|
use serde;
|
||||||
|
|
||||||
/// Top-level config type
|
/// Top-level config type
|
||||||
#[derive(Debug, Deserialize, Default)]
|
#[derive(Debug, Deserialize, Default)]
|
||||||
|
@ -220,6 +222,47 @@ impl FontOffset {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait DeserializeFromF32 : Sized {
|
||||||
|
fn deserialize_from_f32<D>(&mut D) -> ::std::result::Result<Self, D::Error>
|
||||||
|
where D: serde::de::Deserializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeserializeFromF32 for Size {
|
||||||
|
fn deserialize_from_f32<D>(deserializer: &mut D) -> ::std::result::Result<Self, D::Error>
|
||||||
|
where D: serde::de::Deserializer
|
||||||
|
{
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
struct FloatVisitor<__D> {
|
||||||
|
_marker: PhantomData<__D>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<__D> ::serde::de::Visitor for FloatVisitor<__D>
|
||||||
|
where __D: ::serde::de::Deserializer
|
||||||
|
{
|
||||||
|
type Value = f32;
|
||||||
|
|
||||||
|
fn visit_f32<E>(&mut self, value: f32) -> ::std::result::Result<Self::Value, E>
|
||||||
|
where E: ::serde::de::Error
|
||||||
|
{
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(&mut self, value: &str) -> ::std::result::Result<Self::Value, E>
|
||||||
|
where E: ::serde::de::Error
|
||||||
|
{
|
||||||
|
// FIXME serde-yaml visits a str for real numbers.
|
||||||
|
// https://github.com/dtolnay/serde-yaml/issues/24
|
||||||
|
Ok(value.parse::<f32>().expect("size must be float"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer
|
||||||
|
.deserialize_f32(FloatVisitor::<D>{ _marker: PhantomData })
|
||||||
|
.map(|v| Size::new(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Font config
|
/// Font config
|
||||||
///
|
///
|
||||||
/// Defaults are provided at the level of this struct per platform, but not per
|
/// Defaults are provided at the level of this struct per platform, but not per
|
||||||
|
@ -234,8 +277,15 @@ pub struct Font {
|
||||||
/// Font style
|
/// Font style
|
||||||
style: String,
|
style: String,
|
||||||
|
|
||||||
/// Font size in points
|
/// Bold font style
|
||||||
size: f32,
|
bold_style: Option<String>,
|
||||||
|
|
||||||
|
/// Italic font style
|
||||||
|
italic_style: Option<String>,
|
||||||
|
|
||||||
|
// Font size in points
|
||||||
|
#[serde(deserialize_with="DeserializeFromF32::deserialize_from_f32")]
|
||||||
|
size: Size,
|
||||||
|
|
||||||
/// Extra spacing per character
|
/// Extra spacing per character
|
||||||
offset: FontOffset,
|
offset: FontOffset,
|
||||||
|
@ -254,9 +304,25 @@ impl Font {
|
||||||
&self.style[..]
|
&self.style[..]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get italic font style; assumes same family
|
||||||
|
#[inline]
|
||||||
|
pub fn italic_style(&self) -> Option<&str> {
|
||||||
|
self.italic_style
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get bold font style; assumes same family
|
||||||
|
#[inline]
|
||||||
|
pub fn bold_style(&self) -> Option<&str> {
|
||||||
|
self.bold_style
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the font size in points
|
/// Get the font size in points
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn size(&self) -> f32 {
|
pub fn size(&self) -> Size {
|
||||||
self.size
|
self.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,7 +339,9 @@ impl Default for Font {
|
||||||
Font {
|
Font {
|
||||||
family: String::from("Menlo"),
|
family: String::from("Menlo"),
|
||||||
style: String::from("Regular"),
|
style: String::from("Regular"),
|
||||||
size: 11.0,
|
size: Size::new(11.0),
|
||||||
|
bold_style: Some(String::from("Bold")),
|
||||||
|
italic_style: Some(String::from("Italic")),
|
||||||
offset: FontOffset {
|
offset: FontOffset {
|
||||||
x: 0.0,
|
x: 0.0,
|
||||||
y: 0.0
|
y: 0.0
|
||||||
|
@ -288,7 +356,9 @@ impl Default for Font {
|
||||||
Font {
|
Font {
|
||||||
family: String::from("DejaVu Sans Mono"),
|
family: String::from("DejaVu Sans Mono"),
|
||||||
style: String::from("Book"),
|
style: String::from("Book"),
|
||||||
size: 11.0,
|
size: Size::new(11.0),
|
||||||
|
bold_style: Some(String::from("Bold")),
|
||||||
|
italic_style: Some(String::from("Italic")),
|
||||||
offset: FontOffset {
|
offset: FontOffset {
|
||||||
// TODO should improve freetype metrics... shouldn't need such
|
// TODO should improve freetype metrics... shouldn't need such
|
||||||
// drastic offsets for the default!
|
// drastic offsets for the default!
|
||||||
|
|
45
src/main.rs
45
src/main.rs
|
@ -57,7 +57,6 @@ use std::sync::{mpsc, Arc};
|
||||||
use sync::PriorityMutex;
|
use sync::PriorityMutex;
|
||||||
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use font::FontDesc;
|
|
||||||
use meter::Meter;
|
use meter::Meter;
|
||||||
use renderer::{QuadRenderer, GlyphCache};
|
use renderer::{QuadRenderer, GlyphCache};
|
||||||
use term::Term;
|
use term::Term;
|
||||||
|
@ -126,10 +125,28 @@ fn main() {
|
||||||
gl::Enable(gl::MULTISAMPLE);
|
gl::Enable(gl::MULTISAMPLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
let desc = FontDesc::new(font.family(), font.style());
|
let rasterizer = font::Rasterizer::new(dpi.x(), dpi.y(), dpr);
|
||||||
let mut rasterizer = font::Rasterizer::new(dpi.x(), dpi.y(), dpr);
|
|
||||||
|
|
||||||
let metrics = rasterizer.metrics(&desc, font.size());
|
// Create renderer
|
||||||
|
let mut renderer = QuadRenderer::new(width, height);
|
||||||
|
|
||||||
|
// Initialize glyph cache
|
||||||
|
let mut glyph_cache = {
|
||||||
|
println!("Initializing glyph cache");
|
||||||
|
let init_start = ::std::time::Instant::now();
|
||||||
|
|
||||||
|
let cache = renderer.with_loader(|mut api| {
|
||||||
|
GlyphCache::new(rasterizer, &config, &mut api)
|
||||||
|
});
|
||||||
|
|
||||||
|
let stop = init_start.elapsed();
|
||||||
|
let stop_f = stop.as_secs() as f64 + stop.subsec_nanos() as f64 / 1_000_000_000f64;
|
||||||
|
println!("Finished initializing glyph cache in {}", stop_f);
|
||||||
|
|
||||||
|
cache
|
||||||
|
};
|
||||||
|
|
||||||
|
let metrics = glyph_cache.font_metrics();
|
||||||
let cell_width = (metrics.average_advance + font.offset().x() as f64) as u32;
|
let cell_width = (metrics.average_advance + font.offset().x() as f64) as u32;
|
||||||
let cell_height = (metrics.line_height + font.offset().y() as f64) as u32;
|
let cell_height = (metrics.line_height + font.offset().y() as f64) as u32;
|
||||||
|
|
||||||
|
@ -140,7 +157,6 @@ fn main() {
|
||||||
let mut reader = terminal.tty().reader();
|
let mut reader = terminal.tty().reader();
|
||||||
let writer = terminal.tty().writer();
|
let writer = terminal.tty().writer();
|
||||||
|
|
||||||
let mut glyph_cache = GlyphCache::new(rasterizer, desc, font.size());
|
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -192,23 +208,6 @@ fn main() {
|
||||||
let window = Arc::new(window);
|
let window = Arc::new(window);
|
||||||
let window_ref = window.clone();
|
let window_ref = window.clone();
|
||||||
|
|
||||||
// Create renderer
|
|
||||||
let mut renderer = QuadRenderer::new(width, height);
|
|
||||||
|
|
||||||
// Initialize glyph cache
|
|
||||||
{
|
|
||||||
let init_start = ::std::time::Instant::now();
|
|
||||||
println!("Initializing glyph cache");
|
|
||||||
let terminal = term_ref.lock_high();
|
|
||||||
renderer.with_api(terminal.size_info(), |mut api| {
|
|
||||||
glyph_cache.init(&mut api);
|
|
||||||
});
|
|
||||||
|
|
||||||
let stop = init_start.elapsed();
|
|
||||||
let stop_f = stop.as_secs() as f64 + stop.subsec_nanos() as f64 / 1_000_000_000f64;
|
|
||||||
println!("Finished initializing glyph cache in {}", stop_f);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut input_processor = input::Processor::new();
|
let mut input_processor = input::Processor::new();
|
||||||
|
|
||||||
'main_loop: loop {
|
'main_loop: loop {
|
||||||
|
@ -282,7 +281,7 @@ fn main() {
|
||||||
renderer.with_api(&size_info, |mut api| {
|
renderer.with_api(&size_info, |mut api| {
|
||||||
// Draw the grid
|
// Draw the grid
|
||||||
api.render_grid(&terminal.render_grid(), &mut glyph_cache);
|
api.render_grid(&terminal.render_grid(), &mut glyph_cache);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw render timer
|
// Draw render timer
|
||||||
|
|
|
@ -22,13 +22,14 @@ use std::sync::Arc;
|
||||||
use std::sync::atomic::{Ordering, AtomicBool};
|
use std::sync::atomic::{Ordering, AtomicBool};
|
||||||
|
|
||||||
use cgmath;
|
use cgmath;
|
||||||
|
use font::{self, Rasterizer, RasterizedGlyph, FontDesc, GlyphKey, FontKey};
|
||||||
use gl::types::*;
|
use gl::types::*;
|
||||||
use gl;
|
use gl;
|
||||||
use grid::Grid;
|
|
||||||
use notify::{Watcher as WatcherApi, RecommendedWatcher as Watcher, op};
|
use notify::{Watcher as WatcherApi, RecommendedWatcher as Watcher, op};
|
||||||
use term::{self, cell, Cell};
|
|
||||||
|
|
||||||
use font::{Rasterizer, RasterizedGlyph, FontDesc};
|
use config::Config;
|
||||||
|
use grid::Grid;
|
||||||
|
use term::{self, cell, Cell};
|
||||||
|
|
||||||
use super::Rgb;
|
use super::Rgb;
|
||||||
|
|
||||||
|
@ -84,58 +85,110 @@ pub struct Glyph {
|
||||||
/// representations of the same code point.
|
/// representations of the same code point.
|
||||||
pub struct GlyphCache {
|
pub struct GlyphCache {
|
||||||
/// Cache of buffered glyphs
|
/// Cache of buffered glyphs
|
||||||
cache: HashMap<char, Glyph>,
|
cache: HashMap<GlyphKey, Glyph>,
|
||||||
|
|
||||||
/// Rasterizer for loading new glyphs
|
/// Rasterizer for loading new glyphs
|
||||||
rasterizer: Rasterizer,
|
rasterizer: Rasterizer,
|
||||||
|
|
||||||
/// Font description
|
/// regular font
|
||||||
desc: FontDesc,
|
font_key: FontKey,
|
||||||
|
|
||||||
/// Font Size
|
/// italic font
|
||||||
size: f32,
|
italic_key: FontKey,
|
||||||
|
|
||||||
|
/// bold font
|
||||||
|
bold_key: FontKey,
|
||||||
|
|
||||||
|
/// font size
|
||||||
|
font_size: font::Size,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlyphCache {
|
impl GlyphCache {
|
||||||
pub fn new(rasterizer: Rasterizer, desc: FontDesc, font_size: f32) -> GlyphCache {
|
pub fn new<L>(mut rasterizer: Rasterizer, config: &Config, loader: &mut L) -> GlyphCache
|
||||||
GlyphCache {
|
where L: LoadGlyph
|
||||||
|
{
|
||||||
|
let font = config.font();
|
||||||
|
let size = font.size();
|
||||||
|
|
||||||
|
// Load regular font
|
||||||
|
let regular_desc = FontDesc::new(font.family(), font.style());
|
||||||
|
let regular = rasterizer.load_font(®ular_desc, size).expect("regular font load ok");
|
||||||
|
|
||||||
|
// Load bold font
|
||||||
|
let bold_style = font.bold_style().unwrap_or("Bold");
|
||||||
|
let bold_desc = FontDesc::new(font.family(), bold_style);
|
||||||
|
|
||||||
|
let bold = if bold_desc == regular_desc {
|
||||||
|
regular.clone()
|
||||||
|
} else {
|
||||||
|
rasterizer.load_font(&bold_desc, size).unwrap_or_else(|| regular.clone())
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load italic font
|
||||||
|
let italic_style = font.italic_style().unwrap_or("Italic");
|
||||||
|
let italic_desc = FontDesc::new(font.family(), italic_style);
|
||||||
|
|
||||||
|
let italic = if italic_desc == regular_desc {
|
||||||
|
regular.clone()
|
||||||
|
} else {
|
||||||
|
rasterizer.load_font(&italic_desc, size)
|
||||||
|
.unwrap_or_else(|| regular.clone())
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cache = GlyphCache {
|
||||||
cache: HashMap::new(),
|
cache: HashMap::new(),
|
||||||
rasterizer: rasterizer,
|
rasterizer: rasterizer,
|
||||||
desc: desc,
|
font_size: font.size(),
|
||||||
size: font_size,
|
font_key: regular.clone(),
|
||||||
}
|
bold_key: bold.clone(),
|
||||||
}
|
italic_key: italic.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
pub fn init<L>(&mut self, loader: &mut L)
|
macro_rules! load_glyphs_for_font {
|
||||||
where L: LoadGlyph
|
($font:expr) => {
|
||||||
{
|
|
||||||
for i in 32u8...128u8 {
|
for i in 32u8...128u8 {
|
||||||
self.load_and_cache_glyph(i as char, loader);
|
cache.load_and_cache_glyph(GlyphKey {
|
||||||
|
font_key: $font,
|
||||||
|
c: i as char,
|
||||||
|
size: font.size()
|
||||||
|
}, loader);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_and_cache_glyph<L>(&mut self, c: char, loader: &mut L)
|
load_glyphs_for_font!(regular);
|
||||||
|
load_glyphs_for_font!(bold);
|
||||||
|
load_glyphs_for_font!(italic);
|
||||||
|
|
||||||
|
cache
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn font_metrics(&self) -> font::Metrics {
|
||||||
|
self.rasterizer.metrics(self.font_key, self.font_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_and_cache_glyph<L>(&mut self, glyph_key: GlyphKey, loader: &mut L)
|
||||||
where L: LoadGlyph
|
where L: LoadGlyph
|
||||||
{
|
{
|
||||||
let rasterized = self.rasterizer.get_glyph(&self.desc, self.size, c);
|
let rasterized = self.rasterizer.get_glyph(&glyph_key);
|
||||||
let glyph = loader.load_glyph(&rasterized);
|
let glyph = loader.load_glyph(&rasterized);
|
||||||
self.cache.insert(c, glyph);
|
self.cache.insert(glyph_key, glyph);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get<L>(&mut self, c: char, loader: &mut L) -> Option<&Glyph>
|
pub fn get<L>(&mut self, glyph_key: &GlyphKey, loader: &mut L) -> Option<&Glyph>
|
||||||
where L: LoadGlyph
|
where L: LoadGlyph
|
||||||
{
|
{
|
||||||
// Return glyph if it's already loaded
|
// Return glyph if it's already loaded
|
||||||
// hi borrowck
|
// hi borrowck
|
||||||
{
|
{
|
||||||
if self.cache.contains_key(&c) {
|
if self.cache.contains_key(glyph_key) {
|
||||||
return self.cache.get(&c);
|
return self.cache.get(glyph_key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rasterize and load the glyph
|
// Rasterize and load the glyph
|
||||||
self.load_and_cache_glyph(c, loader);
|
self.load_and_cache_glyph(glyph_key.to_owned(), loader);
|
||||||
self.cache.get(&c)
|
self.cache.get(&glyph_key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,6 +240,12 @@ pub struct RenderApi<'a> {
|
||||||
program: &'a mut ShaderProgram,
|
program: &'a mut ShaderProgram,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LoaderApi<'a> {
|
||||||
|
active_tex: &'a mut GLuint,
|
||||||
|
atlas: &'a mut Vec<Atlas>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PackedVertex {
|
pub struct PackedVertex {
|
||||||
x: f32,
|
x: f32,
|
||||||
|
@ -436,8 +495,8 @@ impl QuadRenderer {
|
||||||
renderer
|
renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_api<F>(&mut self, props: &term::SizeInfo, mut func: F)
|
pub fn with_api<F, T>(&mut self, props: &term::SizeInfo, func: F) -> T
|
||||||
where F: FnMut(RenderApi)
|
where F: FnOnce(RenderApi) -> T
|
||||||
{
|
{
|
||||||
if self.should_reload.load(Ordering::Relaxed) {
|
if self.should_reload.load(Ordering::Relaxed) {
|
||||||
self.reload_shaders(props.width as u32, props.height as u32);
|
self.reload_shaders(props.width as u32, props.height as u32);
|
||||||
|
@ -453,7 +512,7 @@ impl QuadRenderer {
|
||||||
gl::ActiveTexture(gl::TEXTURE0);
|
gl::ActiveTexture(gl::TEXTURE0);
|
||||||
}
|
}
|
||||||
|
|
||||||
func(RenderApi {
|
let res = func(RenderApi {
|
||||||
active_tex: &mut self.active_tex,
|
active_tex: &mut self.active_tex,
|
||||||
batch: &mut self.batch,
|
batch: &mut self.batch,
|
||||||
atlas: &mut self.atlas,
|
atlas: &mut self.atlas,
|
||||||
|
@ -467,6 +526,21 @@ impl QuadRenderer {
|
||||||
|
|
||||||
self.program.deactivate();
|
self.program.deactivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_loader<F, T>(&mut self, func: F) -> T
|
||||||
|
where F: FnOnce(LoaderApi) -> T
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
gl::ActiveTexture(gl::TEXTURE0);
|
||||||
|
}
|
||||||
|
|
||||||
|
func(LoaderApi {
|
||||||
|
active_tex: &mut self.active_tex,
|
||||||
|
atlas: &mut self.atlas,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reload_shaders(&mut self, width: u32, height: u32) {
|
pub fn reload_shaders(&mut self, width: u32, height: u32) {
|
||||||
|
@ -545,7 +619,13 @@ impl<'a> RenderApi<'a> {
|
||||||
let mut col = 100.0;
|
let mut col = 100.0;
|
||||||
|
|
||||||
for c in s.chars() {
|
for c in s.chars() {
|
||||||
if let Some(glyph) = glyph_cache.get(c, self) {
|
let glyph_key = GlyphKey {
|
||||||
|
font_key: glyph_cache.font_key,
|
||||||
|
size: glyph_cache.font_size,
|
||||||
|
c: c
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(glyph) = glyph_cache.get(&glyph_key, self) {
|
||||||
let cell = Cell {
|
let cell = Cell {
|
||||||
c: c,
|
c: c,
|
||||||
fg: *color,
|
fg: *color,
|
||||||
|
@ -587,8 +667,23 @@ impl<'a> RenderApi<'a> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add cell to batch if the glyph is laoded
|
// Get font key for cell
|
||||||
if let Some(glyph) = glyph_cache.get(cell.c, self) {
|
// FIXME this is super inefficient.
|
||||||
|
let mut font_key = glyph_cache.font_key;
|
||||||
|
if cell.flags.contains(cell::BOLD) {
|
||||||
|
font_key = glyph_cache.bold_key;
|
||||||
|
} else if cell.flags.contains(cell::ITALIC) {
|
||||||
|
font_key = glyph_cache.italic_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
let glyph_key = GlyphKey {
|
||||||
|
font_key: font_key,
|
||||||
|
size: glyph_cache.font_size,
|
||||||
|
c: cell.c
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add cell to batch if glyph available
|
||||||
|
if let Some(glyph) = glyph_cache.get(&glyph_key, self) {
|
||||||
self.add_render_item(i as f32, j as f32, cell, glyph);
|
self.add_render_item(i as f32, j as f32, cell, glyph);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -596,11 +691,30 @@ impl<'a> RenderApi<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> LoadGlyph for LoaderApi<'a> {
|
||||||
|
/// Load a glyph into a texture atlas
|
||||||
|
///
|
||||||
|
/// If the current atlas is full, a new one will be created.
|
||||||
|
fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
|
||||||
|
// At least one atlas is guaranteed to be in the `self.atlas` list; thus the unwrap.
|
||||||
|
match self.atlas.last_mut().unwrap().insert(rasterized, &mut self.active_tex) {
|
||||||
|
Ok(glyph) => glyph,
|
||||||
|
Err(_) => {
|
||||||
|
let atlas = Atlas::new(ATLAS_SIZE);
|
||||||
|
*self.active_tex = 0; // Atlas::new binds a texture. Ugh this is sloppy.
|
||||||
|
self.atlas.push(atlas);
|
||||||
|
self.load_glyph(rasterized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> LoadGlyph for RenderApi<'a> {
|
impl<'a> LoadGlyph for RenderApi<'a> {
|
||||||
/// Load a glyph into a texture atlas
|
/// Load a glyph into a texture atlas
|
||||||
///
|
///
|
||||||
/// If the current atlas is full, a new one will be created.
|
/// If the current atlas is full, a new one will be created.
|
||||||
fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
|
fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
|
||||||
|
// At least one atlas is guaranteed to be in the `self.atlas` list; thus the unwrap.
|
||||||
match self.atlas.last_mut().unwrap().insert(rasterized, &mut self.active_tex) {
|
match self.atlas.last_mut().unwrap().insert(rasterized, &mut self.active_tex) {
|
||||||
Ok(glyph) => glyph,
|
Ok(glyph) => glyph,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
@ -656,7 +770,7 @@ impl ShaderProgram {
|
||||||
assert!($uniform != gl::INVALID_OPERATION as i32);
|
assert!($uniform != gl::INVALID_OPERATION as i32);
|
||||||
};
|
};
|
||||||
( $( $uniform:expr ),* ) => {
|
( $( $uniform:expr ),* ) => {
|
||||||
$( assert_uniform_valid!($uniform) )*
|
$( assert_uniform_valid!($uniform); )*
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue