Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 59 additions & 27 deletions crates/bevy_sprite/src/text2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<CosmicFontSystem>,
mut swash_cache: ResMut<SwashCache>,
Expand All @@ -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();
Expand All @@ -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(_)
Expand All @@ -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();
}
}
}
Expand Down
210 changes: 0 additions & 210 deletions crates/bevy_text/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,216 +237,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<Font>,
text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextFont, Color, LineHeight)>,
scale_factor: f64,
layout: &TextLayout,
bounds: TextBounds,
font_atlas_set: &mut FontAtlasSet,
texture_atlases: &mut Assets<TextureAtlasLayout>,
textures: &mut Assets<Image>,
computed: &mut ComputedTextBlock,
font_system: &mut CosmicFontSystem,
swash_cache: &mut SwashCache,
) -> Result<(), TextError> {
layout_info.glyphs.clear();
layout_info.run_geometry.clear();
layout_info.size = Default::default();

// 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 box_size = buffer_dimensions(buffer);

let result = buffer.layout_runs().try_for_each(|run| {
let mut current_section: Option<usize> = 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;
Ok(())
}

/// Queues text for measurement
///
/// Produces a [`TextMeasureInfo`] which can be used by a layout system
Expand Down
Loading