diff --git a/crates/bevy_sprite/src/text2d.rs b/crates/bevy_sprite/src/text2d.rs index 572a9572218bf..149f1909451a0 100644 --- a/crates/bevy_sprite/src/text2d.rs +++ b/crates/bevy_sprite/src/text2d.rs @@ -176,6 +176,7 @@ pub fn update_text2d_layout( &mut TextLayoutInfo, &mut ComputedTextBlock, )>, + text_font_query: Query<&TextFont>, mut text_reader: Text2dReader, mut font_system: ResMut, mut swash_cache: ResMut, @@ -197,7 +198,7 @@ pub fn update_text2d_layout( let mut previous_scale_factor = 0.; let mut previous_mask = &RenderLayers::none(); - for (entity, maybe_entity_mask, block, bounds, text_layout_info, mut computed) in + for (entity, maybe_entity_mask, block, bounds, mut text_layout_info, mut computed) in &mut text_query { let entity_mask = maybe_entity_mask.unwrap_or_default(); @@ -219,39 +220,40 @@ pub fn update_text2d_layout( *scale_factor }; - if scale_factor != text_layout_info.scale_factor + let text_changed = scale_factor != text_layout_info.scale_factor + || block.is_changed() || computed.needs_rerender() - || bounds.is_changed() - || (!queue.is_empty() && queue.remove(&entity)) - { - let text_bounds = TextBounds { - width: if block.linebreak == LineBreak::NoWrap { - None - } else { - bounds.width.map(|width| width * scale_factor) - }, - height: bounds.height.map(|height| height * scale_factor), - }; + || (!queue.is_empty() && queue.remove(&entity)); + + if !(text_changed || bounds.is_changed()) { + continue; + } - let text_layout_info = text_layout_info.into_inner(); - match text_pipeline.queue_text( - text_layout_info, + let text_bounds = TextBounds { + width: if block.linebreak == LineBreak::NoWrap { + None + } else { + bounds.width.map(|width| width * scale_factor) + }, + height: bounds.height.map(|height| height * scale_factor), + }; + + if text_changed { + match text_pipeline.update_buffer( &fonts, text_reader.iter(entity), - scale_factor as f64, - &block, + block.linebreak, + block.justify, text_bounds, - &mut font_atlas_set, - &mut texture_atlases, - &mut textures, - computed.as_mut(), + scale_factor as f64, + &mut computed, &mut font_system, - &mut swash_cache, ) { Err(TextError::NoSuchFont) => { // There was an error processing the text layout, let's add this entity to the // queue for further processing queue.insert(entity); + continue; } Err( e @ (TextError::FailedToAddGlyph(_) @@ -262,10 +264,40 @@ pub fn update_text2d_layout( ) => { panic!("Fatal error when processing text: {e}."); } - Ok(()) => { - text_layout_info.scale_factor = scale_factor; - text_layout_info.size *= scale_factor.recip(); - } + Ok(()) => {} + } + } + + match text_pipeline.update_text_layout_info( + &mut text_layout_info, + text_font_query, + scale_factor as f64, + &mut font_atlas_set, + &mut texture_atlases, + &mut textures, + &mut computed, + &mut font_system, + &mut swash_cache, + text_bounds, + ) { + Err(TextError::NoSuchFont) => { + // There was an error processing the text layout, let's add this entity to the + // queue for further processing + queue.insert(entity); + continue; + } + Err( + e @ (TextError::FailedToAddGlyph(_) + | TextError::FailedToGetGlyphImage(_) + | TextError::MissingAtlasLayout + | TextError::MissingAtlasTexture + | TextError::InconsistentAtlasState), + ) => { + panic!("Fatal error when processing text: {e}."); + } + Ok(()) => { + text_layout_info.scale_factor = scale_factor; + text_layout_info.size *= scale_factor.recip(); } } } diff --git a/crates/bevy_text/src/glyph.rs b/crates/bevy_text/src/glyph.rs index 453fc579a6b56..f9edd7b85bd98 100644 --- a/crates/bevy_text/src/glyph.rs +++ b/crates/bevy_text/src/glyph.rs @@ -9,7 +9,7 @@ use bevy_reflect::Reflect; /// /// Contains information about how and where to render a glyph. /// -/// Used in [`TextPipeline::queue_text`](crate::TextPipeline::queue_text) and [`TextLayoutInfo`](`crate::TextLayoutInfo`) for rendering glyphs. +/// Used in [`TextPipeline::update_text_layout_info`](crate::TextPipeline::update_text_layout_info) and [`TextLayoutInfo`](`crate::TextLayoutInfo`) for rendering glyphs. #[derive(Debug, Clone, Reflect)] #[reflect(Clone)] pub struct PositionedGlyph { diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 2beb5b1897034..8c1bd93ae063d 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -21,7 +21,7 @@ //! //! With the actual text bounds defined, the `bevy_ui::widget::text::text_system` system (in a UI context) //! or `bevy_sprite::text2d::update_text2d_layout` system (in a 2d world space context) -//! passes it into [`TextPipeline::queue_text`], which: +//! passes it into [`TextPipeline::update_text_layout_info`], which: //! //! 1. updates a [`Buffer`](cosmic_text::Buffer) from the [`TextSpan`]s, generating new [`FontAtlas`]es if necessary. //! 2. iterates over each glyph in the [`Buffer`](cosmic_text::Buffer) to create a [`PositionedGlyph`], diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs index 40a428510dfa8..c70420451cae5 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -235,216 +235,6 @@ impl TextPipeline { Ok(()) } - /// Queues text for rendering - /// - /// Produces a [`TextLayoutInfo`], containing [`PositionedGlyph`]s - /// which contain information for rendering the text. - pub fn queue_text<'a>( - &mut self, - layout_info: &mut TextLayoutInfo, - fonts: &Assets, - text_spans: impl Iterator, - scale_factor: f64, - layout: &TextLayout, - bounds: TextBounds, - font_atlas_set: &mut FontAtlasSet, - texture_atlases: &mut Assets, - textures: &mut Assets, - computed: &mut ComputedTextBlock, - font_system: &mut CosmicFontSystem, - swash_cache: &mut SwashCache, - ) -> Result<(), TextError> { - layout_info.clear(); - - // Clear this here at the focal point of text rendering to ensure the field's lifecycle has strong boundaries. - computed.needs_rerender = false; - - // Extract font ids from the iterator while traversing it. - let mut glyph_info = core::mem::take(&mut self.glyph_info); - glyph_info.clear(); - let text_spans = text_spans.inspect(|(_, _, _, text_font, _, _)| { - glyph_info.push(( - text_font.font.id(), - text_font.font_smoothing, - text_font.font_size, - 0., - 0., - 0., - )); - }); - - let update_result = self.update_buffer( - fonts, - text_spans, - layout.linebreak, - layout.justify, - bounds, - scale_factor, - computed, - font_system, - ); - - self.glyph_info = glyph_info; - - update_result?; - - for (font, _, size, strikethrough_offset, stroke, underline_offset) in - self.glyph_info.iter_mut() - { - let Some((id, _)) = self.map_handle_to_font_id.get(font) else { - continue; - }; - let weight = font_system - .db() - .face(*id) - .map(|f| f.weight) - .unwrap_or(cosmic_text::Weight::NORMAL); - if let Some(font) = font_system.get_font(*id, weight) { - let swash = font.as_swash(); - let metrics = swash.metrics(&[]); - let upem = metrics.units_per_em as f32; - let scalar = *size * scale_factor as f32 / upem; - *strikethrough_offset = (metrics.strikeout_offset * scalar).round(); - *stroke = (metrics.stroke_size * scalar).round().max(1.); - *underline_offset = (metrics.underline_offset * scalar).round(); - } - } - - let buffer = &mut computed.buffer; - let mut box_size = Vec2::ZERO; - - let result = buffer.layout_runs().try_for_each(|run| { - box_size.x = box_size.x.max(run.line_w); - box_size.y += run.line_height; - let mut current_section: Option = None; - let mut start = 0.; - let mut end = 0.; - let result = run - .glyphs - .iter() - .map(move |layout_glyph| (layout_glyph, run.line_y, run.line_i)) - .try_for_each(|(layout_glyph, line_y, line_i)| { - match current_section { - Some(section) => { - if section != layout_glyph.metadata { - layout_info.run_geometry.push(RunGeometry { - span_index: section, - bounds: Rect::new( - start, - run.line_top, - end, - run.line_top + run.line_height, - ), - strikethrough_y: (run.line_y - self.glyph_info[section].3) - .round(), - strikethrough_thickness: self.glyph_info[section].4, - underline_y: (run.line_y - self.glyph_info[section].5).round(), - underline_thickness: self.glyph_info[section].4, - }); - start = end.max(layout_glyph.x); - current_section = Some(layout_glyph.metadata); - } - end = layout_glyph.x + layout_glyph.w; - } - None => { - current_section = Some(layout_glyph.metadata); - start = layout_glyph.x; - end = start + layout_glyph.w; - } - } - - let mut temp_glyph; - let span_index = layout_glyph.metadata; - let font_id = self.glyph_info[span_index].0; - let font_smoothing = self.glyph_info[span_index].1; - - let layout_glyph = if font_smoothing == FontSmoothing::None { - // If font smoothing is disabled, round the glyph positions and sizes, - // effectively discarding all subpixel layout. - temp_glyph = layout_glyph.clone(); - temp_glyph.x = temp_glyph.x.round(); - temp_glyph.y = temp_glyph.y.round(); - temp_glyph.w = temp_glyph.w.round(); - temp_glyph.x_offset = temp_glyph.x_offset.round(); - temp_glyph.y_offset = temp_glyph.y_offset.round(); - temp_glyph.line_height_opt = temp_glyph.line_height_opt.map(f32::round); - - &temp_glyph - } else { - layout_glyph - }; - - let physical_glyph = layout_glyph.physical((0., 0.), 1.); - - let font_atlases = font_atlas_set - .entry(FontAtlasKey( - font_id, - physical_glyph.cache_key.font_size_bits, - font_smoothing, - )) - .or_default(); - - let atlas_info = get_glyph_atlas_info(font_atlases, physical_glyph.cache_key) - .map(Ok) - .unwrap_or_else(|| { - add_glyph_to_atlas( - font_atlases, - texture_atlases, - textures, - &mut font_system.0, - &mut swash_cache.0, - layout_glyph, - font_smoothing, - ) - })?; - - let texture_atlas = texture_atlases.get(atlas_info.texture_atlas).unwrap(); - let location = atlas_info.location; - let glyph_rect = texture_atlas.textures[location.glyph_index]; - let left = location.offset.x as f32; - let top = location.offset.y as f32; - let glyph_size = UVec2::new(glyph_rect.width(), glyph_rect.height()); - - // offset by half the size because the origin is center - let x = glyph_size.x as f32 / 2.0 + left + physical_glyph.x as f32; - let y = - line_y.round() + physical_glyph.y as f32 - top + glyph_size.y as f32 / 2.0; - - let position = Vec2::new(x, y); - - let pos_glyph = PositionedGlyph { - position, - size: glyph_size.as_vec2(), - atlas_info, - span_index, - byte_index: layout_glyph.start, - byte_length: layout_glyph.end - layout_glyph.start, - line_index: line_i, - }; - layout_info.glyphs.push(pos_glyph); - Ok(()) - }); - if let Some(section) = current_section { - layout_info.run_geometry.push(RunGeometry { - span_index: section, - bounds: Rect::new(start, run.line_top, end, run.line_top + run.line_height), - strikethrough_y: (run.line_y - self.glyph_info[section].3).round(), - strikethrough_thickness: self.glyph_info[section].4, - underline_y: (run.line_y - self.glyph_info[section].5).round(), - underline_thickness: self.glyph_info[section].4, - }); - } - - result - }); - - // Check result. - result?; - - layout_info.size = box_size.ceil(); - Ok(()) - } - /// Queues text for measurement /// /// Produces a [`TextMeasureInfo`] which can be used by a layout system @@ -681,14 +471,14 @@ impl TextPipeline { // Check result. result?; - layout_info.size = box_size; + layout_info.size = box_size.ceil(); Ok(()) } } /// Render information for a corresponding text block. /// -/// Contains scaled glyphs and their size. Generated via [`TextPipeline::queue_text`] when an entity has +/// Contains scaled glyphs and their size. Generated via [`TextPipeline::update_text_layout_info`] when an entity has /// [`TextLayout`] and [`ComputedTextBlock`] components. #[derive(Component, Clone, Default, Debug, Reflect)] #[reflect(Component, Default, Debug, Clone)]