#![allow(unknown_lints)]
#![warn(clippy::all)]
#![allow(
clippy::cyclomatic_complexity,
clippy::doc_markdown,
clippy::cast_lossless,
clippy::many_single_char_names
)]
#![cfg_attr(feature = "bench", feature(test))]
#[cfg(feature = "bench")]
extern crate test;
mod geometry;
mod rasterizer;
#[cfg(feature = "gpu_cache")]
pub mod gpu_cache;
pub use crate::geometry::{point, vector, Curve, Line, Point, Rect, Vector};
use approx::relative_eq;
use stb_truetype as tt;
use std::fmt;
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct FontCollection<'a>(SharedBytes<'a>);
#[derive(Clone)]
pub struct Font<'a> {
info: tt::FontInfo<SharedBytes<'a>>,
}
impl fmt::Debug for Font<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Font")
}
}
#[derive(Clone, Debug)]
pub enum SharedBytes<'a> {
ByRef(&'a [u8]),
ByArc(Arc<[u8]>),
}
impl<'a> ::std::ops::Deref for SharedBytes<'a> {
type Target = [u8];
fn deref(&self) -> &[u8] {
match *self {
SharedBytes::ByRef(bytes) => bytes,
SharedBytes::ByArc(ref bytes) => &**bytes,
}
}
}
impl<'a> From<&'a [u8]> for SharedBytes<'a> {
fn from(bytes: &'a [u8]) -> SharedBytes<'a> {
SharedBytes::ByRef(bytes)
}
}
impl From<Arc<[u8]>> for SharedBytes<'static> {
fn from(bytes: Arc<[u8]>) -> SharedBytes<'static> {
SharedBytes::ByArc(bytes)
}
}
impl From<Box<[u8]>> for SharedBytes<'static> {
fn from(bytes: Box<[u8]>) -> SharedBytes<'static> {
SharedBytes::ByArc(bytes.into())
}
}
impl From<Vec<u8>> for SharedBytes<'static> {
fn from(bytes: Vec<u8>) -> SharedBytes<'static> {
SharedBytes::ByArc(bytes.into())
}
}
impl<'a, T: AsRef<[u8]>> From<&'a T> for SharedBytes<'a> {
fn from(bytes: &'a T) -> SharedBytes<'a> {
SharedBytes::ByRef(bytes.as_ref())
}
}
#[test]
fn lazy_static_shared_bytes() {
lazy_static::lazy_static! {
static ref BYTES: Vec<u8> = vec![0, 1, 2, 3];
}
let shared_bytes: SharedBytes<'static> = (&*BYTES).into();
assert_eq!(&*shared_bytes, &[0, 1, 2, 3]);
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Codepoint(pub u32);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct GlyphId(pub u32);
#[derive(Clone)]
pub struct Glyph<'a> {
inner: GlyphInner<'a>,
}
impl fmt::Debug for Glyph<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Glyph").field("id", &self.id().0).finish()
}
}
#[derive(Clone)]
enum GlyphInner<'a> {
Proxy(Font<'a>, u32),
Shared(Arc<SharedGlyphData>),
}
#[derive(Debug)]
pub struct SharedGlyphData {
pub id: u32,
pub extents: Option<Rect<i32>>,
pub scale_for_1_pixel: f32,
pub unit_h_metrics: HMetrics,
pub shape: Option<Vec<tt::Vertex>>,
}
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct HMetrics {
pub advance_width: f32,
pub left_side_bearing: f32,
}
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct VMetrics {
pub ascent: f32,
pub descent: f32,
pub line_gap: f32,
}
impl From<tt::VMetrics> for VMetrics {
fn from(vm: tt::VMetrics) -> Self {
Self {
ascent: vm.ascent as f32,
descent: vm.descent as f32,
line_gap: vm.line_gap as f32,
}
}
}
impl ::std::ops::Mul<f32> for VMetrics {
type Output = VMetrics;
fn mul(self, rhs: f32) -> Self {
Self {
ascent: self.ascent * rhs,
descent: self.descent * rhs,
line_gap: self.line_gap * rhs,
}
}
}
#[derive(Clone)]
pub struct ScaledGlyph<'a> {
g: Glyph<'a>,
api_scale: Scale,
scale: Vector<f32>,
}
impl fmt::Debug for ScaledGlyph<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ScaledGlyph")
.field("id", &self.id().0)
.field("scale", &self.api_scale)
.finish()
}
}
#[derive(Clone)]
pub struct PositionedGlyph<'a> {
sg: ScaledGlyph<'a>,
position: Point<f32>,
bb: Option<Rect<i32>>,
}
impl fmt::Debug for PositionedGlyph<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PositionedGlyph")
.field("id", &self.id().0)
.field("scale", &self.scale())
.field("position", &self.position)
.finish()
}
}
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
pub struct Scale {
pub x: f32,
pub y: f32,
}
impl Scale {
#[inline]
pub fn uniform(s: f32) -> Scale {
Scale { x: s, y: s }
}
}
pub trait IntoGlyphId {
fn into_glyph_id(self, _: &Font<'_>) -> GlyphId;
}
impl IntoGlyphId for char {
fn into_glyph_id(self, font: &Font<'_>) -> GlyphId {
GlyphId(font.info.find_glyph_index(self as u32))
}
}
impl IntoGlyphId for Codepoint {
fn into_glyph_id(self, font: &Font<'_>) -> GlyphId {
GlyphId(font.info.find_glyph_index(self.0))
}
}
impl IntoGlyphId for GlyphId {
#[inline]
fn into_glyph_id(self, _font: &Font<'_>) -> GlyphId {
self
}
}
impl<'a> FontCollection<'a> {
pub fn from_bytes<B: Into<SharedBytes<'a>>>(bytes: B) -> Result<FontCollection<'a>, Error> {
let bytes = bytes.into();
if !tt::is_font(&bytes) && &bytes[0..4] != b"ttcf" {
return Err(Error::UnrecognizedFormat);
}
Ok(FontCollection(bytes))
}
pub fn into_font(self) -> Result<Font<'a>, Error> {
let offset = if tt::is_font(&self.0) {
0
} else if tt::get_font_offset_for_index(&self.0, 1).is_some() {
return Err(Error::CollectionContainsMultipleFonts);
} else {
match tt::get_font_offset_for_index(&self.0, 0) {
None => return Err(Error::IllFormed),
Some(offset) => offset,
}
};
let info = tt::FontInfo::new(self.0, offset as usize).ok_or(Error::IllFormed)?;
Ok(Font { info })
}
pub fn font_at(&self, i: usize) -> Result<Font<'a>, Error> {
let offset = tt::get_font_offset_for_index(&self.0, i as i32)
.ok_or(Error::CollectionIndexOutOfBounds)?;
let info = tt::FontInfo::new(self.0.clone(), offset as usize).ok_or(Error::IllFormed)?;
Ok(Font { info })
}
pub fn into_fonts(self) -> IntoFontsIter<'a> {
IntoFontsIter {
collection: self,
next_index: 0,
}
}
}
pub struct IntoFontsIter<'a> {
next_index: usize,
collection: FontCollection<'a>,
}
impl<'a> Iterator for IntoFontsIter<'a> {
type Item = Result<Font<'a>, Error>;
fn next(&mut self) -> Option<Self::Item> {
let result = self.collection.font_at(self.next_index);
if let Err(Error::CollectionIndexOutOfBounds) = result {
return None;
}
self.next_index += 1;
Some(result)
}
}
impl<'a> Font<'a> {
pub fn from_bytes<B: Into<SharedBytes<'a>>>(bytes: B) -> Result<Font<'a>, Error> {
FontCollection::from_bytes(bytes).and_then(|c| c.into_font())
}
pub fn v_metrics(&self, scale: Scale) -> VMetrics {
let vm = self.info.get_v_metrics();
let scale = self.info.scale_for_pixel_height(scale.y);
VMetrics::from(vm) * scale
}
pub fn v_metrics_unscaled(&self) -> VMetrics {
VMetrics::from(self.info.get_v_metrics())
}
pub fn units_per_em(&self) -> u16 {
self.info.units_per_em()
}
pub fn glyph_count(&self) -> usize {
self.info.get_num_glyphs() as usize
}
pub fn glyph<C: IntoGlyphId>(&self, id: C) -> Glyph<'a> {
let gid = id.into_glyph_id(self);
assert!((gid.0 as usize) < self.glyph_count());
Glyph::new(GlyphInner::Proxy(self.clone(), gid.0))
}
pub fn glyphs_for<I: Iterator>(&self, itr: I) -> GlyphIter<'_, I>
where
I::Item: IntoGlyphId,
{
GlyphIter { font: self, itr }
}
pub fn font_name_strings(&self) -> tt::FontNameIter<'_, SharedBytes<'a>> {
self.info.get_font_name_strings()
}
pub fn layout<'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,
}
}
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);
let second_id = second.into_glyph_id(self);
let factor = self.info.scale_for_pixel_height(scale.y) * (scale.x / scale.y);
let kern = self.info.get_glyph_kern_advance(first_id.0, second_id.0);
factor * kern as f32
}
}
#[derive(Clone)]
pub struct GlyphIter<'a, I: Iterator>
where
I::Item: IntoGlyphId,
{
font: &'a Font<'a>,
itr: I,
}
impl<'a, I: Iterator> Iterator for GlyphIter<'a, I>
where
I::Item: IntoGlyphId,
{
type Item = Glyph<'a>;
fn next(&mut self) -> Option<Glyph<'a>> {
self.itr.next().map(|c| self.font.glyph(c))
}
}
#[derive(Clone)]
pub struct LayoutIter<'a, 'b> {
font: &'a Font<'a>,
chars: ::std::str::Chars<'b>,
caret: f32,
scale: Scale,
start: Point<f32>,
last_glyph: Option<GlyphId>,
}
impl<'a, 'b> Iterator for LayoutIter<'a, 'b> {
type Item = PositionedGlyph<'a>;
fn next(&mut self) -> Option<PositionedGlyph<'a>> {
self.chars.next().map(|c| {
let g = self.font.glyph(c).scaled(self.scale);
if let Some(last) = self.last_glyph {
self.caret += self.font.pair_kerning(self.scale, last, g.id());
}
let g = g.positioned(point(self.start.x + self.caret, self.start.y));
self.caret += g.sg.h_metrics().advance_width;
self.last_glyph = Some(g.id());
g
})
}
}
impl<'a> Glyph<'a> {
fn new(inner: GlyphInner<'a>) -> Glyph<'a> {
Glyph { inner }
}
pub fn font(&self) -> Option<&Font<'a>> {
match self.inner {
GlyphInner::Proxy(ref f, _) => Some(f),
GlyphInner::Shared(_) => None,
}
}
pub fn id(&self) -> GlyphId {
match self.inner {
GlyphInner::Proxy(_, id) => GlyphId(id),
GlyphInner::Shared(ref data) => GlyphId(data.id),
}
}
pub fn scaled(self, scale: Scale) -> ScaledGlyph<'a> {
let (scale_x, scale_y) = match self.inner {
GlyphInner::Proxy(ref font, _) => {
let scale_y = font.info.scale_for_pixel_height(scale.y);
let scale_x = scale_y * scale.x / scale.y;
(scale_x, scale_y)
}
GlyphInner::Shared(ref data) => {
let scale_y = data.scale_for_1_pixel * scale.y;
let scale_x = scale_y * scale.x / scale.y;
(scale_x, scale_y)
}
};
ScaledGlyph {
g: self,
api_scale: scale,
scale: vector(scale_x, scale_y),
}
}
pub fn standalone(&self) -> Glyph<'static> {
match self.inner {
GlyphInner::Proxy(ref font, id) => {
Glyph::new(GlyphInner::Shared(Arc::new(SharedGlyphData {
id,
scale_for_1_pixel: font.info.scale_for_pixel_height(1.0),
unit_h_metrics: {
let hm = font.info.get_glyph_h_metrics(id);
HMetrics {
advance_width: hm.advance_width as f32,
left_side_bearing: hm.left_side_bearing as f32,
}
},
extents: font.info.get_glyph_box(id).map(|bb| Rect {
min: point(bb.x0 as i32, -(bb.y1 as i32)),
max: point(bb.x1 as i32, -(bb.y0 as i32)),
}),
shape: font.info.get_glyph_shape(id),
})))
}
GlyphInner::Shared(ref data) => Glyph::new(GlyphInner::Shared(data.clone())),
}
}
pub fn get_data(&self) -> Option<Arc<SharedGlyphData>> {
match self.inner {
GlyphInner::Proxy(..) => None,
GlyphInner::Shared(ref s) => Some(s.clone()),
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum Segment {
Line(Line),
Curve(Curve),
}
#[derive(Clone, Debug)]
pub struct Contour {
pub segments: Vec<Segment>,
}
impl<'a> ScaledGlyph<'a> {
pub fn id(&self) -> GlyphId {
self.g.id()
}
pub fn font(&self) -> Option<&Font<'a>> {
self.g.font()
}
pub fn into_unscaled(self) -> Glyph<'a> {
self.g
}
pub fn unscaled(&self) -> &Glyph<'a> {
&self.g
}
pub fn positioned(self, p: Point<f32>) -> PositionedGlyph<'a> {
let bb = self.pixel_bounds_at(p);
PositionedGlyph {
sg: self,
position: p,
bb,
}
}
pub fn scale(&self) -> Scale {
self.api_scale
}
pub fn h_metrics(&self) -> HMetrics {
match self.g.inner {
GlyphInner::Proxy(ref font, id) => {
let hm = font.info.get_glyph_h_metrics(id);
HMetrics {
advance_width: hm.advance_width as f32 * self.scale.x,
left_side_bearing: hm.left_side_bearing as f32 * self.scale.x,
}
}
GlyphInner::Shared(ref data) => HMetrics {
advance_width: data.unit_h_metrics.advance_width * self.scale.x,
left_side_bearing: data.unit_h_metrics.left_side_bearing * self.scale.y,
},
}
}
fn shape_with_offset(&self, offset: Point<f32>) -> Option<Vec<Contour>> {
use stb_truetype::VertexType;
use std::mem::replace;
match self.g.inner {
GlyphInner::Proxy(ref font, id) => font.info.get_glyph_shape(id),
GlyphInner::Shared(ref data) => data.shape.clone(),
}
.map(|shape| {
let mut result = Vec::new();
let mut current = Vec::new();
let mut last = point(0.0, 0.0);
for v in shape {
let end = point(
v.x as f32 * self.scale.x + offset.x,
v.y as f32 * self.scale.y + offset.y,
);
match v.vertex_type() {
VertexType::MoveTo if !current.is_empty() => result.push(Contour {
segments: replace(&mut current, Vec::new()),
}),
VertexType::LineTo => current.push(Segment::Line(Line { p: [last, end] })),
VertexType::CurveTo => {
let control = point(
v.cx as f32 * self.scale.x + offset.x,
v.cy as f32 * self.scale.y + offset.y,
);
current.push(Segment::Curve(Curve {
p: [last, control, end],
}))
}
_ => (),
}
last = end;
}
if !current.is_empty() {
result.push(Contour {
segments: replace(&mut current, Vec::new()),
});
}
result
})
}
pub fn shape(&self) -> Option<Vec<Contour>> {
self.shape_with_offset(point(0.0, 0.0))
}
pub fn exact_bounding_box(&self) -> Option<Rect<f32>> {
match self.g.inner {
GlyphInner::Proxy(ref font, id) => font.info.get_glyph_box(id).map(|bb| Rect {
min: point(bb.x0 as f32 * self.scale.x, -bb.y1 as f32 * self.scale.y),
max: point(bb.x1 as f32 * self.scale.x, -bb.y0 as f32 * self.scale.y),
}),
GlyphInner::Shared(ref data) => data.extents.map(|bb| Rect {
min: point(
bb.min.x as f32 * self.scale.x,
bb.min.y as f32 * self.scale.y,
),
max: point(
bb.max.x as f32 * self.scale.x,
bb.max.y as f32 * self.scale.y,
),
}),
}
}
pub fn standalone(&self) -> ScaledGlyph<'static> {
ScaledGlyph {
g: self.g.standalone(),
api_scale: self.api_scale,
scale: self.scale,
}
}
#[inline]
fn pixel_bounds_at(&self, p: Point<f32>) -> Option<Rect<i32>> {
let (x_trunc, x_fract) = (p.x.trunc() as i32, p.x.fract());
let (y_trunc, y_fract) = (p.y.trunc() as i32, p.y.fract());
match self.g.inner {
GlyphInner::Proxy(ref font, id) => font
.info
.get_glyph_bitmap_box_subpixel(id, self.scale.x, self.scale.y, x_fract, y_fract)
.map(|bb| Rect {
min: point(x_trunc + bb.x0, y_trunc + bb.y0),
max: point(x_trunc + bb.x1, y_trunc + bb.y1),
}),
GlyphInner::Shared(ref data) => data.extents.map(|bb| Rect {
min: point(
(bb.min.x as f32 * self.scale.x + x_fract).floor() as i32 + x_trunc,
(bb.min.y as f32 * self.scale.y + y_fract).floor() as i32 + y_trunc,
),
max: point(
(bb.max.x as f32 * self.scale.x + x_fract).ceil() as i32 + x_trunc,
(bb.max.y as f32 * self.scale.y + y_fract).ceil() as i32 + y_trunc,
),
}),
}
}
}
impl<'a> PositionedGlyph<'a> {
pub fn id(&self) -> GlyphId {
self.sg.id()
}
pub fn font(&self) -> Option<&Font<'a>> {
self.sg.font()
}
pub fn unpositioned(&self) -> &ScaledGlyph<'a> {
&self.sg
}
pub fn into_unpositioned(self) -> ScaledGlyph<'a> {
self.sg
}
pub fn pixel_bounding_box(&self) -> Option<Rect<i32>> {
self.bb
}
pub fn shape(&self) -> Option<Vec<Contour>> {
self.sg.shape_with_offset(self.position)
}
pub fn scale(&self) -> Scale {
self.sg.api_scale
}
pub fn position(&self) -> Point<f32> {
self.position
}
pub fn draw<O: FnMut(u32, u32, f32)>(&self, o: O) {
use crate::geometry::{Curve, Line};
use stb_truetype::VertexType;
let shape = match self.sg.g.inner {
GlyphInner::Proxy(ref font, id) => {
font.info.get_glyph_shape(id).unwrap_or_else(Vec::new)
}
GlyphInner::Shared(ref data) => data.shape.clone().unwrap_or_else(Vec::new),
};
let bb = if let Some(bb) = self.bb.as_ref() {
bb
} else {
return;
};
let offset = vector(bb.min.x as f32, bb.min.y as f32);
let mut lines = Vec::new();
let mut curves = Vec::new();
let mut last = point(0.0, 0.0);
for v in shape {
let end = point(
v.x as f32 * self.sg.scale.x + self.position.x,
-v.y as f32 * self.sg.scale.y + self.position.y,
) - offset;
match v.vertex_type() {
VertexType::LineTo => lines.push(Line { p: [last, end] }),
VertexType::CurveTo => {
let control = point(
v.cx as f32 * self.sg.scale.x + self.position.x,
-v.cy as f32 * self.sg.scale.y + self.position.y,
) - offset;
curves.push(Curve {
p: [last, control, end],
})
}
VertexType::MoveTo => {}
}
last = end;
}
rasterizer::rasterize(
&lines,
&curves,
(bb.max.x - bb.min.x) as u32,
(bb.max.y - bb.min.y) as u32,
o,
);
}
pub fn standalone(&self) -> PositionedGlyph<'static> {
PositionedGlyph {
sg: self.sg.standalone(),
bb: self.bb,
position: self.position,
}
}
pub fn set_position(&mut self, p: Point<f32>) {
let p_diff = p - self.position;
if relative_eq!(p_diff.x.fract(), 0.0) && relative_eq!(p_diff.y.fract(), 0.0) {
if let Some(bb) = self.bb.as_mut() {
let rounded_diff = vector(p_diff.x.round() as i32, p_diff.y.round() as i32);
bb.min = bb.min + rounded_diff;
bb.max = bb.max + rounded_diff;
}
} else {
self.bb = self.sg.pixel_bounds_at(p);
}
self.position = p;
}
}
#[derive(Debug)]
pub enum Error {
UnrecognizedFormat,
IllFormed,
CollectionIndexOutOfBounds,
CollectionContainsMultipleFonts,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> {
f.write_str(std::error::Error::description(self))
}
}
impl std::error::Error for Error {
fn description(&self) -> &str {
use self::Error::*;
match *self {
UnrecognizedFormat => "Font data in unrecognized format",
IllFormed => "Font data is ill-formed",
CollectionIndexOutOfBounds => "Font collection has no font at the given index",
CollectionContainsMultipleFonts => {
"Attempted to convert collection into a font, \
but collection contais more than one font"
}
}
}
}
impl std::convert::From<Error> for std::io::Error {
fn from(error: Error) -> Self {
std::io::Error::new(std::io::ErrorKind::Other, error)
}
}