Verified Commit 3076234b authored by Alex Butler's avatar Alex Butler
Browse files

Adapt to use ttf-parser

parent 767a7275
...@@ -26,9 +26,9 @@ exclude = ["/dev/**"] ...@@ -26,9 +26,9 @@ exclude = ["/dev/**"]
features = ["gpu_cache"] features = ["gpu_cache"]
[dependencies] [dependencies]
stb_truetype = { version = "0.3.1", default-features = false }
ordered-float = { version = "1", default-features = false } ordered-float = { version = "1", default-features = false }
approx = { version = "0.3", default-features = false } approx = { version = "0.3", default-features = false }
ttf-parser = "0.4"
libm = { version = "0.2.1", default-features = false, optional = true } libm = { version = "0.2.1", default-features = false, optional = true }
...@@ -46,9 +46,9 @@ num_cpus = { version = "1.0", optional = true } ...@@ -46,9 +46,9 @@ num_cpus = { version = "1.0", optional = true }
[features] [features]
default = ["std", "has-atomics"] default = ["std", "has-atomics"]
# Activates usage of std. # Activates usage of std.
std = ["has-atomics", "stb_truetype/std"] std = ["has-atomics"]
# Uses libm when not using std. This needs to be active in that case. # Uses libm when not using std. This needs to be active in that case.
libm-math = ["libm", "stb_truetype/libm"] libm-math = ["libm"]
# Some targets don't have atomics, this activates usage of Arc<T> instead of Rc<T>. # Some targets don't have atomics, this activates usage of Arc<T> instead of Rc<T>.
has-atomics = [] has-atomics = []
# Adds `gpu_cache` module # Adds `gpu_cache` module
......
...@@ -12,6 +12,7 @@ image = { version = "0.23", default-features = false, features = ["png"] } ...@@ -12,6 +12,7 @@ image = { version = "0.23", default-features = false, features = ["png"] }
once_cell = "1" once_cell = "1"
blake2 = "0.8" blake2 = "0.8"
criterion = "0.3" criterion = "0.3"
ttf-parser = "0.4"
[[bench]] [[bench]]
name = "cache" name = "cache"
......
...@@ -67,7 +67,7 @@ static FONTS: Lazy<Vec<Font<'static>>> = Lazy::new(|| { ...@@ -67,7 +67,7 @@ static FONTS: Lazy<Vec<Font<'static>>> = Lazy::new(|| {
include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8], include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8],
] ]
.into_iter() .into_iter()
.map(|bytes| Font::from_bytes(bytes).unwrap()) .map(|bytes| Font::try_from_bytes(bytes).unwrap())
.collect() .collect()
}); });
......
...@@ -4,10 +4,10 @@ use once_cell::sync::Lazy; ...@@ -4,10 +4,10 @@ use once_cell::sync::Lazy;
use rusttype::*; use rusttype::*;
static DEJA_VU_MONO: Lazy<Font<'static>> = Lazy::new(|| { static DEJA_VU_MONO: Lazy<Font<'static>> = Lazy::new(|| {
Font::from_bytes(include_bytes!("../fonts/dejavu/DejaVuSansMono.ttf") as &[u8]).unwrap() Font::try_from_bytes(include_bytes!("../fonts/dejavu/DejaVuSansMono.ttf") as &[u8]).unwrap()
}); });
static OPEN_SANS_ITALIC: Lazy<Font<'static>> = Lazy::new(|| { static OPEN_SANS_ITALIC: Lazy<Font<'static>> = Lazy::new(|| {
Font::from_bytes(include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8]).unwrap() Font::try_from_bytes(include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8]).unwrap()
}); });
fn bench_draw_big_biohazard(c: &mut Criterion) { fn bench_draw_big_biohazard(c: &mut Criterion) {
...@@ -36,7 +36,7 @@ fn bench_draw_big_biohazard(c: &mut Criterion) { ...@@ -36,7 +36,7 @@ fn bench_draw_big_biohazard(c: &mut Criterion) {
// verify the draw result against static reference hash // verify the draw result against static reference hash
assert_eq!( assert_eq!(
format!("{:x}", Blake2s::digest(&target)), format!("{:x}", Blake2s::digest(&target)),
"8e3927a33c6d563d45f82fb9620dea8036274b403523a2e98cd5f93eafdb2125" "307a2514a191b827a214174d6c5d109599f0ec4b42d466bde91d10bdd5f8e22d"
); );
}); });
} }
...@@ -98,7 +98,7 @@ fn bench_draw_iota(c: &mut Criterion) { ...@@ -98,7 +98,7 @@ fn bench_draw_iota(c: &mut Criterion) {
// verify the draw result against static reference hash // verify the draw result against static reference hash
assert_eq!( assert_eq!(
format!("{:x}", Blake2s::digest(&target)), format!("{:x}", Blake2s::digest(&target)),
"cdad348e38263a13f68ae41a95ce3b900d2881375a745232309ebd568a27cd4c" "d8fa90d375a7dc2c8c821395e8cef8baefb78046e4a7a93d87f96509add6a65c"
); );
}); });
} }
......
...@@ -10,9 +10,40 @@ fn bench_layout_a_sentence(c: &mut Criterion) { ...@@ -10,9 +10,40 @@ fn bench_layout_a_sentence(c: &mut Criterion) {
clause and sometimes one or more subordinate clauses."; clause and sometimes one or more subordinate clauses.";
let font = let font =
Font::from_bytes(include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8]).unwrap(); Font::try_from_bytes(include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8])
.unwrap();
c.bench_function("layout_a_sentence (bytes)", |b| {
let mut glyphs = vec![];
b.iter(|| {
glyphs.clear();
glyphs.extend(font.layout(SENTENCE, Scale::uniform(25.0), point(100.0, 25.0)))
});
// verify the layout result against static reference hash
let mut hash = Blake2s::default();
for g in glyphs {
write!(
hash,
"{id}:{scale_x}:{scale_y}:{pos_x}:{pos_y}",
id = g.id().0,
scale_x = g.scale().x,
scale_y = g.scale().y,
pos_x = g.position().x,
pos_y = g.position().y,
)
.unwrap();
}
assert_eq!(
format!("{:x}", hash.result()),
"c2a3483ddf5598ec869440c62d17efa5a4fe72f9893bcc05dd17be2adcaa7629"
);
});
c.bench_function("layout_a_sentence", |b| { let font = Font::try_from_vec(include_bytes!("../fonts/opensans/OpenSans-Italic.ttf").to_vec())
.unwrap();
c.bench_function("layout_a_sentence (vec)", |b| {
let mut glyphs = vec![]; let mut glyphs = vec![];
b.iter(|| { b.iter(|| {
...@@ -41,6 +72,9 @@ fn bench_layout_a_sentence(c: &mut Criterion) { ...@@ -41,6 +72,9 @@ fn bench_layout_a_sentence(c: &mut Criterion) {
}); });
} }
criterion_group!(benches, bench_layout_a_sentence); criterion_group!(
name = benches;
config = Criterion::default().sample_size(400);
targets = bench_layout_a_sentence);
criterion_main!(benches); criterion_main!(benches);
...@@ -56,7 +56,7 @@ fn main() -> Result<(), Box<dyn Error>> { ...@@ -56,7 +56,7 @@ fn main() -> Result<(), Box<dyn Error>> {
} }
let font_data = include_bytes!("../fonts/wqy-microhei/WenQuanYiMicroHei.ttf"); let font_data = include_bytes!("../fonts/wqy-microhei/WenQuanYiMicroHei.ttf");
let font: Font<'static> = Font::from_bytes(font_data as &[u8])?; let font = Font::try_from_bytes(font_data as &[u8]).unwrap();
let window = glium::glutin::window::WindowBuilder::new() let window = glium::glutin::window::WindowBuilder::new()
.with_inner_size(glium::glutin::dpi::PhysicalSize::new(512, 512)) .with_inner_size(glium::glutin::dpi::PhysicalSize::new(512, 512))
......
...@@ -5,7 +5,7 @@ fn main() { ...@@ -5,7 +5,7 @@ fn main() {
// Load the font // Load the font
let font_data = include_bytes!("../fonts/wqy-microhei/WenQuanYiMicroHei.ttf"); let font_data = include_bytes!("../fonts/wqy-microhei/WenQuanYiMicroHei.ttf");
// This only succeeds if collection consists of one font // This only succeeds if collection consists of one font
let font = Font::from_bytes(font_data as &[u8]).expect("Error constructing Font"); let font = Font::try_from_bytes(font_data as &[u8]).expect("Error constructing Font");
// The font size to use // The font size to use
let scale = Scale::uniform(32.0); let scale = Scale::uniform(32.0);
......
use rusttype::{point, FontCollection, PositionedGlyph, Scale}; use rusttype::{point, Font, Scale};
use std::io::Write; use std::io::Write;
fn main() { fn main() {
let font_data = include_bytes!("../fonts/wqy-microhei/WenQuanYiMicroHei.ttf"); let font = if let Some(font_path) = std::env::args().nth(1) {
let collection = FontCollection::from_bytes(font_data as &[u8]).unwrap_or_else(|e| { let font_path = std::env::current_dir().unwrap().join(font_path);
panic!("error constructing a FontCollection from bytes: {}", e); let data = std::fs::read(&font_path).unwrap();
}); Font::try_from_vec(data).unwrap_or_else(|| {
let font = collection panic!(format!(
.into_font() // only succeeds if collection consists of one font "error constructing a Font from data at {:?}",
.unwrap_or_else(|e| { font_path
panic!("error turning FontCollection into a Font: {}", e); ));
}); })
} else {
panic!("No font specified")
};
//
// let font_data =
// include_bytes!("../fonts/wqy-microhei/WenQuanYiMicroHei.ttf");
// Font::try_from_bytes(font_data as &[u8]).unwrap_or_else(|_| {
// panic!("error constructing a Font from bytes");
// })
// };
// Desired font pixel height // Desired font pixel height
let height: f32 = 12.4; // to get 80 chars across (fits most terminals); adjust as desired let height: f32 = 12.4; // to get 80 chars across (fits most terminals); adjust as desired
...@@ -31,7 +42,7 @@ fn main() { ...@@ -31,7 +42,7 @@ fn main() {
let offset = point(0.0, v_metrics.ascent); let offset = point(0.0, v_metrics.ascent);
// Glyphs to draw for "RustType". Feel free to try other strings. // Glyphs to draw for "RustType". Feel free to try other strings.
let glyphs: Vec<PositionedGlyph<'_>> = font.layout("RustType", scale, offset).collect(); let glyphs: Vec<_> = font.layout("RustType", scale, offset).collect();
// Find the most visually pleasing width to display // Find the most visually pleasing width to display
let width = glyphs let width = glyphs
......
use rusttype::*;
#[test]
fn static_lazy_shared_bytes() {
use once_cell::sync::Lazy;
static FONT_BYTES: Lazy<Vec<u8>> = Lazy::new(|| vec![0, 1, 2, 3]);
let shared_bytes: SharedBytes<'static> = (&*FONT_BYTES).into();
assert_eq!(&*shared_bytes, &[0, 1, 2, 3]);
}
...@@ -4,7 +4,7 @@ static ROBOTO_REGULAR: &[u8] = include_bytes!("../fonts/Roboto-Regular.ttf"); ...@@ -4,7 +4,7 @@ static ROBOTO_REGULAR: &[u8] = include_bytes!("../fonts/Roboto-Regular.ttf");
#[test] #[test]
fn consistent_bounding_box_subpixel_size_proxy() { fn consistent_bounding_box_subpixel_size_proxy() {
let font = Font::from_bytes(ROBOTO_REGULAR).unwrap(); let font = Font::try_from_bytes(ROBOTO_REGULAR).unwrap();
let height_at_y = |y| { let height_at_y = |y| {
font.glyph('s') font.glyph('s')
.scaled(rusttype::Scale::uniform(20.0)) .scaled(rusttype::Scale::uniform(20.0))
...@@ -15,18 +15,3 @@ fn consistent_bounding_box_subpixel_size_proxy() { ...@@ -15,18 +15,3 @@ fn consistent_bounding_box_subpixel_size_proxy() {
}; };
assert_eq!(height_at_y(50.833_336), height_at_y(110.833_336)); assert_eq!(height_at_y(50.833_336), height_at_y(110.833_336));
} }
#[test]
fn consistent_bounding_box_subpixel_size_standalone() {
let font = Font::from_bytes(ROBOTO_REGULAR).unwrap();
let height_at_y = |y| {
font.glyph('s')
.standalone()
.scaled(rusttype::Scale::uniform(20.0))
.positioned(rusttype::Point { x: 0.0, y })
.pixel_bounding_box()
.unwrap()
.height()
};
assert_eq!(height_at_y(50.833_336), height_at_y(110.833_336));
}
use rusttype::*;
#[test]
fn move_and_use() {
let owned_data = include_bytes!("../fonts/opensans/OpenSans-Italic.ttf").to_vec();
let pin_font = Font::try_from_vec(owned_data).unwrap();
let ascent = pin_font.v_metrics_unscaled().ascent;
// force a move
let moved = Box::new(pin_font);
assert_eq!(moved.v_metrics_unscaled().ascent, ascent);
}
dev/tests/reference_big_biohazard.png

9.85 KB | W: | H:

dev/tests/reference_big_biohazard.png

9.88 KB | W: | H:

dev/tests/reference_big_biohazard.png
dev/tests/reference_big_biohazard.png
dev/tests/reference_big_biohazard.png
dev/tests/reference_big_biohazard.png
  • 2-up
  • Swipe
  • Onion skin
dev/tests/reference_iota.png

540 Bytes | W: | H:

dev/tests/reference_iota.png

539 Bytes | W: | H:

dev/tests/reference_iota.png
dev/tests/reference_iota.png
dev/tests/reference_iota.png
dev/tests/reference_iota.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -4,10 +4,10 @@ use rusttype::{point, Font, Scale, ScaledGlyph}; ...@@ -4,10 +4,10 @@ use rusttype::{point, Font, Scale, ScaledGlyph};
use std::io::Cursor; use std::io::Cursor;
static DEJA_VU_MONO: Lazy<Font<'static>> = Lazy::new(|| { static DEJA_VU_MONO: Lazy<Font<'static>> = Lazy::new(|| {
Font::from_bytes(include_bytes!("../fonts/dejavu/DejaVuSansMono.ttf") as &[u8]).unwrap() Font::try_from_bytes(include_bytes!("../fonts/dejavu/DejaVuSansMono.ttf") as &[u8]).unwrap()
}); });
static OPEN_SANS_ITALIC: Lazy<Font<'static>> = Lazy::new(|| { static OPEN_SANS_ITALIC: Lazy<Font<'static>> = Lazy::new(|| {
Font::from_bytes(include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8]).unwrap() Font::try_from_bytes(include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8]).unwrap()
}); });
fn draw_luma_alpha(glyph: ScaledGlyph<'_>) -> image::GrayAlphaImage { fn draw_luma_alpha(glyph: ScaledGlyph<'_>) -> image::GrayAlphaImage {
......
use crate::{Glyph, GlyphIter, IntoGlyphId, LayoutIter, Point, Scale, VMetrics};
use core::fmt;
#[cfg(not(feature = "has-atomics"))]
use alloc::rc::Rc as Arc;
#[cfg(feature = "has-atomics")]
use alloc::sync::Arc;
/// A single font. This may or may not own the font data.
///
/// # Lifetime
/// The lifetime reflects the font data lifetime. `Font<'static>` covers most
/// cases ie both dynamically loaded owned data and for referenced compile time
/// font data.
///
/// # Example
///
/// ```
/// # use rusttype::Font;
/// # fn example() -> Option<()> {
/// let font_data: &[u8] = include_bytes!("../dev/fonts/dejavu/DejaVuSansMono.ttf");
/// let font: Font<'static> = Font::try_from_bytes(font_data)?;
///
/// let owned_font_data: Vec<u8> = font_data.to_vec();
/// let from_owned_font: Font<'static> = Font::try_from_vec(owned_font_data)?;
/// # Some(())
/// # }
/// ```
#[derive(Clone)]
pub enum Font<'a> {
Ref(Arc<ttf_parser::Font<'a>>),
Owned(Arc<owned_ttf_parser::OwnedFont>),
}
impl fmt::Debug for Font<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Font")
}
}
impl Font<'_> {
/// Constructs a font from a byte-slice.
pub fn try_from_bytes(bytes: &[u8]) -> Option<Font<'_>> {
Self::try_from_bytes_and_index(bytes, 0)
}
pub fn try_from_bytes_and_index(bytes: &[u8], index: u32) -> Option<Font<'_>> {
let inner = Arc::new(ttf_parser::Font::from_data(bytes, index)?);
Some(Font::Ref(inner))
}
/// Constructs a font that owns it's data.
pub fn try_from_vec(data: Vec<u8>) -> Option<Font<'static>> {
Self::try_from_vec_and_index(data, 0)
}
pub fn try_from_vec_and_index(data: Vec<u8>, index: u32) -> Option<Font<'static>> {
let inner = owned_ttf_parser::VecFont::try_from_vec(data, index)?;
Some(Font::Owned(inner))
}
}
impl<'font> Font<'font> {
#[inline]
pub(crate) fn inner(&self) -> &ttf_parser::Font<'_> {
match self {
Self::Ref(f) => f,
Self::Owned(f) => f.inner_ref(),
}
}
/// The "vertical metrics" for this font at a given scale. These metrics are
/// shared by all of the glyphs in the font. See `VMetrics` for more detail.
pub fn v_metrics(&self, scale: Scale) -> VMetrics {
self.v_metrics_unscaled() * self.scale_for_pixel_height(scale.y)
}
/// Get the unscaled VMetrics for this font, shared by all glyphs.
/// See `VMetrics` for more detail.
pub fn v_metrics_unscaled(&self) -> VMetrics {
let font = self.inner();
VMetrics {
ascent: font.ascender() as f32,
descent: font.descender() as f32,
line_gap: font.line_gap() as f32,
}
}
/// Returns the units per EM square of this font
pub fn units_per_em(&self) -> u16 {
self.inner()
.units_per_em()
.expect("Invalid font units_per_em")
}
/// The number of glyphs present in this font. Glyph identifiers for this
/// font will always be in the range `0..self.glyph_count()`
pub fn glyph_count(&self) -> usize {
self.inner().number_of_glyphs() as _
}
/// Returns the corresponding glyph for a Unicode code point or a glyph id
/// for this font.
///
/// If `id` is a `GlyphId`, it must be valid for this font; otherwise, this
/// function panics. `GlyphId`s should always be produced by looking up some
/// other sort of designator (like a Unicode code point) in a font, and
/// should only be used to index the font they were produced for.
///
/// Note that code points without corresponding glyphs in this font map to
/// the ".notdef" glyph, glyph 0.
pub fn glyph<C: IntoGlyphId>(&self, id: C) -> Glyph<'font> {
let gid = id.into_glyph_id(self);
assert!((gid.0 as usize) < self.glyph_count());
// font clone either a reference clone, or arc clone
Glyph {
font: self.clone(),
id: gid,
}
}
/// A convenience function.
///
/// Returns an iterator that produces the glyphs corresponding to the code
/// points or glyph ids produced by the given iterator `itr`.
///
/// This is equivalent in behaviour to `itr.map(|c| font.glyph(c))`.
pub fn glyphs_for<I: Iterator>(&self, itr: I) -> GlyphIter<'_, I>
where
I::Item: IntoGlyphId,
{
GlyphIter { font: self, itr }
}
/// A convenience function for laying out glyphs for a string horizontally.
/// It does not take control characters like line breaks into account, as
/// treatment of these is likely to depend on the application.
///
/// Note that this function does not perform Unicode normalisation.
/// Composite characters (such as ö constructed from two code points, ¨ and
/// o), will not be normalised to single code points. So if a font does not
/// contain a glyph for each separate code point, but does contain one for
/// the normalised single code point (which is common), the desired glyph
/// will not be produced, despite being present in the font. Deal with this
/// by performing Unicode normalisation on the input string before passing
/// it to `layout`. The crate
/// [unicode-normalization](http://crates.io/crates/unicode-normalization)
/// is perfect for this purpose.
///
/// Calling this function is equivalent to a longer sequence of operations
/// involving `glyphs_for`, e.g.
///
/// ```no_run
/// # use rusttype::*;
/// # let (scale, start) = (Scale::uniform(0.0), point(0.0, 0.0));
/// # let font: Font = unimplemented!();
/// font.layout("Hello World!", scale, start)
/// # ;
/// ```
///
/// produces an iterator with behaviour equivalent to the following:
///
/// ```no_run
/// # use rusttype::*;
/// # let (scale, start) = (Scale::uniform(0.0), point(0.0, 0.0));
/// # let font: Font = unimplemented!();
/// font.glyphs_for("Hello World!".chars())
/// .scan((None, 0.0), |&mut (mut last, mut x), g| {
/// let g = g.scaled(scale);
/// if let Some(last) = last {
/// x += font.pair_kerning(scale, last, g.id());
/// }
/// let w = g.h_metrics().advance_width;
/// let next = g.positioned(start + vector(x, 0.0));
/// last = Some(next.id());
/// x += w;
/// Some(next)
/// })
/// # ;
/// ```
pub fn layout<'b>(&'b self, s: &'b str, scale: Scale, start: Point<f32>) -> LayoutIter<'b> {
LayoutIter {
font: self,
chars: s.chars(),
caret: 0.0,
scale,
start,
last_glyph: None,
}
}
/// Returns additional kerning to apply as well as that given by HMetrics
/// for a particular pair of glyphs.
pub fn pair_kerning<A, B>(&self, scale: Scale, first: A, second: B) -> f32
where
A: IntoGlyphId,
B: IntoGlyphId,
{
let first_id = first.into_glyph_id(self).into();
let second_id = second.into_glyph_id(self).into();
let factor = {
let hscale = self.scale_for_pixel_height(scale.y);
hscale * (scale.x / scale.y)
};
let kern = self
.inner()
.glyphs_kerning(first_id, second_id)
.unwrap_or(0);
factor * kern as f32
}
/// Computes a scale factor to produce a font whose "height" is 'pixels'
/// tall. Height is measured as the distance from the highest ascender
/// to the lowest descender; in other words, it's equivalent to calling
/// GetFontVMetrics and computing:
/// scale = pixels / (ascent - descent)
/// so if you prefer to measure height by the ascent only, use a similar
/// calculation.
pub fn scale_for_pixel_height(&self, height: f32) -> f32 {
let inner = self.inner();
let fheight = f32::from(inner.ascender()) - f32::from(inner.descender());
height / fheight
}
}
mod owned_ttf_parser {
use alloc::sync::Arc;
use core::marker::PhantomPinned;
use core::pin::Pin;
use core::slice;
pub type OwnedFont = Pin<Box<VecFont>>;
pub struct VecFont {
data: Vec<u8>,
font: Option<ttf_parser::Font<'static>>,
_pin: PhantomPinned,
}
impl VecFont {
pub fn try_from_vec(data: Vec<u8>, index: u32) -> Option<Arc<Pin<Box<Self>>>> {
let font = Self {
data,
font: None,
_pin: PhantomPinned,
};
let mut b = Box::pin(font);
unsafe {
// 'static lifetime is a lie this data is owned
let slice: &'static [u8] = slice::from_raw_parts(b.data.as_ptr(), b.data.len());
let mut_ref: Pin<&mut Self> = Pin::as_mut(&mut b);
let mut_inner = mut_ref.get_unchecked_mut();
mut_inner.font = Some(ttf_parser::Font::from_data(slice, index)?);
}
Some(Arc::new(b))
}
// Note: Must not leak the fake 'static lifetime
#[inline]
pub fn inner_ref<'a>(self: &'a Pin<Box<Self>>) -> &'a ttf_parser::Font<'a> {
match self.font.as_ref() {
Some(f) => f,
None => unsafe { core::hint::unreachable_unchecked() },
}
}
}