diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs index d5d3b6e5e078f7..6933bc7a50e623 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -11,7 +11,7 @@ use bevy_utils::{ HashMap, }; -use cosmic_text::{Attrs, AttrsList, Buffer, BufferLine, Family, Metrics, Wrap}; +use cosmic_text::{Attrs, Buffer, Metrics, Shaping, Wrap}; use crate::{ error::TextError, BreakLineOn, Font, FontAtlasSets, FontAtlasWarning, PositionedGlyph, @@ -26,7 +26,6 @@ use crate::{ // TODO: (future work) split text entities into section entities // TODO: (future work) text editing // TODO: font validation - // TODO: the only reason we need a mutex is due to TextMeasure // - is there a way to do this without it? /// A wrapper around a [`cosmic_text::FontSystem`] @@ -104,117 +103,27 @@ impl TextPipeline { let (font_size, line_height) = (font_size as f32, line_height as f32); let metrics = Metrics::new(font_size, line_height); - let font_system = &mut acquire_font_system(&mut self.font_system)?; + let mut font_system = &mut acquire_font_system(&mut self.font_system)?; - // TODO: cache buffers (see Iced / glyphon) - let mut buffer = Buffer::new(font_system, metrics); - - buffer.lines.clear(); - let mut attrs_list = AttrsList::new(Attrs::new()); - let mut line_string = String::new(); - // all sections need to be combined and broken up into lines - // e.g. - // style0"Lorem ipsum\ndolor sit amet," - // style1" consectetur adipiscing\nelit," - // style2" sed do eiusmod tempor\nincididunt" - // style3" ut labore et dolore\nmagna aliqua." - // becomes: - // line0: style0"Lorem ipsum" - // line1: style0"dolor sit amet," - // style1" consectetur adipiscing," - // line2: style1"elit," - // style2" sed do eiusmod tempor" - // line3: style2"incididunt" - // style3"ut labore et dolore" - // line4: style3"magna aliqua." - - // combine all sections into a string - // as well as metadata that links those sections to that string - let mut end = 0; - let (string, sections_data): (String, Vec<_>) = sections + let spans: Vec<(&str, Attrs)> = sections .iter() .enumerate() .map(|(section_index, section)| { - let start = end; - end += section.value.len(); - (section.value.as_str(), (section, section_index, start..end)) + ( + §ion.value[..], + get_attrs( + §ion, + section_index, + &mut font_system, + &mut self.map_handle_to_font_id, + fonts, + ), + ) }) - .unzip(); - - let mut sections_iter = sections_data.into_iter(); - let mut maybe_section = sections_iter.next(); - - // split the string into lines, as ranges - let string_start = string.as_ptr() as usize; - let mut lines_iter = BidiParagraphs::new(&string).map(|line: &str| { - let start = line.as_ptr() as usize - string_start; - let end = start + line.len(); - start..end - }); - let mut maybe_line = lines_iter.next(); - - loop { - let (Some(line_range), Some((section, section_index, section_range))) = - (&maybe_line, &maybe_section) - else { - // this is reached only if this text is empty - break; - }; - - // start..end is the intersection of this line and this section - let start = line_range.start.max(section_range.start); - let end = line_range.end.min(section_range.end); - if start < end { - let text = &string[start..end]; - add_span( - &mut line_string, - &mut attrs_list, - section, - *section_index, - text, - font_system, - &mut self.map_handle_to_font_id, - fonts, - ); - } + .collect(); - // we know that at the end of a line, - // section text's end index is always >= line text's end index - // so if this section ends before this line ends, - // there is another section in this line. - // otherwise, we move on to the next line. - if section_range.end < line_range.end { - maybe_section = sections_iter.next(); - } else { - maybe_line = lines_iter.next(); - if maybe_line.is_some() { - // finalize this line and start a new line - let prev_attrs_list = - std::mem::replace(&mut attrs_list, AttrsList::new(Attrs::new())); - let prev_line_string = std::mem::take(&mut line_string); - //TODO: is basic the correct way? - buffer.lines.push(BufferLine::new( - prev_line_string, - prev_attrs_list, - cosmic_text::Shaping::Basic, - )); - } else { - // finalize the final line - //TODO: is basic the correct way? - buffer.lines.push(BufferLine::new( - line_string, - attrs_list, - cosmic_text::Shaping::Basic, - )); - break; - } - } - } - - // node size (bounds) is already scaled by the systems that call queue_text - // TODO: cosmic text does not shape/layout text outside the buffer height - // consider a better way to do this - // let buffer_height = bounds.y; + // TODO: cache buffers (see Iced / glyphon) + let mut buffer = Buffer::new_empty(metrics); let buffer_height = f32::INFINITY; buffer.set_size(font_system, bounds.x.ceil(), buffer_height); @@ -228,7 +137,7 @@ impl TextPipeline { ); // TODO: other shaping methods? - buffer.shape_until_scroll(font_system); + buffer.set_rich_text(font_system, spans, Shaping::Basic); if buffer.visible_lines() == 0 { // Presumably the font(s) are not available yet @@ -445,8 +354,8 @@ pub struct TextMeasureInfo { impl std::fmt::Debug for TextMeasureInfo { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("TextMeasureInfo") - .field("min_width_content_size", &self.min) - .field("max_width_content_size", &self.max) + .field("min", &self.min) + .field("max", &self.max) .field("buffer", &"_") .field("font_system", &"_") .finish() @@ -462,24 +371,15 @@ impl TextMeasureInfo { } } -/// For the current line, -/// adds a span to the attributes list and pushes the text into the line string, -/// loading fonts into the [`Database`](cosmic_text::fontdb::Database) if required. -#[allow(clippy::too_many_arguments)] -fn add_span( - line_string: &mut String, - attrs_list: &mut AttrsList, - section: &TextSection, +// /// get attr for from textstyle +// /// loading fonts into the [`Database`](cosmic_text::fontdb::Database) if required. +fn get_attrs<'a>( + section: &'a TextSection, section_index: usize, - text: &str, font_system: &mut cosmic_text::FontSystem, map_handle_to_font_id: &mut HashMap, cosmic_text::fontdb::ID>, fonts: &Assets, -) { - let start = line_string.len(); - line_string.push_str(text); - let end = line_string.len(); - +) -> Attrs<'a> { let font_handle = section.style.font.clone(); let font_handle_id = font_handle.id(); let face_id = map_handle_to_font_id @@ -505,17 +405,17 @@ fn add_span( let face = font_system.db().face(*face_id).unwrap(); // TODO: validate this is the correct string to extract - let family_name = &face.families[0].0; + // let family_name = &face.families[0].0; let attrs = Attrs::new() // TODO: validate that we can use metadata .metadata(section_index) - .family(Family::Name(family_name)) + // TODO: this reference, becomes owned by the font system, which is not really wanted... + // .family(Family::Name(family_name)) .stretch(face.stretch) .style(face.style) .weight(face.weight) .color(cosmic_text::Color(section.style.color.as_linear_rgba_u32())); - - attrs_list.add_span(start..end, attrs); + attrs } /// Calculate the size of the text area for the given buffer.