Make attribute spans use ranges and update docs

This commit is contained in:
Jeremy Soller 2022-10-27 09:56:53 -06:00
parent 16f0eb9efe
commit e4c8d4ba6b
No known key found for this signature in database
GPG key ID: 87F211AF2BE4C2FE
8 changed files with 147 additions and 123 deletions

View file

@ -1,7 +1,10 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use std::ops::Range;
pub use fontdb::{Family, Stretch, Style, Weight};
/// Text color
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Color(pub u32);
@ -48,6 +51,7 @@ impl Color {
}
}
/// Text attributes
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Attrs<'a> {
//TODO: should this be an option?
@ -60,6 +64,9 @@ pub struct Attrs<'a> {
}
impl<'a> Attrs<'a> {
/// Create a new set of attributes with sane defaults
///
/// This defaults to a regular Sans-Serif font.
pub fn new() -> Self {
Self {
color_opt: None,
@ -71,36 +78,43 @@ impl<'a> Attrs<'a> {
}
}
/// Set [Color]
pub fn color(mut self, color: Color) -> Self {
self.color_opt = Some(color);
self
}
/// Set [Family]
pub fn family(mut self, family: Family<'a>) -> Self {
self.family = family;
self
}
/// Set monospaced
pub fn monospaced(mut self, monospaced: bool) -> Self {
self.monospaced = monospaced;
self
}
/// Set [Stretch]
pub fn stretch(mut self, stretch: Stretch) -> Self {
self.stretch = stretch;
self
}
/// Set [Style]
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
/// Set [Weight]
pub fn weight(mut self, weight: Weight) -> Self {
self.weight = weight;
self
}
/// Check if font matches
pub fn matches(&self, face: &fontdb::FaceInfo) -> bool {
//TODO: smarter way of including emoji
face.post_script_name.contains("Emoji") ||
@ -112,6 +126,7 @@ impl<'a> Attrs<'a> {
)
}
/// Check if this set of attributes can be shaped with another
pub fn compatible(&self, other: &Self) -> bool {
self.family == other.family
&& self.monospaced == other.monospaced
@ -121,13 +136,15 @@ impl<'a> Attrs<'a> {
}
}
/// List of text attributes to apply to a line
#[derive(Eq, PartialEq)]
pub struct AttrsList<'a> {
defaults: Attrs<'a>,
spans: Vec<(usize, usize, Attrs<'a>)>,
spans: Vec<(Range<usize>, Attrs<'a>)>,
}
impl<'a> AttrsList<'a> {
/// Create a new attributes list with a set of default [Attrs]
pub fn new(defaults: Attrs<'a>) -> Self {
Self {
defaults,
@ -135,29 +152,65 @@ impl<'a> AttrsList<'a> {
}
}
/// Get the default [Attrs]
pub fn defaults(&self) -> Attrs<'a> {
self.defaults
}
pub fn spans(&self) -> &Vec<(usize, usize, Attrs<'a>)> {
/// Get the current attribute spans
pub fn spans(&self) -> &Vec<(Range<usize>, Attrs<'a>)> {
&self.spans
}
/// Clear the current attribute spans
pub fn clear_spans(&mut self) {
self.spans.clear();
}
pub fn add_span(&mut self, start: usize, end: usize, attrs: Attrs<'a>) {
self.spans.push((start, end, attrs));
/// Add an attribute span
pub fn add_span(&mut self, range: Range<usize>, attrs: Attrs<'a>) {
self.spans.push((range, attrs));
}
pub fn get_span(&self, start: usize, end: usize) -> Attrs<'a> {
/// Get the highest priority attribute span for a range
///
/// This returns the latest added span that contains the range
pub fn get_span(&self, range: Range<usize>) -> Attrs<'a> {
let mut attrs = self.defaults;
for span in self.spans.iter() {
if start >= span.0 && end <= span.1 {
attrs = span.2;
if range.start >= span.0.start && range.end <= span.0.end {
attrs = span.1;
}
}
attrs
}
/// Split attributes list at an offset
pub fn split_off(&mut self, index: usize) -> Self {
let mut new = Self::new(self.defaults);
let mut i = 0;
while i < self.spans.len() {
if self.spans[i].0.end <= index {
// Leave this in the previous attributes list
i += 1;
} else if self.spans[i].0.start >= index {
// Move this to the new attributes list
let (range, attrs) = self.spans.remove(i);
new.spans.push((
range.start - index..range.end - index,
attrs
));
} else {
// New span has index..end
new.spans.push((
0..self.spans[i].0.end - index,
self.spans[i].1
));
// Old span has start..index
self.spans[i].0.end = index;
i += 1;
}
}
new
}
}

View file

@ -173,6 +173,7 @@ impl fmt::Display for TextMetrics {
}
}
/// A line (or paragraph) of text that is shaped and laid out
pub struct TextBufferLine<'a> {
text: String,
attrs_list: AttrsList<'a>,
@ -182,9 +183,12 @@ pub struct TextBufferLine<'a> {
}
impl<'a> TextBufferLine<'a> {
pub fn new(text: String, attrs_list: AttrsList<'a>) -> Self {
/// Create a new line with the given text and attributes list
/// Cached shaping and layout can be done using the [Self::shape] and
/// [Self::layout] functions
pub fn new<T: Into<String>>(text: T, attrs_list: AttrsList<'a>) -> Self {
Self {
text,
text: text.into(),
attrs_list,
wrap_simple: false,
shape_opt: None,
@ -198,7 +202,8 @@ impl<'a> TextBufferLine<'a> {
}
/// Set text
/// Will reset shape and layout if it differs from current text
///
/// Will reset shape and layout if it differs from current text.
/// Returns true if the line was reset
pub fn set_text<T: AsRef<str> + Into<String>>(&mut self, text: T) -> bool {
if text.as_ref() != &self.text {
@ -215,8 +220,9 @@ impl<'a> TextBufferLine<'a> {
&self.attrs_list
}
/// Set attributes list.
/// Will reset shape and layout if it differs from current attributes list
/// Set attributes list
///
/// Will reset shape and layout if it differs from current attributes list.
/// Returns true if the line was reset
pub fn set_attrs_list(&mut self, attrs_list: AttrsList<'a>) -> bool {
if attrs_list != self.attrs_list {
@ -234,7 +240,8 @@ impl<'a> TextBufferLine<'a> {
}
/// Set simple wrapping setting (wrap by characters only)
/// Will reset shape and layout if it differs from current simple wrapping setting
///
/// Will reset shape and layout if it differs from current simple wrapping setting.
/// Returns true if the line was reset
pub fn set_wrap_simple(&mut self, wrap_simple: bool) -> bool {
if wrap_simple != self.wrap_simple {
@ -288,7 +295,7 @@ impl<'a> TextBufferLine<'a> {
/// A buffer of text that is shaped and laid out
pub struct TextBuffer<'a> {
font_system: &'a FontSystem<'a>,
attrs: Attrs<'a>,
/// Lines (or paragraphs) of text in the buffer
pub lines: Vec<TextBufferLine<'a>>,
metrics: TextMetrics,
width: i32,
@ -297,19 +304,22 @@ pub struct TextBuffer<'a> {
cursor: TextCursor,
cursor_x_opt: Option<i32>,
select_opt: Option<TextCursor>,
/// True if the cursor has been moved. Set to false after processing
///
/// Usually, if this is true, you should run [Self::shape_until_cursor] before redrawing.
/// Otherwise, you should run [Self::shape_until_scroll]
pub cursor_moved: bool,
/// True if a redraw is requires. Set to false after processing
pub redraw: bool,
}
impl<'a> TextBuffer<'a> {
pub fn new(
font_system: &'a FontSystem<'a>,
attrs: Attrs<'a>,
metrics: TextMetrics,
) -> Self {
let mut buffer = Self {
font_system,
attrs,
lines: Vec::new(),
metrics,
width: 0,
@ -321,7 +331,7 @@ impl<'a> TextBuffer<'a> {
cursor_moved: false,
redraw: false,
};
buffer.set_text("");
buffer.set_text("", Attrs::new());
buffer
}
@ -400,6 +410,7 @@ impl<'a> TextBuffer<'a> {
self.shape_until_scroll();
}
/// Shape lines until scroll
pub fn shape_until_scroll(&mut self) {
let lines = self.lines();
@ -558,33 +569,15 @@ impl<'a> TextBuffer<'a> {
self.height / self.metrics.line_height
}
pub fn attrs(&self) -> &Attrs<'a> {
&self.attrs
}
/// Set attributes
pub fn set_attrs(&mut self, attrs: Attrs<'a>) {
if attrs != self.attrs {
self.attrs = attrs;
for line in self.lines.iter_mut() {
line.attrs_list = AttrsList::new(attrs);
line.reset();
}
self.shape_until_scroll();
}
}
/// Set text of buffer
pub fn set_text(&mut self, text: &str) {
/// Set text of buffer, using provided attributes for each line by default
pub fn set_text(&mut self, text: &str, attrs: Attrs<'a>) {
self.lines.clear();
for line in text.lines() {
self.lines.push(TextBufferLine::new(line.to_string(), AttrsList::new(self.attrs)));
self.lines.push(TextBufferLine::new(line.to_string(), AttrsList::new(attrs)));
}
// Make sure there is always one line
if self.lines.is_empty() {
self.lines.push(TextBufferLine::new(String::new(), AttrsList::new(self.attrs)));
self.lines.push(TextBufferLine::new(String::new(), AttrsList::new(attrs)));
}
self.scroll = 0;
@ -594,11 +587,6 @@ impl<'a> TextBuffer<'a> {
self.shape_until_scroll();
}
/// Get the lines of the original text
pub fn text_lines(&self) -> &[TextBufferLine] {
&self.lines
}
/// Perform a [TextAction] on the buffer
pub fn action(&mut self, action: TextAction) {
let old_cursor = self.cursor;
@ -763,14 +751,16 @@ impl<'a> TextBuffer<'a> {
let new_line = {
let line = &mut self.lines[self.cursor.line];
line.reset();
line.text.split_off(self.cursor.index)
TextBufferLine::new(
line.text.split_off(self.cursor.index),
line.attrs_list.split_off(self.cursor.index)
)
};
let next_line = self.cursor.line + 1;
self.lines.insert(next_line, TextBufferLine::new(new_line, AttrsList::new(self.attrs)));
self.cursor.line = next_line;
self.cursor.line += 1;
self.cursor.index = 0;
self.lines.insert(self.cursor.line, new_line);
},
TextAction::Backspace => {
if self.cursor.index > 0 {

View file

@ -94,7 +94,7 @@ fn shape_fallback(
// Set color
//TODO: these attributes should not be related to shaping
for glyph in glyphs.iter_mut() {
let attrs = attrs_list.get_span(glyph.start, glyph.end);
let attrs = attrs_list.get_span(glyph.start..glyph.end);
glyph.color_opt = attrs.color_opt;
}
@ -129,7 +129,7 @@ fn shape_run<'a>(
&line[start_run..end_run],
);
let attrs = attrs_list.get_span(start_run, end_run);
let attrs = attrs_list.get_span(start_run..end_run);
let font_matches = font_system.get_font_matches(attrs);
@ -234,6 +234,7 @@ fn shape_run<'a>(
glyphs
}
/// A shaped glyph
pub struct ShapeGlyph {
pub start: usize,
pub end: usize,
@ -272,6 +273,7 @@ impl ShapeGlyph {
}
}
/// A shaped word (for word wrapping)
pub struct ShapeWord {
pub blank: bool,
pub glyphs: Vec<ShapeGlyph>,
@ -302,7 +304,7 @@ impl ShapeWord {
for (egc_i, egc) in word.grapheme_indices(true) {
let start_egc = start_word + egc_i;
let end_egc = start_egc + egc.len();
let attrs_egc = attrs_list.get_span(start_egc, end_egc);
let attrs_egc = attrs_list.get_span(start_egc..end_egc);
if ! attrs.compatible(&attrs_egc) {
//TODO: more efficient
glyphs.append(&mut shape_run(
@ -334,6 +336,7 @@ impl ShapeWord {
}
}
/// A shaped span (for bidirectional processing)
pub struct ShapeSpan {
pub rtl: bool,
pub words: Vec<ShapeWord>,
@ -413,6 +416,7 @@ impl ShapeSpan {
}
}
/// A shaped line (or paragraph)
pub struct ShapeLine {
pub rtl: bool,
pub spans: Vec<ShapeSpan>,

View file

@ -47,6 +47,7 @@ fn swash_image<'a>(font_system: &'a FontSystem<'a>, context: &mut ScaleContext,
.render(&mut scaler, cache_key.glyph_id)
}
/// Cache for rasterizing with the swash scaler
pub struct SwashCache<'a> {
font_system: &'a FontSystem<'a>,
context: ScaleContext,