diff --git a/examples/rich-text/src/main.rs b/examples/rich-text/src/main.rs index b5d1ab4..66092db 100644 --- a/examples/rich-text/src/main.rs +++ b/examples/rich-text/src/main.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use cosmic_text::{ - Action, Attrs, AttrsList, Buffer, BufferLine, Color, Edit, Editor, Family, FontSystem, Metrics, - Shaping, Style, SwashCache, Weight, + Action, Attrs, Buffer, Color, Edit, Editor, Family, FontSystem, Metrics, Shaping, Style, + SwashCache, Weight, }; use orbclient::{EventOption, Renderer, Window, WindowFlag}; use std::{ @@ -36,8 +36,7 @@ fn main() { ) .unwrap(); - let mut editor = Editor::new(Buffer::new( - &mut font_system, + let mut editor = Editor::new(Buffer::new_empty( Metrics::new(32.0, 44.0).scale(display_scale), )); @@ -52,99 +51,75 @@ fn main() { let mono_attrs = attrs.family(Family::Monospace); let comic_attrs = attrs.family(Family::Name("Comic Neue")); - editor.buffer_mut().lines.clear(); - - let lines: &[&[(&str, Attrs)]] = &[ - &[ - ("B", attrs.weight(Weight::BOLD)), - ("old ", attrs), - ("I", attrs.style(Style::Italic)), - ("talic ", attrs), - ("f", attrs), - ("i ", attrs), - ("f", attrs.weight(Weight::BOLD)), - ("i ", attrs), - ("f", attrs.style(Style::Italic)), - ("i ", attrs), - ], - &[ - ("Sans-Serif Normal ", attrs), - ("Sans-Serif Bold ", attrs.weight(Weight::BOLD)), - ("Sans-Serif Italic ", attrs.style(Style::Italic)), - ( - "Sans-Serif Bold Italic", - attrs.weight(Weight::BOLD).style(Style::Italic), - ), - ], - &[ - ("Serif Normal ", serif_attrs), - ("Serif Bold ", serif_attrs.weight(Weight::BOLD)), - ("Serif Italic ", serif_attrs.style(Style::Italic)), - ( - "Serif Bold Italic", - serif_attrs.weight(Weight::BOLD).style(Style::Italic), - ), - ], - &[ - ("Mono Normal ", mono_attrs), - ("Mono Bold ", mono_attrs.weight(Weight::BOLD)), - ("Mono Italic ", mono_attrs.style(Style::Italic)), - ( - "Mono Bold Italic", - mono_attrs.weight(Weight::BOLD).style(Style::Italic), - ), - ], - &[ - ("Comic Normal ", comic_attrs), - ("Comic Bold ", comic_attrs.weight(Weight::BOLD)), - ("Comic Italic ", comic_attrs.style(Style::Italic)), - ( - "Comic Bold Italic", - comic_attrs.weight(Weight::BOLD).style(Style::Italic), - ), - ], - &[ - ("R", attrs.color(Color::rgb(0xFF, 0x00, 0x00))), - ("A", attrs.color(Color::rgb(0xFF, 0x7F, 0x00))), - ("I", attrs.color(Color::rgb(0xFF, 0xFF, 0x00))), - ("N", attrs.color(Color::rgb(0x00, 0xFF, 0x00))), - ("B", attrs.color(Color::rgb(0x00, 0x00, 0xFF))), - ("O", attrs.color(Color::rgb(0x4B, 0x00, 0x82))), - ("W ", attrs.color(Color::rgb(0x94, 0x00, 0xD3))), - ("Red ", attrs.color(Color::rgb(0xFF, 0x00, 0x00))), - ("Orange ", attrs.color(Color::rgb(0xFF, 0x7F, 0x00))), - ("Yellow ", attrs.color(Color::rgb(0xFF, 0xFF, 0x00))), - ("Green ", attrs.color(Color::rgb(0x00, 0xFF, 0x00))), - ("Blue ", attrs.color(Color::rgb(0x00, 0x00, 0xFF))), - ("Indigo ", attrs.color(Color::rgb(0x4B, 0x00, 0x82))), - ("Violet ", attrs.color(Color::rgb(0x94, 0x00, 0xD3))), - ("U", attrs.color(Color::rgb(0x94, 0x00, 0xD3))), - ("N", attrs.color(Color::rgb(0x4B, 0x00, 0x82))), - ("I", attrs.color(Color::rgb(0x00, 0x00, 0xFF))), - ("C", attrs.color(Color::rgb(0x00, 0xFF, 0x00))), - ("O", attrs.color(Color::rgb(0xFF, 0xFF, 0x00))), - ("R", attrs.color(Color::rgb(0xFF, 0x7F, 0x00))), - ("N", attrs.color(Color::rgb(0xFF, 0x00, 0x00))), - ], - &[( - "生活,삶,जिंदगी 😀 FPS", + let spans: &[(&str, Attrs)] = &[ + ("B", attrs.weight(Weight::BOLD)), + ("old ", attrs), + ("I", attrs.style(Style::Italic)), + ("talic ", attrs), + ("f", attrs), + ("i ", attrs), + ("f", attrs.weight(Weight::BOLD)), + ("i ", attrs), + ("f", attrs.style(Style::Italic)), + ("i \n", attrs), + ("Sans-Serif Normal ", attrs), + ("Sans-Serif Bold ", attrs.weight(Weight::BOLD)), + ("Sans-Serif Italic ", attrs.style(Style::Italic)), + ( + "Sans-Serif Bold Italic\n", + attrs.weight(Weight::BOLD).style(Style::Italic), + ), + ("Serif Normal ", serif_attrs), + ("Serif Bold ", serif_attrs.weight(Weight::BOLD)), + ("Serif Italic ", serif_attrs.style(Style::Italic)), + ( + "Serif Bold Italic\n", + serif_attrs.weight(Weight::BOLD).style(Style::Italic), + ), + ("Mono Normal ", mono_attrs), + ("Mono Bold ", mono_attrs.weight(Weight::BOLD)), + ("Mono Italic ", mono_attrs.style(Style::Italic)), + ( + "Mono Bold Italic\n", + mono_attrs.weight(Weight::BOLD).style(Style::Italic), + ), + ("Comic Normal ", comic_attrs), + ("Comic Bold ", comic_attrs.weight(Weight::BOLD)), + ("Comic Italic ", comic_attrs.style(Style::Italic)), + ( + "Comic Bold Italic\n", + comic_attrs.weight(Weight::BOLD).style(Style::Italic), + ), + ("R", attrs.color(Color::rgb(0xFF, 0x00, 0x00))), + ("A", attrs.color(Color::rgb(0xFF, 0x7F, 0x00))), + ("I", attrs.color(Color::rgb(0xFF, 0xFF, 0x00))), + ("N", attrs.color(Color::rgb(0x00, 0xFF, 0x00))), + ("B", attrs.color(Color::rgb(0x00, 0x00, 0xFF))), + ("O", attrs.color(Color::rgb(0x4B, 0x00, 0x82))), + ("W ", attrs.color(Color::rgb(0x94, 0x00, 0xD3))), + ("Red ", attrs.color(Color::rgb(0xFF, 0x00, 0x00))), + ("Orange ", attrs.color(Color::rgb(0xFF, 0x7F, 0x00))), + ("Yellow ", attrs.color(Color::rgb(0xFF, 0xFF, 0x00))), + ("Green ", attrs.color(Color::rgb(0x00, 0xFF, 0x00))), + ("Blue ", attrs.color(Color::rgb(0x00, 0x00, 0xFF))), + ("Indigo ", attrs.color(Color::rgb(0x4B, 0x00, 0x82))), + ("Violet ", attrs.color(Color::rgb(0x94, 0x00, 0xD3))), + ("U", attrs.color(Color::rgb(0x94, 0x00, 0xD3))), + ("N", attrs.color(Color::rgb(0x4B, 0x00, 0x82))), + ("I", attrs.color(Color::rgb(0x00, 0x00, 0xFF))), + ("C", attrs.color(Color::rgb(0x00, 0xFF, 0x00))), + ("O", attrs.color(Color::rgb(0xFF, 0xFF, 0x00))), + ("R", attrs.color(Color::rgb(0xFF, 0x7F, 0x00))), + ("N\n", attrs.color(Color::rgb(0xFF, 0x00, 0x00))), + ( + "生活,삶,जिंदगी 😀 FPS\n", attrs.color(Color::rgb(0xFF, 0x00, 0x00)), - )], + ), ]; - for &line in lines { - let mut line_text = String::new(); - let mut attrs_list = AttrsList::new(attrs); - for &(text, attrs) in line { - let start = line_text.len(); - line_text.push_str(text); - let end = line_text.len(); - attrs_list.add_span(start..end, attrs); - } - editor - .buffer_mut() - .lines - .push(BufferLine::new(line_text, attrs_list, Shaping::Advanced)); - } + + editor + .buffer_mut() + .set_rich_text(spans.iter().copied(), Shaping::Advanced); let mut swash_cache = SwashCache::new(); diff --git a/src/buffer.rs b/src/buffer.rs index 227b228..a04675d 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -618,21 +618,104 @@ impl Buffer { attrs: Attrs, shaping: Shaping, ) { + self.set_rich_text(font_system, [(text, attrs)], shaping); + } + + /// Set text of buffer, using an iterator of styled spans (pairs of text and attributes) + /// + /// ``` + /// # use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping}; + /// # let mut font_system = FontSystem::new(); + /// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0)); + /// let attrs = Attrs::new().family(Family::Serif); + /// buffer.set_rich_text( + /// &mut font_system, + /// [ + /// ("hello, ", attrs), + /// ("cosmic\ntext", attrs.family(Family::Monospace)), + /// ], + /// Shaping::Advanced, + /// ); + /// ``` + pub fn set_rich_text<'r, 's, I>( + &mut self, + font_system: &mut FontSystem, + spans: I, + shaping: Shaping, + ) where + I: IntoIterator)>, + { self.lines.clear(); - for line in BidiParagraphs::new(text) { - self.lines.push(BufferLine::new( - line.to_string(), - AttrsList::new(attrs), - shaping, - )); - } - // Make sure there is always one line - if self.lines.is_empty() { - self.lines.push(BufferLine::new( - String::new(), - AttrsList::new(attrs), - shaping, - )); + + let mut attrs_list = AttrsList::new(Attrs::new()); + let mut line_string = String::new(); + let mut end = 0; + let (string, spans_data): (String, Vec<_>) = spans + .into_iter() + .map(|(s, attrs)| { + let start = end; + end += s.len(); + (s, (attrs, start..end)) + }) + .unzip(); + + let mut spans_iter = spans_data.into_iter(); + let mut maybe_span = spans_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((attrs, span_range))) = (&maybe_line, &maybe_span) else { + // this is reached only if this text is empty + self.lines.push(BufferLine::new( + String::new(), + AttrsList::new(Attrs::new()), + shaping, + )); + break; + }; + + // start..end is the intersection of this line and this span + let start = line_range.start.max(span_range.start); + let end = line_range.end.min(span_range.end); + if start < end { + let text = &string[start..end]; + let text_start = line_string.len(); + line_string.push_str(text); + let text_end = line_string.len(); + attrs_list.add_span(text_start..text_end, *attrs); + } + + // we know that at the end of a line, + // span text's end index is always >= line text's end index + // so if this span ends before this line ends, + // there is another span in this line. + // otherwise, we move on to the next line. + if span_range.end < line_range.end { + maybe_span = spans_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 = + core::mem::replace(&mut attrs_list, AttrsList::new(Attrs::new())); + let prev_line_string = core::mem::take(&mut line_string); + let buffer_line = BufferLine::new(prev_line_string, prev_attrs_list, shaping); + self.lines.push(buffer_line); + } else { + // finalize the final line + let buffer_line = BufferLine::new(line_string, attrs_list, shaping); + self.lines.push(buffer_line); + break; + } + } } self.scroll = 0; @@ -845,6 +928,29 @@ impl<'a> BorrowedWithFontSystem<'a, Buffer> { self.inner.set_text(self.font_system, text, attrs, shaping); } + /// Set text of buffer, using an iterator of styled spans (pairs of text and attributes) + /// + /// ``` + /// # use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping}; + /// # let mut font_system = FontSystem::new(); + /// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0)); + /// let mut buffer = buffer.borrow_with(&mut font_system); + /// let attrs = Attrs::new().family(Family::Serif); + /// buffer.set_rich_text( + /// [ + /// ("hello, ", attrs), + /// ("cosmic\ntext", attrs.family(Family::Monospace)), + /// ], + /// Shaping::Advanced, + /// ); + /// ``` + pub fn set_rich_text<'r, 's, I>(&mut self, spans: I, shaping: Shaping) + where + I: IntoIterator)>, + { + self.inner.set_rich_text(self.font_system, spans, shaping); + } + /// Draw the buffer #[cfg(feature = "swash")] pub fn draw(&mut self, cache: &mut crate::SwashCache, color: Color, f: F)