retrofire_core/render/text.rs
1use core::fmt;
2#[cfg(feature = "std")]
3use std::io;
4
5use crate::geom::{Mesh, tri, vertex};
6use crate::math::{Color3, Point2, Vec2, pt2, vec2, vec3};
7use crate::util::buf::Buf2;
8
9use super::tex::{Atlas, Layout, SamplerClamp, TexCoord};
10
11/// Text represented as texture-mapped geometry, one quad per glyph.
12#[derive(Clone)]
13pub struct Text {
14 pub font: Atlas<Color3>,
15 pub geom: Mesh<TexCoord>,
16 // TODO Private until fixed
17 _anchor: Vec2,
18 cursor: Point2,
19}
20
21//
22// Inherent impls
23//
24
25impl Text {
26 /// Creates a new empty text object with the given font.
27 pub fn new(font: Atlas<Color3>) -> Self {
28 Self {
29 font,
30 geom: Mesh::default(),
31 _anchor: Vec2::default(),
32 cursor: Point2::default(),
33 }
34 }
35
36 /// Sets the anchor point of the text.
37 ///
38 /// The anchor is a vector that determines how the text is aligned relative
39 /// to the (local) origin. The default is (0, 0) which places the origin to
40 /// the top left corner. Use (0.5, 0.5) to center the text vertically and
41 /// horizontally relative to the origin.
42 ///
43 /// Note that this value does not affect how individual lines of text
44 /// are aligned relative to each other.
45 // TODO private until fixed
46 fn _anchor(mut self, x: f32, y: f32) -> Self {
47 self._anchor = vec2(x, y);
48 self
49 }
50
51 /// Erases all text from `self`.
52 pub fn clear(&mut self) {
53 self.cursor = Point2::origin();
54 self.geom.faces.clear();
55 self.geom.verts.clear();
56 }
57
58 /// Samples the font at `uv`.
59 pub fn sample(&self, uv: TexCoord) -> Color3 {
60 // TODO Figure out why coords go out of bounds -> SamplerOnce panics
61 SamplerClamp.sample(&self.font.texture, uv)
62 }
63
64 fn write_char(&mut self, idx: u32) {
65 let Self { font, geom, cursor, .. } = self;
66
67 let Layout::Grid { sub_dims: (gw, gh) } = font.layout;
68 let (glyph_w, glyph_h) = (gw as f32, gh as f32);
69
70 let [tl, tr, bl, br] = font.coords(idx);
71 // TODO doesn't work when the text is written in several pieces,
72 // such as when writing a formatted string. Total row and col
73 // counts are only known when everything is written.
74 /*let offset = vec2(
75 anchor.x() * glyph_w * cols as f32,
76 anchor.y() * glyph_h * rows as f32,
77 );*/
78 let offset = vec2(0.0, 0.0);
79 let pos = (*cursor - offset).to_pt3().to();
80 let l = geom.verts.len();
81
82 geom.verts.extend([
83 vertex(pos, tl),
84 vertex(pos + vec3(glyph_w, 0.0, 0.0), tr),
85 vertex(pos + vec3(0.0, glyph_h, 0.0), bl),
86 vertex(pos + vec3(glyph_w, glyph_h, 0.0), br),
87 ]);
88 geom.faces.push(tri(l, l + 1, l + 3));
89 geom.faces.push(tri(l, l + 3, l + 2));
90
91 *cursor += vec2(glyph_w, 0.0);
92 }
93}
94
95//
96// Trait impls
97//
98
99#[cfg(feature = "std")]
100impl io::Write for Text {
101 /// Creates geometry to represent the bytes in `buf`.
102 ///
103 /// This method uses each byte in `buf` as an index to the font. Only up to
104 /// 256 glyphs are thus supported. The font should have enough glyphs to
105 /// cover each byte value in `buf`.
106 ///
107 /// Because a one-to-one mapping from bytes to glyphs is used, the result
108 /// will be [mojibake][1] if the buffer contains UTF-8 encoded data beyond
109 /// the ASCII range.
110 ///
111 /// [1]: https://en.wikipedia.org/wiki/Mojibake
112 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
113 /*let (rows, cols) = buf
114 .split(|&b| b == b'\n')
115 .fold((0, 0), |(rs, cs), row| (rs + 1, cs.max(row.len() as u32)));
116 if rows == 0 || cols == 0 {
117 return Ok(0);
118 }*/
119 let Layout::Grid { sub_dims } = self.font.layout;
120 let glyph_h = sub_dims.1 as f32;
121
122 for &b in buf {
123 match b {
124 b'\n' => self.cursor = pt2(0.0, self.cursor.y() + glyph_h),
125 _ => self.write_char(b.into()),
126 }
127 }
128 Ok(buf.len())
129 }
130
131 fn flush(&mut self) -> io::Result<()> {
132 Ok(())
133 }
134}
135
136impl fmt::Write for Text {
137 /// Creates geometry to represent the characters in `s`.
138 ///
139 /// This method iterates over the `char`s of the string, and uses the value
140 /// of each `char` as an index into the font. As such, the font should have
141 /// enough glyphs to cover all the characters used.
142 fn write_str(&mut self, s: &str) -> fmt::Result {
143 /*let (rows, cols) = s
144 .split(|c| c == '\n')
145 .fold((0, 0), |(rs, cs), row| {
146 (rs + 1, cs.max(row.chars().count() as u32))
147 });
148 if rows == 0 || cols == 0 {
149 return Ok(());
150 }*/
151 let Layout::Grid { sub_dims } = self.font.layout;
152 let glyph_h = sub_dims.1 as f32;
153
154 for c in s.chars() {
155 match c {
156 '\n' => self.cursor = pt2(0.0, self.cursor.y() + glyph_h),
157 _ => self.write_char(c.into()),
158 }
159 }
160 Ok(())
161 }
162}
163
164/// Renders ("bakes") the byte string into a buffer.
165pub fn bake<T>(s: &[u8], font: &Atlas<T>) -> Buf2<T>
166where
167 T: Copy + Default,
168{
169 let rows = s.split(|&c| c == b'\n');
170 let (mut num_rows, mut num_cols) = (0, 0);
171
172 for row in rows.clone() {
173 num_rows += 1;
174 num_cols = num_cols.max(row.len() as u32);
175 }
176 if num_rows == 0 || num_cols == 0 {
177 return Buf2::new((0, 0));
178 }
179
180 let Layout::Grid { sub_dims: (gw, gh) } = font.layout;
181 let mut buf = Buf2::new((num_cols * gw, num_rows * gh));
182
183 let (mut x, mut y) = (0, 0);
184 for row in rows {
185 for ch in row {
186 let dest = (x..x + gw, y..y + gh);
187 buf.slice_mut(dest)
188 .copy_from(*font.get((*ch).into()).data());
189 x += gw;
190 }
191 (x, y) = (0, y + gh);
192 }
193 buf
194}