Commit 3076234b authored by Alex Butler's avatar Alex Butler

Adapt to use ttf-parser

parent 767a7275
......@@ -26,9 +26,9 @@ exclude = ["/dev/**"]
features = ["gpu_cache"]
[dependencies]
stb_truetype = { version = "0.3.1", default-features = false }
ordered-float = { version = "1", default-features = false }
approx = { version = "0.3", default-features = false }
ttf-parser = "0.4"
libm = { version = "0.2.1", default-features = false, optional = true }
......@@ -46,9 +46,9 @@ num_cpus = { version = "1.0", optional = true }
[features]
default = ["std", "has-atomics"]
# 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.
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>.
has-atomics = []
# Adds `gpu_cache` module
......
......@@ -12,6 +12,7 @@ image = { version = "0.23", default-features = false, features = ["png"] }
once_cell = "1"
blake2 = "0.8"
criterion = "0.3"
ttf-parser = "0.4"
[[bench]]
name = "cache"
......
......@@ -67,7 +67,7 @@ static FONTS: Lazy<Vec<Font<'static>>> = Lazy::new(|| {
include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8],
]
.into_iter()
.map(|bytes| Font::from_bytes(bytes).unwrap())
.map(|bytes| Font::try_from_bytes(bytes).unwrap())
.collect()
});
......
......@@ -4,10 +4,10 @@ use once_cell::sync::Lazy;
use rusttype::*;
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(|| {
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) {
......@@ -36,7 +36,7 @@ fn bench_draw_big_biohazard(c: &mut Criterion) {
// verify the draw result against static reference hash
assert_eq!(
format!("{:x}", Blake2s::digest(&target)),
"8e3927a33c6d563d45f82fb9620dea8036274b403523a2e98cd5f93eafdb2125"
"307a2514a191b827a214174d6c5d109599f0ec4b42d466bde91d10bdd5f8e22d"
);
});
}
......@@ -98,7 +98,7 @@ fn bench_draw_iota(c: &mut Criterion) {
// verify the draw result against static reference hash
assert_eq!(
format!("{:x}", Blake2s::digest(&target)),
"cdad348e38263a13f68ae41a95ce3b900d2881375a745232309ebd568a27cd4c"
"d8fa90d375a7dc2c8c821395e8cef8baefb78046e4a7a93d87f96509add6a65c"
);
});
}
......
......@@ -10,9 +10,40 @@ fn bench_layout_a_sentence(c: &mut Criterion) {
clause and sometimes one or more subordinate clauses.";
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![];
b.iter(|| {
......@@ -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);
......@@ -56,7 +56,7 @@ fn main() -> Result<(), Box<dyn Error>> {
}
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()
.with_inner_size(glium::glutin::dpi::PhysicalSize::new(512, 512))
......
......@@ -5,7 +5,7 @@ fn main() {
// Load the font
let font_data = include_bytes!("../fonts/wqy-microhei/WenQuanYiMicroHei.ttf");
// 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
let scale = Scale::uniform(32.0);
......
use rusttype::{point, FontCollection, PositionedGlyph, Scale};
use rusttype::{point, Font, Scale};
use std::io::Write;
fn main() {
let font_data = include_bytes!("../fonts/wqy-microhei/WenQuanYiMicroHei.ttf");
let collection = FontCollection::from_bytes(font_data as &[u8]).unwrap_or_else(|e| {
panic!("error constructing a FontCollection from bytes: {}", e);
});
let font = collection
.into_font() // only succeeds if collection consists of one font
.unwrap_or_else(|e| {
panic!("error turning FontCollection into a Font: {}", e);
});
let font = if let Some(font_path) = std::env::args().nth(1) {
let font_path = std::env::current_dir().unwrap().join(font_path);
let data = std::fs::read(&font_path).unwrap();
Font::try_from_vec(data).unwrap_or_else(|| {
panic!(format!(
"error constructing a Font from data at {:?}",
font_path
));
})
} 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
let height: f32 = 12.4; // to get 80 chars across (fits most terminals); adjust as desired
......@@ -31,7 +42,7 @@ fn main() {
let offset = point(0.0, v_metrics.ascent);
// 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
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");
#[test]
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| {
font.glyph('s')
.scaled(rusttype::Scale::uniform(20.0))
......@@ -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));
}
#[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};
use std::io::Cursor;
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(|| {
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 {
......
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() },
}
}
}
}
......@@ -989,16 +989,13 @@ fn draw_glyph(tex_coords: Rect<u32>, glyph: &PositionedGlyph<'_>, pad_glyphs: bo
#[cfg(test)]
mod test {
use super::*;
use crate::{Font, FontCollection, Scale};
use crate::{Font, Scale};
use approx::*;
#[test]
fn cache_test() {
let font_data = include_bytes!("../dev/fonts/wqy-microhei/WenQuanYiMicroHei.ttf");
let font = FontCollection::from_bytes(font_data as &[u8])
.unwrap()
.into_font()
.unwrap();
let font = Font::try_from_bytes(font_data as &[u8]).unwrap();
let mut cache = Cache::builder()
.dimensions(32, 32)
......@@ -1026,7 +1023,7 @@ mod test {
#[test]
fn need_to_check_whole_cache() {
let font_data = include_bytes!("../dev/fonts/wqy-microhei/WenQuanYiMicroHei.ttf");
let font = Font::from_bytes(font_data as &[u8]).unwrap();
let font = Font::try_from_bytes(font_data as &[u8]).unwrap();
let glyph = font.glyph('l');
......@@ -1059,7 +1056,7 @@ mod test {
#[test]
fn lossy_info() {
let font_data = include_bytes!("../dev/fonts/wqy-microhei/WenQuanYiMicroHei.ttf");
let font = Font::from_bytes(font_data as &[u8]).unwrap();
let font = Font::try_from_bytes(font_data as &[u8]).unwrap();
let glyph = font.glyph('l');
let small = glyph.clone().scaled(Scale::uniform(9.91));
......@@ -1125,7 +1122,7 @@ mod test {
.multithread(true)
.build();
let font = Font::from_bytes(include_bytes!(
let font = Font::try_from_bytes(include_bytes!(
"../dev/fonts/wqy-microhei/WenQuanYiMicroHei.ttf"
) as &[u8])
.unwrap();
......@@ -1173,10 +1170,7 @@ mod test {
#[test]
fn return_cache_by_reordering() {
let font_data = include_bytes!("../dev/fonts/wqy-microhei/WenQuanYiMicroHei.ttf");
let font = FontCollection::from_bytes(font_data as &[u8])
.unwrap()
.into_font()
.unwrap();
let font = Font::try_from_bytes(font_data as &[u8]).unwrap();
let mut cache = Cache::builder()
.dimensions(36, 27)
......@@ -1208,7 +1202,7 @@ mod test {
.dimensions(64, 64)
.align_4x4(align_4x4)
.build();
let font = Font::from_bytes(include_bytes!(
let font = Font::try_from_bytes(include_bytes!(
"../dev/fonts/wqy-microhei/WenQuanYiMicroHei.ttf"
) as &[u8])
.unwrap();
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment