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
190 changes: 188 additions & 2 deletions crates/bevy_text/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ use bevy_asset::{AssetId, Assets};
use bevy_color::Color;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
component::Component, entity::Entity, reflect::ReflectComponent, resource::Resource,
system::ResMut,
component::Component,
entity::Entity,
reflect::ReflectComponent,
resource::Resource,
system::{Query, ResMut},
};
use bevy_image::prelude::*;
use bevy_log::{once, warn};
Expand Down Expand Up @@ -513,6 +516,189 @@ impl TextPipeline {
.cloned()
.map(|(id, _)| id)
}

/// Update [`TextLayoutInfo`] with the new [`PositionedGlyph`] layout.
pub fn update_text_layout_info<'a>(
&mut self,
layout_info: &mut TextLayoutInfo,
text_font_query: Query<&'a TextFont>,
scale_factor: f64,
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,
bounds: TextBounds,
) -> Result<(), TextError> {
layout_info.glyphs.clear();
layout_info.run_geometry.clear();
layout_info.size = Default::default();

self.glyph_info.clear();

for text_font in text_font_query.iter_many(computed.entities.iter().map(|e| e.entity)) {
let mut section_info = (
text_font.font.id(),
text_font.font_smoothing,
text_font.font_size,
0.0,
0.0,
0.0,
);

if let Some((id, _)) = self.map_handle_to_font_id.get(&section_info.0) {
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 = section_info.2 * scale_factor as f32 / upem;
section_info.3 = (metrics.strikeout_offset * scalar).round();
section_info.4 = (metrics.stroke_size * scalar).round().max(1.);
section_info.5 = (metrics.underline_offset * scalar).round();
}
}
self.glyph_info.push(section_info);
}

let buffer = &mut computed.buffer;
buffer.set_size(font_system, bounds.width, bounds.height);
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(())
}
}

/// Render information for a corresponding text block.
Expand Down
96 changes: 45 additions & 51 deletions crates/bevy_ui/src/widget/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,72 +319,66 @@ pub fn measure_text_system(
/// It does not modify or observe existing ones. The exception is when adding new glyphs to a [`bevy_text::FontAtlas`].
pub fn text_system(
mut textures: ResMut<Assets<Image>>,
fonts: Res<Assets<Font>>,
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
mut font_atlas_set: ResMut<FontAtlasSet>,
mut text_pipeline: ResMut<TextPipeline>,
mut text_query: Query<(
Entity,
Ref<ComputedNode>,
&TextLayout,
&mut TextLayoutInfo,
&mut TextNodeFlags,
&mut ComputedTextBlock,
)>,
mut text_reader: TextUiReader,
text_font_query: Query<&TextFont>,
mut font_system: ResMut<CosmicFontSystem>,
mut swash_cache: ResMut<SwashCache>,
) {
for (entity, node, block, text_layout_info, mut text_flags, mut computed) in &mut text_query {
// Skip the text node if it is waiting for a new measure func
if text_flags.needs_measure_fn {
continue;
}

if !(node.is_changed() || text_flags.needs_recompute) {
continue;
}
for (node, block, mut text_layout_info, mut text_flags, mut computed) in &mut text_query {
if node.is_changed() || text_flags.needs_recompute {
// Skip the text node if it is waiting for a new measure func
if text_flags.needs_measure_fn {
return;
}

let physical_node_size = if block.linebreak == LineBreak::NoWrap {
// With `NoWrap` set, no constraints are placed on the width of the text.
TextBounds::UNBOUNDED
} else {
// `scale_factor` is already multiplied by `UiScale`
TextBounds::new(node.unrounded_size.x, node.unrounded_size.y)
};
let scale_factor = node.inverse_scale_factor().recip().into();
let physical_node_size = if block.linebreak == LineBreak::NoWrap {
// With `NoWrap` set, no constraints are placed on the width of the text.
TextBounds::UNBOUNDED
} else {
// `scale_factor` is already multiplied by `UiScale`
TextBounds::new(node.unrounded_size.x, node.unrounded_size.y)
};

let text_layout_info = text_layout_info.into_inner();
match text_pipeline.queue_text(
text_layout_info,
&fonts,
text_reader.iter(entity),
node.inverse_scale_factor.recip() as f64,
block,
physical_node_size,
&mut font_atlas_set,
&mut texture_atlases,
&mut textures,
&mut computed,
&mut font_system,
&mut swash_cache,
) {
Err(TextError::NoSuchFont) => {
// There was an error processing the text layout, try again next frame
text_flags.needs_recompute = true;
}
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 = node.inverse_scale_factor.recip();
text_layout_info.size *= node.inverse_scale_factor;
text_flags.needs_recompute = false;
match text_pipeline.update_text_layout_info(
&mut text_layout_info,
text_font_query,
scale_factor,
&mut font_atlas_set,
&mut texture_atlases,
&mut textures,
&mut computed,
&mut font_system,
&mut swash_cache,
physical_node_size,
) {
Err(TextError::NoSuchFont) => {
// There was an error processing the text layout, try again next frame
text_flags.needs_recompute = true;
}
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 as f32;
text_layout_info.size *= node.inverse_scale_factor();
text_flags.needs_recompute = false;
}
}
}
}
Expand Down