1#![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
78pub 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 #[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 #[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 #[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 #[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 #[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 #[inline]
170 pub fn fonts(&self) -> &[F] {
171 self.glyph_brush.fonts()
172 }
173
174 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 #[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 #[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.pipeline.draw(pipeline, shading_gate, transform, None)
227 }
228
229 #[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.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 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#[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}