ttf2mesh/
lib.rs

1//! # Overview
2//!
3//! A high-level Rust wrapper API for [fetisov's ttf2mesh](https://github.com/fetisov/ttf2mesh/)
4//! library for generating a 2d/3d mesh (vertices, indices and normals [only for 3D])
5//! from TrueType (`.ttf`) glyphs.
6//!
7//! Usage:
8//! ```rust
9//! # use ttf2mesh::{TTFFile, Quality, Value, Mesh, Mesh2d, Mesh3d};
10//! #
11//! let mut ttf = TTFFile::from_file("./fonts/FiraMono-Medium.ttf").unwrap();
12//!
13//! // export all glyphs as 2d meshes to a .obj file
14//! ttf.export_to_obj("./fonts/FiraMono-Medium.obj", Quality::Low).unwrap();
15//!
16//! // generate 2d mesh for a glyph
17//! let mut glyph = ttf.glyph_from_char('€').unwrap();
18//! let mesh_2d: Mesh<Mesh2d> = glyph.to_2d_mesh(Quality::Medium).unwrap();
19//!
20//! // work with Mesh vertices, faces (indices). See Mesh documentation for more
21//! assert_eq!(mesh_2d.vertices_len(), 56);
22//! assert_eq!(mesh_2d.iter_vertices().next().unwrap().val(), (0.555, 0.656));
23//!
24//! assert_eq!(mesh_2d.faces_len(), 54);
25//! assert_eq!(mesh_2d.iter_faces().next().unwrap().val(), (53, 52, 5));
26//!
27//! // 3d mesh with depth of 0.5
28//! let mesh_3d: Mesh<Mesh3d> = glyph.to_3d_mesh(Quality::Medium, 0.5).unwrap();
29//! ```
30#![cfg_attr(feature = "unstable", feature(test))]
31
32use std::{ffi::CString, path::Path};
33
34mod error;
35mod glyph;
36mod mesh;
37mod output;
38mod quality;
39mod ttf;
40
41pub use error::Error;
42pub use glyph::Glyph;
43pub use mesh::{Mesh, Mesh2d, Mesh3d};
44pub use output::{DataIterator, Value};
45pub use quality::Quality;
46pub use ttf::TTFFile;
47
48// TODO: support TTF_FEATURE_IGN_ERR as bitflag
49
50#[cfg(not(windows))]
51fn path_to_cstring<P: AsRef<Path>>(path: P) -> CString {
52    use std::os::unix::ffi::OsStrExt;
53    CString::new(path.as_ref().as_os_str().as_bytes()).unwrap()
54}
55
56#[cfg(windows)]
57fn path_to_cstring<P: AsRef<Path>>(path: P) -> CString {
58    CString::new(path.as_ref().as_os_str().to_str().unwrap()).unwrap()
59}
60
61#[cfg(test)]
62mod tests {
63    use std::path::PathBuf;
64
65    use super::*;
66
67    fn get_font_path() -> PathBuf {
68        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fonts")
69    }
70
71    fn get_font(font_file: Option<&str>) -> PathBuf {
72        match font_file {
73            Some(file) => get_font_path().join(file),
74            None => get_font_path().join("FiraMono-Medium.ttf"),
75        }
76    }
77
78    pub(crate) fn read_font(font_file: Option<&str>) -> Vec<u8> {
79        std::fs::read(get_font(font_file)).unwrap()
80    }
81
82    #[test]
83    fn test_from_buffer_vec() {
84        let _ = TTFFile::from_buffer_vec(read_font(None)).unwrap();
85    }
86
87    #[test]
88    fn test_from_file() {
89        let _ = TTFFile::from_file(get_font(None)).unwrap();
90    }
91
92    #[test]
93    fn test_get_glyph_from_char() {
94        let mut font = TTFFile::from_buffer_vec(read_font(None)).unwrap();
95        let _ = font.glyph_from_char('A').unwrap();
96    }
97
98    #[test]
99    fn test_to_3d_mesh() {
100        let mut font = TTFFile::from_buffer_vec(read_font(None)).unwrap();
101        let mut glyph = font.glyph_from_char('€').unwrap();
102        let mesh = glyph.to_3d_mesh(Quality::Low, 0.5).unwrap();
103
104        let mut sizes = Vec::new();
105        sizes.extend_from_slice(&[
106            mesh.iter_vertices().collect::<Vec<_>>().len(),
107            mesh.iter_normals().unwrap().collect::<Vec<_>>().len(),
108            mesh.iter_faces().collect::<Vec<_>>().len(),
109        ]);
110
111        let mesh = glyph.to_3d_mesh(Quality::High, 1.5).unwrap();
112
113        sizes.extend_from_slice(&[
114            mesh.iter_vertices().collect::<Vec<_>>().len(),
115            mesh.iter_normals().unwrap().collect::<Vec<_>>().len(),
116            mesh.iter_faces().collect::<Vec<_>>().len(),
117        ]);
118
119        let mesh = glyph.to_3d_mesh(Quality::Custom(255), 0.5).unwrap();
120
121        sizes.extend_from_slice(&[
122            mesh.iter_vertices().collect::<Vec<_>>().len(),
123            mesh.iter_normals().unwrap().collect::<Vec<_>>().len(),
124            mesh.iter_faces().collect::<Vec<_>>().len(),
125        ]);
126
127        assert_eq!(sizes, &[246, 246, 160, 552, 552, 364, 1164, 1164, 772]);
128    }
129
130    #[test]
131    fn test_to_2d_mesh() {
132        let mut font = TTFFile::from_buffer_vec(read_font(None)).unwrap();
133        let mut glyph = font.glyph_from_char('€').unwrap();
134
135        let mut sizes = Vec::new();
136        let mesh = glyph.to_2d_mesh(Quality::Low).unwrap();
137        assert!(mesh.iter_normals().is_none());
138        sizes.extend_from_slice(&[
139            mesh.iter_vertices().collect::<Vec<_>>().len(),
140            mesh.iter_faces().collect::<Vec<_>>().len(),
141        ]);
142
143        let mesh = glyph.to_2d_mesh(Quality::High).unwrap();
144        sizes.extend_from_slice(&[
145            mesh.iter_vertices().collect::<Vec<_>>().len(),
146            mesh.iter_faces().collect::<Vec<_>>().len(),
147        ]);
148
149        let mesh = glyph.to_2d_mesh(Quality::Custom(255)).unwrap();
150        sizes.extend_from_slice(&[
151            mesh.iter_vertices().collect::<Vec<_>>().len(),
152            mesh.iter_faces().collect::<Vec<_>>().len(),
153        ]);
154
155        assert_eq!(sizes, &[41, 39, 92, 90, 194, 192]);
156    }
157}
158
159#[cfg(all(feature = "unstable", test))]
160mod bench {
161    extern crate test;
162
163    use super::*;
164    use test::Bencher;
165
166    #[bench]
167    fn bench_open_font(b: &mut Bencher) {
168        let buffer = tests::read_font(None);
169
170        b.iter(|| {
171            let _ = TTFFile::from_buffer_vec(buffer.clone()).unwrap();
172        });
173    }
174
175    #[bench]
176    fn bench_get_glyph(b: &mut Bencher) {
177        let mut font = TTFFile::from_buffer_vec(tests::read_font(None)).unwrap();
178
179        b.iter(|| {
180            let _ = font.glyph_from_char('€').unwrap();
181        });
182    }
183
184    #[bench]
185    fn bench_glyph_to_3d_mesh_low_quality(b: &mut Bencher) {
186        let mut font = TTFFile::from_buffer_vec(tests::read_font(None)).unwrap();
187        let mut glyph = font.glyph_from_char('€').unwrap();
188
189        b.iter(|| {
190            let _ = glyph.to_3d_mesh(Quality::Low, 0.1).unwrap();
191        });
192    }
193
194    #[bench]
195    fn bench_glyph_to_3d_mesh_high_quality(b: &mut Bencher) {
196        let mut font = TTFFile::from_buffer_vec(tests::read_font(None)).unwrap();
197        let mut glyph = font.glyph_from_char('€').unwrap();
198
199        b.iter(|| {
200            let _ = glyph.to_3d_mesh(Quality::High, 0.1).unwrap();
201        });
202    }
203
204    #[bench]
205    fn bench_glyph_to_2d_mesh_low_quality(b: &mut Bencher) {
206        let mut font = TTFFile::from_buffer_vec(tests::read_font(None)).unwrap();
207        let mut glyph = font.glyph_from_char('€').unwrap();
208
209        b.iter(|| {
210            let _ = glyph.to_2d_mesh(Quality::Low).unwrap();
211        });
212    }
213
214    #[bench]
215    fn bench_glyph_to_2d_mesh_high_quality(b: &mut Bencher) {
216        let mut font = TTFFile::from_buffer_vec(tests::read_font(None)).unwrap();
217        let mut glyph = font.glyph_from_char('€').unwrap();
218
219        b.iter(|| {
220            let _ = glyph.to_2d_mesh(Quality::High).unwrap();
221        });
222    }
223}