use ab_glyph::{Font as _, FontArc, Glyph, ScaleFont as _}; use bevy_asset::{Assets, Handle}; use bevy_math::Vec2; use bevy_render::texture::Image; use bevy_sprite::TextureAtlas; use bevy_utils::tracing::warn; use glyph_brush_layout::{ BuiltInLineBreaker, FontId, GlyphPositioner, Layout, SectionGeometry, SectionGlyph, SectionText, ToSectionText, }; use crate::{ error::TextError, BreakLineOn, Font, FontAtlasSet, FontAtlasWarning, GlyphAtlasInfo, TextAlignment, TextSettings, YAxisOrientation, }; pub struct GlyphBrush { fonts: Vec, handles: Vec>, latest_font_id: FontId, } impl Default for GlyphBrush { fn default() -> Self { GlyphBrush { fonts: Vec::new(), handles: Vec::new(), latest_font_id: FontId(0), } } } impl GlyphBrush { pub fn compute_glyphs( &self, sections: &[S], bounds: Vec2, text_alignment: TextAlignment, linebreak_behaviour: BreakLineOn, ) -> Result, TextError> { let geom = SectionGeometry { bounds: (bounds.x, bounds.y), ..Default::default() }; let lbb: BuiltInLineBreaker = linebreak_behaviour.into(); let section_glyphs = Layout::default() .h_align(text_alignment.into()) .line_breaker(lbb) .calculate_glyphs(&self.fonts, &geom, sections); Ok(section_glyphs) } #[allow(clippy::too_many_arguments)] pub fn process_glyphs( &self, glyphs: Vec, sections: &[SectionText], font_atlas_set_storage: &mut Assets, fonts: &Assets, texture_atlases: &mut Assets, textures: &mut Assets, text_settings: &TextSettings, font_atlas_warning: &mut FontAtlasWarning, y_axis_orientation: YAxisOrientation, ) -> Result, TextError> { if glyphs.is_empty() { return Ok(Vec::new()); } let sections_data = sections .iter() .map(|section| { let handle = &self.handles[section.font_id.0]; let font = fonts.get(handle).ok_or(TextError::NoSuchFont)?; let font_size = section.scale.y; Ok(( handle, font, font_size, ab_glyph::Font::as_scaled(&font.font, font_size), )) }) .collect::, _>>()?; let mut min_x = std::f32::MAX; let mut min_y = std::f32::MAX; let mut max_y = std::f32::MIN; for sg in &glyphs { let glyph = &sg.glyph; let scaled_font = sections_data[sg.section_index].3; min_x = min_x.min(glyph.position.x); min_y = min_y.min(glyph.position.y - scaled_font.ascent()); max_y = max_y.max(glyph.position.y - scaled_font.descent()); } min_x = min_x.floor(); min_y = min_y.floor(); max_y = max_y.floor(); let mut positioned_glyphs = Vec::new(); for sg in glyphs { let SectionGlyph { section_index: _, byte_index, mut glyph, font_id: _, } = sg; let glyph_id = glyph.id; let glyph_position = glyph.position; let adjust = GlyphPlacementAdjuster::new(&mut glyph); let section_data = sections_data[sg.section_index]; if let Some(outlined_glyph) = section_data.1.font.outline_glyph(glyph) { let bounds = outlined_glyph.px_bounds(); let handle_font_atlas: Handle = section_data.0.cast_weak(); let font_atlas_set = font_atlas_set_storage .get_or_insert_with(handle_font_atlas, FontAtlasSet::default); let atlas_info = font_atlas_set .get_glyph_atlas_info(section_data.2, glyph_id, glyph_position) .map(Ok) .unwrap_or_else(|| { font_atlas_set.add_glyph_to_atlas(texture_atlases, textures, outlined_glyph) })?; if !text_settings.allow_dynamic_font_size && !font_atlas_warning.warned && font_atlas_set.num_font_atlases() > text_settings.max_font_atlases.get() { warn!("warning[B0005]: Number of font atlases has exceeded the maximum of {}. Performance and memory usage may suffer.", text_settings.max_font_atlases.get()); font_atlas_warning.warned = true; } let texture_atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap(); let glyph_rect = texture_atlas.textures[atlas_info.glyph_index]; let size = Vec2::new(glyph_rect.width(), glyph_rect.height()); let x = bounds.min.x + size.x / 2.0 - min_x; let y = match y_axis_orientation { YAxisOrientation::BottomToTop => max_y - bounds.max.y + size.y / 2.0, YAxisOrientation::TopToBottom => bounds.min.y + size.y / 2.0 - min_y, }; let position = adjust.position(Vec2::new(x, y)); positioned_glyphs.push(PositionedGlyph { position, size, atlas_info, section_index: sg.section_index, byte_index, }); } } Ok(positioned_glyphs) } pub fn add_font(&mut self, handle: Handle, font: FontArc) -> FontId { self.fonts.push(font); self.handles.push(handle); let font_id = self.latest_font_id; self.latest_font_id = FontId(font_id.0 + 1); font_id } } #[derive(Debug, Clone)] pub struct PositionedGlyph { pub position: Vec2, pub size: Vec2, pub atlas_info: GlyphAtlasInfo, pub section_index: usize, pub byte_index: usize, } #[cfg(feature = "subpixel_glyph_atlas")] struct GlyphPlacementAdjuster; #[cfg(feature = "subpixel_glyph_atlas")] impl GlyphPlacementAdjuster { #[inline(always)] pub fn new(_: &mut Glyph) -> Self { Self } #[inline(always)] pub fn position(&self, p: Vec2) -> Vec2 { p } } #[cfg(not(feature = "subpixel_glyph_atlas"))] struct GlyphPlacementAdjuster(f32); #[cfg(not(feature = "subpixel_glyph_atlas"))] impl GlyphPlacementAdjuster { #[inline(always)] pub fn new(glyph: &mut Glyph) -> Self { let v = glyph.position.x.round(); glyph.position.x = 0.; glyph.position.y = glyph.position.y.ceil(); Self(v) } #[inline(always)] pub fn position(&self, v: Vec2) -> Vec2 { Vec2::new(self.0, 0.) + v } }