Skip to content

Commit 6ee2bc5

Browse files
committed
Cleanup some bevy_text pipeline.rs
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>` The `ResMut<TextPipeline>` argument to `measure_text_system` doesn't exist anymore. If you were calling this system manually, you should remove the argument.
1 parent 7c3131a commit 6ee2bc5

File tree

3 files changed

+84
-124
lines changed

3 files changed

+84
-124
lines changed

crates/bevy_text/src/pipeline.rs

Lines changed: 73 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use ab_glyph::{PxScale, ScaleFont};
1+
use ab_glyph::{Font as AbFont, PxScale, ScaleFont};
22
use bevy_asset::{Assets, Handle, HandleId};
33
use bevy_ecs::component::Component;
44
use bevy_ecs::system::Resource;
@@ -7,11 +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
error::TextError, glyph_brush::GlyphBrush, scale_value, BreakLineOn, Font, FontAtlasSet,
14-
FontAtlasWarning, PositionedGlyph, TextAlignment, TextSection, TextSettings, YAxisOrientation,
14+
FontAtlasWarning, PositionedGlyph, Text, TextAlignment, TextSection, TextSettings,
15+
YAxisOrientation,
1516
};
1617

1718
#[derive(Default, Resource)]
@@ -117,116 +118,93 @@ impl TextPipeline {
117118

118119
Ok(TextLayoutInfo { glyphs, size })
119120
}
121+
}
120122

121-
pub fn create_text_measure(
122-
&mut self,
123+
#[derive(Debug, Clone)]
124+
pub struct TextMeasureSection {
125+
pub text: Box<str>,
126+
pub scale: f32,
127+
pub font_id: FontId,
128+
}
129+
130+
#[derive(Debug, Clone, Default)]
131+
pub struct TextMeasureInfo {
132+
pub fonts: Box<[ab_glyph::FontArc]>,
133+
pub sections: Box<[TextMeasureSection]>,
134+
pub text_alignment: TextAlignment,
135+
pub linebreak_behavior: glyph_brush_layout::BuiltInLineBreaker,
136+
pub min: Vec2,
137+
pub max: Vec2,
138+
}
139+
140+
impl TextMeasureInfo {
141+
pub fn from_text(
142+
text: &Text,
123143
fonts: &Assets<Font>,
124-
sections: &[TextSection],
125144
scale_factor: f64,
126-
text_alignment: TextAlignment,
127-
linebreak_behaviour: BreakLineOn,
128145
) -> Result<TextMeasureInfo, TextError> {
129-
let mut auto_fonts = Vec::with_capacity(sections.len());
130-
let mut scaled_fonts = Vec::with_capacity(sections.len());
131-
let sections = sections
146+
let sections = &text.sections;
147+
for section in sections {
148+
if !fonts.contains(&section.style.font) {
149+
return Err(TextError::NoSuchFont);
150+
}
151+
}
152+
let (auto_fonts, sections) = sections
132153
.iter()
133154
.enumerate()
134155
.map(|(i, section)| {
135-
let font = fonts
136-
.get(&section.style.font)
137-
.ok_or(TextError::NoSuchFont)?;
138-
let font_size = scale_value(section.style.font_size, scale_factor);
139-
auto_fonts.push(font.font.clone());
140-
let px_scale_font = ab_glyph::Font::into_scaled(font.font.clone(), font_size);
141-
scaled_fonts.push(px_scale_font);
142-
143-
let section = TextMeasureSection {
144-
font_id: FontId(i),
145-
scale: PxScale::from(font_size),
146-
text: section.value.clone(),
147-
};
148-
149-
Ok(section)
156+
// SAFETY: we exited early earlier in this function if
157+
// one of the fonts was missing.
158+
let font = unsafe { fonts.get(&section.style.font).unwrap_unchecked() };
159+
(
160+
font.font.clone(),
161+
TextMeasureSection {
162+
font_id: FontId(i),
163+
scale: scale_value(section.style.font_size, scale_factor),
164+
text: section.value.clone().into_boxed_str(),
165+
},
166+
)
150167
})
151-
.collect::<Result<Vec<_>, _>>()?;
168+
.unzip();
152169

153-
Ok(TextMeasureInfo::new(
170+
Ok(Self::new(
154171
auto_fonts,
155-
scaled_fonts,
156172
sections,
157-
text_alignment,
158-
linebreak_behaviour.into(),
173+
text.alignment,
174+
text.linebreak_behavior.into(),
159175
))
160176
}
161-
}
162-
163-
#[derive(Debug, Clone)]
164-
pub struct TextMeasureSection {
165-
pub text: String,
166-
pub scale: PxScale,
167-
pub font_id: FontId,
168-
}
169-
170-
#[derive(Debug, Clone)]
171-
pub struct TextMeasureInfo {
172-
pub fonts: Vec<ab_glyph::FontArc>,
173-
pub scaled_fonts: Vec<ab_glyph::PxScaleFont<ab_glyph::FontArc>>,
174-
pub sections: Vec<TextMeasureSection>,
175-
pub text_alignment: TextAlignment,
176-
pub linebreak_behaviour: glyph_brush_layout::BuiltInLineBreaker,
177-
pub min_width_content_size: Vec2,
178-
pub max_width_content_size: Vec2,
179-
}
180-
181-
impl TextMeasureInfo {
182177
fn new(
183178
fonts: Vec<ab_glyph::FontArc>,
184-
scaled_fonts: Vec<ab_glyph::PxScaleFont<ab_glyph::FontArc>>,
185179
sections: Vec<TextMeasureSection>,
186180
text_alignment: TextAlignment,
187-
linebreak_behaviour: glyph_brush_layout::BuiltInLineBreaker,
181+
linebreak_behavior: glyph_brush_layout::BuiltInLineBreaker,
188182
) -> Self {
189183
let mut info = Self {
190-
fonts,
191-
scaled_fonts,
192-
sections,
184+
fonts: fonts.into_boxed_slice(),
185+
sections: sections.into_boxed_slice(),
193186
text_alignment,
194-
linebreak_behaviour,
195-
min_width_content_size: Vec2::ZERO,
196-
max_width_content_size: Vec2::ZERO,
187+
linebreak_behavior,
188+
min: Vec2::ZERO,
189+
max: Vec2::ZERO,
197190
};
198191

199-
let section_texts = info.prepare_section_texts();
200-
let min =
201-
info.compute_size_from_section_texts(&section_texts, Vec2::new(0.0, f32::INFINITY));
202-
let max = info.compute_size_from_section_texts(
203-
&section_texts,
204-
Vec2::new(f32::INFINITY, f32::INFINITY),
205-
);
206-
info.min_width_content_size = min;
207-
info.max_width_content_size = max;
192+
let min = info.compute_size(Vec2::new(0.0, f32::INFINITY));
193+
let max = info.compute_size(Vec2::INFINITY);
194+
info.min = min;
195+
info.max = max;
208196
info
209197
}
210198

211-
fn prepare_section_texts(&self) -> Vec<SectionText> {
212-
self.sections
213-
.iter()
214-
.map(|section| SectionText {
215-
font_id: section.font_id,
216-
scale: section.scale,
217-
text: &section.text,
218-
})
219-
.collect::<Vec<_>>()
220-
}
221-
222-
fn compute_size_from_section_texts(&self, sections: &[SectionText], bounds: Vec2) -> Vec2 {
199+
pub fn compute_size(&self, bounds: Vec2) -> Vec2 {
200+
let sections = &self.sections;
223201
let geom = SectionGeometry {
224202
bounds: (bounds.x, bounds.y),
225203
..Default::default()
226204
};
227205
let section_glyphs = glyph_brush_layout::Layout::default()
228206
.h_align(self.text_alignment.into())
229-
.line_breaker(self.linebreak_behaviour)
207+
.line_breaker(self.linebreak_behavior)
230208
.calculate_glyphs(&self.fonts, &geom, sections);
231209

232210
let mut min_x: f32 = std::f32::MAX;
@@ -235,7 +213,10 @@ impl TextMeasureInfo {
235213
let mut max_y: f32 = std::f32::MIN;
236214

237215
for sg in section_glyphs {
238-
let scaled_font = &self.scaled_fonts[sg.section_index];
216+
let font = &self.fonts[sg.section_index];
217+
let font_size = self.sections[sg.section_index].scale;
218+
let scaled_font = font.into_scaled(font_size);
219+
239220
let glyph = &sg.glyph;
240221
// The fonts use a coordinate system increasing upwards so ascent is a positive value
241222
// and descent is negative, but Bevy UI uses a downwards increasing coordinate system,
@@ -248,9 +229,14 @@ impl TextMeasureInfo {
248229

249230
Vec2::new(max_x - min_x, max_y - min_y)
250231
}
251-
252-
pub fn compute_size(&self, bounds: Vec2) -> Vec2 {
253-
let sections = self.prepare_section_texts();
254-
self.compute_size_from_section_texts(&sections, bounds)
232+
}
233+
impl ToSectionText for TextMeasureSection {
234+
#[inline(always)]
235+
fn to_section_text(&self) -> SectionText<'_> {
236+
SectionText {
237+
text: &self.text,
238+
scale: PxScale::from(self.scale),
239+
font_id: self.font_id,
240+
}
255241
}
256242
}

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
}
@@ -120,7 +108,6 @@ pub fn measure_text_system(
120108
fonts: Res<Assets<Font>>,
121109
windows: Query<&Window, With<PrimaryWindow>>,
122110
ui_scale: Res<UiScale>,
123-
mut text_pipeline: ResMut<TextPipeline>,
124111
mut text_query: Query<(Ref<Text>, &mut ContentSize, &mut TextFlags), With<Node>>,
125112
) {
126113
let window_scale_factor = windows
@@ -135,29 +122,15 @@ pub fn measure_text_system(
135122
// scale factor unchanged, only create new measure funcs for modified text
136123
for (text, content_size, text_flags) in text_query.iter_mut() {
137124
if text.is_changed() || text_flags.needs_new_measure_func {
138-
create_text_measure(
139-
&fonts,
140-
&mut text_pipeline,
141-
scale_factor,
142-
text,
143-
content_size,
144-
text_flags,
145-
);
125+
create_text_measure(&fonts, scale_factor, text, content_size, text_flags);
146126
}
147127
}
148128
} else {
149129
// scale factor changed, create new measure funcs for all text
150130
*last_scale_factor = scale_factor;
151131

152132
for (text, content_size, text_flags) in text_query.iter_mut() {
153-
create_text_measure(
154-
&fonts,
155-
&mut text_pipeline,
156-
scale_factor,
157-
text,
158-
content_size,
159-
text_flags,
160-
);
133+
create_text_measure(&fonts, scale_factor, text, content_size, text_flags);
161134
}
162135
}
163136
}

0 commit comments

Comments
 (0)