Skip to content

Commit 76ae785

Browse files
committed
refactor TextPipeline::update_buffer to accept an interator
1 parent 54006b1 commit 76ae785

File tree

1 file changed

+90
-72
lines changed

1 file changed

+90
-72
lines changed

crates/bevy_text/src/pipeline.rs

Lines changed: 90 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use cosmic_text::{Attrs, Buffer, Family, Metrics, Shaping, Wrap};
1717

1818
use crate::{
1919
error::TextError, BreakLineOn, CosmicBuffer, Font, FontAtlasSets, FontSmoothing, JustifyText,
20-
PositionedGlyph, TextBounds, TextSection, YAxisOrientation,
20+
PositionedGlyph, TextBounds, TextSection, TextStyle, YAxisOrientation,
2121
};
2222

2323
/// A wrapper resource around a [`cosmic_text::FontSystem`]
@@ -51,28 +51,37 @@ impl Default for SwashCache {
5151
}
5252
}
5353

54+
/// Information about a font collected as part of preparing for text layout.
55+
#[derive(Clone)]
56+
struct FontFaceInfo {
57+
stretch: cosmic_text::fontdb::Stretch,
58+
style: cosmic_text::fontdb::Style,
59+
weight: cosmic_text::fontdb::Weight,
60+
family_name: Arc<str>,
61+
}
62+
5463
/// The `TextPipeline` is used to layout and render [`Text`](crate::Text).
5564
///
5665
/// See the [crate-level documentation](crate) for more information.
5766
#[derive(Default, Resource)]
5867
pub struct TextPipeline {
5968
/// Identifies a font [`ID`](cosmic_text::fontdb::ID) by its [`Font`] [`Asset`](bevy_asset::Asset).
60-
map_handle_to_font_id: HashMap<AssetId<Font>, (cosmic_text::fontdb::ID, String)>,
69+
map_handle_to_font_id: HashMap<AssetId<Font>, (cosmic_text::fontdb::ID, Arc<str>)>,
6170
/// Buffered vec for collecting spans.
6271
///
6372
/// See [this dark magic](https://users.rust-lang.org/t/how-to-cache-a-vectors-capacity/94478/10).
64-
spans_buffer: Vec<(&'static str, Attrs<'static>)>,
73+
spans_buffer: Vec<(usize, &'static str, &'static TextStyle, FontFaceInfo)>,
6574
}
6675

6776
impl TextPipeline {
6877
/// Utilizes [`cosmic_text::Buffer`] to shape and layout text
6978
///
7079
/// Negative or 0.0 font sizes will not be laid out.
7180
#[allow(clippy::too_many_arguments)]
72-
pub fn update_buffer(
81+
pub fn update_buffer<'a>(
7382
&mut self,
7483
fonts: &Assets<Font>,
75-
sections: &[TextSection],
84+
text_spans: impl Iterator<Item = (&'a str, &'a TextStyle)>,
7685
linebreak_behavior: BreakLineOn,
7786
bounds: TextBounds,
7887
scale_factor: f64,
@@ -82,16 +91,45 @@ impl TextPipeline {
8291
) -> Result<(), TextError> {
8392
let font_system = &mut font_system.0;
8493

85-
// return early if the fonts are not loaded yet
86-
let mut font_size = 0.;
87-
for section in sections {
88-
if section.style.font_size > font_size {
89-
font_size = section.style.font_size;
94+
// Collect span information into a vec. This is necessary because font loading requires mut access
95+
// to FontSystem, which the cosmic-text Buffer also needs.
96+
let mut font_size: f32 = 0.;
97+
let mut spans: Vec<(usize, &str, &TextStyle, FontFaceInfo)> =
98+
core::mem::take(&mut self.spans_buffer)
99+
.into_iter()
100+
.map(|_| -> (usize, &str, &TextStyle, FontFaceInfo) { unreachable!() })
101+
.collect();
102+
103+
for (span_index, (span, style)) in text_spans.enumerate() {
104+
// Return early if a font is not loaded yet.
105+
if !fonts.contains(style.font.id()) {
106+
spans.clear();
107+
self.spans_buffer = spans
108+
.into_iter()
109+
.map(
110+
|_| -> (usize, &'static str, &'static TextStyle, FontFaceInfo) {
111+
unreachable!()
112+
},
113+
)
114+
.collect();
115+
116+
return Err(TextError::NoSuchFont);
117+
}
118+
119+
// Get max font size for use in cosmic Metrics.
120+
font_size = font_size.max(style.font_size);
121+
122+
// Load Bevy fonts into cosmic-text's font system.
123+
let face_info =
124+
load_font_to_fontdb(style, font_system, &mut self.map_handle_to_font_id, fonts);
125+
126+
// Save spans that aren't zero-sized.
127+
if scale_factor <= 0.0 || style.font_size <= 0.0 {
128+
continue;
90129
}
91-
fonts
92-
.get(section.style.font.id())
93-
.ok_or(TextError::NoSuchFont)?;
130+
spans.push((span_index, span, style, face_info));
94131
}
132+
95133
let line_height = font_size * 1.2;
96134
let mut metrics = Metrics::new(font_size, line_height).scale(scale_factor as f32);
97135
// Metrics of 0.0 cause `Buffer::set_metrics` to panic. We hack around this by 'falling
@@ -100,45 +138,20 @@ impl TextPipeline {
100138
metrics.font_size = metrics.font_size.max(0.000001);
101139
metrics.line_height = metrics.line_height.max(0.000001);
102140

103-
// Load Bevy fonts into cosmic-text's font system.
104-
// This is done as as separate pre-pass to avoid borrow checker issues
105-
for section in sections.iter() {
106-
load_font_to_fontdb(section, font_system, &mut self.map_handle_to_font_id, fonts);
107-
}
108-
109141
// Map text sections to cosmic-text spans, and ignore sections with negative or zero fontsizes,
110142
// since they cannot be rendered by cosmic-text.
111143
//
112144
// The section index is stored in the metadata of the spans, and could be used
113145
// to look up the section the span came from and is not used internally
114146
// in cosmic-text.
115-
let mut spans: Vec<(&str, Attrs)> = core::mem::take(&mut self.spans_buffer)
116-
.into_iter()
117-
.map(|_| -> (&str, Attrs) { unreachable!() })
118-
.collect();
119-
// `metrics.font_size` hack continued: ignore all spans when scale_factor is zero.
120-
if scale_factor > 0.0 {
121-
spans.extend(
122-
sections
123-
.iter()
124-
.enumerate()
125-
.filter(|(_section_index, section)| section.style.font_size > 0.0)
126-
.map(|(section_index, section)| {
127-
(
128-
&section.value[..],
129-
get_attrs(
130-
section,
131-
section_index,
132-
font_system,
133-
&self.map_handle_to_font_id,
134-
scale_factor,
135-
),
136-
)
137-
}),
138-
);
139-
}
140-
let spans_iter = spans.iter().copied();
147+
let spans_iter = spans.iter().map(|(span_index, span, style, font_info)| {
148+
(
149+
*span,
150+
get_attrs(*span_index, *style, font_info, scale_factor),
151+
)
152+
});
141153

154+
// Update the buffer.
142155
buffer.set_metrics(font_system, metrics);
143156
buffer.set_size(font_system, bounds.width, bounds.height);
144157

@@ -165,7 +178,7 @@ impl TextPipeline {
165178
spans.clear();
166179
self.spans_buffer = spans
167180
.into_iter()
168-
.map(|_| -> (&'static str, Attrs<'static>) { unreachable!() })
181+
.map(|_| -> (usize, &'static str, &'static TextStyle, FontFaceInfo) { unreachable!() })
169182
.collect();
170183

171184
Ok(())
@@ -203,7 +216,9 @@ impl TextPipeline {
203216

204217
self.update_buffer(
205218
fonts,
206-
sections,
219+
sections
220+
.iter()
221+
.map(|section| (section.value.as_str(), &section.style)),
207222
linebreak_behavior,
208223
bounds,
209224
scale_factor,
@@ -310,7 +325,9 @@ impl TextPipeline {
310325

311326
self.update_buffer(
312327
fonts,
313-
sections,
328+
sections
329+
.iter()
330+
.map(|section| (section.value.as_str(), &section.style)),
314331
linebreak_behavior,
315332
MIN_WIDTH_CONTENT_BOUNDS,
316333
scale_factor,
@@ -384,13 +401,13 @@ impl TextMeasureInfo {
384401
}
385402

386403
fn load_font_to_fontdb(
387-
section: &TextSection,
404+
style: &TextStyle,
388405
font_system: &mut cosmic_text::FontSystem,
389-
map_handle_to_font_id: &mut HashMap<AssetId<Font>, (cosmic_text::fontdb::ID, String)>,
406+
map_handle_to_font_id: &mut HashMap<AssetId<Font>, (cosmic_text::fontdb::ID, Arc<str>)>,
390407
fonts: &Assets<Font>,
391-
) {
392-
let font_handle = section.style.font.clone();
393-
map_handle_to_font_id
408+
) -> FontFaceInfo {
409+
let font_handle = style.font.clone();
410+
let (face_id, family_name) = map_handle_to_font_id
394411
.entry(font_handle.id())
395412
.or_insert_with(|| {
396413
let font = fonts.get(font_handle.id()).expect(
@@ -404,34 +421,35 @@ fn load_font_to_fontdb(
404421
// TODO: it is assumed this is the right font face
405422
let face_id = *ids.last().unwrap();
406423
let face = font_system.db().face(face_id).unwrap();
407-
let family_name = face.families[0].0.to_owned();
424+
let family_name = Arc::from(face.families[0].0.as_str());
408425

409426
(face_id, family_name)
410427
});
428+
let face = font_system.db().face(*face_id).unwrap();
429+
430+
FontFaceInfo {
431+
stretch: face.stretch,
432+
style: face.style,
433+
weight: face.weight,
434+
family_name: family_name.clone(),
435+
}
411436
}
412437

413-
/// Translates [`TextSection`] to [`Attrs`],
414-
/// loading fonts into the [`Database`](cosmic_text::fontdb::Database) if required.
438+
/// Translates [`TextStyle`] to [`Attrs`].
415439
fn get_attrs<'a>(
416-
section: &TextSection,
417-
section_index: usize,
418-
font_system: &mut cosmic_text::FontSystem,
419-
map_handle_to_font_id: &'a HashMap<AssetId<Font>, (cosmic_text::fontdb::ID, String)>,
440+
span_index: usize,
441+
style: &TextStyle,
442+
face_info: &'a FontFaceInfo,
420443
scale_factor: f64,
421444
) -> Attrs<'a> {
422-
let (face_id, family_name) = map_handle_to_font_id
423-
.get(&section.style.font.id())
424-
.expect("Already loaded with load_font_to_fontdb");
425-
let face = font_system.db().face(*face_id).unwrap();
426-
427445
let attrs = Attrs::new()
428-
.metadata(section_index)
429-
.family(Family::Name(family_name))
430-
.stretch(face.stretch)
431-
.style(face.style)
432-
.weight(face.weight)
433-
.metrics(Metrics::relative(section.style.font_size, 1.2).scale(scale_factor as f32))
434-
.color(cosmic_text::Color(section.style.color.to_linear().as_u32()));
446+
.metadata(span_index)
447+
.family(Family::Name(&*face_info.family_name))
448+
.stretch(face_info.stretch)
449+
.style(face_info.style)
450+
.weight(face_info.weight)
451+
.metrics(Metrics::relative(style.font_size, 1.2).scale(scale_factor as f32))
452+
.color(cosmic_text::Color(style.color.to_linear().as_u32()));
435453
attrs
436454
}
437455

0 commit comments

Comments
 (0)