|
@@ -1,3 +1,6 @@
|
|
|
+#![doc = include_str!("../README.md")]
|
|
|
+#![warn(missing_docs)]
|
|
|
+
|
|
|
use std::{collections::HashMap, ffi::c_void, rc::Rc};
|
|
|
|
|
|
#[allow(unused)]
|
|
@@ -9,6 +12,7 @@ mod bindings {
|
|
|
include!(concat!(env!("OUT_DIR"), "/schrift_bindings.rs"));
|
|
|
}
|
|
|
|
|
|
+/// Retrieve the libschrift version information string.
|
|
|
pub fn version_info() -> &'static str {
|
|
|
unsafe {
|
|
|
std::ffi::CStr::from_ptr(bindings::sft_version())
|
|
@@ -17,26 +21,40 @@ pub fn version_info() -> &'static str {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/// Error type for schrift-rs operations.
|
|
|
#[derive(Debug)]
|
|
|
pub enum Error {
|
|
|
- FormatError,
|
|
|
+ /// Logic error in invocation of schrift-rs.
|
|
|
+ LogicError(&'static str),
|
|
|
+ /// Internal libschrift error from a malformed font file.
|
|
|
+ FormatError(&'static str),
|
|
|
+ /// I/O error while loading font.
|
|
|
IOError(std::io::Error),
|
|
|
}
|
|
|
|
|
|
-/// Font information, from a single TTF file
|
|
|
+impl std::fmt::Display for Error {
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
+ std::fmt::Debug::fmt(self, f)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl std::error::Error for Error { }
|
|
|
+
|
|
|
+/// Size-independent font information, loaded from a single TTF file.
|
|
|
pub struct Font {
|
|
|
_data: Box<[u8]>,
|
|
|
sft_font: *mut bindings::SFT_Font,
|
|
|
}
|
|
|
|
|
|
impl Font {
|
|
|
+ /// Load font information from a filesystem path.
|
|
|
pub fn load_from_file(path: impl AsRef<std::path::Path>) -> Result<Rc<Self>, Error> {
|
|
|
let font_data = std::fs::read(path.as_ref()).map_err(Error::IOError)?;
|
|
|
let data = font_data.into_boxed_slice();
|
|
|
|
|
|
let sft_font = unsafe { bindings::sft_loadmem(data.as_ptr() as *const c_void, data.len()) };
|
|
|
if sft_font.is_null() {
|
|
|
- return Err(Error::FormatError);
|
|
|
+ return Err(Error::FormatError("could not load font"));
|
|
|
}
|
|
|
|
|
|
Ok(Self {
|
|
@@ -55,22 +73,35 @@ impl Drop for Font {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/// Line metrics for a [`Schrift`] instance.
|
|
|
#[derive(Debug)]
|
|
|
pub struct LineMetrics {
|
|
|
+ /// Line ascender (height above baseline)
|
|
|
pub ascender: f64,
|
|
|
+ /// Line descender, as a negative value (height below baseline)
|
|
|
pub descender: f64,
|
|
|
+ /// Linegap (recommended spacing between lines). Use (ascender + descender + line_gap) for
|
|
|
+ /// baseline-to-baseline distance.
|
|
|
pub line_gap: f64,
|
|
|
}
|
|
|
|
|
|
+/// Glyph metrics from a [`Schrift`] instance.
|
|
|
#[derive(Debug)]
|
|
|
pub struct GlyphMetrics {
|
|
|
+ /// Horizontal advancing distance for this glyph.
|
|
|
pub advance: f64,
|
|
|
+ /// Left-bearing for this glyph, or distance between origin and min-x of the glyph's bounding
|
|
|
+ /// box.
|
|
|
pub left_bearing: f64,
|
|
|
+ /// Pixel rendering y-offset from baseline.
|
|
|
pub y_offset: i32,
|
|
|
+ /// Minimum width for a pixel rendering of this glyph.
|
|
|
pub min_width: i32,
|
|
|
+ /// Minimum height for a pixel rendering of this glyph.
|
|
|
pub min_height: i32,
|
|
|
}
|
|
|
|
|
|
+/// TrueType glyph index type.
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
pub struct Glyph(bindings::SFT_Glyph);
|
|
|
|
|
@@ -80,13 +111,16 @@ impl std::fmt::Debug for Glyph {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/// Glyph-pair kerning information.
|
|
|
#[derive(Debug)]
|
|
|
pub struct Kerning {
|
|
|
+ /// Additional x-shift on top of usual glyph spacing information.
|
|
|
pub x_shift: f64,
|
|
|
+ /// Additional y-shift on top of usual glyph spacing information.
|
|
|
pub y_shift: f64,
|
|
|
}
|
|
|
|
|
|
-/// Context with both a Font and size information.
|
|
|
+/// Size-dependent font functionality.
|
|
|
pub struct Schrift {
|
|
|
// keep a reference to Font to keep the sft_font pointer in sft valid
|
|
|
_font: Rc<Font>,
|
|
@@ -94,11 +128,13 @@ pub struct Schrift {
|
|
|
}
|
|
|
|
|
|
impl Schrift {
|
|
|
+ /// Instantiate a [`SchriftBuilder`] with a given font.
|
|
|
pub fn build(font: Rc<Font>) -> SchriftBuilder {
|
|
|
SchriftBuilder::new(font)
|
|
|
}
|
|
|
|
|
|
- pub fn line_metrics(&self) -> Option<LineMetrics> {
|
|
|
+ /// Compute the line metrics for this font context.
|
|
|
+ pub fn line_metrics(&self) -> Result<LineMetrics, Error> {
|
|
|
unsafe {
|
|
|
let mut lm = bindings::SFT_LMetrics {
|
|
|
ascender: 0.,
|
|
@@ -106,37 +142,38 @@ impl Schrift {
|
|
|
lineGap: 0.,
|
|
|
};
|
|
|
if bindings::sft_lmetrics(&self.sft as *const _, &mut lm as *mut _) != 0 {
|
|
|
- None
|
|
|
+ Err(Error::FormatError("line metrics table"))
|
|
|
} else {
|
|
|
- LineMetrics {
|
|
|
+ Ok(LineMetrics {
|
|
|
ascender: lm.ascender,
|
|
|
descender: lm.descender,
|
|
|
line_gap: lm.lineGap,
|
|
|
- }
|
|
|
- .into()
|
|
|
+ })
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- pub fn glyph_lookup(&self, c: char) -> Option<Glyph> {
|
|
|
+ /// Perform unicode-to-glyph lookup.
|
|
|
+ pub fn glyph_lookup(&self, c: char) -> Result<Glyph, Error> {
|
|
|
unsafe {
|
|
|
let mut g = bindings::SFT_Glyph::default();
|
|
|
if bindings::sft_lookup(&self.sft as *const _, c as u32, &mut g as *mut _) != 0 {
|
|
|
- None
|
|
|
+ Err(Error::FormatError("glyph cmap"))
|
|
|
} else {
|
|
|
- Some(Glyph(g))
|
|
|
+ Ok(Glyph(g))
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- pub fn glyph_metrics(&self, glyph: Glyph) -> Option<GlyphMetrics> {
|
|
|
+ /// Look up glyph metrics for a given glyph index.
|
|
|
+ pub fn glyph_metrics(&self, glyph: Glyph) -> Result<GlyphMetrics, Error> {
|
|
|
unsafe {
|
|
|
let mut gmet = std::mem::MaybeUninit::<bindings::SFT_GMetrics>::uninit();
|
|
|
if bindings::sft_gmetrics(&self.sft as *const _, glyph.0, gmet.as_mut_ptr()) != 0 {
|
|
|
- None
|
|
|
+ Err(Error::FormatError("glyph mtx"))
|
|
|
} else {
|
|
|
let gmet = gmet.assume_init();
|
|
|
- Some(GlyphMetrics {
|
|
|
+ Ok(GlyphMetrics {
|
|
|
advance: gmet.advanceWidth,
|
|
|
left_bearing: gmet.leftSideBearing,
|
|
|
y_offset: gmet.yOffset,
|
|
@@ -147,28 +184,37 @@ impl Schrift {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- pub fn kerning(&self, left: Glyph, right: Glyph) -> Kerning {
|
|
|
+ /// Look up kerning information for a given pair of glyphs.
|
|
|
+ pub fn kerning(&self, left: Glyph, right: Glyph) -> Result<Kerning, Error> {
|
|
|
unsafe {
|
|
|
let mut kern = std::mem::MaybeUninit::<bindings::SFT_Kerning>::uninit();
|
|
|
if bindings::sft_kerning(&self.sft as *const _, left.0, right.0, kern.as_mut_ptr()) != 0
|
|
|
{
|
|
|
- Kerning {
|
|
|
- x_shift: 0.,
|
|
|
- y_shift: 0.,
|
|
|
- }
|
|
|
+ Err(Error::FormatError("kerning table"))
|
|
|
} else {
|
|
|
let kern = kern.assume_init();
|
|
|
- Kerning {
|
|
|
+ Ok(Kerning {
|
|
|
x_shift: kern.xShift,
|
|
|
y_shift: kern.yShift,
|
|
|
- }
|
|
|
+ })
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- pub fn render(&self, glyph: Glyph, alpha_map: &mut [u8], width: usize, height: usize) {
|
|
|
+ /// Render a glyph into an alpha-valued bitmap.
|
|
|
+ ///
|
|
|
+ /// Note that 0=no colour, 255=full colour. Subpixel hinting is out of scope for libschrift.
|
|
|
+ pub fn render(
|
|
|
+ &self,
|
|
|
+ glyph: Glyph,
|
|
|
+ alpha_map: &mut [u8],
|
|
|
+ width: usize,
|
|
|
+ height: usize,
|
|
|
+ ) -> Result<(), Error> {
|
|
|
if alpha_map.len() < width * height {
|
|
|
- return;
|
|
|
+ return Err(Error::LogicError(
|
|
|
+ "alpha_map not large enough for given width and height",
|
|
|
+ ));
|
|
|
}
|
|
|
|
|
|
unsafe {
|
|
@@ -177,11 +223,16 @@ impl Schrift {
|
|
|
width: width as i32,
|
|
|
height: height as i32,
|
|
|
};
|
|
|
- bindings::sft_render(&self.sft as *const _, glyph.0, img);
|
|
|
+ if bindings::sft_render(&self.sft as *const _, glyph.0, img) != 0 {
|
|
|
+ Err(Error::FormatError("rendering failed"))
|
|
|
+ } else {
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/// Builder pattern for a [`Schrift`] instance.
|
|
|
pub struct SchriftBuilder {
|
|
|
font: Rc<Font>,
|
|
|
x_scale: Option<f64>,
|
|
@@ -203,21 +254,26 @@ impl SchriftBuilder {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// Set both x- and y- scaling information.
|
|
|
pub fn with_scale(mut self, scale: f64) -> Self {
|
|
|
self.x_scale = Some(scale);
|
|
|
self.y_scale = Some(scale);
|
|
|
self
|
|
|
}
|
|
|
+ /// Set render offset information.
|
|
|
pub fn with_offset(mut self, x: f64, y: f64) -> Self {
|
|
|
self.x_offset = Some(x);
|
|
|
self.y_offset = Some(y);
|
|
|
self
|
|
|
}
|
|
|
+ /// Make the resulting coordinate space consider +y to be the bottom of the glyph, instead of
|
|
|
+ /// the top.
|
|
|
pub fn flag_y_downwards(mut self) -> Self {
|
|
|
self.downward_y = Some(true);
|
|
|
self
|
|
|
}
|
|
|
|
|
|
+ /// Construct a [`Schrift`] instance.
|
|
|
pub fn build(self) -> Schrift {
|
|
|
let scales = match (self.x_scale, self.y_scale) {
|
|
|
(Some(xs), Some(ys)) => (xs, ys),
|
|
@@ -244,53 +300,80 @@ impl SchriftBuilder {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/// Caching wrapper around [`Schrift`]
|
|
|
+/// Caching wrapper around [`Schrift`] that treats format errors as panics.
|
|
|
pub struct CachedSchrift {
|
|
|
schrift: Schrift,
|
|
|
lmetrics: LineMetrics,
|
|
|
+ glyph_cache: HashMap<char, Glyph>,
|
|
|
image_cache: HashMap<Glyph, (Box<[u8]>, u32, u32)>,
|
|
|
metric_cache: HashMap<Glyph, GlyphMetrics>,
|
|
|
kern_cache: HashMap<(Glyph, Glyph), Kerning>,
|
|
|
}
|
|
|
|
|
|
impl CachedSchrift {
|
|
|
+ /// Wrap a [`Schrift`] instance with this caching layer.
|
|
|
pub fn new(schrift: Schrift) -> Self {
|
|
|
Self {
|
|
|
- lmetrics: schrift.line_metrics().unwrap(),
|
|
|
+ lmetrics: schrift.line_metrics().expect("couldn't get line metrics"),
|
|
|
schrift,
|
|
|
+ glyph_cache: Default::default(),
|
|
|
image_cache: Default::default(),
|
|
|
metric_cache: Default::default(),
|
|
|
kern_cache: Default::default(),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// Get line metrics.
|
|
|
pub fn line_metrics(&self) -> &LineMetrics {
|
|
|
&self.lmetrics
|
|
|
}
|
|
|
|
|
|
+ /// Perform glyph lookup.
|
|
|
+ pub fn glyph_lookup(&mut self, ch: char) -> Glyph {
|
|
|
+ *self.glyph_cache.entry(ch).or_insert_with(|| {
|
|
|
+ self.schrift
|
|
|
+ .glyph_lookup(ch)
|
|
|
+ .expect("couldn't lookup glyph")
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Get glyph metrics.
|
|
|
pub fn glyph_metrics(&mut self, glyph: Glyph) -> &GlyphMetrics {
|
|
|
- self.metric_cache
|
|
|
- .entry(glyph)
|
|
|
- .or_insert_with(|| self.schrift.glyph_metrics(glyph).unwrap())
|
|
|
+ self.metric_cache.entry(glyph).or_insert_with(|| {
|
|
|
+ self.schrift
|
|
|
+ .glyph_metrics(glyph)
|
|
|
+ .expect("couldn't lookup glyph metrics")
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
+ /// Get a rendered alphamap image for a given glyph.
|
|
|
pub fn glyph_image(&mut self, glyph: Glyph) -> &(Box<[u8]>, u32, u32) {
|
|
|
+ // this is written as an explicit contains_key()/get().unwrap() so that the long-lived
|
|
|
+ // borrow happens inside the if block.
|
|
|
if self.image_cache.contains_key(&glyph) {
|
|
|
+ // this unwrap is always OK
|
|
|
return self.image_cache.get(&glyph).unwrap();
|
|
|
}
|
|
|
let metrics = self.glyph_metrics(glyph);
|
|
|
let w = metrics.min_width as usize;
|
|
|
let h = metrics.min_height as usize;
|
|
|
let mut data = vec![0u8; (metrics.min_width * metrics.min_height) as usize];
|
|
|
- self.schrift.render(glyph, &mut data, w, h);
|
|
|
+ self.schrift
|
|
|
+ .render(glyph, &mut data, w, h)
|
|
|
+ .expect("couldn't render glyph");
|
|
|
|
|
|
self.image_cache
|
|
|
.entry(glyph)
|
|
|
.or_insert((data.into_boxed_slice(), w as u32, h as u32))
|
|
|
}
|
|
|
|
|
|
+ /// Get the kerning information for a given pair of glyphs.
|
|
|
pub fn glyph_kerning(&mut self, left: Glyph, right: Glyph) -> &Kerning {
|
|
|
- self.kern_cache.entry((left, right)).or_insert_with(|| self.schrift.kerning(left, right))
|
|
|
+ self.kern_cache.entry((left, right)).or_insert_with(|| {
|
|
|
+ self.schrift
|
|
|
+ .kerning(left, right)
|
|
|
+ .expect("couldn't get kerning")
|
|
|
+ })
|
|
|
}
|
|
|
}
|
|
|
|