|
@@ -1,14 +1,298 @@
|
|
|
-pub fn add(left: usize, right: usize) -> usize {
|
|
|
- left + right
|
|
|
+use std::{collections::HashMap, ffi::c_void, rc::Rc};
|
|
|
+
|
|
|
+#[allow(unused)]
|
|
|
+#[allow(non_snake_case)]
|
|
|
+#[allow(non_camel_case_types)]
|
|
|
+#[allow(non_upper_case_globals)]
|
|
|
+#[allow(clippy::upper_case_acronyms)]
|
|
|
+mod bindings {
|
|
|
+ include!(concat!(env!("OUT_DIR"), "/schrift_bindings.rs"));
|
|
|
}
|
|
|
|
|
|
-#[cfg(test)]
|
|
|
-mod tests {
|
|
|
- use super::*;
|
|
|
+pub fn version_info() -> &'static str {
|
|
|
+ unsafe {
|
|
|
+ std::ffi::CStr::from_ptr(bindings::sft_version())
|
|
|
+ .to_str()
|
|
|
+ .unwrap()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Debug)]
|
|
|
+pub enum Error {
|
|
|
+ FormatError,
|
|
|
+ IOError(std::io::Error),
|
|
|
+}
|
|
|
+
|
|
|
+/// Font information, from a single TTF file
|
|
|
+pub struct Font {
|
|
|
+ _data: Box<[u8]>,
|
|
|
+ sft_font: *mut bindings::SFT_Font,
|
|
|
+}
|
|
|
+
|
|
|
+impl Font {
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(Self {
|
|
|
+ _data: data,
|
|
|
+ sft_font,
|
|
|
+ }
|
|
|
+ .into())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Drop for Font {
|
|
|
+ fn drop(&mut self) {
|
|
|
+ unsafe {
|
|
|
+ bindings::sft_freefont(self.sft_font);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Debug)]
|
|
|
+pub struct LineMetrics {
|
|
|
+ pub ascender: f64,
|
|
|
+ pub descender: f64,
|
|
|
+ pub line_gap: f64,
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Debug)]
|
|
|
+pub struct GlyphMetrics {
|
|
|
+ pub advance: f64,
|
|
|
+ pub left_bearing: f64,
|
|
|
+ pub y_offset: i32,
|
|
|
+ pub min_width: i32,
|
|
|
+ pub min_height: i32,
|
|
|
+}
|
|
|
|
|
|
- #[test]
|
|
|
- fn it_works() {
|
|
|
- let result = add(2, 2);
|
|
|
- assert_eq!(result, 4);
|
|
|
+#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
+pub struct Glyph(bindings::SFT_Glyph);
|
|
|
+
|
|
|
+impl std::fmt::Debug for Glyph {
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
+ f.debug_tuple("Glyph").field(&self.0).finish()
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+#[derive(Debug)]
|
|
|
+pub struct Kerning {
|
|
|
+ pub x_shift: f64,
|
|
|
+ pub y_shift: f64,
|
|
|
+}
|
|
|
+
|
|
|
+/// Context with both a Font and size information.
|
|
|
+pub struct Schrift {
|
|
|
+ // keep a reference to Font to keep the sft_font pointer in sft valid
|
|
|
+ _font: Rc<Font>,
|
|
|
+ sft: bindings::SFT,
|
|
|
+}
|
|
|
+
|
|
|
+impl Schrift {
|
|
|
+ pub fn build(font: Rc<Font>) -> SchriftBuilder {
|
|
|
+ SchriftBuilder::new(font)
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn line_metrics(&self) -> Option<LineMetrics> {
|
|
|
+ unsafe {
|
|
|
+ let mut lm = bindings::SFT_LMetrics {
|
|
|
+ ascender: 0.,
|
|
|
+ descender: 0.,
|
|
|
+ lineGap: 0.,
|
|
|
+ };
|
|
|
+ if bindings::sft_lmetrics(&self.sft as *const _, &mut lm as *mut _) != 0 {
|
|
|
+ None
|
|
|
+ } else {
|
|
|
+ LineMetrics {
|
|
|
+ ascender: lm.ascender,
|
|
|
+ descender: lm.descender,
|
|
|
+ line_gap: lm.lineGap,
|
|
|
+ }
|
|
|
+ .into()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn glyph_lookup(&self, c: char) -> Option<Glyph> {
|
|
|
+ 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
|
|
|
+ } else {
|
|
|
+ Some(Glyph(g))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn glyph_metrics(&self, glyph: Glyph) -> Option<GlyphMetrics> {
|
|
|
+ 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
|
|
|
+ } else {
|
|
|
+ let gmet = gmet.assume_init();
|
|
|
+ Some(GlyphMetrics {
|
|
|
+ advance: gmet.advanceWidth,
|
|
|
+ left_bearing: gmet.leftSideBearing,
|
|
|
+ y_offset: gmet.yOffset,
|
|
|
+ min_width: gmet.minWidth,
|
|
|
+ min_height: gmet.minHeight,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn kerning(&self, left: Glyph, right: Glyph) -> Kerning {
|
|
|
+ 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.,
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ let kern = kern.assume_init();
|
|
|
+ Kerning {
|
|
|
+ x_shift: kern.xShift,
|
|
|
+ y_shift: kern.yShift,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn render(&self, glyph: Glyph, alpha_map: &mut [u8], width: usize, height: usize) {
|
|
|
+ if alpha_map.len() < width * height {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ unsafe {
|
|
|
+ let img = bindings::SFT_Image {
|
|
|
+ pixels: alpha_map.as_mut_ptr() as *mut c_void,
|
|
|
+ width: width as i32,
|
|
|
+ height: height as i32,
|
|
|
+ };
|
|
|
+ bindings::sft_render(&self.sft as *const _, glyph.0, img);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+pub struct SchriftBuilder {
|
|
|
+ font: Rc<Font>,
|
|
|
+ x_scale: Option<f64>,
|
|
|
+ y_scale: Option<f64>,
|
|
|
+ x_offset: Option<f64>,
|
|
|
+ y_offset: Option<f64>,
|
|
|
+ downward_y: Option<bool>,
|
|
|
+}
|
|
|
+
|
|
|
+impl SchriftBuilder {
|
|
|
+ fn new(font: Rc<Font>) -> Self {
|
|
|
+ Self {
|
|
|
+ font,
|
|
|
+ x_scale: None,
|
|
|
+ y_scale: None,
|
|
|
+ x_offset: None,
|
|
|
+ y_offset: None,
|
|
|
+ downward_y: None,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn with_scale(mut self, scale: f64) -> Self {
|
|
|
+ self.x_scale = Some(scale);
|
|
|
+ self.y_scale = Some(scale);
|
|
|
+ self
|
|
|
+ }
|
|
|
+ pub fn with_offset(mut self, x: f64, y: f64) -> Self {
|
|
|
+ self.x_offset = Some(x);
|
|
|
+ self.y_offset = Some(y);
|
|
|
+ self
|
|
|
+ }
|
|
|
+ pub fn flag_y_downwards(mut self) -> Self {
|
|
|
+ self.downward_y = Some(true);
|
|
|
+ self
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn build(self) -> Schrift {
|
|
|
+ let scales = match (self.x_scale, self.y_scale) {
|
|
|
+ (Some(xs), Some(ys)) => (xs, ys),
|
|
|
+ (Some(xs), None) => (xs, xs),
|
|
|
+ (None, Some(ys)) => (ys, ys),
|
|
|
+ (None, None) => (12.0, 12.0),
|
|
|
+ };
|
|
|
+
|
|
|
+ Schrift {
|
|
|
+ sft: bindings::SFT {
|
|
|
+ font: self.font.sft_font,
|
|
|
+ xScale: scales.0,
|
|
|
+ yScale: scales.1,
|
|
|
+ xOffset: self.x_offset.unwrap_or(0.0),
|
|
|
+ yOffset: self.y_offset.unwrap_or(0.0),
|
|
|
+ flags: if self.downward_y.unwrap_or(false) {
|
|
|
+ bindings::SFT_DOWNWARD_Y as i32
|
|
|
+ } else {
|
|
|
+ 0
|
|
|
+ },
|
|
|
+ },
|
|
|
+ _font: self.font,
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// Caching wrapper around [`Schrift`]
|
|
|
+pub struct CachedSchrift {
|
|
|
+ schrift: Schrift,
|
|
|
+ lmetrics: LineMetrics,
|
|
|
+ image_cache: HashMap<Glyph, (Box<[u8]>, u32, u32)>,
|
|
|
+ metric_cache: HashMap<Glyph, GlyphMetrics>,
|
|
|
+ kern_cache: HashMap<(Glyph, Glyph), Kerning>,
|
|
|
+}
|
|
|
+
|
|
|
+impl CachedSchrift {
|
|
|
+ pub fn new(schrift: Schrift) -> Self {
|
|
|
+ Self {
|
|
|
+ lmetrics: schrift.line_metrics().unwrap(),
|
|
|
+ schrift,
|
|
|
+ image_cache: Default::default(),
|
|
|
+ metric_cache: Default::default(),
|
|
|
+ kern_cache: Default::default(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn line_metrics(&self) -> &LineMetrics {
|
|
|
+ &self.lmetrics
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn glyph_metrics(&mut self, glyph: Glyph) -> &GlyphMetrics {
|
|
|
+ self.metric_cache
|
|
|
+ .entry(glyph)
|
|
|
+ .or_insert_with(|| self.schrift.glyph_metrics(glyph).unwrap())
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn glyph_image(&mut self, glyph: Glyph) -> &(Box<[u8]>, u32, u32) {
|
|
|
+ if self.image_cache.contains_key(&glyph) {
|
|
|
+ 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.image_cache
|
|
|
+ .entry(glyph)
|
|
|
+ .or_insert((data.into_boxed_slice(), w as u32, h as u32))
|
|
|
+ }
|
|
|
+
|
|
|
+ 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))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod test;
|