Skip to content

Commit afcb1fe

Browse files
authored
Cleanup some bevy_text pipeline.rs (#9111)
## Objective - `bevy_text/src/pipeline.rs` had some crufty code. ## Solution Remove the cruft. - `&mut self` argument was unused by `TextPipeline::create_text_measure`, so we replace it with a constructor `TextMeasureInfo::from_text`. - We also pass a `&Text` to `from_text` since there is no reason to split the struct before passing it as argument. - from_text also checks beforehand that every Font exist in the Assets<Font>. This allows rust to skip the drop code on the Vecs we create in the method, since there is no early exit. - We also remove the scaled_fonts field on `TextMeasureInfo`. This avoids an additional allocation. We can re-use the font on `fonts` instead in `compute_size`. Building a `ScaledFont` seems fairly cheap, when looking at the ab_glyph internals. - We also implement ToSectionText on TextMeasureSection, this let us skip creating a whole new Vec each time we call compute_size. - This let us remove compute_size_from_section_text, since its only purpose was to not have to allocate the Vec we just made redundant. - Make some immutabe `Vec<T>` into `Box<[T]>` and `String` into `Box<str>` - `{min,max}_width_content_size` fields of `TextMeasureInfo` have name `width` in them, yet the contain information on both width and height. - `TextMeasureInfo::linebreak_behaviour` -> `linebreak_behavior` ## Migration Guide - The `ResMut<TextPipeline>` argument to `measure_text_system` doesn't exist anymore. If you were calling this system manually, you should remove the argument. - The `{min,max}_width_content_size` fields of `TextMeasureInfo` are renamed to `min` and `max` respectively - Other changes to `TextMeasureInfo` may also break your code if you were manually building it. Please consider using the new `TextMeasureInfo::from_text` to build one instead. - `TextPipeline::create_text_measure` has been removed in favor of `TextMeasureInfo::from_text`
1 parent db5f80b commit afcb1fe

File tree

4 files changed

+91
-130
lines changed

4 files changed

+91
-130
lines changed

crates/bevy_text/src/glyph_brush.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ impl GlyphBrush {
8484
})
8585
.collect::<Result<Vec<_>, _>>()?;
8686

87-
let text_bounds = compute_text_bounds(&glyphs, |index| &sections_data[index].3);
87+
let text_bounds = compute_text_bounds(&glyphs, |index| sections_data[index].3);
8888

8989
let mut positioned_glyphs = Vec::new();
9090
for sg in glyphs {
@@ -203,12 +203,12 @@ impl GlyphPlacementAdjuster {
203203

204204
/// Computes the minimal bounding rectangle for a block of text.
205205
/// Ignores empty trailing lines.
206-
pub(crate) fn compute_text_bounds<'a, T>(
206+
pub(crate) fn compute_text_bounds<T>(
207207
section_glyphs: &[SectionGlyph],
208-
get_scaled_font: impl Fn(usize) -> &'a PxScaleFont<T>,
208+
get_scaled_font: impl Fn(usize) -> PxScaleFont<T>,
209209
) -> bevy_math::Rect
210210
where
211-
T: ab_glyph::Font + 'a,
211+
T: ab_glyph::Font,
212212
{
213213
let mut text_bounds = Rect {
214214
min: Vec2::splat(std::f32::MAX),

crates/bevy_text/src/pipeline.rs

Lines changed: 76 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use ab_glyph::PxScale;
1+
use ab_glyph::{Font as AbglyphFont, PxScale};
22
use bevy_asset::{Assets, Handle, HandleId};
33
use bevy_ecs::component::Component;
44
use bevy_ecs::system::Resource;
@@ -7,12 +7,12 @@ use bevy_render::texture::Image;
77
use bevy_sprite::TextureAtlas;
88
use bevy_utils::HashMap;
99

10-
use glyph_brush_layout::{FontId, GlyphPositioner, SectionGeometry, SectionText};
10+
use glyph_brush_layout::{FontId, GlyphPositioner, SectionGeometry, SectionText, ToSectionText};
1111

1212
use crate::{
1313
compute_text_bounds, error::TextError, glyph_brush::GlyphBrush, scale_value, BreakLineOn, Font,
14-
FontAtlasSet, FontAtlasWarning, PositionedGlyph, TextAlignment, TextSection, TextSettings,
15-
YAxisOrientation,
14+
FontAtlasSet, FontAtlasWarning, PositionedGlyph, Text, TextAlignment, TextSection,
15+
TextSettings, YAxisOrientation,
1616
};
1717

1818
#[derive(Default, Resource)]
@@ -85,7 +85,7 @@ impl TextPipeline {
8585
return Ok(TextLayoutInfo::default());
8686
}
8787

88-
let size = compute_text_bounds(&section_glyphs, |index| &scaled_fonts[index]).size();
88+
let size = compute_text_bounds(&section_glyphs, |index| scaled_fonts[index]).size();
8989

9090
let glyphs = self.brush.process_glyphs(
9191
section_glyphs,
@@ -101,123 +101,110 @@ impl TextPipeline {
101101

102102
Ok(TextLayoutInfo { glyphs, size })
103103
}
104+
}
104105

105-
pub fn create_text_measure(
106-
&mut self,
106+
#[derive(Debug, Clone)]
107+
pub struct TextMeasureSection {
108+
pub text: Box<str>,
109+
pub scale: f32,
110+
pub font_id: FontId,
111+
}
112+
113+
#[derive(Debug, Clone, Default)]
114+
pub struct TextMeasureInfo {
115+
pub fonts: Box<[ab_glyph::FontArc]>,
116+
pub sections: Box<[TextMeasureSection]>,
117+
pub text_alignment: TextAlignment,
118+
pub linebreak_behavior: glyph_brush_layout::BuiltInLineBreaker,
119+
pub min: Vec2,
120+
pub max: Vec2,
121+
}
122+
123+
impl TextMeasureInfo {
124+
pub fn from_text(
125+
text: &Text,
107126
fonts: &Assets<Font>,
108-
sections: &[TextSection],
109127
scale_factor: f64,
110-
text_alignment: TextAlignment,
111-
linebreak_behaviour: BreakLineOn,
112128
) -> Result<TextMeasureInfo, TextError> {
113-
let mut auto_fonts = Vec::with_capacity(sections.len());
114-
let mut scaled_fonts = Vec::with_capacity(sections.len());
115-
let sections = sections
129+
let sections = &text.sections;
130+
for section in sections {
131+
if !fonts.contains(&section.style.font) {
132+
return Err(TextError::NoSuchFont);
133+
}
134+
}
135+
let (auto_fonts, sections) = sections
116136
.iter()
117137
.enumerate()
118138
.map(|(i, section)| {
119-
let font = fonts
120-
.get(&section.style.font)
121-
.ok_or(TextError::NoSuchFont)?;
122-
let font_size = scale_value(section.style.font_size, scale_factor);
123-
auto_fonts.push(font.font.clone());
124-
let px_scale_font = ab_glyph::Font::into_scaled(font.font.clone(), font_size);
125-
scaled_fonts.push(px_scale_font);
126-
127-
let section = TextMeasureSection {
128-
font_id: FontId(i),
129-
scale: PxScale::from(font_size),
130-
text: section.value.clone(),
131-
};
132-
133-
Ok(section)
139+
// SAFETY: we exited early earlier in this function if
140+
// one of the fonts was missing.
141+
let font = unsafe { fonts.get(&section.style.font).unwrap_unchecked() };
142+
(
143+
font.font.clone(),
144+
TextMeasureSection {
145+
font_id: FontId(i),
146+
scale: scale_value(section.style.font_size, scale_factor),
147+
text: section.value.clone().into_boxed_str(),
148+
},
149+
)
134150
})
135-
.collect::<Result<Vec<_>, _>>()?;
151+
.unzip();
136152

137-
Ok(TextMeasureInfo::new(
153+
Ok(Self::new(
138154
auto_fonts,
139-
scaled_fonts,
140155
sections,
141-
text_alignment,
142-
linebreak_behaviour.into(),
156+
text.alignment,
157+
text.linebreak_behavior.into(),
143158
))
144159
}
145-
}
146-
147-
#[derive(Debug, Clone)]
148-
pub struct TextMeasureSection {
149-
pub text: String,
150-
pub scale: PxScale,
151-
pub font_id: FontId,
152-
}
153-
154-
#[derive(Debug, Clone)]
155-
pub struct TextMeasureInfo {
156-
pub fonts: Vec<ab_glyph::FontArc>,
157-
pub scaled_fonts: Vec<ab_glyph::PxScaleFont<ab_glyph::FontArc>>,
158-
pub sections: Vec<TextMeasureSection>,
159-
pub text_alignment: TextAlignment,
160-
pub linebreak_behaviour: glyph_brush_layout::BuiltInLineBreaker,
161-
pub min_width_content_size: Vec2,
162-
pub max_width_content_size: Vec2,
163-
}
164-
165-
impl TextMeasureInfo {
166160
fn new(
167161
fonts: Vec<ab_glyph::FontArc>,
168-
scaled_fonts: Vec<ab_glyph::PxScaleFont<ab_glyph::FontArc>>,
169162
sections: Vec<TextMeasureSection>,
170163
text_alignment: TextAlignment,
171-
linebreak_behaviour: glyph_brush_layout::BuiltInLineBreaker,
164+
linebreak_behavior: glyph_brush_layout::BuiltInLineBreaker,
172165
) -> Self {
173166
let mut info = Self {
174-
fonts,
175-
scaled_fonts,
176-
sections,
167+
fonts: fonts.into_boxed_slice(),
168+
sections: sections.into_boxed_slice(),
177169
text_alignment,
178-
linebreak_behaviour,
179-
min_width_content_size: Vec2::ZERO,
180-
max_width_content_size: Vec2::ZERO,
170+
linebreak_behavior,
171+
min: Vec2::ZERO,
172+
max: Vec2::ZERO,
181173
};
182174

183-
let section_texts = info.prepare_section_texts();
184-
let min =
185-
info.compute_size_from_section_texts(&section_texts, Vec2::new(0.0, f32::INFINITY));
186-
let max = info.compute_size_from_section_texts(
187-
&section_texts,
188-
Vec2::new(f32::INFINITY, f32::INFINITY),
189-
);
190-
info.min_width_content_size = min;
191-
info.max_width_content_size = max;
175+
let min = info.compute_size(Vec2::new(0.0, f32::INFINITY));
176+
let max = info.compute_size(Vec2::INFINITY);
177+
info.min = min;
178+
info.max = max;
192179
info
193180
}
194181

195-
fn prepare_section_texts(&self) -> Vec<SectionText> {
196-
self.sections
197-
.iter()
198-
.map(|section| SectionText {
199-
font_id: section.font_id,
200-
scale: section.scale,
201-
text: &section.text,
202-
})
203-
.collect::<Vec<_>>()
204-
}
205-
206-
fn compute_size_from_section_texts(&self, sections: &[SectionText], bounds: Vec2) -> Vec2 {
182+
pub fn compute_size(&self, bounds: Vec2) -> Vec2 {
183+
let sections = &self.sections;
207184
let geom = SectionGeometry {
208185
bounds: (bounds.x, bounds.y),
209186
..Default::default()
210187
};
211188
let section_glyphs = glyph_brush_layout::Layout::default()
212189
.h_align(self.text_alignment.into())
213-
.line_breaker(self.linebreak_behaviour)
190+
.line_breaker(self.linebreak_behavior)
214191
.calculate_glyphs(&self.fonts, &geom, sections);
215192

216-
compute_text_bounds(&section_glyphs, |index| &self.scaled_fonts[index]).size()
193+
compute_text_bounds(&section_glyphs, |index| {
194+
let font = &self.fonts[index];
195+
let font_size = self.sections[index].scale;
196+
font.into_scaled(font_size)
197+
})
198+
.size()
217199
}
218-
219-
pub fn compute_size(&self, bounds: Vec2) -> Vec2 {
220-
let sections = self.prepare_section_texts();
221-
self.compute_size_from_section_texts(&sections, bounds)
200+
}
201+
impl ToSectionText for TextMeasureSection {
202+
#[inline(always)]
203+
fn to_section_text(&self) -> SectionText<'_> {
204+
SectionText {
205+
text: &self.text,
206+
scale: PxScale::from(self.scale),
207+
font_id: self.font_id,
208+
}
222209
}
223210
}

crates/bevy_text/src/text.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,12 @@ impl TextSection {
140140
}
141141

142142
/// Describes horizontal alignment preference for positioning & bounds.
143-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
143+
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
144144
#[reflect(Serialize, Deserialize)]
145145
pub enum TextAlignment {
146146
/// Leftmost character is immediately to the right of the render position.<br/>
147147
/// Bounds start from the render position and advance rightwards.
148+
#[default]
148149
Left,
149150
/// Leftmost & rightmost characters are equidistant to the render position.<br/>
150151
/// Bounds start from the render position and advance equally left & right.

crates/bevy_ui/src/widget/text.rs

Lines changed: 9 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,17 @@ impl Measure for TextMeasure {
5353
_available_height: AvailableSpace,
5454
) -> Vec2 {
5555
let x = width.unwrap_or_else(|| match available_width {
56-
AvailableSpace::Definite(x) => x.clamp(
57-
self.info.min_width_content_size.x,
58-
self.info.max_width_content_size.x,
59-
),
60-
AvailableSpace::MinContent => self.info.min_width_content_size.x,
61-
AvailableSpace::MaxContent => self.info.max_width_content_size.x,
56+
AvailableSpace::Definite(x) => x.clamp(self.info.min.x, self.info.max.x),
57+
AvailableSpace::MinContent => self.info.min.x,
58+
AvailableSpace::MaxContent => self.info.max.x,
6259
});
6360

6461
height
6562
.map_or_else(
6663
|| match available_width {
6764
AvailableSpace::Definite(_) => self.info.compute_size(Vec2::new(x, f32::MAX)),
68-
AvailableSpace::MinContent => Vec2::new(x, self.info.min_width_content_size.y),
69-
AvailableSpace::MaxContent => Vec2::new(x, self.info.max_width_content_size.y),
65+
AvailableSpace::MinContent => Vec2::new(x, self.info.min.y),
66+
AvailableSpace::MaxContent => Vec2::new(x, self.info.max.y),
7067
},
7168
|y| Vec2::new(x, y),
7269
)
@@ -77,24 +74,15 @@ impl Measure for TextMeasure {
7774
#[inline]
7875
fn create_text_measure(
7976
fonts: &Assets<Font>,
80-
text_pipeline: &mut TextPipeline,
8177
scale_factor: f64,
8278
text: Ref<Text>,
8379
mut content_size: Mut<ContentSize>,
8480
mut text_flags: Mut<TextFlags>,
8581
) {
86-
match text_pipeline.create_text_measure(
87-
fonts,
88-
&text.sections,
89-
scale_factor,
90-
text.alignment,
91-
text.linebreak_behavior,
92-
) {
82+
match TextMeasureInfo::from_text(&text, fonts, scale_factor) {
9383
Ok(measure) => {
9484
if text.linebreak_behavior == BreakLineOn::NoWrap {
95-
content_size.set(FixedMeasure {
96-
size: measure.max_width_content_size,
97-
});
85+
content_size.set(FixedMeasure { size: measure.max });
9886
} else {
9987
content_size.set(TextMeasure { info: measure });
10088
}
@@ -127,7 +115,6 @@ pub fn measure_text_system(
127115
fonts: Res<Assets<Font>>,
128116
windows: Query<&Window, With<PrimaryWindow>>,
129117
ui_scale: Res<UiScale>,
130-
mut text_pipeline: ResMut<TextPipeline>,
131118
mut text_query: Query<(Ref<Text>, &mut ContentSize, &mut TextFlags), With<Node>>,
132119
) {
133120
let window_scale_factor = windows
@@ -142,29 +129,15 @@ pub fn measure_text_system(
142129
// scale factor unchanged, only create new measure funcs for modified text
143130
for (text, content_size, text_flags) in text_query.iter_mut() {
144131
if text.is_changed() || text_flags.needs_new_measure_func {
145-
create_text_measure(
146-
&fonts,
147-
&mut text_pipeline,
148-
scale_factor,
149-
text,
150-
content_size,
151-
text_flags,
152-
);
132+
create_text_measure(&fonts, scale_factor, text, content_size, text_flags);
153133
}
154134
}
155135
} else {
156136
// scale factor changed, create new measure funcs for all text
157137
*last_scale_factor = scale_factor;
158138

159139
for (text, content_size, text_flags) in text_query.iter_mut() {
160-
create_text_measure(
161-
&fonts,
162-
&mut text_pipeline,
163-
scale_factor,
164-
text,
165-
content_size,
166-
text_flags,
167-
);
140+
create_text_measure(&fonts, scale_factor, text, content_size, text_flags);
168141
}
169142
}
170143
}

0 commit comments

Comments
 (0)