Skip to main content

ndless_sdl/
text.rs

1pub mod freetype {
2	use ndless::prelude::*;
3	use unicode_segmentation::UnicodeSegmentation;
4
5	use crate::video::Color::{RGB, RGBA};
6	use crate::{video::Color, video::Surface, video::SurfaceFlag::SWSurface};
7
8	#[derive(Debug, PartialEq, Clone)]
9	pub struct Text<'a> {
10		text: String,
11		matrix: ndless_freetype::Matrix,
12		surface: Option<Surface>,
13		up_to_date: bool,
14		height: isize,
15		font: &'a ndless_freetype::Face,
16		color: Color,
17	}
18
19	impl<'a> Text<'a> {
20		pub fn new(font: &'a ndless_freetype::Face) -> Self {
21			Self {
22				text: "".to_string(),
23				matrix: Self::radians_to_matrix(0.),
24				surface: None,
25				up_to_date: false,
26				height: 40,
27				font,
28				color: RGB(0, 0, 0),
29			}
30		}
31		pub fn text(&mut self, str: impl Into<String>) -> &mut Self {
32			let str = str.into();
33			if str != self.text {
34				self.text = str;
35				self.up_to_date = false;
36			}
37			self
38		}
39		pub fn font(&mut self, font: &'a ndless_freetype::Face) -> &mut Self {
40			if font != self.font {
41				self.font = font;
42				self.up_to_date = false;
43			}
44			self
45		}
46		pub fn color(&mut self, color: Color) -> &mut Self {
47			if color != self.color {
48				self.color = color;
49				self.up_to_date = false;
50			}
51			self
52		}
53		pub fn height(&mut self, height: isize) -> &mut Self {
54			if height != self.height {
55				self.height = height;
56				self.up_to_date = false;
57			}
58			self
59		}
60		fn radians_to_matrix(angle: f64) -> ndless_freetype::Matrix {
61			ndless_freetype::Matrix {
62				xx: (angle.cos() * f64::from(0x10000)) as ndless_freetype::FT_Fixed,
63				xy: (-angle.sin() * f64::from(0x10000)) as ndless_freetype::FT_Fixed,
64				yx: (angle.sin() * f64::from(0x10000)) as ndless_freetype::FT_Fixed,
65				yy: (angle.cos() * f64::from(0x10000)) as ndless_freetype::FT_Fixed,
66			}
67		}
68		// Removed for now because rotation can cause integer overflow
69		/*/// Angle in degrees
70		pub fn rotate(&mut self, angle: f64) -> &mut Self {
71			let angle = angle.to_radians();
72			let matrix = Self::radians_to_matrix(angle);
73			if matrix != self.matrix {
74				self.matrix = matrix;
75				self.up_to_date = false;
76			}
77			self
78		}*/
79		/// Use when the size of text
80		pub fn reallocate(&mut self) {
81			self.surface = None;
82		}
83		#[allow(clippy::many_single_char_names)]
84		pub fn render(&mut self) -> &Surface {
85			if self.up_to_date && self.surface.as_ref().is_some() {
86				return self.surface.as_ref().unwrap();
87			}
88			self.font.set_char_size(self.height * 64, 0, 50, 0).unwrap();
89			let chars = self.text.graphemes(true);
90			let mut pen = ndless_freetype::Vector { x: 0, y: 0 };
91			let mut max_height = 0;
92			let mut baseline_height = 0;
93			let mut max_width = 0;
94			let mut min_height = 0;
95			for letter in chars.clone() {
96				self.font.set_transform(&mut self.matrix, &mut pen);
97				self.font
98					.load_char(
99						letter.chars().next().unwrap() as usize,
100						ndless_freetype::face::LoadFlag::RENDER,
101					)
102					.unwrap();
103				let glyph = self.font.glyph();
104				let cbox = glyph.get_glyph().unwrap().get_cbox(0);
105				if cbox.xMax / 64 > max_width {
106					max_width = cbox.xMax / 64
107				}
108				if cbox.yMin / 64 < min_height {
109					min_height = cbox.yMin / 64
110				}
111				if cbox.yMax / 64 > max_height {
112					max_height = cbox.yMax / 64
113				}
114				let y = glyph.bitmap_top() as usize;
115				pen.x += glyph.advance().x;
116				pen.y += glyph.advance().y;
117				if y > baseline_height {
118					baseline_height = y;
119				}
120			}
121			let max_height = -min_height + max_height;
122			let reassign = match &self.surface {
123				Some(surface) => {
124					surface.get_width() < max_width as u16
125						|| surface.get_height() < max_height as u16
126				}
127				None => true,
128			};
129			if reassign {
130				self.surface = Some(
131					Surface::new(
132						&[SWSurface],
133						max_width as isize,
134						max_height as isize,
135						32,
136						0xFF00_0000,
137						0x00FF_0000,
138						0x0000_FF00,
139						0x0000_00FF,
140					)
141					.unwrap(),
142				);
143			} else {
144				self.surface.as_ref().unwrap().fill(RGBA(0, 0, 0, 0));
145			}
146			let scr = self.surface.as_ref().unwrap();
147			let mut pen = ndless_freetype::Vector { x: 0, y: 0 };
148			let max_width = scr.get_width();
149			let max_height = scr.get_height();
150			for letter in chars {
151				self.font.set_transform(&mut self.matrix, &mut pen);
152				self.font
153					.load_char(
154						letter.chars().next().unwrap() as usize,
155						ndless_freetype::face::LoadFlag::RENDER,
156					)
157					.unwrap();
158				let glyph = self.font.glyph();
159				let bitmap = glyph.bitmap();
160				let x = glyph.bitmap_left() as usize;
161				let y = baseline_height - glyph.bitmap_top() as usize;
162				let mut col = 0;
163				let width = bitmap.width() as usize;
164				let x_max = x + width;
165				let y_max = y + bitmap.rows() as usize;
166
167				for (row, x_scaled) in (x..x_max).enumerate() {
168					for y_scaled in y..y_max {
169						if x_scaled < max_width as usize && y_scaled < max_height as usize {
170							let alpha = bitmap.buffer()[col * width + row];
171							scr.fill_rect(
172								Some(crate::Rect {
173									x: x_scaled as i16,
174									y: y_scaled as i16,
175									w: 1,
176									h: 1,
177								}),
178								match self.color {
179									RGB(r, g, b) => RGBA(r, g, b, alpha),
180									RGBA(r, g, b, _) => RGBA(r, g, b, alpha),
181								},
182							);
183							col += 1;
184						}
185					}
186					col = 0;
187				}
188				pen.x += glyph.advance().x;
189				pen.y += glyph.advance().y;
190			}
191			scr
192		}
193	}
194}