From f092bdde73c7e0ff870708de6dec8d08dd1e092a Mon Sep 17 00:00:00 2001 From: tigregalis Date: Tue, 27 Jun 2023 21:53:05 +0800 Subject: [PATCH 1/6] add `Buffer::set_rich_text` method --- src/buffer.rs | 112 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 98 insertions(+), 14 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index fefa7ac..61d208e 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -598,21 +598,105 @@ 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)), + /// ] + /// .into_iter(), + /// 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 section + 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, + // 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 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 = + std::mem::replace(&mut attrs_list, AttrsList::new(Attrs::new())); + let prev_line_string = std::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; From 4a05c9c1cf805d8c67cf8dd88db783da8da2c309 Mon Sep 17 00:00:00 2001 From: tigregalis Date: Tue, 27 Jun 2023 21:53:27 +0800 Subject: [PATCH 2/6] add `BorrowedWithFontSystem<_>::set_rich_text` --- src/buffer.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/buffer.rs b/src/buffer.rs index 61d208e..5708f86 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -898,6 +898,30 @@ 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)), + /// ] + /// .into_iter(), + /// 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) From 0d9173c05bedc88faf37f17be1c126f810fb6e25 Mon Sep 17 00:00:00 2001 From: tigregalis Date: Tue, 27 Jun 2023 21:56:17 +0800 Subject: [PATCH 3/6] update `rich_text` example with `set_rich_text` --- examples/rich-text/src/main.rs | 165 ++++++++++++++------------------- 1 file changed, 70 insertions(+), 95 deletions(-) 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(); From 48f6aefb80fb3b949657e0c608d390140d2748ba Mon Sep 17 00:00:00 2001 From: tigregalis Date: Tue, 27 Jun 2023 21:59:58 +0800 Subject: [PATCH 4/6] remove unnecessary `into_iter` in doctests --- src/buffer.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 5708f86..ac2208c 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -613,8 +613,7 @@ impl Buffer { /// [ /// ("hello, ", attrs), /// ("cosmic\ntext", attrs.family(Family::Monospace)), - /// ] - /// .into_iter(), + /// ], /// Shaping::Advanced, /// ); /// ``` @@ -910,8 +909,7 @@ impl<'a> BorrowedWithFontSystem<'a, Buffer> { /// [ /// ("hello, ", attrs), /// ("cosmic\ntext", attrs.family(Family::Monospace)), - /// ] - /// .into_iter(), + /// ], /// Shaping::Advanced, /// ); /// ``` From 7db01ebf3aaf3c7ccca4d6270399d6050a544b44 Mon Sep 17 00:00:00 2001 From: tigregalis Date: Fri, 30 Jun 2023 20:31:29 +0800 Subject: [PATCH 5/6] use `core::mem::{replace, take}` not `std::` --- src/buffer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index ac2208c..e73a283 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -685,8 +685,8 @@ impl Buffer { 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); + 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 { From fb8bb609725b2b05e1e56bc12f94d8be2ab47b3f Mon Sep 17 00:00:00 2001 From: tigregalis Date: Fri, 30 Jun 2023 20:40:12 +0800 Subject: [PATCH 6/6] update comments --- src/buffer.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index e73a283..0bd163c 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -662,7 +662,7 @@ impl Buffer { break; }; - // start..end is the intersection of this line and this section + // 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 { @@ -674,9 +674,9 @@ impl Buffer { } // 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. + // 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();