spandex/fonts/
mod.rs

1//! This module contains everything that helps us dealing with fonts.
2
3pub mod configuration;
4pub mod manager;
5pub mod styles;
6
7use std::fs::File;
8use std::io::Cursor;
9use std::path::{Path, PathBuf};
10
11use freetype::{face, Face, Library};
12use printpdf::types::plugins::graphics::two_dimensional::font::IndirectFontRef;
13use printpdf::Pt;
14
15use crate::document::Document;
16use crate::{Error, Result};
17
18/// A font that contains the printpdf object font needed to render text and the freetype font
19/// needed to measure text.
20#[derive(Debug)]
21pub struct Font {
22    /// The freetype face.
23    freetype: Face,
24
25    /// The printpdf font.
26    printpdf: IndirectFontRef,
27}
28
29impl Font {
30    /// Creates a font from a path to a file.
31    pub fn from_file<P: AsRef<Path>>(
32        path: P,
33        library: &Library,
34        document: &mut Document,
35    ) -> Result<Font> {
36        let file = File::open(path.as_ref())
37            .map_err(|_| Error::FontNotFound(PathBuf::from(path.as_ref())))?;
38        Ok(Font {
39            freetype: library.new_face(path.as_ref(), 0)?,
40            printpdf: document.inner_mut().add_external_font(file)?,
41        })
42    }
43
44    /// Creates a font from a byte array.
45    pub fn from_bytes(bytes: &[u8], library: &Library, document: &mut Document) -> Result<Font> {
46        let cursor = Cursor::new(bytes);
47        Ok(Font {
48            // I don't like this bytes.to_vec() but I'm not sure there's a better way of doing
49            // this...
50            freetype: library.new_memory_face(bytes.to_vec(), 0)?,
51            printpdf: document.inner_mut().add_external_font(cursor)?,
52        })
53    }
54
55    /// Computes the width of a char of the font at a specified size.
56    pub fn char_width(&self, c: char, scale: Pt) -> Pt {
57        let scale = scale.0;
58
59        // vertical scale for the space character
60        let vert_scale = {
61            if self
62                .freetype
63                .load_char(0x0020, face::LoadFlag::NO_SCALE)
64                .is_ok()
65            {
66                self.freetype.glyph().metrics().vertAdvance
67            } else {
68                1000
69            }
70        };
71
72        // calculate the width of the text in unscaled units
73        let is_ok = self
74            .freetype
75            .load_char(c as usize, face::LoadFlag::NO_SCALE)
76            .is_ok();
77
78        let width = if is_ok {
79            self.freetype.glyph().metrics().horiAdvance
80        } else {
81            0
82        };
83
84        Pt(width as f64 / (vert_scale as f64 / scale))
85    }
86
87    /// Computes the text width of the font at a specified size.
88    pub fn text_width(&self, text: &str, scale: Pt) -> Pt {
89        let scale = scale.0;
90
91        // vertical scale for the space character
92        let vert_scale = {
93            if self
94                .freetype
95                .load_char(0x0020, face::LoadFlag::NO_SCALE)
96                .is_ok()
97            {
98                self.freetype.glyph().metrics().vertAdvance
99            } else {
100                1000
101            }
102        };
103
104        // calculate the width of the text in unscaled units
105        let sum_width = text.chars().fold(0, |acc, ch| {
106            let is_ok = self
107                .freetype
108                .load_char(ch as usize, face::LoadFlag::NO_SCALE)
109                .is_ok();
110
111            if is_ok {
112                acc + self.freetype.glyph().metrics().horiAdvance
113            } else {
114                acc
115            }
116        });
117
118        Pt(sum_width as f64 / (vert_scale as f64 / scale))
119    }
120
121    /// Returns a reference to the printpdf font.
122    pub fn printpdf(&self) -> &IndirectFontRef {
123        &self.printpdf
124    }
125}