1#![cfg_attr(not(feature = "std"), no_std)]
4
5#[cfg(not(feature = "std"))]
6extern crate alloc;
7
8#[cfg(not(feature = "std"))]
9use num_traits::float::FloatCore;
10
11#[cfg(all(feature = "std", not(target_os = "redox")))]
12pub use font_loader::{
13 self,
14 system_fonts::{self, FontProperty, FontPropertyBuilder},
15};
16
17#[cfg(not(feature = "std"))]
18use alloc::string::{String, ToString};
19#[cfg(not(feature = "std"))]
20use alloc::vec::Vec;
21
22use orbclient::{Color, Renderer};
23
24#[derive(Clone)]
25pub struct Font {
26 inner: rusttype::Font<'static>,
27}
28
29impl Font {
30 #[cfg(all(feature = "std", target_os = "redox"))]
32 pub fn find(
33 typeface: Option<&str>,
34 family: Option<&str>,
35 style: Option<&str>,
36 ) -> Result<Font, String> {
37 Font::from_path(&format!(
38 "/usr/share/fonts/{}/{}/{}.ttf",
39 typeface.unwrap_or("Mono"),
40 family.unwrap_or("Fira"),
41 style.unwrap_or("Regular")
42 ))
43 }
44
45 #[cfg(all(feature = "std", not(target_os = "redox")))]
47 fn build_fontproperty(
48 typeface: Option<&str>,
49 family: Option<&str>,
50 style: Option<&str>,
51 ) -> FontProperty {
52 let mut font = FontPropertyBuilder::new();
53 if let Some(style) = style {
54 let style_caps = &style.to_uppercase();
55 let italic = style_caps.contains("ITALIC");
56 let oblique = style_caps.contains("OBLIQUE");
57 let bold = style_caps.contains("BOLD");
58 if italic {
59 font = font.italic();
60 }
61 if oblique {
62 font = font.oblique();
63 }
64 if bold {
65 font = font.bold();
66 }
67 }
68 if let Some(typeface) = typeface {
69 let typeface_caps = &typeface.to_uppercase();
71 if typeface_caps.contains("MONO") {
72 font = font.monospace();
73 }
74 }
75 if let Some(family) = family {
76 if let Some(typeface) = typeface {
77 let typeface_caps = &typeface.to_uppercase();
78 if typeface_caps.contains("SERIF") {
80 font = font.family(&[family, "Serif"].concat());
81 } else if typeface_caps.contains("SANS") {
82 font = font.family(&[family, "Sans"].concat());
83 }
84 } else {
85 font = font.family(family);
86 }
87 }
88 font.build()
89 }
90
91 #[cfg(all(feature = "std", not(target_os = "redox")))]
92 pub fn find(
93 typeface: Option<&str>,
94 family: Option<&str>,
95 style: Option<&str>,
96 ) -> Result<Font, String> {
97 let mut font = Font::build_fontproperty(typeface, family, style);
101 let fonts = system_fonts::query_specific(&mut font); if !fonts.is_empty() {
107 let font_data = system_fonts::get(&font); match font_data {
110 Some((data, _)) => {
111 if let Some(font) = rusttype::Font::try_from_vec(data) {
112 Ok(Font { inner: font })
113 } else {
114 Err("error constructing a Font from bytes".to_string())
115 }
116 }
117 None => Err(format!("Could not get font {} from data", &fonts[0])),
118 }
119 } else {
120 let mut font = Font::build_fontproperty(None, family, style);
122 let fonts = system_fonts::query_specific(&mut font);
123 if !fonts.is_empty() {
124 let font_data = system_fonts::get(&font);
125 match font_data {
126 Some((data, _)) => {
127 if let Some(font) = rusttype::Font::try_from_vec(data) {
128 Ok(Font { inner: font })
129 } else {
130 Err("error constructing a Font from bytes".to_string())
131 }
132 }
133 None => Err(format!("Could not get font {} from data", &fonts[0])),
134 }
135 } else {
136 Font::from_path("/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf")
138 }
139 }
140 }
141
142 #[cfg(feature = "std")]
144 pub fn from_path<P: AsRef<std::path::Path>>(path: P) -> Result<Font, String> {
145 let data = std::fs::read(path).map_err(|err| format!("failed to read font: {}", err))?;
146 if let Some(font) = rusttype::Font::try_from_vec(data) {
147 Ok(Font { inner: font })
148 } else {
149 Err("error constructing a Font from bytes".to_string())
150 }
151 }
152
153 pub fn from_data(data: &'static [u8]) -> Result<Font, String> {
155 if let Some(font) = rusttype::Font::try_from_bytes(data) {
156 Ok(Font { inner: font })
157 } else {
158 Err("error constructing a Font from bytes".to_string())
159 }
160 }
161
162 pub fn render<'a>(&'a self, text: &str, height: f32) -> Text<'a> {
164 let scale = rusttype::Scale::uniform(height);
165
166 let v_metrics = self.inner.v_metrics(scale);
171 let offset = rusttype::point(0.0, v_metrics.ascent);
172
173 let glyphs: Vec<rusttype::PositionedGlyph> =
175 self.inner.layout(text, scale, offset).collect();
176
177 let width = glyphs
179 .iter()
180 .rev()
181 .find_map(|g| {
182 g.pixel_bounding_box()
183 .map(|b| b.min.x as f32 + g.unpositioned().h_metrics().advance_width)
184 })
185 .unwrap_or(0.0);
186
187 Text {
188 w: width.ceil() as u32,
189 h: height.ceil() as u32,
190 glyphs,
191 }
192 }
193}
194
195pub struct Text<'a> {
196 w: u32,
197 h: u32,
198 glyphs: Vec<rusttype::PositionedGlyph<'a>>,
199}
200
201impl<'a> Text<'a> {
202 pub fn width(&self) -> u32 {
204 self.w
205 }
206
207 pub fn height(&self) -> u32 {
209 self.h
210 }
211
212 pub fn draw_clipped<R: Renderer + ?Sized>(
214 &self,
215 renderer: &mut R,
216 x: i32,
217 y: i32,
218 bounds_x: i32,
219 bounds_width: u32,
220 color: Color,
221 ) {
222 for g in &self.glyphs {
223 if let Some(bb) = g.pixel_bounding_box() {
224 g.draw(|off_x, off_y, v| {
225 let off_x = off_x as i32 + bb.min.x;
226 let off_y = off_y as i32 + bb.min.y;
227 if off_x >= 0
229 && off_x < self.w as i32
230 && off_y >= 0
231 && off_y < self.h as i32
232 && x + off_x >= bounds_x
233 && x + off_x <= bounds_x + bounds_width as i32
234 {
235 let c = (v * 255.0) as u32;
236 renderer.pixel(
237 x + off_x,
238 y + off_y,
239 Color {
240 data: c << 24 | (color.data & 0x00FF_FFFF),
241 },
242 );
243 }
244 });
245 }
246 }
247 }
248
249 pub fn draw<R: Renderer + ?Sized>(&self, renderer: &mut R, x: i32, y: i32, color: Color) {
251 self.draw_clipped(renderer, x, y, x, self.w, color);
252 }
253}