luminance_glyph/
lib.rs

1//! A fast text renderer for [`luminance`], powered by [`glyph_brush`].
2//!
3//! Initially forked and modified from [glow_glyph](https://github.com/hecrj/glow_glyph) by [hecrj](https://github.com/hecrj). Many thanks to [hecrj](https://github.com/hecrj)!
4//!
5//! [`luminance`]: https://github.com/phaazon/luminance-rs
6//! [`glyph_brush`]: https://github.com/alexheretic/glyph-brush/tree/master/glyph-brush
7#![deny(unused_results)]
8mod builder;
9mod pipeline;
10mod region;
11
12pub use region::Region;
13
14use luminance::{
15    backend,
16    context::GraphicsContext,
17    pipeline::PipelineError,
18    pipeline::{Pipeline as LuminancePipeline, TextureBinding},
19    pixel::NormR8UI,
20    pixel::NormUnsigned,
21    shader::types::Mat44,
22    shading_gate::ShadingGate,
23    tess::Interleaved,
24    texture::Dim2,
25};
26
27use pipeline::Pipeline;
28
29pub use builder::GlyphBrushBuilder;
30pub use glyph_brush::ab_glyph;
31pub use glyph_brush::{
32    BuiltInLineBreaker, Extra, FontId, GlyphCruncher, GlyphPositioner, GlyphVertex,
33    HorizontalAlign, Layout, LineBreak, LineBreaker, Section, SectionGeometry, SectionGlyph,
34    SectionGlyphIter, SectionText, Text, VerticalAlign,
35};
36pub use pipeline::{Instance, LeftTop, RightBottom, TexLeftTop, TexRightBottom, VertexColor};
37
38use ab_glyph::{Font, FontArc, Rect};
39
40use core::hash::BuildHasher;
41use std::borrow::Cow;
42
43use glyph_brush::{BrushAction, BrushError, DefaultSectionHasher};
44use log::{log_enabled, warn};
45
46pub trait GlyphBrushBackend:
47    backend::pipeline::PipelineTexture<Dim2, NormR8UI>
48    + backend::texture::Texture<Dim2, NormR8UI>
49    + backend::shader::Shader
50    + for<'a> backend::shader::Uniformable<'a, Mat44<f32>, Target = Mat44<f32>>
51    + for<'a> backend::shader::Uniformable<
52        'a,
53        TextureBinding<Dim2, NormUnsigned>,
54        Target = TextureBinding<Dim2, NormUnsigned>,
55    > + backend::tess::Tess<(), u32, Instance, Interleaved>
56    + backend::pipeline::PipelineBase
57    + backend::render_gate::RenderGate
58    + backend::tess_gate::TessGate<(), u32, Instance, Interleaved>
59{
60}
61
62impl<B: ?Sized> GlyphBrushBackend for B where
63    B: backend::pipeline::PipelineTexture<Dim2, NormR8UI>
64        + backend::texture::Texture<Dim2, NormR8UI>
65        + backend::shader::Shader
66        + for<'a> backend::shader::Uniformable<'a, Mat44<f32>, Target = Mat44<f32>>
67        + for<'a> backend::shader::Uniformable<
68            'a,
69            TextureBinding<Dim2, NormUnsigned>,
70            Target = TextureBinding<Dim2, NormUnsigned>,
71        > + backend::tess::Tess<(), u32, Instance, Interleaved>
72        + backend::pipeline::PipelineBase
73        + backend::render_gate::RenderGate
74        + backend::tess_gate::TessGate<(), u32, Instance, Interleaved>
75{
76}
77
78/// Object allowing glyph drawing, containing cache state. Manages glyph positioning cacheing,
79/// glyph draw caching & efficient GPU texture cache updating and re-sizing on demand.
80///
81/// Build using a [`GlyphBrushBuilder`](struct.GlyphBrushBuilder.html).
82pub struct GlyphBrush<B, F = FontArc, H = DefaultSectionHasher>
83where
84    B: GlyphBrushBackend,
85{
86    pipeline: Pipeline<B>,
87    glyph_brush: glyph_brush::GlyphBrush<Instance, Extra, F, H>,
88}
89
90impl<B, F: Font, H: BuildHasher> GlyphBrush<B, F, H>
91where
92    B: GlyphBrushBackend,
93{
94    /// Queues a section/layout to be drawn by the next call of
95    /// [`draw_queued`](struct.GlyphBrush.html#method.draw_queued). Can be
96    /// called multiple times to queue multiple sections for drawing.
97    ///
98    /// Benefits from caching, see [caching behaviour](#caching-behaviour).
99    #[inline]
100    pub fn queue<'a, S>(&mut self, section: S)
101    where
102        S: Into<Cow<'a, Section<'a>>>,
103    {
104        self.glyph_brush.queue(section)
105    }
106
107    /// Queues a section/layout to be drawn by the next call of
108    /// [`draw_queued`](struct.GlyphBrush.html#method.draw_queued). Can be
109    /// called multiple times to queue multiple sections for drawing.
110    ///
111    /// Used to provide custom `GlyphPositioner` logic, if using built-in
112    /// [`Layout`](enum.Layout.html) simply use
113    /// [`queue`](struct.GlyphBrush.html#method.queue)
114    ///
115    /// Benefits from caching, see [caching behaviour](#caching-behaviour).
116    #[inline]
117    pub fn queue_custom_layout<'a, S, G>(&mut self, section: S, custom_layout: &G)
118    where
119        G: GlyphPositioner,
120        S: Into<Cow<'a, Section<'a>>>,
121    {
122        self.glyph_brush.queue_custom_layout(section, custom_layout)
123    }
124
125    /// Queues pre-positioned glyphs to be processed by the next call of
126    /// [`draw_queued`](struct.GlyphBrush.html#method.draw_queued). Can be
127    /// called multiple times.
128    #[inline]
129    pub fn queue_pre_positioned(
130        &mut self,
131        glyphs: Vec<SectionGlyph>,
132        extra: Vec<Extra>,
133        bounds: Rect,
134    ) {
135        self.glyph_brush.queue_pre_positioned(glyphs, extra, bounds)
136    }
137
138    /// Retains the section in the cache as if it had been used in the last
139    /// draw-frame.
140    ///
141    /// Should not be necessary unless using multiple draws per frame with
142    /// distinct transforms, see [caching behaviour](#caching-behaviour).
143    #[inline]
144    pub fn keep_cached_custom_layout<'a, S, G>(&mut self, section: S, custom_layout: &G)
145    where
146        S: Into<Cow<'a, Section<'a>>>,
147        G: GlyphPositioner,
148    {
149        self.glyph_brush
150            .keep_cached_custom_layout(section, custom_layout)
151    }
152
153    /// Retains the section in the cache as if it had been used in the last
154    /// draw-frame.
155    ///
156    /// Should not be necessary unless using multiple draws per frame with
157    /// distinct transforms, see [caching behaviour](#caching-behaviour).
158    #[inline]
159    pub fn keep_cached<'a, S>(&mut self, section: S)
160    where
161        S: Into<Cow<'a, Section<'a>>>,
162    {
163        self.glyph_brush.keep_cached(section)
164    }
165
166    /// Returns the available fonts.
167    ///
168    /// The `FontId` corresponds to the index of the font data.
169    #[inline]
170    pub fn fonts(&self) -> &[F] {
171        self.glyph_brush.fonts()
172    }
173
174    /// Adds an additional font to the one(s) initially added on build.
175    ///
176    /// Returns a new [`FontId`](struct.FontId.html) to reference this font.
177    pub fn add_font(&mut self, font: F) -> FontId {
178        self.glyph_brush.add_font(font)
179    }
180}
181
182impl<B, F: Font + Sync, H: BuildHasher> GlyphBrush<B, F, H>
183where
184    B: GlyphBrushBackend,
185{
186    /// Draws all queued sections onto a render target.
187    /// See [`queue`](struct.GlyphBrush.html#method.queue).
188    ///
189    /// Trims the cache, see [caching behaviour](#caching-behaviour).
190    ///
191    /// # Panics
192    /// Panics if the provided `target` has a texture format that does not match
193    /// the `render_format` provided on creation of the `GlyphBrush`.
194    #[inline]
195    pub fn draw_queued<'a>(
196        &mut self,
197        pipeline: &mut LuminancePipeline<'a, B>,
198        shading_gate: &mut ShadingGate<'a, B>,
199        target_width: u32,
200        target_height: u32,
201    ) -> Result<(), PipelineError> {
202        self.draw_queued_with_transform(
203            pipeline,
204            shading_gate,
205            orthographic_projection(target_width, target_height),
206        )
207    }
208
209    /// Draws all queued sections onto a render target, applying a position
210    /// transform (e.g. a projection).
211    /// See [`queue`](struct.GlyphBrush.html#method.queue).
212    ///
213    /// Trims the cache, see [caching behaviour](#caching-behaviour).
214    ///
215    /// # Panics
216    /// Panics if the provided `target` has a texture format that does not match
217    /// the `render_format` provided on creation of the `GlyphBrush`.
218    #[inline]
219    pub fn draw_queued_with_transform<'a>(
220        &mut self,
221        pipeline: &mut LuminancePipeline<'a, B>,
222        shading_gate: &mut ShadingGate<'a, B>,
223        transform: [f32; 16],
224    ) -> Result<(), PipelineError> {
225        //self.process_queued(context);
226        self.pipeline.draw(pipeline, shading_gate, transform, None)
227    }
228
229    /// Draws all queued sections onto a render target, applying a position
230    /// transform (e.g. a projection) and a scissoring region.
231    /// See [`queue`](struct.GlyphBrush.html#method.queue).
232    ///
233    /// Trims the cache, see [caching behaviour](#caching-behaviour).
234    ///
235    /// # Panics
236    /// Panics if the provided `target` has a texture format that does not match
237    /// the `render_format` provided on creation of the `GlyphBrush`.
238    #[inline]
239    pub fn draw_queued_with_transform_and_scissoring<'a>(
240        &mut self,
241        pipeline: &mut LuminancePipeline<'a, B>,
242        shading_gate: &mut ShadingGate<'a, B>,
243        transform: [f32; 16],
244        region: Region,
245    ) -> Result<(), PipelineError> {
246        //self.process_queued(context);
247        self.pipeline
248            .draw(pipeline, shading_gate, transform, Some(region))
249    }
250
251    pub fn process_queued<C>(&mut self, context: &mut C)
252    where
253        C: GraphicsContext<Backend = B>,
254    {
255        self.process_queued_with_vertex_constructor(context, Instance::from_vertex)
256    }
257
258    pub fn process_queued_with_vertex_constructor<C>(
259        &mut self,
260        context: &mut C,
261        into_vertex: impl Fn(GlyphVertex) -> Instance,
262    ) where
263        C: GraphicsContext<Backend = B>,
264    {
265        let pipeline = &mut self.pipeline;
266
267        let mut brush_action;
268
269        loop {
270            brush_action = self.glyph_brush.process_queued(
271                |rect, tex_data| {
272                    let offset = [rect.min[0] as u16, rect.min[1] as u16];
273                    let size = [rect.width() as u16, rect.height() as u16];
274
275                    pipeline.update_cache(offset, size, tex_data);
276                },
277                &into_vertex,
278            );
279
280            match brush_action {
281                Ok(_) => break,
282                Err(BrushError::TextureTooSmall { suggested }) => {
283                    // TODO: Obtain max texture dimensions
284                    let max_image_dimension = 2048;
285
286                    let (new_width, new_height) = if (suggested.0 > max_image_dimension
287                        || suggested.1 > max_image_dimension)
288                        && (self.glyph_brush.texture_dimensions().0 < max_image_dimension
289                            || self.glyph_brush.texture_dimensions().1 < max_image_dimension)
290                    {
291                        (max_image_dimension, max_image_dimension)
292                    } else {
293                        suggested
294                    };
295
296                    if log_enabled!(log::Level::Warn) {
297                        warn!(
298                            "Increasing glyph texture size {old:?} -> {new:?}. \
299                             Consider building with `.initial_cache_size({new:?})` to avoid \
300                             resizing",
301                            old = self.glyph_brush.texture_dimensions(),
302                            new = (new_width, new_height),
303                        );
304                    }
305
306                    pipeline.increase_cache_size(context, new_width, new_height);
307                    self.glyph_brush.resize_texture(new_width, new_height);
308                }
309            }
310        }
311
312        match brush_action.unwrap() {
313            BrushAction::Draw(verts) => {
314                self.pipeline.upload(context, &verts);
315            }
316            BrushAction::ReDraw => {}
317        };
318    }
319}
320
321impl<B, F: Font, H: BuildHasher> GlyphBrush<B, F, H>
322where
323    B: GlyphBrushBackend,
324{
325    fn new<C>(context: &mut C, raw_builder: glyph_brush::GlyphBrushBuilder<F, H>) -> Self
326    where
327        C: GraphicsContext<Backend = B>,
328    {
329        let glyph_brush = raw_builder.build();
330        let (cache_width, cache_height) = glyph_brush.texture_dimensions();
331
332        GlyphBrush {
333            pipeline: Pipeline::new(context, cache_width, cache_height),
334            glyph_brush,
335        }
336    }
337}
338
339/// Helper function to generate a generate a transform matrix.
340#[rustfmt::skip]
341pub fn orthographic_projection(width: u32, height: u32) -> [f32; 16] {
342    [
343        2.0 / width as f32, 0.0, 0.0, 0.0,
344        0.0, -2.0 / height as f32, 0.0, 0.0,
345        0.0, 0.0, 1.0, 0.0,
346        -1.0, 1.0, 0.0, 1.0,
347    ]
348}
349
350impl<B, F: Font, H: BuildHasher> GlyphCruncher<F> for GlyphBrush<B, F, H>
351where
352    B: GlyphBrushBackend,
353{
354    #[inline]
355    fn glyphs_custom_layout<'a, 'b, S, L>(
356        &'b mut self,
357        section: S,
358        custom_layout: &L,
359    ) -> SectionGlyphIter<'b>
360    where
361        L: GlyphPositioner + std::hash::Hash,
362        S: Into<Cow<'a, Section<'a>>>,
363    {
364        self.glyph_brush
365            .glyphs_custom_layout(section, custom_layout)
366    }
367
368    #[inline]
369    fn glyph_bounds_custom_layout<'a, S, L>(
370        &mut self,
371        section: S,
372        custom_layout: &L,
373    ) -> Option<Rect>
374    where
375        L: GlyphPositioner + std::hash::Hash,
376        S: Into<Cow<'a, Section<'a>>>,
377    {
378        self.glyph_brush
379            .glyph_bounds_custom_layout(section, custom_layout)
380    }
381
382    #[inline]
383    fn fonts(&self) -> &[F] {
384        self.glyph_brush.fonts()
385    }
386}
387
388impl<B, F, H> std::fmt::Debug for GlyphBrush<B, F, H>
389where
390    B: GlyphBrushBackend,
391{
392    #[inline]
393    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394        write!(f, "GlyphBrush")
395    }
396}