Cache font matches, use usize for line index, use font system for swash
This commit is contained in:
parent
94576fb682
commit
119a570ee9
8 changed files with 105 additions and 120 deletions
|
|
@ -64,7 +64,7 @@ pub struct Window {
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
path_opt: Option<PathBuf>,
|
path_opt: Option<PathBuf>,
|
||||||
buffer: Mutex<TextBuffer<'static>>,
|
buffer: Mutex<TextBuffer<'static>>,
|
||||||
cache: Mutex<SwashCache>,
|
cache: Mutex<SwashCache<'static>>,
|
||||||
bold: bool,
|
bold: bool,
|
||||||
italic: bool,
|
italic: bool,
|
||||||
monospaced: bool,
|
monospaced: bool,
|
||||||
|
|
@ -117,11 +117,13 @@ impl Application for Window {
|
||||||
FONT_SIZES[font_size_i],
|
FONT_SIZES[font_size_i],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let cache = SwashCache::new(&FONT_SYSTEM);
|
||||||
|
|
||||||
let mut window = Window {
|
let mut window = Window {
|
||||||
theme: Theme::Dark,
|
theme: Theme::Dark,
|
||||||
path_opt: None,
|
path_opt: None,
|
||||||
buffer: Mutex::new(buffer),
|
buffer: Mutex::new(buffer),
|
||||||
cache: Mutex::new(SwashCache::new()),
|
cache: Mutex::new(cache),
|
||||||
bold: false,
|
bold: false,
|
||||||
italic: false,
|
italic: false,
|
||||||
monospaced: true,
|
monospaced: true,
|
||||||
|
|
@ -178,7 +180,7 @@ impl Application for Window {
|
||||||
} else {
|
} else {
|
||||||
cosmic_text::Weight::NORMAL
|
cosmic_text::Weight::NORMAL
|
||||||
});
|
});
|
||||||
buffer.set_attrs(&FONT_SYSTEM, attrs);
|
buffer.set_attrs(attrs);
|
||||||
},
|
},
|
||||||
Message::Italic(italic) => {
|
Message::Italic(italic) => {
|
||||||
self.italic = italic;
|
self.italic = italic;
|
||||||
|
|
@ -189,7 +191,7 @@ impl Application for Window {
|
||||||
} else {
|
} else {
|
||||||
cosmic_text::Style::Normal
|
cosmic_text::Style::Normal
|
||||||
});
|
});
|
||||||
buffer.set_attrs(&FONT_SYSTEM, attrs);
|
buffer.set_attrs(attrs);
|
||||||
},
|
},
|
||||||
Message::Monospaced(monospaced) => {
|
Message::Monospaced(monospaced) => {
|
||||||
self.monospaced = monospaced;
|
self.monospaced = monospaced;
|
||||||
|
|
@ -202,7 +204,7 @@ impl Application for Window {
|
||||||
cosmic_text::Family::SansSerif
|
cosmic_text::Family::SansSerif
|
||||||
})
|
})
|
||||||
.monospaced(monospaced);
|
.monospaced(monospaced);
|
||||||
buffer.set_attrs(&FONT_SYSTEM, attrs);
|
buffer.set_attrs(attrs);
|
||||||
},
|
},
|
||||||
Message::MetricsChanged(metrics) => {
|
Message::MetricsChanged(metrics) => {
|
||||||
let mut buffer = self.buffer.lock().unwrap();
|
let mut buffer = self.buffer.lock().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -63,16 +63,16 @@ impl StyleSheet for Theme {
|
||||||
|
|
||||||
pub struct TextBox<'a> {
|
pub struct TextBox<'a> {
|
||||||
buffer: &'a Mutex<TextBuffer<'static>>,
|
buffer: &'a Mutex<TextBuffer<'static>>,
|
||||||
cache: &'a Mutex<SwashCache>,
|
cache: &'a Mutex<SwashCache<'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TextBox<'a> {
|
impl<'a> TextBox<'a> {
|
||||||
pub fn new(buffer: &'a Mutex<TextBuffer<'static>>, cache: &'a Mutex<SwashCache>) -> Self {
|
pub fn new(buffer: &'a Mutex<TextBuffer<'static>>, cache: &'a Mutex<SwashCache<'static>>) -> Self {
|
||||||
Self { buffer, cache }
|
Self { buffer, cache }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text_box<'a>(buffer: &'a Mutex<TextBuffer<'static>>, cache: &'a Mutex<SwashCache>) -> TextBox<'a> {
|
pub fn text_box<'a>(buffer: &'a Mutex<TextBuffer<'static>>, cache: &'a Mutex<SwashCache<'static>>) -> TextBox<'a> {
|
||||||
TextBox::new(buffer, cache)
|
TextBox::new(buffer, cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ fn main() {
|
||||||
default_text.to_string()
|
default_text.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut swash_cache = SwashCache::new();
|
let mut swash_cache = SwashCache::new(&font_system);
|
||||||
|
|
||||||
let line_x = 8 * display_scale;
|
let line_x = 8 * display_scale;
|
||||||
let attrs = cosmic_text::Attrs::new().monospaced(cfg!(feature = "mono"));
|
let attrs = cosmic_text::Attrs::new().monospaced(cfg!(feature = "mono"));
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ fn main() {
|
||||||
window.height() as i32
|
window.height() as i32
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut swash_cache = SwashCache::new();
|
let mut swash_cache = SwashCache::new(&font_system);
|
||||||
|
|
||||||
let text = if let Some(arg) = env::args().nth(1) {
|
let text = if let Some(arg) = env::args().nth(1) {
|
||||||
fs::read_to_string(&arg).expect("failed to open file")
|
fs::read_to_string(&arg).expect("failed to open file")
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
pub use fontdb::{Family, Stretch, Style, Weight};
|
pub use fontdb::{Family, Stretch, Style, Weight};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
pub struct Attrs<'a> {
|
pub struct Attrs<'a> {
|
||||||
pub family: Family<'a>,
|
pub family: Family<'a>,
|
||||||
pub monospaced: bool,
|
pub monospaced: bool,
|
||||||
|
|
|
||||||
109
src/buffer.rs
109
src/buffer.rs
|
|
@ -3,6 +3,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
cmp,
|
cmp,
|
||||||
fmt,
|
fmt,
|
||||||
|
sync::Arc,
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
@ -52,32 +53,32 @@ pub enum TextAction {
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
pub struct TextCursor {
|
pub struct TextCursor {
|
||||||
/// Text line the cursor is on
|
/// Text line the cursor is on
|
||||||
pub line: TextLineIndex,
|
pub line: usize,
|
||||||
/// Index of glyph at cursor (will insert behind this glyph)
|
/// Index of glyph at cursor (will insert behind this glyph)
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextCursor {
|
impl TextCursor {
|
||||||
pub const fn new(line: TextLineIndex, index: usize) -> Self {
|
pub const fn new(line: usize, index: usize) -> Self {
|
||||||
Self { line, index }
|
Self { line, index }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TextLayoutCursor {
|
struct TextLayoutCursor {
|
||||||
line: TextLineIndex,
|
line: usize,
|
||||||
layout: usize,
|
layout: usize,
|
||||||
glyph: usize,
|
glyph: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextLayoutCursor {
|
impl TextLayoutCursor {
|
||||||
fn new(line: TextLineIndex, layout: usize, glyph: usize) -> Self {
|
fn new(line: usize, layout: usize, glyph: usize) -> Self {
|
||||||
Self { line, layout, glyph }
|
Self { line, layout, glyph }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TextLayoutRun<'a> {
|
pub struct TextLayoutRun<'a> {
|
||||||
/// The index of the original text line
|
/// The index of the original text line
|
||||||
pub line_i: TextLineIndex,
|
pub line_i: usize,
|
||||||
/// The original text line
|
/// The original text line
|
||||||
pub text: &'a str,
|
pub text: &'a str,
|
||||||
/// True if the original paragraph direction is RTL
|
/// True if the original paragraph direction is RTL
|
||||||
|
|
@ -130,7 +131,7 @@ impl<'a, 'b> Iterator for TextLayoutRunIter<'a, 'b> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Some(TextLayoutRun {
|
return Some(TextLayoutRun {
|
||||||
line_i: TextLineIndex::new(self.line_i),
|
line_i: self.line_i,
|
||||||
text: line.text.as_str(),
|
text: line.text.as_str(),
|
||||||
rtl: shape.rtl,
|
rtl: shape.rtl,
|
||||||
glyphs: &layout_line.glyphs,
|
glyphs: &layout_line.glyphs,
|
||||||
|
|
@ -145,20 +146,6 @@ impl<'a, 'b> Iterator for TextLayoutRunIter<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Index of a text line
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
|
|
||||||
pub struct TextLineIndex(usize);
|
|
||||||
|
|
||||||
impl TextLineIndex {
|
|
||||||
pub fn new(index: usize) -> Self {
|
|
||||||
Self(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self) -> usize {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Metrics of text
|
/// Metrics of text
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
pub struct TextMetrics {
|
pub struct TextMetrics {
|
||||||
|
|
@ -237,8 +224,10 @@ impl TextBufferLine {
|
||||||
|
|
||||||
/// A buffer of text that is shaped and laid out
|
/// A buffer of text that is shaped and laid out
|
||||||
pub struct TextBuffer<'a> {
|
pub struct TextBuffer<'a> {
|
||||||
font_matches: FontMatches<'a>,
|
font_system: &'a FontSystem<'a>,
|
||||||
|
font_matches: Arc<FontMatches<'a>>,
|
||||||
attrs: Attrs<'a>,
|
attrs: Attrs<'a>,
|
||||||
|
attr_spans: Vec<(TextCursor, usize, Attrs<'a>)>,
|
||||||
lines: Vec<TextBufferLine>,
|
lines: Vec<TextBufferLine>,
|
||||||
metrics: TextMetrics,
|
metrics: TextMetrics,
|
||||||
width: i32,
|
width: i32,
|
||||||
|
|
@ -256,10 +245,12 @@ impl<'a> TextBuffer<'a> {
|
||||||
attrs: Attrs<'a>,
|
attrs: Attrs<'a>,
|
||||||
metrics: TextMetrics,
|
metrics: TextMetrics,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let font_matches = font_system.matches_attrs(&attrs);
|
let font_matches = font_system.get_font_matches(attrs);
|
||||||
let mut buffer = Self {
|
let mut buffer = Self {
|
||||||
|
font_system,
|
||||||
font_matches,
|
font_matches,
|
||||||
attrs,
|
attrs,
|
||||||
|
attr_spans: Vec::new(),
|
||||||
lines: Vec::new(),
|
lines: Vec::new(),
|
||||||
metrics,
|
metrics,
|
||||||
width: 0,
|
width: 0,
|
||||||
|
|
@ -312,7 +303,7 @@ impl<'a> TextBuffer<'a> {
|
||||||
let mut reshaped = 0;
|
let mut reshaped = 0;
|
||||||
let mut layout_i = 0;
|
let mut layout_i = 0;
|
||||||
for (line_i, line) in self.lines.iter_mut().enumerate() {
|
for (line_i, line) in self.lines.iter_mut().enumerate() {
|
||||||
if line_i > self.cursor.line.get() {
|
if line_i > self.cursor.line {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -324,7 +315,7 @@ impl<'a> TextBuffer<'a> {
|
||||||
self.metrics.font_size,
|
self.metrics.font_size,
|
||||||
self.width
|
self.width
|
||||||
);
|
);
|
||||||
if line_i == self.cursor.line.get() {
|
if line_i == self.cursor.line {
|
||||||
let layout_cursor = self.layout_cursor(&self.cursor);
|
let layout_cursor = self.layout_cursor(&self.cursor);
|
||||||
layout_i += layout_cursor.layout as i32;
|
layout_i += layout_cursor.layout as i32;
|
||||||
break;
|
break;
|
||||||
|
|
@ -385,7 +376,7 @@ impl<'a> TextBuffer<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_cursor(&self, cursor: &TextCursor) -> TextLayoutCursor {
|
fn layout_cursor(&self, cursor: &TextCursor) -> TextLayoutCursor {
|
||||||
let line = &self.lines[cursor.line.get()];
|
let line = &self.lines[cursor.line];
|
||||||
|
|
||||||
let layout = line.layout_opt.as_ref().unwrap(); //TODO: ensure layout is done?
|
let layout = line.layout_opt.as_ref().unwrap(); //TODO: ensure layout is done?
|
||||||
for (layout_i, layout_line) in layout.iter().enumerate() {
|
for (layout_i, layout_line) in layout.iter().enumerate() {
|
||||||
|
|
@ -428,7 +419,7 @@ impl<'a> TextBuffer<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_layout_cursor(&mut self, cursor: TextLayoutCursor) {
|
fn set_layout_cursor(&mut self, cursor: TextLayoutCursor) {
|
||||||
let line = &mut self.lines[cursor.line.get()];
|
let line = &mut self.lines[cursor.line];
|
||||||
let layout = line.layout(
|
let layout = line.layout(
|
||||||
&self.font_matches,
|
&self.font_matches,
|
||||||
self.metrics.font_size,
|
self.metrics.font_size,
|
||||||
|
|
@ -516,9 +507,9 @@ impl<'a> TextBuffer<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set attributes
|
/// Set attributes
|
||||||
pub fn set_attrs(&mut self, font_system: &'a FontSystem<'a>, attrs: Attrs<'a>) {
|
pub fn set_attrs(&mut self, attrs: Attrs<'a>) {
|
||||||
if attrs != self.attrs {
|
if attrs != self.attrs {
|
||||||
self.font_matches = font_system.matches_attrs(&attrs);
|
self.font_matches = self.font_system.get_font_matches(attrs);
|
||||||
self.attrs = attrs;
|
self.attrs = attrs;
|
||||||
|
|
||||||
for line in self.lines.iter_mut() {
|
for line in self.lines.iter_mut() {
|
||||||
|
|
@ -529,6 +520,10 @@ impl<'a> TextBuffer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_attr_span(&mut self, cursor: TextCursor, len: usize, attrs: Attrs<'a>) {
|
||||||
|
self.attr_spans.push((cursor, len, attrs));
|
||||||
|
}
|
||||||
|
|
||||||
/// Set text of buffer
|
/// Set text of buffer
|
||||||
pub fn set_text(&mut self, text: &str) {
|
pub fn set_text(&mut self, text: &str) {
|
||||||
self.lines.clear();
|
self.lines.clear();
|
||||||
|
|
@ -558,7 +553,7 @@ impl<'a> TextBuffer<'a> {
|
||||||
|
|
||||||
match action {
|
match action {
|
||||||
TextAction::Previous => {
|
TextAction::Previous => {
|
||||||
let line = &mut self.lines[self.cursor.line.get()];
|
let line = &mut self.lines[self.cursor.line];
|
||||||
|
|
||||||
if self.cursor.index > 0 {
|
if self.cursor.index > 0 {
|
||||||
// Find previous character index
|
// Find previous character index
|
||||||
|
|
@ -573,14 +568,14 @@ impl<'a> TextBuffer<'a> {
|
||||||
|
|
||||||
self.cursor.index = prev_index;
|
self.cursor.index = prev_index;
|
||||||
self.redraw = true;
|
self.redraw = true;
|
||||||
} else if self.cursor.line.get() > 0 {
|
} else if self.cursor.line > 0 {
|
||||||
self.cursor.line = TextLineIndex::new(self.cursor.line.get() - 1);
|
self.cursor.line -= 1;
|
||||||
self.cursor.index = self.lines[self.cursor.line.get()].text.len();
|
self.cursor.index = self.lines[self.cursor.line].text.len();
|
||||||
self.redraw = true;
|
self.redraw = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
TextAction::Next => {
|
TextAction::Next => {
|
||||||
let line = &mut self.lines[self.cursor.line.get()];
|
let line = &mut self.lines[self.cursor.line];
|
||||||
|
|
||||||
if self.cursor.index < line.text.len() {
|
if self.cursor.index < line.text.len() {
|
||||||
for (i, c) in line.text.grapheme_indices(true) {
|
for (i, c) in line.text.grapheme_indices(true) {
|
||||||
|
|
@ -590,14 +585,14 @@ impl<'a> TextBuffer<'a> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if self.cursor.line.get() + 1 < self.lines.len() {
|
} else if self.cursor.line + 1 < self.lines.len() {
|
||||||
self.cursor.line = TextLineIndex::new(self.cursor.line.get() + 1);
|
self.cursor.line += 1;
|
||||||
self.cursor.index = 0;
|
self.cursor.index = 0;
|
||||||
self.redraw = true;
|
self.redraw = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
TextAction::Left => {
|
TextAction::Left => {
|
||||||
let rtl_opt = self.lines[self.cursor.line.get()].shape_opt.as_ref().map(|shape| shape.rtl);
|
let rtl_opt = self.lines[self.cursor.line].shape_opt.as_ref().map(|shape| shape.rtl);
|
||||||
if let Some(rtl) = rtl_opt {
|
if let Some(rtl) = rtl_opt {
|
||||||
if rtl {
|
if rtl {
|
||||||
self.action(TextAction::Next);
|
self.action(TextAction::Next);
|
||||||
|
|
@ -607,7 +602,7 @@ impl<'a> TextBuffer<'a> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
TextAction::Right => {
|
TextAction::Right => {
|
||||||
let rtl_opt = self.lines[self.cursor.line.get()].shape_opt.as_ref().map(|shape| shape.rtl);
|
let rtl_opt = self.lines[self.cursor.line].shape_opt.as_ref().map(|shape| shape.rtl);
|
||||||
if let Some(rtl) = rtl_opt {
|
if let Some(rtl) = rtl_opt {
|
||||||
if rtl {
|
if rtl {
|
||||||
self.action(TextAction::Previous);
|
self.action(TextAction::Previous);
|
||||||
|
|
@ -621,8 +616,8 @@ impl<'a> TextBuffer<'a> {
|
||||||
let mut cursor = self.layout_cursor(&self.cursor);
|
let mut cursor = self.layout_cursor(&self.cursor);
|
||||||
if cursor.layout > 0 {
|
if cursor.layout > 0 {
|
||||||
cursor.layout -= 1;
|
cursor.layout -= 1;
|
||||||
} else if cursor.line.get() > 0 {
|
} else if cursor.line > 0 {
|
||||||
cursor.line = TextLineIndex::new(cursor.line.get() - 1);
|
cursor.line -= 1;
|
||||||
cursor.layout = usize::max_value();
|
cursor.layout = usize::max_value();
|
||||||
}
|
}
|
||||||
self.set_layout_cursor(cursor);
|
self.set_layout_cursor(cursor);
|
||||||
|
|
@ -631,7 +626,7 @@ impl<'a> TextBuffer<'a> {
|
||||||
//TODO: make this preserve X as best as possible!
|
//TODO: make this preserve X as best as possible!
|
||||||
let mut cursor = self.layout_cursor(&self.cursor);
|
let mut cursor = self.layout_cursor(&self.cursor);
|
||||||
let layout_len = {
|
let layout_len = {
|
||||||
let line = &mut self.lines[cursor.line.get()];
|
let line = &mut self.lines[cursor.line];
|
||||||
let layout = line.layout(
|
let layout = line.layout(
|
||||||
&self.font_matches,
|
&self.font_matches,
|
||||||
self.metrics.font_size,
|
self.metrics.font_size,
|
||||||
|
|
@ -641,8 +636,8 @@ impl<'a> TextBuffer<'a> {
|
||||||
};
|
};
|
||||||
if cursor.layout + 1 < layout_len {
|
if cursor.layout + 1 < layout_len {
|
||||||
cursor.layout += 1;
|
cursor.layout += 1;
|
||||||
} else if cursor.line.get() + 1 < self.lines.len() {
|
} else if cursor.line + 1 < self.lines.len() {
|
||||||
cursor.line = TextLineIndex::new(cursor.line.get() + 1);
|
cursor.line += 1;
|
||||||
cursor.layout = 0;
|
cursor.layout = 0;
|
||||||
}
|
}
|
||||||
self.set_layout_cursor(cursor);
|
self.set_layout_cursor(cursor);
|
||||||
|
|
@ -678,7 +673,7 @@ impl<'a> TextBuffer<'a> {
|
||||||
// Filter out special chars (except for tab), use TextAction instead
|
// Filter out special chars (except for tab), use TextAction instead
|
||||||
log::debug!("Refusing to insert control character {:?}", character);
|
log::debug!("Refusing to insert control character {:?}", character);
|
||||||
} else {
|
} else {
|
||||||
let line = &mut self.lines[self.cursor.line.get()];
|
let line = &mut self.lines[self.cursor.line];
|
||||||
line.reset();
|
line.reset();
|
||||||
line.text.insert(self.cursor.index, character);
|
line.text.insert(self.cursor.index, character);
|
||||||
|
|
||||||
|
|
@ -687,20 +682,20 @@ impl<'a> TextBuffer<'a> {
|
||||||
},
|
},
|
||||||
TextAction::Enter => {
|
TextAction::Enter => {
|
||||||
let new_line = {
|
let new_line = {
|
||||||
let line = &mut self.lines[self.cursor.line.get()];
|
let line = &mut self.lines[self.cursor.line];
|
||||||
line.reset();
|
line.reset();
|
||||||
line.text.split_off(self.cursor.index)
|
line.text.split_off(self.cursor.index)
|
||||||
};
|
};
|
||||||
|
|
||||||
let next_line = self.cursor.line.get() + 1;
|
let next_line = self.cursor.line + 1;
|
||||||
self.lines.insert(next_line, TextBufferLine::new(new_line));
|
self.lines.insert(next_line, TextBufferLine::new(new_line));
|
||||||
|
|
||||||
self.cursor.line = TextLineIndex::new(next_line);
|
self.cursor.line = next_line;
|
||||||
self.cursor.index = 0;
|
self.cursor.index = 0;
|
||||||
},
|
},
|
||||||
TextAction::Backspace => {
|
TextAction::Backspace => {
|
||||||
if self.cursor.index > 0 {
|
if self.cursor.index > 0 {
|
||||||
let line = &mut self.lines[self.cursor.line.get()];
|
let line = &mut self.lines[self.cursor.line];
|
||||||
line.reset();
|
line.reset();
|
||||||
|
|
||||||
// Find previous character index
|
// Find previous character index
|
||||||
|
|
@ -716,23 +711,23 @@ impl<'a> TextBuffer<'a> {
|
||||||
self.cursor.index = prev_index;
|
self.cursor.index = prev_index;
|
||||||
|
|
||||||
line.text.remove(self.cursor.index);
|
line.text.remove(self.cursor.index);
|
||||||
} else if self.cursor.line.get() > 0 {
|
} else if self.cursor.line > 0 {
|
||||||
let mut line_index = self.cursor.line.get();
|
let mut line_index = self.cursor.line;
|
||||||
let old_line = self.lines.remove(line_index);
|
let old_line = self.lines.remove(line_index);
|
||||||
line_index -= 1;
|
line_index -= 1;
|
||||||
|
|
||||||
let line = &mut self.lines[line_index];
|
let line = &mut self.lines[line_index];
|
||||||
line.reset();
|
line.reset();
|
||||||
|
|
||||||
self.cursor.line = TextLineIndex::new(line_index);
|
self.cursor.line = line_index;
|
||||||
self.cursor.index = line.text.len();
|
self.cursor.index = line.text.len();
|
||||||
|
|
||||||
line.text.push_str(&old_line.text);
|
line.text.push_str(&old_line.text);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
TextAction::Delete => {
|
TextAction::Delete => {
|
||||||
if self.cursor.index < self.lines[self.cursor.line.get()].text.len() {
|
if self.cursor.index < self.lines[self.cursor.line].text.len() {
|
||||||
let line = &mut self.lines[self.cursor.line.get()];
|
let line = &mut self.lines[self.cursor.line];
|
||||||
line.reset();
|
line.reset();
|
||||||
|
|
||||||
if let Some((i, c)) = line
|
if let Some((i, c)) = line
|
||||||
|
|
@ -744,10 +739,10 @@ impl<'a> TextBuffer<'a> {
|
||||||
line.text.replace_range(i..(i + c.len()), "");
|
line.text.replace_range(i..(i + c.len()), "");
|
||||||
self.cursor.index = i;
|
self.cursor.index = i;
|
||||||
}
|
}
|
||||||
} else if self.cursor.line.get() + 1 < self.lines.len() {
|
} else if self.cursor.line + 1 < self.lines.len() {
|
||||||
let old_line = self.lines.remove(self.cursor.line.get() + 1);
|
let old_line = self.lines.remove(self.cursor.line + 1);
|
||||||
|
|
||||||
let line = &mut self.lines[self.cursor.line.get()];
|
let line = &mut self.lines[self.cursor.line];
|
||||||
line.reset();
|
line.reset();
|
||||||
|
|
||||||
line.text.push_str(&old_line.text);
|
line.text.push_str(&old_line.text);
|
||||||
|
|
@ -860,7 +855,7 @@ impl<'a> TextBuffer<'a> {
|
||||||
let text_glyph = &run.text[glyph.start..glyph.end];
|
let text_glyph = &run.text[glyph.start..glyph.end];
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"{}, {}: '{}' ('{}'): '{}' ({:?})",
|
"{}, {}: '{}' ('{}'): '{}' ({:?})",
|
||||||
self.cursor.line.get(),
|
self.cursor.line,
|
||||||
self.cursor.index,
|
self.cursor.index,
|
||||||
font_opt.as_ref().map_or("?", |font| font.info.family.as_str()),
|
font_opt.as_ref().map_or("?", |font| font.info.family.as_str()),
|
||||||
font_opt.as_ref().map_or("?", |font| font.info.post_script_name.as_str()),
|
font_opt.as_ref().map_or("?", |font| font.info.post_script_name.as_str()),
|
||||||
|
|
@ -1045,7 +1040,7 @@ impl<'a> TextBuffer<'a> {
|
||||||
|
|
||||||
for glyph in run.glyphs.iter() {
|
for glyph in run.glyphs.iter() {
|
||||||
let (cache_key, x_int, y_int) = (glyph.cache_key, glyph.x_int, glyph.y_int);
|
let (cache_key, x_int, y_int) = (glyph.cache_key, glyph.x_int, glyph.y_int);
|
||||||
cache.with_pixels(&self.font_matches, cache_key, color, |x, y, color| {
|
cache.with_pixels(cache_key, color, |x, y, color| {
|
||||||
f(x_int + x, line_y + y_int + y, 1, 1, color)
|
f(x_int + x, line_y + y_int + y, 1, 1, color)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,14 @@ use std::{
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Attrs, Family, Font, FontMatches};
|
use crate::{Attrs, Font, FontMatches};
|
||||||
|
|
||||||
/// Access system fonts
|
/// Access system fonts
|
||||||
pub struct FontSystem<'a> {
|
pub struct FontSystem<'a> {
|
||||||
pub locale: String,
|
pub locale: String,
|
||||||
pub db: fontdb::Database,
|
pub db: fontdb::Database,
|
||||||
pub font_cache: Mutex<HashMap<fontdb::ID, Option<Arc<Font<'a>>>>>,
|
pub font_cache: Mutex<HashMap<fontdb::ID, Option<Arc<Font<'a>>>>>,
|
||||||
|
pub font_matches_cache: Mutex<HashMap<Attrs<'a>, Arc<FontMatches<'a>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FontSystem<'a> {
|
impl<'a> FontSystem<'a> {
|
||||||
|
|
@ -60,6 +61,7 @@ impl<'a> FontSystem<'a> {
|
||||||
locale,
|
locale,
|
||||||
db,
|
db,
|
||||||
font_cache: Mutex::new(HashMap::new()),
|
font_cache: Mutex::new(HashMap::new()),
|
||||||
|
font_matches_cache: Mutex::new(HashMap::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,48 +79,33 @@ impl<'a> FontSystem<'a> {
|
||||||
}).clone()
|
}).clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn matches<F: Fn(&fontdb::FaceInfo) -> bool>(
|
pub fn get_font_matches(&'a self, attrs: Attrs<'a>) -> Arc<FontMatches<'a>> {
|
||||||
&'a self,
|
let mut font_matches_cache = self.font_matches_cache.lock().unwrap();
|
||||||
default_family: &Family,
|
font_matches_cache.entry(attrs).or_insert_with(|| {
|
||||||
f: F,
|
let now = std::time::Instant::now();
|
||||||
) -> FontMatches<'_> {
|
|
||||||
let mut fonts = Vec::new();
|
let mut fonts = Vec::new();
|
||||||
for face in self.db.faces() {
|
for face in self.db.faces() {
|
||||||
if !f(face) {
|
if !attrs.matches(face) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.get_font(face.id) {
|
||||||
|
Some(font) => fonts.push(font),
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.get_font(face.id) {
|
let font_matches = Arc::new(FontMatches {
|
||||||
Some(font) => fonts.push(font),
|
locale: &self.locale,
|
||||||
None => (),
|
default_family: self.db.family_name(&attrs.family).to_string(),
|
||||||
}
|
fonts
|
||||||
}
|
});
|
||||||
|
|
||||||
FontMatches {
|
let elapsed = now.elapsed();
|
||||||
locale: &self.locale,
|
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
|
||||||
default_family: self.db.family_name(default_family).to_string(),
|
|
||||||
fonts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn matches_attrs(&'a self, attrs: &Attrs) -> FontMatches<'_> {
|
font_matches
|
||||||
self.matches(&attrs.family, |face| {
|
}).clone()
|
||||||
let matched = attrs.matches(face);
|
|
||||||
|
|
||||||
if matched {
|
|
||||||
log::debug!(
|
|
||||||
"{:?}: family '{}' postscript name '{}' style {:?} weight {:?} stretch {:?} monospaced {:?}",
|
|
||||||
face.id,
|
|
||||||
face.family,
|
|
||||||
face.post_script_name,
|
|
||||||
face.style,
|
|
||||||
face.weight,
|
|
||||||
face.stretch,
|
|
||||||
face.monospaced
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
matched
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
src/swash.rs
25
src/swash.rs
|
|
@ -5,12 +5,12 @@ use swash::scale::{ScaleContext, image::Content};
|
||||||
use swash::scale::{Render, Source, StrikeWith};
|
use swash::scale::{Render, Source, StrikeWith};
|
||||||
use swash::zeno::{Format, Vector};
|
use swash::zeno::{Format, Vector};
|
||||||
|
|
||||||
use crate::{CacheKey, FontMatches};
|
use crate::{CacheKey, FontSystem};
|
||||||
|
|
||||||
pub use swash::scale::image::{Content as SwashContent, Image as SwashImage};
|
pub use swash::scale::image::{Content as SwashContent, Image as SwashImage};
|
||||||
|
|
||||||
fn swash_image(context: &mut ScaleContext, matches: &FontMatches, cache_key: CacheKey) -> Option<SwashImage> {
|
fn swash_image<'a>(font_system: &'a FontSystem<'a>, context: &mut ScaleContext, cache_key: CacheKey) -> Option<SwashImage> {
|
||||||
let font = match matches.get_font(&cache_key.font_id) {
|
let font = match font_system.get_font(cache_key.font_id) {
|
||||||
Some(some) => some,
|
Some(some) => some,
|
||||||
None => {
|
None => {
|
||||||
log::warn!("did not find font {:?}", cache_key.font_id);
|
log::warn!("did not find font {:?}", cache_key.font_id);
|
||||||
|
|
@ -47,41 +47,42 @@ fn swash_image(context: &mut ScaleContext, matches: &FontMatches, cache_key: Cac
|
||||||
.render(&mut scaler, cache_key.glyph_id)
|
.render(&mut scaler, cache_key.glyph_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SwashCache {
|
pub struct SwashCache<'a> {
|
||||||
|
font_system: &'a FontSystem<'a>,
|
||||||
context: ScaleContext,
|
context: ScaleContext,
|
||||||
pub image_cache: HashMap<CacheKey, Option<SwashImage>>,
|
pub image_cache: HashMap<CacheKey, Option<SwashImage>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SwashCache {
|
impl<'a> SwashCache<'a> {
|
||||||
/// Create a new swash cache
|
/// Create a new swash cache
|
||||||
pub fn new() -> Self {
|
pub fn new(font_system: &'a FontSystem<'a>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
font_system: font_system,
|
||||||
context: ScaleContext::new(),
|
context: ScaleContext::new(),
|
||||||
image_cache: HashMap::new()
|
image_cache: HashMap::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a swash Image from a cache key, without caching results
|
/// Create a swash Image from a cache key, without caching results
|
||||||
pub fn get_image_uncached(&mut self, matches: &FontMatches, cache_key: CacheKey) -> Option<SwashImage> {
|
pub fn get_image_uncached(&mut self, cache_key: CacheKey) -> Option<SwashImage> {
|
||||||
swash_image(&mut self.context, matches, cache_key)
|
swash_image(self.font_system, &mut self.context, cache_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a swash Image from a cache key, caching results
|
/// Create a swash Image from a cache key, caching results
|
||||||
pub fn get_image(&mut self, matches: &FontMatches, cache_key: CacheKey) -> &Option<SwashImage> {
|
pub fn get_image(&mut self, cache_key: CacheKey) -> &Option<SwashImage> {
|
||||||
self.image_cache.entry(cache_key).or_insert_with(|| {
|
self.image_cache.entry(cache_key).or_insert_with(|| {
|
||||||
swash_image(&mut self.context, matches, cache_key)
|
swash_image(self.font_system, &mut self.context, cache_key)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enumerate pixels in an Image, use `with_image` for better performance
|
/// Enumerate pixels in an Image, use `with_image` for better performance
|
||||||
pub fn with_pixels<F: FnMut(i32, i32, u32)>(
|
pub fn with_pixels<F: FnMut(i32, i32, u32)>(
|
||||||
&mut self,
|
&mut self,
|
||||||
matches: &FontMatches<'_>,
|
|
||||||
cache_key: CacheKey,
|
cache_key: CacheKey,
|
||||||
base: u32,
|
base: u32,
|
||||||
mut f: F
|
mut f: F
|
||||||
) {
|
) {
|
||||||
if let Some(image) = self.get_image(matches, cache_key) {
|
if let Some(image) = self.get_image(cache_key) {
|
||||||
let x = image.placement.left;
|
let x = image.placement.left;
|
||||||
let y = -image.placement.top;
|
let y = -image.placement.top;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue