Skip to content
116 changes: 31 additions & 85 deletions crates/bevy_text/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use bevy_image::prelude::*;
use bevy_log::{once, warn};
use bevy_math::{Rect, UVec2, Vec2};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use smol_str::SmolStr;

use crate::{
add_glyph_to_atlas, error::TextError, get_glyph_atlas_info, ComputedTextBlock, Font,
Expand Down Expand Up @@ -49,28 +48,15 @@ impl Default for SwashCache {
}
}

/// Information about a font collected as part of preparing for text layout.
#[derive(Clone)]
pub struct FontFaceInfo {
/// Font family name
pub family_name: SmolStr,
}

/// The `TextPipeline` is used to layout and render text blocks (see `Text`/`Text2d`).
///
/// See the [crate-level documentation](crate) for more information.
#[derive(Default, Resource)]
pub struct TextPipeline {
/// Buffered vec for collecting spans.
/// Buffered vec for collecting text sections.
///
/// See [this dark magic](https://users.rust-lang.org/t/how-to-cache-a-vectors-capacity/94478/10).
spans_buffer: Vec<(
usize,
&'static str,
&'static TextFont,
FontFaceInfo,
LineHeight,
)>,
sections_buffer: Vec<(&'static str, Attrs<'static>)>,
}

impl TextPipeline {
Expand Down Expand Up @@ -102,43 +88,32 @@ impl TextPipeline {

let font_system = &mut font_system.0;

// Collect span information into a vec. This is necessary because font loading requires mut access
// Collect section information into a vec. This is necessary because font loading requires mut access
// to FontSystem, which the cosmic-text Buffer also needs.
let mut spans: Vec<(usize, &str, &TextFont, FontFaceInfo, Color, LineHeight)> =
core::mem::take(&mut self.spans_buffer)
.into_iter()
.map(
|_| -> (usize, &str, &TextFont, FontFaceInfo, Color, LineHeight) {
unreachable!()
},
)
.collect();
let mut sections: Vec<(&str, Attrs)> = core::mem::take(&mut self.sections_buffer)
.into_iter()
.map(|_| -> (&str, Attrs) { unreachable!() })
.collect();

let result = {
for (span_index, (entity, depth, span, text_font, color, line_height)) in
for (span_index, (entity, depth, span, text_font, _color, line_height)) in
text_spans.enumerate()
{
// Save this span entity in the computed text block.
// Save this section entity in the computed text block.
computed.entities.push(TextEntity { entity, depth });

if span.is_empty() {
continue;
}

let family_name: SmolStr = match &text_font.font {
let family: Family = match &text_font.font {
FontSource::Handle(handle) => {
// Return early if a font is not loaded yet.
fonts
.get(handle.id())
.ok_or(TextError::NoSuchFont)?
.family_name
.clone()
let font = fonts.get(handle.id()).ok_or(TextError::NoSuchFont)?;
Family::Name(font.family_name.as_str())
}
FontSource::Family(family) => family.clone(),
FontSource::Family(family) => Family::Name(family.as_str()),
};

let face_info = FontFaceInfo { family_name };

// Save spans that aren't zero-sized.
if text_font.font_size <= 0.0 {
once!(warn!(
Expand All @@ -147,38 +122,19 @@ impl TextPipeline {

continue;
}
spans.push((span_index, span, text_font, face_info, color, line_height));
}

// Map text sections to cosmic-text spans, and ignore sections with negative or zero fontsizes,
// since they cannot be rendered by cosmic-text.
//
// The section index is stored in the metadata of the spans, and could be used
// to look up the section the span came from and is not used internally
// in cosmic-text.
let spans_iter = spans.iter().map(
|(span_index, span, text_font, font_info, color, line_height)| {
(
*span,
get_attrs(
*span_index,
text_font,
*line_height,
*color,
font_info,
scale_factor,
),
)
},
);
let attrs = get_attrs(span_index, text_font, line_height, family, scale_factor);

sections.push((span, attrs));
}

// Update the buffer.
let buffer = &mut computed.buffer;
// Update the Cosmic Text buffer.
let cosmic_buffer = &mut computed.buffer;

// Set the metrics hinting strategy
buffer.set_hinting(font_system, hinting.into());
cosmic_buffer.set_hinting(font_system, hinting.into());

buffer.set_wrap(
cosmic_buffer.set_wrap(
font_system,
match linebreak {
LineBreak::WordBoundary => Wrap::Word,
Expand All @@ -188,9 +144,9 @@ impl TextPipeline {
},
);

buffer.set_rich_text(
cosmic_buffer.set_rich_text(
font_system,
spans_iter,
sections.drain(..),
&Attrs::new(),
Shaping::Advanced,
Some(justify.into()),
Expand All @@ -199,25 +155,17 @@ impl TextPipeline {
// Workaround for alignment not working for unbounded text.
// See https://github.com/pop-os/cosmic-text/issues/343
let width = (bounds.width.is_none() && justify != Justify::Left)
.then(|| buffer_dimensions(buffer).x)
.then(|| buffer_dimensions(cosmic_buffer).x)
.or(bounds.width);
buffer.set_size(font_system, width, bounds.height);
cosmic_buffer.set_size(font_system, width, bounds.height);
Ok(())
};

// Recover the spans buffer.
spans.clear();
self.spans_buffer = spans
// Recover the sections buffer.
self.sections_buffer.clear();
self.sections_buffer = sections
.into_iter()
.map(
|_| -> (
usize,
&'static str,
&'static TextFont,
FontFaceInfo,
LineHeight,
) { unreachable!() },
)
.map(|_| -> (&'static str, Attrs<'static>) { unreachable!() })
.collect();

result
Expand Down Expand Up @@ -537,13 +485,12 @@ fn get_attrs<'a>(
span_index: usize,
text_font: &TextFont,
line_height: LineHeight,
color: Color,
face_info: &'a FontFaceInfo,
family: Family<'a>,
scale_factor: f64,
) -> Attrs<'a> {
Attrs::new()
.metadata(span_index)
.family(Family::Name(&face_info.family_name))
.family(family)
.stretch(text_font.width.into())
.style(text_font.style.into())
.weight(text_font.weight.into())
Expand All @@ -555,7 +502,6 @@ fn get_attrs<'a>(
.scale(scale_factor as f32),
)
.font_features((&text_font.font_features).into())
.color(cosmic_text::Color(color.to_linear().as_u32()))
}

/// Calculate the size of the text area for the given buffer.
Expand Down