solstice_2d/d2/
text.rs

1use glyph_brush::ab_glyph::{point, FontVec as ABFontVec};
2use glyph_brush::{BrushAction, BrushError, FontId};
3use solstice::image::{Image, Settings};
4use solstice::mesh::IndexedMesh;
5use solstice::quad_batch::*;
6use solstice::texture::*;
7use solstice::Context;
8
9pub struct FontVec(ABFontVec);
10
11impl std::ops::Deref for FontVec {
12    type Target = ABFontVec;
13
14    fn deref(&self) -> &Self::Target {
15        &self.0
16    }
17}
18
19impl std::convert::TryFrom<Vec<u8>> for FontVec {
20    type Error = glyph_brush::ab_glyph::InvalidFont;
21
22    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
23        Ok(Self(ABFontVec::try_from_vec(value)?))
24    }
25}
26
27pub struct Text {
28    quad_batch: QuadBatch<super::Vertex2D>,
29    font_texture: Image,
30    glyph_brush: glyph_brush::GlyphBrush<Quad<super::Vertex2D>, glyph_brush::Extra, ABFontVec>,
31}
32
33pub const DEFAULT_VERT: &str = r#"
34vec4 pos(mat4 transform_projection, vec4 vertex_position) {
35    return transform_projection * vertex_position;
36}
37"#;
38
39pub const DEFAULT_FRAG: &str = r#"
40vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
41    float a = Texel(texture, texture_coords).a;
42    color.a *= a;
43    return color;
44}
45"#;
46
47impl Text {
48    pub fn new(ctx: &mut Context) -> Result<Self, crate::GraphicsError> {
49        let glyph_brush = glyph_brush::GlyphBrushBuilder::using_fonts(vec![]).build();
50
51        let font_texture = {
52            let (width, height) = glyph_brush.texture_dimensions();
53            Image::new(
54                ctx,
55                TextureType::Tex2D,
56                solstice::PixelFormat::Alpha,
57                width,
58                height,
59                Settings {
60                    mipmaps: false,
61                    ..Settings::default()
62                },
63            )?
64        };
65        // will only do texture sub data updates to initialize as empty texture
66        ctx.set_texture_data(
67            font_texture.get_texture_key(),
68            font_texture.get_texture_info(),
69            font_texture.get_texture_type(),
70            None,
71        );
72
73        let quad_batch = QuadBatch::new(ctx, 1000)?;
74
75        Ok(Self {
76            quad_batch,
77            font_texture,
78            glyph_brush,
79        })
80    }
81
82    pub fn add_font(&mut self, font_data: FontVec) -> FontId {
83        self.glyph_brush.add_font(font_data.0)
84    }
85
86    pub fn set_text(
87        &mut self,
88        text: glyph_brush::Text,
89        bounds: super::Rectangle,
90        layout: glyph_brush::Layout<glyph_brush::BuiltInLineBreaker>,
91        ctx: &mut Context,
92    ) {
93        self.glyph_brush.queue(glyph_brush::Section {
94            text: vec![text],
95            screen_position: (bounds.x, bounds.y),
96            bounds: (bounds.width, bounds.height),
97            layout,
98        });
99        self.update(ctx);
100    }
101
102    pub fn texture(&self) -> &solstice::image::Image {
103        &self.font_texture
104    }
105
106    pub fn geometry(
107        &mut self,
108        ctx: &mut Context,
109    ) -> solstice::Geometry<&IndexedMesh<super::Vertex2D, u16>> {
110        self.quad_batch.unmap(ctx)
111    }
112
113    fn update(&mut self, ctx: &mut Context) {
114        let Self {
115            quad_batch,
116            font_texture,
117            glyph_brush,
118            ..
119        } = self;
120
121        let to_vertex = |glyph_vertex: glyph_brush::GlyphVertex| {
122            let glyph_brush::GlyphVertex {
123                mut tex_coords,
124                pixel_coords,
125                bounds,
126                extra,
127            } = glyph_vertex;
128            let mut gl_rect = glyph_brush::ab_glyph::Rect {
129                min: point(pixel_coords.min.x as f32, pixel_coords.min.y as f32),
130                max: point(pixel_coords.max.x as f32, pixel_coords.max.y as f32),
131            };
132
133            // handle overlapping bounds, modify uv_rect to preserve texture aspect
134            if gl_rect.max.x > bounds.max.x {
135                let old_width = gl_rect.width();
136                gl_rect.max.x = bounds.max.x;
137                tex_coords.max.x =
138                    tex_coords.min.x + tex_coords.width() * gl_rect.width() / old_width;
139            }
140            if gl_rect.min.x < bounds.min.x {
141                let old_width = gl_rect.width();
142                gl_rect.min.x = bounds.min.x;
143                tex_coords.min.x =
144                    tex_coords.max.x - tex_coords.width() * gl_rect.width() / old_width;
145            }
146            if gl_rect.max.y > bounds.max.y {
147                let old_height = gl_rect.height();
148                gl_rect.max.y = bounds.max.y;
149                tex_coords.max.y =
150                    tex_coords.min.y + tex_coords.height() * gl_rect.height() / old_height;
151            }
152            if gl_rect.min.y < bounds.min.y {
153                let old_height = gl_rect.height();
154                gl_rect.min.y = bounds.min.y;
155                tex_coords.min.y =
156                    tex_coords.max.y - tex_coords.height() * gl_rect.height() / old_height;
157            }
158
159            Quad {
160                vertices: [
161                    super::Vertex2D {
162                        position: [gl_rect.min.x as f32, gl_rect.min.y as f32],
163                        uv: [tex_coords.min.x, tex_coords.min.y],
164                        color: extra.color,
165                    },
166                    super::Vertex2D {
167                        position: [gl_rect.max.x as f32, gl_rect.min.y as f32],
168                        uv: [tex_coords.max.x, tex_coords.min.y],
169                        color: extra.color,
170                    },
171                    super::Vertex2D {
172                        position: [gl_rect.max.x as f32, gl_rect.max.y as f32],
173                        uv: [tex_coords.max.x, tex_coords.max.y],
174                        color: extra.color,
175                    },
176                    super::Vertex2D {
177                        position: [gl_rect.min.x as f32, gl_rect.max.y as f32],
178                        uv: [tex_coords.min.x, tex_coords.max.y],
179                        color: extra.color,
180                    },
181                ],
182            }
183        };
184
185        loop {
186            let update_texture = |rect: glyph_brush::Rectangle<u32>, data: &[u8]| {
187                let mut info = font_texture.get_texture_info();
188                info.set_width(rect.width());
189                info.set_height(rect.height());
190                ctx.set_texture_sub_data(
191                    font_texture.get_texture_key(),
192                    info,
193                    font_texture.get_texture_type(),
194                    data,
195                    rect.min[0],
196                    rect.min[1],
197                );
198            };
199            match glyph_brush.process_queued(update_texture, to_vertex) {
200                Ok(action) => match action {
201                    BrushAction::Draw(quads) => {
202                        quad_batch.clear();
203                        for quad in quads {
204                            quad_batch.push(quad);
205                        }
206                        break;
207                    }
208                    BrushAction::ReDraw => {
209                        break;
210                    }
211                },
212                Err(error) => match error {
213                    BrushError::TextureTooSmall { suggested: (w, h) } => {
214                        let mut info = font_texture.get_texture_info();
215                        info.set_width(w);
216                        info.set_height(h);
217                        font_texture.set_texture_info(info);
218                        ctx.set_texture_data(
219                            font_texture.get_texture_key(),
220                            font_texture.get_texture_info(),
221                            font_texture.get_texture_type(),
222                            None,
223                        );
224                        glyph_brush.resize_texture(w, h);
225                    }
226                },
227            }
228        }
229    }
230}