#![deny(unused_results)]
mod builder;
mod pipeline;
mod region;
pub use region::Region;
use pipeline::{Pipeline, Vertex2D};
pub use builder::GlyphBrushBuilder;
pub use glyph_brush::ab_glyph;
pub use glyph_brush::{
BuiltInLineBreaker, Extra, FontId, GlyphCruncher, GlyphPositioner, HorizontalAlign, Layout,
LineBreak, LineBreaker, Section, SectionGeometry, SectionGlyph, SectionGlyphIter, SectionText,
Text, VerticalAlign,
};
use ab_glyph::{Font, FontArc, Rect};
use core::hash::BuildHasher;
use std::borrow::Cow;
use glyph_brush::{BrushAction, BrushError, DefaultSectionHasher};
use log::{log_enabled, warn};
pub struct GlyphBrush<F = FontArc, H = DefaultSectionHasher> {
pipeline: Pipeline,
glyph_brush: glyph_brush::GlyphBrush<solstice::quad_batch::Quad<Vertex2D>, Extra, F, H>,
}
impl<F: Font, H: BuildHasher> GlyphBrush<F, H> {
#[inline]
pub fn queue<'a, S>(&mut self, section: S)
where
S: Into<Cow<'a, Section<'a>>>,
{
self.glyph_brush.queue(section)
}
#[inline]
pub fn queue_custom_layout<'a, S, G>(&mut self, section: S, custom_layout: &G)
where
G: GlyphPositioner,
S: Into<Cow<'a, Section<'a>>>,
{
self.glyph_brush.queue_custom_layout(section, custom_layout)
}
#[inline]
pub fn queue_pre_positioned(
&mut self,
glyphs: Vec<SectionGlyph>,
extra: Vec<Extra>,
bounds: Rect,
) {
self.glyph_brush.queue_pre_positioned(glyphs, extra, bounds)
}
#[inline]
pub fn keep_cached_custom_layout<'a, S, G>(&mut self, section: S, custom_layout: &G)
where
S: Into<Cow<'a, Section<'a>>>,
G: GlyphPositioner,
{
self.glyph_brush
.keep_cached_custom_layout(section, custom_layout)
}
#[inline]
pub fn keep_cached<'a, S>(&mut self, section: S)
where
S: Into<Cow<'a, Section<'a>>>,
{
self.glyph_brush.keep_cached(section)
}
#[inline]
pub fn fonts(&self) -> &[F] {
self.glyph_brush.fonts()
}
pub fn add_font(&mut self, font: F) -> FontId {
self.glyph_brush.add_font(font)
}
}
impl<F: Font + Sync, H: BuildHasher> GlyphBrush<F, H> {
#[inline]
pub fn draw_queued(
&mut self,
context: &mut solstice::Context,
target_width: u32,
target_height: u32,
) -> Result<(), String> {
self.draw_queued_with_transform(
context,
orthographic_projection(target_width, target_height),
)
}
#[inline]
pub fn draw_queued_with_transform(
&mut self,
context: &mut solstice::Context,
transform: [f32; 16],
) -> Result<(), String> {
self.process_queued(context);
self.pipeline.draw(context, transform, None);
Ok(())
}
#[inline]
pub fn draw_queued_with_transform_and_scissoring(
&mut self,
context: &mut solstice::Context,
transform: [f32; 16],
region: Region,
) -> Result<(), String> {
self.process_queued(context);
self.pipeline.draw(context, transform, Some(region));
Ok(())
}
fn process_queued(&mut self, context: &mut solstice::Context) {
let pipeline = &mut self.pipeline;
let mut brush_action;
loop {
brush_action = self.glyph_brush.process_queued(
|rect, tex_data| {
let offset = [rect.min[0] as u16, rect.min[1] as u16];
let size = [rect.width() as u16, rect.height() as u16];
pipeline.update_cache(context, offset, size, tex_data);
},
Vertex2D::from_vertex,
);
match brush_action {
Ok(_) => break,
Err(BrushError::TextureTooSmall { suggested }) => {
let max_image_dimension = 2048;
let (new_width, new_height) = if (suggested.0 > max_image_dimension
|| suggested.1 > max_image_dimension)
&& (self.glyph_brush.texture_dimensions().0 < max_image_dimension
|| self.glyph_brush.texture_dimensions().1 < max_image_dimension)
{
(max_image_dimension, max_image_dimension)
} else {
suggested
};
if log_enabled!(log::Level::Warn) {
warn!(
"Increasing glyph texture size {old:?} -> {new:?}. \
Consider building with `.initial_cache_size({new:?})` to avoid \
resizing",
old = self.glyph_brush.texture_dimensions(),
new = (new_width, new_height),
);
}
pipeline.increase_cache_size(context, new_width, new_height);
self.glyph_brush.resize_texture(new_width, new_height);
}
}
}
match brush_action.unwrap() {
BrushAction::Draw(quads) => pipeline.upload(quads),
BrushAction::ReDraw => {}
};
}
}
impl<F: Font, H: BuildHasher> GlyphBrush<F, H> {
fn new(gl: &mut solstice::Context, raw_builder: glyph_brush::GlyphBrushBuilder<F, H>) -> Self {
let glyph_brush = raw_builder.build();
let (cache_width, cache_height) = glyph_brush.texture_dimensions();
GlyphBrush {
pipeline: Pipeline::new(gl, cache_width, cache_height),
glyph_brush,
}
}
}
pub fn orthographic_projection(width: u32, height: u32) -> [f32; 16] {
#[cfg_attr(rustfmt, rustfmt_skip)]
[
2.0 / width as f32, 0.0, 0.0, 0.0,
0.0, -2.0 / height as f32, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
-1.0, 1.0, 0.0, 1.0,
]
}
impl<F: Font, H: BuildHasher> GlyphCruncher<F> for GlyphBrush<F, H> {
#[inline]
fn glyphs_custom_layout<'a, 'b, S, L>(
&'b mut self,
section: S,
custom_layout: &L,
) -> SectionGlyphIter<'b>
where
L: GlyphPositioner + std::hash::Hash,
S: Into<Cow<'a, Section<'a>>>,
{
self.glyph_brush
.glyphs_custom_layout(section, custom_layout)
}
#[inline]
fn glyph_bounds_custom_layout<'a, S, L>(
&mut self,
section: S,
custom_layout: &L,
) -> Option<Rect>
where
L: GlyphPositioner + std::hash::Hash,
S: Into<Cow<'a, Section<'a>>>,
{
self.glyph_brush
.glyph_bounds_custom_layout(section, custom_layout)
}
#[inline]
fn fonts(&self) -> &[F] {
self.glyph_brush.fonts()
}
}
impl<F, H> std::fmt::Debug for GlyphBrush<F, H> {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "GlyphBrush")
}
}