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}