Merge pull request #149 from tigregalis/set-rich-text

Add `Buffer::set_rich_text` method
This commit is contained in:
Jeremy Soller 2023-09-11 10:58:39 -06:00 committed by GitHub
commit 1eab951e27
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 190 additions and 109 deletions

View file

@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use cosmic_text::{ use cosmic_text::{
Action, Attrs, AttrsList, Buffer, BufferLine, Color, Edit, Editor, Family, FontSystem, Metrics, Action, Attrs, Buffer, Color, Edit, Editor, Family, FontSystem, Metrics, Shaping, Style,
Shaping, Style, SwashCache, Weight, SwashCache, Weight,
}; };
use orbclient::{EventOption, Renderer, Window, WindowFlag}; use orbclient::{EventOption, Renderer, Window, WindowFlag};
use std::{ use std::{
@ -36,8 +36,7 @@ fn main() {
) )
.unwrap(); .unwrap();
let mut editor = Editor::new(Buffer::new( let mut editor = Editor::new(Buffer::new_empty(
&mut font_system,
Metrics::new(32.0, 44.0).scale(display_scale), Metrics::new(32.0, 44.0).scale(display_scale),
)); ));
@ -52,99 +51,75 @@ fn main() {
let mono_attrs = attrs.family(Family::Monospace); let mono_attrs = attrs.family(Family::Monospace);
let comic_attrs = attrs.family(Family::Name("Comic Neue")); let comic_attrs = attrs.family(Family::Name("Comic Neue"));
editor.buffer_mut().lines.clear(); let spans: &[(&str, Attrs)] = &[
("B", attrs.weight(Weight::BOLD)),
let lines: &[&[(&str, Attrs)]] = &[ ("old ", attrs),
&[ ("I", attrs.style(Style::Italic)),
("B", attrs.weight(Weight::BOLD)), ("talic ", attrs),
("old ", attrs), ("f", attrs),
("I", attrs.style(Style::Italic)), ("i ", attrs),
("talic ", attrs), ("f", attrs.weight(Weight::BOLD)),
("f", attrs), ("i ", attrs),
("i ", attrs), ("f", attrs.style(Style::Italic)),
("f", attrs.weight(Weight::BOLD)), ("i \n", attrs),
("i ", attrs), ("Sans-Serif Normal ", attrs),
("f", attrs.style(Style::Italic)), ("Sans-Serif Bold ", attrs.weight(Weight::BOLD)),
("i ", attrs), ("Sans-Serif Italic ", attrs.style(Style::Italic)),
], (
&[ "Sans-Serif Bold Italic\n",
("Sans-Serif Normal ", attrs), attrs.weight(Weight::BOLD).style(Style::Italic),
("Sans-Serif Bold ", attrs.weight(Weight::BOLD)), ),
("Sans-Serif Italic ", attrs.style(Style::Italic)), ("Serif Normal ", serif_attrs),
( ("Serif Bold ", serif_attrs.weight(Weight::BOLD)),
"Sans-Serif Bold Italic", ("Serif Italic ", serif_attrs.style(Style::Italic)),
attrs.weight(Weight::BOLD).style(Style::Italic), (
), "Serif Bold Italic\n",
], serif_attrs.weight(Weight::BOLD).style(Style::Italic),
&[ ),
("Serif Normal ", serif_attrs), ("Mono Normal ", mono_attrs),
("Serif Bold ", serif_attrs.weight(Weight::BOLD)), ("Mono Bold ", mono_attrs.weight(Weight::BOLD)),
("Serif Italic ", serif_attrs.style(Style::Italic)), ("Mono Italic ", mono_attrs.style(Style::Italic)),
( (
"Serif Bold Italic", "Mono Bold Italic\n",
serif_attrs.weight(Weight::BOLD).style(Style::Italic), mono_attrs.weight(Weight::BOLD).style(Style::Italic),
), ),
], ("Comic Normal ", comic_attrs),
&[ ("Comic Bold ", comic_attrs.weight(Weight::BOLD)),
("Mono Normal ", mono_attrs), ("Comic Italic ", comic_attrs.style(Style::Italic)),
("Mono Bold ", mono_attrs.weight(Weight::BOLD)), (
("Mono Italic ", mono_attrs.style(Style::Italic)), "Comic Bold Italic\n",
( comic_attrs.weight(Weight::BOLD).style(Style::Italic),
"Mono Bold Italic", ),
mono_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))),
("Comic Normal ", comic_attrs), ("B", attrs.color(Color::rgb(0x00, 0x00, 0xFF))),
("Comic Bold ", comic_attrs.weight(Weight::BOLD)), ("O", attrs.color(Color::rgb(0x4B, 0x00, 0x82))),
("Comic Italic ", comic_attrs.style(Style::Italic)), ("W ", attrs.color(Color::rgb(0x94, 0x00, 0xD3))),
( ("Red ", attrs.color(Color::rgb(0xFF, 0x00, 0x00))),
"Comic Bold Italic", ("Orange ", attrs.color(Color::rgb(0xFF, 0x7F, 0x00))),
comic_attrs.weight(Weight::BOLD).style(Style::Italic), ("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))),
("R", attrs.color(Color::rgb(0xFF, 0x00, 0x00))), ("Violet ", attrs.color(Color::rgb(0x94, 0x00, 0xD3))),
("A", attrs.color(Color::rgb(0xFF, 0x7F, 0x00))), ("U", attrs.color(Color::rgb(0x94, 0x00, 0xD3))),
("I", attrs.color(Color::rgb(0xFF, 0xFF, 0x00))), ("N", attrs.color(Color::rgb(0x4B, 0x00, 0x82))),
("N", attrs.color(Color::rgb(0x00, 0xFF, 0x00))), ("I", attrs.color(Color::rgb(0x00, 0x00, 0xFF))),
("B", attrs.color(Color::rgb(0x00, 0x00, 0xFF))), ("C", attrs.color(Color::rgb(0x00, 0xFF, 0x00))),
("O", attrs.color(Color::rgb(0x4B, 0x00, 0x82))), ("O", attrs.color(Color::rgb(0xFF, 0xFF, 0x00))),
("W ", attrs.color(Color::rgb(0x94, 0x00, 0xD3))), ("R", attrs.color(Color::rgb(0xFF, 0x7F, 0x00))),
("Red ", attrs.color(Color::rgb(0xFF, 0x00, 0x00))), ("N\n", attrs.color(Color::rgb(0xFF, 0x00, 0x00))),
("Orange ", attrs.color(Color::rgb(0xFF, 0x7F, 0x00))), (
("Yellow ", attrs.color(Color::rgb(0xFF, 0xFF, 0x00))), "生活,삶,जिंदगी 😀 FPS\n",
("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",
attrs.color(Color::rgb(0xFF, 0x00, 0x00)), attrs.color(Color::rgb(0xFF, 0x00, 0x00)),
)], ),
]; ];
for &line in lines {
let mut line_text = String::new(); editor
let mut attrs_list = AttrsList::new(attrs); .buffer_mut()
for &(text, attrs) in line { .set_rich_text(spans.iter().copied(), Shaping::Advanced);
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));
}
let mut swash_cache = SwashCache::new(); let mut swash_cache = SwashCache::new();

View file

@ -618,21 +618,104 @@ impl Buffer {
attrs: Attrs, attrs: Attrs,
shaping: Shaping, 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<Item = (&'s str, Attrs<'r>)>,
{
self.lines.clear(); self.lines.clear();
for line in BidiParagraphs::new(text) {
self.lines.push(BufferLine::new( let mut attrs_list = AttrsList::new(Attrs::new());
line.to_string(), let mut line_string = String::new();
AttrsList::new(attrs), let mut end = 0;
shaping, let (string, spans_data): (String, Vec<_>) = spans
)); .into_iter()
} .map(|(s, attrs)| {
// Make sure there is always one line let start = end;
if self.lines.is_empty() { end += s.len();
self.lines.push(BufferLine::new( (s, (attrs, start..end))
String::new(), })
AttrsList::new(attrs), .unzip();
shaping,
)); 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; self.scroll = 0;
@ -845,6 +928,29 @@ impl<'a> BorrowedWithFontSystem<'a, Buffer> {
self.inner.set_text(self.font_system, text, attrs, shaping); 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<Item = (&'s str, Attrs<'r>)>,
{
self.inner.set_rich_text(self.font_system, spans, shaping);
}
/// Draw the buffer /// Draw the buffer
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, color: Color, f: F) pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, color: Color, f: F)