aoer_plotty_rs/context/
typography.rs1use font_kit::font::Font;
2use font_kit::hinting::HintingOptions;
3use geo::bounding_rect::BoundingRect;
4use geo::map_coords::MapCoords;
5use geo::translate::Translate;
6use geo_types::{coord, Geometry, GeometryCollection, Rect};
7use std::error::Error;
8use std::fmt::{Display, Formatter};
9use std::sync::Arc;
10
11use crate::context::glyph_proxy::GlyphProxy;
12use crate::geo_types::ToGTGeometry;
13use pathfinder_geometry::rect::RectF;
14use pathfinder_geometry::vector::Vector2F;
15
16type TypographicBounds = RectF;
17
18#[derive(Debug)]
19pub enum TypographyError {
20 FontError,
21 NoFontSet,
22 GlyphNotFound(u32),
23}
24
25impl Display for TypographyError {
26 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
27 write!(f, "{:?}", self)
28 }
29}
30
31impl Error for TypographyError {}
32
33#[derive(Clone, Debug)]
34pub enum TextAlignment {
35 Left,
36 Center,
37 Right,
38}
39
40#[derive(Debug)]
41pub struct RenderedGlyph {
42 geo: Geometry<f64>,
43 pub bounds: TypographicBounds,
44 pub advance: Vector2F,
45}
46
47#[derive(Clone, Debug)]
48pub struct Typography {
49 font: Option<Font>,
50 hinting: HintingOptions,
51 em: f64,
52 close: bool,
53 align: TextAlignment, }
55
56impl Typography {
57 pub fn default_font() -> Font {
59 let font_data =
60 include_bytes!("../../resources/fonts/ReliefSingleLineOutline-Regular.otf").to_vec();
61 Font::from_bytes(Arc::new(font_data), 0).unwrap() }
63
64 pub fn new() -> Self {
66 Typography {
67 font: Some(Self::default_font()),
68 hinting: HintingOptions::None,
69 em: 1.0,
70 close: false,
71 align: TextAlignment::Left,
72 }
73 }
74
75 pub fn mm_per_em() -> f64 {
76 8.0f64 * 0.3527777778f64
77 }
78
79 pub fn align(&mut self, align: TextAlignment) -> &mut Self {
80 self.align = align;
81 self
82 }
83
84 pub fn close(&mut self, close: bool) -> &mut Self {
85 self.close = close;
86 self
87 }
88
89 pub fn hint(&mut self, hint: HintingOptions) -> &mut Self {
90 self.hinting = hint;
91 self
92 }
93
94 pub fn font(&mut self, font: &Font) -> &mut Self {
95 self.font = Some(font.clone());
96 self
97 }
98
99 pub fn size(&mut self, em: f64) -> &mut Self {
100 self.em = em;
101 self
102 }
103
104 pub fn render(&self, text: &String, accuracy: f64) -> Result<Geometry<f64>, Box<dyn Error>> {
105 let font = match &self.font {
106 None => return Err(Box::new(TypographyError::NoFontSet)),
107 Some(font) => font.clone(),
108 };
109 let metrics = font.metrics();
110 let mut glyphs: Vec<RenderedGlyph> = vec![];
111 let mut advance = Vector2F::new(0.0, 0.0);
112 for char in text.chars() {
113 let mut gp = GlyphProxy::new(self.close);
114 let glyph = font.glyph_for_char(char).or(Some(32)).unwrap();
115 font.outline(glyph, self.hinting, &mut gp)?;
116 let thisadvance = font.advance(glyph)?;
117 let gtgeo = gp.path().to_gt_geometry(accuracy)?;
118 let rglyph = RenderedGlyph {
121 geo: gtgeo.translate(advance.x().into(), advance.y().into()),
122 bounds: font.typographic_bounds(glyph)?,
123 advance: advance.clone(),
124 };
125 advance = advance + thisadvance;
126 glyphs.push(rglyph);
128 }
129 let units_per_em: f64 = &self.em / f64::from(metrics.units_per_em);
132 let output_geometries: Vec<Geometry<f64>> = glyphs
133 .iter()
134 .map(|g| {
135 g.geo.map_coords(|xy| {
136 coord!(
137 x: units_per_em * xy.x * Self::mm_per_em(),
138 y: units_per_em * xy.y * Self::mm_per_em(),
139 )
140 }) })
142 .collect();
143 let output_geo_collection = GeometryCollection::new_from(output_geometries);
144 let bounds = output_geo_collection
145 .bounding_rect()
146 .unwrap_or(Rect::new(coord! {x: 0.0, y:0.0}, coord! {x:0.0, y:0.0}));
147 let output_geo_collection = match &self.align {
150 TextAlignment::Left => output_geo_collection,
151 TextAlignment::Right => {
152 output_geo_collection.translate(-(bounds.max().x - bounds.min().x), 0.0)
153 }
154 TextAlignment::Center => {
155 output_geo_collection.translate(-(bounds.max().x - bounds.min().x) / 2.0, 0.0)
156 }
157 };
158 Ok(Geometry::GeometryCollection(output_geo_collection))
159 }
160}
161
162#[cfg(test)]
163pub mod tests {
164 use crate::context::typography::Typography;
165 use font_kit::font::Font;
166 use std::sync::Arc;
167
168 #[test]
169 fn test_simple() {
170 let fdata = include_bytes!("../../resources/fonts/ReliefSingleLine-Regular.ttf").to_vec();
171 let f = Font::from_bytes(Arc::new(fdata), 0).unwrap(); let mut t = Typography::new();
173 let _geo = t
174 .size(2.0)
175 .font(&f)
176 .render(&"YES: This is some text XXX".to_string(), 0.1);
177 }
178}