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`
This commit is contained in:
parent
db5f80b2be
commit
afcb1fee90
@ -84,7 +84,7 @@ impl GlyphBrush {
|
|||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
let text_bounds = compute_text_bounds(&glyphs, |index| §ions_data[index].3);
|
let text_bounds = compute_text_bounds(&glyphs, |index| sections_data[index].3);
|
||||||
|
|
||||||
let mut positioned_glyphs = Vec::new();
|
let mut positioned_glyphs = Vec::new();
|
||||||
for sg in glyphs {
|
for sg in glyphs {
|
||||||
@ -203,12 +203,12 @@ impl GlyphPlacementAdjuster {
|
|||||||
|
|
||||||
/// Computes the minimal bounding rectangle for a block of text.
|
/// Computes the minimal bounding rectangle for a block of text.
|
||||||
/// Ignores empty trailing lines.
|
/// Ignores empty trailing lines.
|
||||||
pub(crate) fn compute_text_bounds<'a, T>(
|
pub(crate) fn compute_text_bounds<T>(
|
||||||
section_glyphs: &[SectionGlyph],
|
section_glyphs: &[SectionGlyph],
|
||||||
get_scaled_font: impl Fn(usize) -> &'a PxScaleFont<T>,
|
get_scaled_font: impl Fn(usize) -> PxScaleFont<T>,
|
||||||
) -> bevy_math::Rect
|
) -> bevy_math::Rect
|
||||||
where
|
where
|
||||||
T: ab_glyph::Font + 'a,
|
T: ab_glyph::Font,
|
||||||
{
|
{
|
||||||
let mut text_bounds = Rect {
|
let mut text_bounds = Rect {
|
||||||
min: Vec2::splat(std::f32::MAX),
|
min: Vec2::splat(std::f32::MAX),
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use ab_glyph::PxScale;
|
use ab_glyph::{Font as AbglyphFont, PxScale};
|
||||||
use bevy_asset::{Assets, Handle, HandleId};
|
use bevy_asset::{Assets, Handle, HandleId};
|
||||||
use bevy_ecs::component::Component;
|
use bevy_ecs::component::Component;
|
||||||
use bevy_ecs::system::Resource;
|
use bevy_ecs::system::Resource;
|
||||||
@ -7,12 +7,12 @@ use bevy_render::texture::Image;
|
|||||||
use bevy_sprite::TextureAtlas;
|
use bevy_sprite::TextureAtlas;
|
||||||
use bevy_utils::HashMap;
|
use bevy_utils::HashMap;
|
||||||
|
|
||||||
use glyph_brush_layout::{FontId, GlyphPositioner, SectionGeometry, SectionText};
|
use glyph_brush_layout::{FontId, GlyphPositioner, SectionGeometry, SectionText, ToSectionText};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
compute_text_bounds, error::TextError, glyph_brush::GlyphBrush, scale_value, BreakLineOn, Font,
|
compute_text_bounds, error::TextError, glyph_brush::GlyphBrush, scale_value, BreakLineOn, Font,
|
||||||
FontAtlasSet, FontAtlasWarning, PositionedGlyph, TextAlignment, TextSection, TextSettings,
|
FontAtlasSet, FontAtlasWarning, PositionedGlyph, Text, TextAlignment, TextSection,
|
||||||
YAxisOrientation,
|
TextSettings, YAxisOrientation,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default, Resource)]
|
#[derive(Default, Resource)]
|
||||||
@ -85,7 +85,7 @@ impl TextPipeline {
|
|||||||
return Ok(TextLayoutInfo::default());
|
return Ok(TextLayoutInfo::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
let size = compute_text_bounds(§ion_glyphs, |index| &scaled_fonts[index]).size();
|
let size = compute_text_bounds(§ion_glyphs, |index| scaled_fonts[index]).size();
|
||||||
|
|
||||||
let glyphs = self.brush.process_glyphs(
|
let glyphs = self.brush.process_glyphs(
|
||||||
section_glyphs,
|
section_glyphs,
|
||||||
@ -101,123 +101,110 @@ impl TextPipeline {
|
|||||||
|
|
||||||
Ok(TextLayoutInfo { glyphs, size })
|
Ok(TextLayoutInfo { glyphs, size })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_text_measure(
|
|
||||||
&mut self,
|
|
||||||
fonts: &Assets<Font>,
|
|
||||||
sections: &[TextSection],
|
|
||||||
scale_factor: f64,
|
|
||||||
text_alignment: TextAlignment,
|
|
||||||
linebreak_behaviour: BreakLineOn,
|
|
||||||
) -> Result<TextMeasureInfo, TextError> {
|
|
||||||
let mut auto_fonts = Vec::with_capacity(sections.len());
|
|
||||||
let mut scaled_fonts = Vec::with_capacity(sections.len());
|
|
||||||
let sections = sections
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, section)| {
|
|
||||||
let font = fonts
|
|
||||||
.get(§ion.style.font)
|
|
||||||
.ok_or(TextError::NoSuchFont)?;
|
|
||||||
let font_size = scale_value(section.style.font_size, scale_factor);
|
|
||||||
auto_fonts.push(font.font.clone());
|
|
||||||
let px_scale_font = ab_glyph::Font::into_scaled(font.font.clone(), font_size);
|
|
||||||
scaled_fonts.push(px_scale_font);
|
|
||||||
|
|
||||||
let section = TextMeasureSection {
|
|
||||||
font_id: FontId(i),
|
|
||||||
scale: PxScale::from(font_size),
|
|
||||||
text: section.value.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(section)
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
|
|
||||||
Ok(TextMeasureInfo::new(
|
|
||||||
auto_fonts,
|
|
||||||
scaled_fonts,
|
|
||||||
sections,
|
|
||||||
text_alignment,
|
|
||||||
linebreak_behaviour.into(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TextMeasureSection {
|
pub struct TextMeasureSection {
|
||||||
pub text: String,
|
pub text: Box<str>,
|
||||||
pub scale: PxScale,
|
pub scale: f32,
|
||||||
pub font_id: FontId,
|
pub font_id: FontId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct TextMeasureInfo {
|
pub struct TextMeasureInfo {
|
||||||
pub fonts: Vec<ab_glyph::FontArc>,
|
pub fonts: Box<[ab_glyph::FontArc]>,
|
||||||
pub scaled_fonts: Vec<ab_glyph::PxScaleFont<ab_glyph::FontArc>>,
|
pub sections: Box<[TextMeasureSection]>,
|
||||||
pub sections: Vec<TextMeasureSection>,
|
|
||||||
pub text_alignment: TextAlignment,
|
pub text_alignment: TextAlignment,
|
||||||
pub linebreak_behaviour: glyph_brush_layout::BuiltInLineBreaker,
|
pub linebreak_behavior: glyph_brush_layout::BuiltInLineBreaker,
|
||||||
pub min_width_content_size: Vec2,
|
pub min: Vec2,
|
||||||
pub max_width_content_size: Vec2,
|
pub max: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextMeasureInfo {
|
impl TextMeasureInfo {
|
||||||
|
pub fn from_text(
|
||||||
|
text: &Text,
|
||||||
|
fonts: &Assets<Font>,
|
||||||
|
scale_factor: f64,
|
||||||
|
) -> Result<TextMeasureInfo, TextError> {
|
||||||
|
let sections = &text.sections;
|
||||||
|
for section in sections {
|
||||||
|
if !fonts.contains(§ion.style.font) {
|
||||||
|
return Err(TextError::NoSuchFont);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (auto_fonts, sections) = sections
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, section)| {
|
||||||
|
// SAFETY: we exited early earlier in this function if
|
||||||
|
// one of the fonts was missing.
|
||||||
|
let font = unsafe { fonts.get(§ion.style.font).unwrap_unchecked() };
|
||||||
|
(
|
||||||
|
font.font.clone(),
|
||||||
|
TextMeasureSection {
|
||||||
|
font_id: FontId(i),
|
||||||
|
scale: scale_value(section.style.font_size, scale_factor),
|
||||||
|
text: section.value.clone().into_boxed_str(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unzip();
|
||||||
|
|
||||||
|
Ok(Self::new(
|
||||||
|
auto_fonts,
|
||||||
|
sections,
|
||||||
|
text.alignment,
|
||||||
|
text.linebreak_behavior.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
fn new(
|
fn new(
|
||||||
fonts: Vec<ab_glyph::FontArc>,
|
fonts: Vec<ab_glyph::FontArc>,
|
||||||
scaled_fonts: Vec<ab_glyph::PxScaleFont<ab_glyph::FontArc>>,
|
|
||||||
sections: Vec<TextMeasureSection>,
|
sections: Vec<TextMeasureSection>,
|
||||||
text_alignment: TextAlignment,
|
text_alignment: TextAlignment,
|
||||||
linebreak_behaviour: glyph_brush_layout::BuiltInLineBreaker,
|
linebreak_behavior: glyph_brush_layout::BuiltInLineBreaker,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut info = Self {
|
let mut info = Self {
|
||||||
fonts,
|
fonts: fonts.into_boxed_slice(),
|
||||||
scaled_fonts,
|
sections: sections.into_boxed_slice(),
|
||||||
sections,
|
|
||||||
text_alignment,
|
text_alignment,
|
||||||
linebreak_behaviour,
|
linebreak_behavior,
|
||||||
min_width_content_size: Vec2::ZERO,
|
min: Vec2::ZERO,
|
||||||
max_width_content_size: Vec2::ZERO,
|
max: Vec2::ZERO,
|
||||||
};
|
};
|
||||||
|
|
||||||
let section_texts = info.prepare_section_texts();
|
let min = info.compute_size(Vec2::new(0.0, f32::INFINITY));
|
||||||
let min =
|
let max = info.compute_size(Vec2::INFINITY);
|
||||||
info.compute_size_from_section_texts(§ion_texts, Vec2::new(0.0, f32::INFINITY));
|
info.min = min;
|
||||||
let max = info.compute_size_from_section_texts(
|
info.max = max;
|
||||||
§ion_texts,
|
|
||||||
Vec2::new(f32::INFINITY, f32::INFINITY),
|
|
||||||
);
|
|
||||||
info.min_width_content_size = min;
|
|
||||||
info.max_width_content_size = max;
|
|
||||||
info
|
info
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_section_texts(&self) -> Vec<SectionText> {
|
pub fn compute_size(&self, bounds: Vec2) -> Vec2 {
|
||||||
self.sections
|
let sections = &self.sections;
|
||||||
.iter()
|
|
||||||
.map(|section| SectionText {
|
|
||||||
font_id: section.font_id,
|
|
||||||
scale: section.scale,
|
|
||||||
text: §ion.text,
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_size_from_section_texts(&self, sections: &[SectionText], bounds: Vec2) -> Vec2 {
|
|
||||||
let geom = SectionGeometry {
|
let geom = SectionGeometry {
|
||||||
bounds: (bounds.x, bounds.y),
|
bounds: (bounds.x, bounds.y),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let section_glyphs = glyph_brush_layout::Layout::default()
|
let section_glyphs = glyph_brush_layout::Layout::default()
|
||||||
.h_align(self.text_alignment.into())
|
.h_align(self.text_alignment.into())
|
||||||
.line_breaker(self.linebreak_behaviour)
|
.line_breaker(self.linebreak_behavior)
|
||||||
.calculate_glyphs(&self.fonts, &geom, sections);
|
.calculate_glyphs(&self.fonts, &geom, sections);
|
||||||
|
|
||||||
compute_text_bounds(§ion_glyphs, |index| &self.scaled_fonts[index]).size()
|
compute_text_bounds(§ion_glyphs, |index| {
|
||||||
}
|
let font = &self.fonts[index];
|
||||||
|
let font_size = self.sections[index].scale;
|
||||||
pub fn compute_size(&self, bounds: Vec2) -> Vec2 {
|
font.into_scaled(font_size)
|
||||||
let sections = self.prepare_section_texts();
|
})
|
||||||
self.compute_size_from_section_texts(§ions, bounds)
|
.size()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ToSectionText for TextMeasureSection {
|
||||||
|
#[inline(always)]
|
||||||
|
fn to_section_text(&self) -> SectionText<'_> {
|
||||||
|
SectionText {
|
||||||
|
text: &self.text,
|
||||||
|
scale: PxScale::from(self.scale),
|
||||||
|
font_id: self.font_id,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -140,11 +140,12 @@ impl TextSection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Describes horizontal alignment preference for positioning & bounds.
|
/// Describes horizontal alignment preference for positioning & bounds.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
|
||||||
#[reflect(Serialize, Deserialize)]
|
#[reflect(Serialize, Deserialize)]
|
||||||
pub enum TextAlignment {
|
pub enum TextAlignment {
|
||||||
/// Leftmost character is immediately to the right of the render position.<br/>
|
/// Leftmost character is immediately to the right of the render position.<br/>
|
||||||
/// Bounds start from the render position and advance rightwards.
|
/// Bounds start from the render position and advance rightwards.
|
||||||
|
#[default]
|
||||||
Left,
|
Left,
|
||||||
/// Leftmost & rightmost characters are equidistant to the render position.<br/>
|
/// Leftmost & rightmost characters are equidistant to the render position.<br/>
|
||||||
/// Bounds start from the render position and advance equally left & right.
|
/// Bounds start from the render position and advance equally left & right.
|
||||||
|
|||||||
@ -53,20 +53,17 @@ impl Measure for TextMeasure {
|
|||||||
_available_height: AvailableSpace,
|
_available_height: AvailableSpace,
|
||||||
) -> Vec2 {
|
) -> Vec2 {
|
||||||
let x = width.unwrap_or_else(|| match available_width {
|
let x = width.unwrap_or_else(|| match available_width {
|
||||||
AvailableSpace::Definite(x) => x.clamp(
|
AvailableSpace::Definite(x) => x.clamp(self.info.min.x, self.info.max.x),
|
||||||
self.info.min_width_content_size.x,
|
AvailableSpace::MinContent => self.info.min.x,
|
||||||
self.info.max_width_content_size.x,
|
AvailableSpace::MaxContent => self.info.max.x,
|
||||||
),
|
|
||||||
AvailableSpace::MinContent => self.info.min_width_content_size.x,
|
|
||||||
AvailableSpace::MaxContent => self.info.max_width_content_size.x,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
height
|
height
|
||||||
.map_or_else(
|
.map_or_else(
|
||||||
|| match available_width {
|
|| match available_width {
|
||||||
AvailableSpace::Definite(_) => self.info.compute_size(Vec2::new(x, f32::MAX)),
|
AvailableSpace::Definite(_) => self.info.compute_size(Vec2::new(x, f32::MAX)),
|
||||||
AvailableSpace::MinContent => Vec2::new(x, self.info.min_width_content_size.y),
|
AvailableSpace::MinContent => Vec2::new(x, self.info.min.y),
|
||||||
AvailableSpace::MaxContent => Vec2::new(x, self.info.max_width_content_size.y),
|
AvailableSpace::MaxContent => Vec2::new(x, self.info.max.y),
|
||||||
},
|
},
|
||||||
|y| Vec2::new(x, y),
|
|y| Vec2::new(x, y),
|
||||||
)
|
)
|
||||||
@ -77,24 +74,15 @@ impl Measure for TextMeasure {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn create_text_measure(
|
fn create_text_measure(
|
||||||
fonts: &Assets<Font>,
|
fonts: &Assets<Font>,
|
||||||
text_pipeline: &mut TextPipeline,
|
|
||||||
scale_factor: f64,
|
scale_factor: f64,
|
||||||
text: Ref<Text>,
|
text: Ref<Text>,
|
||||||
mut content_size: Mut<ContentSize>,
|
mut content_size: Mut<ContentSize>,
|
||||||
mut text_flags: Mut<TextFlags>,
|
mut text_flags: Mut<TextFlags>,
|
||||||
) {
|
) {
|
||||||
match text_pipeline.create_text_measure(
|
match TextMeasureInfo::from_text(&text, fonts, scale_factor) {
|
||||||
fonts,
|
|
||||||
&text.sections,
|
|
||||||
scale_factor,
|
|
||||||
text.alignment,
|
|
||||||
text.linebreak_behavior,
|
|
||||||
) {
|
|
||||||
Ok(measure) => {
|
Ok(measure) => {
|
||||||
if text.linebreak_behavior == BreakLineOn::NoWrap {
|
if text.linebreak_behavior == BreakLineOn::NoWrap {
|
||||||
content_size.set(FixedMeasure {
|
content_size.set(FixedMeasure { size: measure.max });
|
||||||
size: measure.max_width_content_size,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
content_size.set(TextMeasure { info: measure });
|
content_size.set(TextMeasure { info: measure });
|
||||||
}
|
}
|
||||||
@ -127,7 +115,6 @@ pub fn measure_text_system(
|
|||||||
fonts: Res<Assets<Font>>,
|
fonts: Res<Assets<Font>>,
|
||||||
windows: Query<&Window, With<PrimaryWindow>>,
|
windows: Query<&Window, With<PrimaryWindow>>,
|
||||||
ui_scale: Res<UiScale>,
|
ui_scale: Res<UiScale>,
|
||||||
mut text_pipeline: ResMut<TextPipeline>,
|
|
||||||
mut text_query: Query<(Ref<Text>, &mut ContentSize, &mut TextFlags), With<Node>>,
|
mut text_query: Query<(Ref<Text>, &mut ContentSize, &mut TextFlags), With<Node>>,
|
||||||
) {
|
) {
|
||||||
let window_scale_factor = windows
|
let window_scale_factor = windows
|
||||||
@ -142,14 +129,7 @@ pub fn measure_text_system(
|
|||||||
// scale factor unchanged, only create new measure funcs for modified text
|
// scale factor unchanged, only create new measure funcs for modified text
|
||||||
for (text, content_size, text_flags) in text_query.iter_mut() {
|
for (text, content_size, text_flags) in text_query.iter_mut() {
|
||||||
if text.is_changed() || text_flags.needs_new_measure_func {
|
if text.is_changed() || text_flags.needs_new_measure_func {
|
||||||
create_text_measure(
|
create_text_measure(&fonts, scale_factor, text, content_size, text_flags);
|
||||||
&fonts,
|
|
||||||
&mut text_pipeline,
|
|
||||||
scale_factor,
|
|
||||||
text,
|
|
||||||
content_size,
|
|
||||||
text_flags,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -157,14 +137,7 @@ pub fn measure_text_system(
|
|||||||
*last_scale_factor = scale_factor;
|
*last_scale_factor = scale_factor;
|
||||||
|
|
||||||
for (text, content_size, text_flags) in text_query.iter_mut() {
|
for (text, content_size, text_flags) in text_query.iter_mut() {
|
||||||
create_text_measure(
|
create_text_measure(&fonts, scale_factor, text, content_size, text_flags);
|
||||||
&fonts,
|
|
||||||
&mut text_pipeline,
|
|
||||||
scale_factor,
|
|
||||||
text,
|
|
||||||
content_size,
|
|
||||||
text_flags,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user