Run cargo fmt

This commit is contained in:
Jeremy Soller 2023-01-04 20:03:03 -07:00
parent 00bc4d1e88
commit 8cc988d374
25 changed files with 732 additions and 731 deletions

View file

@ -24,12 +24,7 @@ impl Color {
/// Create new color with red, green, blue, and alpha components
#[inline]
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self(
((a as u32) << 24) |
((r as u32) << 16) |
((g as u32) << 8) |
(b as u32)
)
Self(((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32))
}
/// Get the red component
@ -165,22 +160,20 @@ impl<'a> Attrs<'a> {
/// Check if font matches
pub fn matches(&self, face: &fontdb::FaceInfo) -> bool {
//TODO: smarter way of including emoji
face.post_script_name.contains("Emoji") ||
(
face.style == self.style &&
face.weight == self.weight &&
face.stretch == self.stretch &&
face.monospaced == self.monospaced
)
face.post_script_name.contains("Emoji")
|| (face.style == self.style
&& face.weight == self.weight
&& face.stretch == self.stretch
&& face.monospaced == self.monospaced)
}
/// 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
&& self.stretch == other.stretch
&& self.style == other.style
&& self.weight == other.weight
&& self.monospaced == other.monospaced
&& self.stretch == other.stretch
&& self.style == other.style
&& self.weight == other.weight
}
}
@ -268,7 +261,10 @@ impl AttrsList {
///
/// This returns a span that contains the index
pub fn get_span(&self, index: usize) -> Attrs {
self.spans.get(&index).map(|v| v.as_attrs()).unwrap_or(self.defaults.as_attrs())
self.spans
.get(&index)
.map(|v| v.as_attrs())
.unwrap_or(self.defaults.as_attrs())
}
/// Split attributes list at an offset
@ -288,20 +284,19 @@ impl AttrsList {
}
for (key, resize) in removes {
let (range, attrs) = self.spans.get_key_value(&key.start).map(|v| (v.0.clone(), v.1.clone())).expect("attrs span not found");
let (range, attrs) = self
.spans
.get_key_value(&key.start)
.map(|v| (v.0.clone(), v.1.clone()))
.expect("attrs span not found");
self.spans.remove(key);
if resize {
new.spans.insert(
0..range.end - index,
attrs.clone()
);
new.spans.insert(0..range.end - index, attrs.clone());
self.spans.insert(range.start..index, attrs);
} else {
new.spans.insert(
range.start - index..range.end - index,
attrs
);
new.spans
.insert(range.start - index..range.end - index, attrs);
}
}
new

View file

@ -5,15 +5,12 @@ use alloc::{
string::{String, ToString},
vec::Vec,
};
use core::{
cmp,
fmt,
};
use core::{cmp, fmt};
use unicode_segmentation::UnicodeSegmentation;
use crate::{Attrs, AttrsList, BufferLine, FontSystem, LayoutGlyph, LayoutLine, ShapeLine, Wrap};
#[cfg(feature = "swash")]
use crate::Color;
use crate::{Attrs, AttrsList, BufferLine, FontSystem, LayoutGlyph, LayoutLine, ShapeLine, Wrap};
/// Current cursor location
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
@ -39,7 +36,11 @@ pub struct LayoutCursor {
impl LayoutCursor {
pub fn new(line: usize, layout: usize, glyph: usize) -> Self {
Self { line, layout, glyph }
Self {
line,
layout,
glyph,
}
}
}
@ -81,13 +82,25 @@ impl<'a> LayoutRun<'a> {
let cursor = Cursor::new(self.line_i, self.glyphs.last().map_or(0, |glyph| glyph.end));
if cursor >= cursor_start && cursor <= cursor_end {
if x_start.is_none() {
x_start = Some(self.glyphs.last().map_or(0., |glyph| glyph.x + glyph.w * ltr_factor));
x_start = Some(
self.glyphs
.last()
.map_or(0., |glyph| glyph.x + glyph.w * ltr_factor),
);
}
x_end = Some(self.glyphs.last().map_or(0., |glyph| glyph.x + glyph.w * ltr_factor));
x_end = Some(
self.glyphs
.last()
.map_or(0., |glyph| glyph.x + glyph.w * ltr_factor),
);
}
if let Some(x_start) = x_start {
let x_end = x_end.expect("end of cursor not found");
let (x_start, x_end) = if x_start < x_end { (x_start, x_end) } else { (x_end, x_start) };
let (x_start, x_end) = if x_start < x_end {
(x_start, x_end)
} else {
(x_end, x_start)
};
Some((x_start, x_end - x_start))
} else {
None
@ -107,14 +120,28 @@ pub struct LayoutRunIter<'a, 'b> {
impl<'a, 'b> LayoutRunIter<'a, 'b> {
pub fn new(buffer: &'b Buffer<'a>) -> Self {
let total_layout_lines: usize = buffer.lines.iter().map(|line| line.layout_opt().as_ref().map(|layout| layout.len()).unwrap_or_default()).sum();
let top_cropped_layout_lines = total_layout_lines.saturating_sub(buffer.scroll.try_into().unwrap_or_default());
let maximum_lines = buffer.height.checked_div(buffer.metrics.line_height).unwrap_or_default();
let bottom_cropped_layout_lines = if top_cropped_layout_lines > maximum_lines.try_into().unwrap_or_default() {
maximum_lines.try_into().unwrap_or_default()
} else {
top_cropped_layout_lines
};
let total_layout_lines: usize = buffer
.lines
.iter()
.map(|line| {
line.layout_opt()
.as_ref()
.map(|layout| layout.len())
.unwrap_or_default()
})
.sum();
let top_cropped_layout_lines =
total_layout_lines.saturating_sub(buffer.scroll.try_into().unwrap_or_default());
let maximum_lines = buffer
.height
.checked_div(buffer.metrics.line_height)
.unwrap_or_default();
let bottom_cropped_layout_lines =
if top_cropped_layout_lines > maximum_lines.try_into().unwrap_or_default() {
maximum_lines.try_into().unwrap_or_default()
} else {
top_cropped_layout_lines
};
Self {
buffer,
line_i: 0,
@ -169,7 +196,7 @@ impl<'a, 'b> Iterator for LayoutRunIter<'a, 'b> {
}
}
impl<'a, 'b> ExactSizeIterator for LayoutRunIter<'a, 'b> { }
impl<'a, 'b> ExactSizeIterator for LayoutRunIter<'a, 'b> {}
/// Metrics of text
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
@ -182,7 +209,10 @@ pub struct Metrics {
impl Metrics {
pub const fn new(font_size: i32, line_height: i32) -> Self {
Self { font_size, line_height }
Self {
font_size,
line_height,
}
}
pub const fn scale(self, scale: i32) -> Self {
@ -215,10 +245,7 @@ pub struct Buffer<'a> {
impl<'a> Buffer<'a> {
/// Create a new [`Buffer`] with the provided [`FontSystem`] and [`Metrics`]
pub fn new(
font_system: &'a FontSystem,
metrics: Metrics,
) -> Self {
pub fn new(font_system: &'a FontSystem, metrics: Metrics) -> Self {
let mut buffer = Self {
font_system,
lines: Vec::new(),
@ -244,7 +271,7 @@ impl<'a> Buffer<'a> {
self.font_system,
self.metrics.font_size,
self.width,
self.wrap
self.wrap,
);
}
}
@ -274,7 +301,7 @@ impl<'a> Buffer<'a> {
self.font_system,
self.metrics.font_size,
self.width,
self.wrap
self.wrap,
);
total_layout += layout.len() as i32;
}
@ -307,7 +334,7 @@ impl<'a> Buffer<'a> {
self.font_system,
self.metrics.font_size,
self.width,
self.wrap
self.wrap,
);
if line_i == cursor.line {
let layout_cursor = self.layout_cursor(&cursor);
@ -341,13 +368,7 @@ impl<'a> Buffer<'a> {
let scroll_end = self.scroll + lines;
let total_layout = self.shape_until(scroll_end);
self.scroll = cmp::max(
0,
cmp::min(
total_layout - (lines - 1),
self.scroll,
),
);
self.scroll = cmp::max(0, cmp::min(total_layout - (lines - 1), self.scroll));
}
pub fn layout_cursor(&self, cursor: &Cursor) -> LayoutCursor {
@ -358,40 +379,24 @@ impl<'a> Buffer<'a> {
for (layout_i, layout_line) in layout.iter().enumerate() {
for (glyph_i, glyph) in layout_line.glyphs.iter().enumerate() {
if cursor.index == glyph.start {
return LayoutCursor::new(
cursor.line,
layout_i,
glyph_i
);
return LayoutCursor::new(cursor.line, layout_i, glyph_i);
}
}
match layout_line.glyphs.last() {
Some(glyph) => {
if cursor.index == glyph.end {
return LayoutCursor::new(
cursor.line,
layout_i,
layout_line.glyphs.len()
);
return LayoutCursor::new(cursor.line, layout_i, layout_line.glyphs.len());
}
},
}
None => {
return LayoutCursor::new(
cursor.line,
layout_i,
0
);
return LayoutCursor::new(cursor.line, layout_i, 0);
}
}
}
// Fall back to start of line
//TODO: should this be the end of the line?
LayoutCursor::new(
cursor.line,
0,
0
)
LayoutCursor::new(cursor.line, 0, 0)
}
/// Get [`FontSystem`] used by this [`Buffer`]
@ -408,7 +413,12 @@ impl<'a> Buffer<'a> {
/// Lay out the provided line index and return the result
pub fn line_layout(&mut self, line_i: usize) -> Option<&[LayoutLine]> {
let line = self.lines.get_mut(line_i)?;
Some(line.layout(self.font_system, self.metrics.font_size, self.width, self.wrap))
Some(line.layout(
self.font_system,
self.metrics.font_size,
self.width,
self.wrap,
))
}
/// Get the current [`Metrics`]
@ -476,11 +486,13 @@ impl<'a> Buffer<'a> {
pub fn set_text(&mut self, text: &str, attrs: Attrs<'a>) {
self.lines.clear();
for line in text.lines() {
self.lines.push(BufferLine::new(line.to_string(), AttrsList::new(attrs)));
self.lines
.push(BufferLine::new(line.to_string(), AttrsList::new(attrs)));
}
// Make sure there is always one line
if self.lines.is_empty() {
self.lines.push(BufferLine::new(String::new(), AttrsList::new(attrs)));
self.lines
.push(BufferLine::new(String::new(), AttrsList::new(attrs)));
}
self.scroll = 0;
@ -522,9 +534,7 @@ impl<'a> Buffer<'a> {
first_run = false;
let new_cursor = Cursor::new(run.line_i, 0);
new_cursor_opt = Some(new_cursor);
} else if y >= line_y - font_size
&& y < line_y - font_size + line_height
{
} else if y >= line_y - font_size && y < line_y - font_size + line_height {
let mut new_cursor_glyph = run.glyphs.len();
let mut new_cursor_char = 0;
@ -538,9 +548,7 @@ impl<'a> Buffer<'a> {
new_cursor_char = 0;
}
}
if x >= glyph.x as i32
&& x <= (glyph.x + glyph.w) as i32
{
if x >= glyph.x as i32 && x <= (glyph.x + glyph.w) as i32 {
new_cursor_glyph = glyph_i;
let cluster = &run.text[glyph.start..glyph.end];
@ -548,9 +556,7 @@ impl<'a> Buffer<'a> {
let mut egc_x = glyph.x;
let egc_w = glyph.w / (total as f32);
for (egc_i, egc) in cluster.grapheme_indices(true) {
if x >= egc_x as i32
&& x <= (egc_x + egc_w) as i32
{
if x >= egc_x as i32 && x <= (egc_x + egc_w) as i32 {
new_cursor_char = egc_i;
let right_half = x >= (egc_x + egc_w / 2.0) as i32;
@ -578,11 +584,13 @@ impl<'a> Buffer<'a> {
Some(glyph) => {
// Position at glyph
new_cursor.index = glyph.start + new_cursor_char;
},
None => if let Some(glyph) = run.glyphs.last() {
// Position at end of line
new_cursor.index = glyph.end;
},
}
None => {
if let Some(glyph) = run.glyphs.last() {
// Position at end of line
new_cursor.index = glyph.end;
}
}
}
new_cursor_opt = Some(new_cursor);
@ -606,7 +614,8 @@ impl<'a> Buffer<'a> {
/// Draw the buffer
#[cfg(feature = "swash")]
pub fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F)
where F: FnMut(i32, i32, u32, u32, Color)
where
F: FnMut(i32, i32, u32, u32, Color),
{
for run in self.layout_runs() {
for glyph in run.glyphs.iter() {

View file

@ -1,8 +1,5 @@
#[cfg(not(feature = "std"))]
use alloc::{
string::String,
vec::Vec,
};
use alloc::{string::String, vec::Vec};
use crate::{AttrsList, FontSystem, LayoutLine, ShapeLine, Wrap};
@ -39,7 +36,11 @@ impl BufferLine {
///
/// Will reset shape and layout if it differs from current text and attributes list.
/// Returns true if the line was reset
pub fn set_text<T: AsRef<str> + Into<String>>(&mut self, text: T, attrs_list: AttrsList) -> bool {
pub fn set_text<T: AsRef<str> + Into<String>>(
&mut self,
text: T,
attrs_list: AttrsList,
) -> bool {
if text.as_ref() != self.text || attrs_list != self.attrs_list {
self.text = text.into();
self.attrs_list = attrs_list;
@ -102,7 +103,8 @@ impl BufferLine {
if other.attrs_list.defaults() != self.attrs_list.defaults() {
// If default formatting does not match, make a new span for it
self.attrs_list.add_span(len..len + other.text().len(), other.attrs_list.defaults());
self.attrs_list
.add_span(len..len + other.text().len(), other.attrs_list.defaults());
}
for (other_range, attrs) in other.attrs_list.spans() {
@ -157,15 +159,17 @@ impl BufferLine {
}
/// Layout line, will cache results
pub fn layout(&mut self, font_system: &FontSystem, font_size: i32, width: i32, wrap: Wrap) -> &[LayoutLine] {
pub fn layout(
&mut self,
font_system: &FontSystem,
font_size: i32,
width: i32,
wrap: Wrap,
) -> &[LayoutLine] {
if self.layout_opt.is_none() {
self.wrap = wrap;
let shape = self.shape(font_system);
let layout = shape.layout(
font_size,
width,
wrap
);
let layout = shape.layout(font_size, width, wrap);
self.layout_opt = Some(layout);
}
self.layout_opt.as_ref().expect("layout not found")

View file

@ -5,9 +5,9 @@ use alloc::string::String;
use core::{cmp, iter::once};
use unicode_segmentation::UnicodeSegmentation;
use crate::{Action, AttrsList, Buffer, BufferLine, Cursor, Edit, LayoutCursor};
#[cfg(feature = "swash")]
use crate::Color;
use crate::{Action, AttrsList, Buffer, BufferLine, Cursor, Edit, LayoutCursor};
/// A wrapper of [`Buffer`] for easy editing
pub struct Editor<'a> {
@ -31,14 +31,17 @@ impl<'a> Editor<'a> {
}
fn set_layout_cursor(&mut self, cursor: LayoutCursor) {
let layout = self.buffer.line_layout(cursor.line).expect("layout not found");
let layout = self
.buffer
.line_layout(cursor.line)
.expect("layout not found");
let layout_line = match layout.get(cursor.layout) {
Some(some) => some,
None => match layout.last() {
Some(some) => some,
None => todo!("layout cursor in line with no layouts"),
}
},
};
let new_index = match layout_line.glyphs.get(cursor.glyph) {
@ -47,7 +50,7 @@ impl<'a> Editor<'a> {
Some(glyph) => glyph.end,
//TODO: is this correct?
None => 0,
}
},
};
if self.cursor.line != cursor.line || self.cursor.index != new_index {
@ -217,7 +220,8 @@ impl<'a> Edit<'a> for Editor<'a> {
let after_len = after.text().len();
// Collect attributes
let mut final_attrs = attrs_list.unwrap_or_else(|| AttrsList::new(line.attrs_list().get_span(line.text().len())));
let mut final_attrs = attrs_list
.unwrap_or_else(|| AttrsList::new(line.attrs_list().get_span(line.text().len())));
// Append the inserted text, line by line
// we want to see a blank entry if the string ends with a newline
@ -227,13 +231,23 @@ impl<'a> Edit<'a> for Editor<'a> {
let mut these_attrs = final_attrs.split_off(data_line.len());
remaining_split_len -= data_line.len();
core::mem::swap(&mut these_attrs, &mut final_attrs);
line.append(BufferLine::new(data_line.strip_suffix(char::is_control).unwrap_or(data_line), these_attrs));
line.append(BufferLine::new(
data_line
.strip_suffix(char::is_control)
.unwrap_or(data_line),
these_attrs,
));
} else {
panic!("str::lines() did not yield any elements");
}
if let Some(data_line) = lines_iter.next_back() {
remaining_split_len -= data_line.len();
let mut tmp = BufferLine::new(data_line.strip_suffix(char::is_control).unwrap_or(data_line), final_attrs.split_off(remaining_split_len));
let mut tmp = BufferLine::new(
data_line
.strip_suffix(char::is_control)
.unwrap_or(data_line),
final_attrs.split_off(remaining_split_len),
);
tmp.append(after);
self.buffer.lines.insert(insert_line, tmp);
self.cursor.line += 1;
@ -242,7 +256,12 @@ impl<'a> Edit<'a> for Editor<'a> {
}
for data_line in lines_iter.rev() {
remaining_split_len -= data_line.len();
let tmp = BufferLine::new(data_line.strip_suffix(char::is_control).unwrap_or(data_line), final_attrs.split_off(remaining_split_len));
let tmp = BufferLine::new(
data_line
.strip_suffix(char::is_control)
.unwrap_or(data_line),
final_attrs.split_off(remaining_split_len),
);
self.buffer.lines.insert(insert_line, tmp);
self.cursor.line += 1;
}
@ -278,7 +297,7 @@ impl<'a> Edit<'a> for Editor<'a> {
self.buffer.set_redraw(true);
}
self.cursor_x_opt = None;
},
}
Action::Next => {
let line = &mut self.buffer.lines[self.cursor.line];
if self.cursor.index < line.text().len() {
@ -295,9 +314,12 @@ impl<'a> Edit<'a> for Editor<'a> {
self.buffer.set_redraw(true);
}
self.cursor_x_opt = None;
},
}
Action::Left => {
let rtl_opt = self.buffer.lines[self.cursor.line].shape_opt().as_ref().map(|shape| shape.rtl);
let rtl_opt = self.buffer.lines[self.cursor.line]
.shape_opt()
.as_ref()
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
self.action(Action::Next);
@ -305,9 +327,12 @@ impl<'a> Edit<'a> for Editor<'a> {
self.action(Action::Previous);
}
}
},
}
Action::Right => {
let rtl_opt = self.buffer.lines[self.cursor.line].shape_opt().as_ref().map(|shape| shape.rtl);
let rtl_opt = self.buffer.lines[self.cursor.line]
.shape_opt()
.as_ref()
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
self.action(Action::Previous);
@ -315,14 +340,14 @@ impl<'a> Edit<'a> for Editor<'a> {
self.action(Action::Next);
}
}
},
}
Action::Up => {
//TODO: make this preserve X as best as possible!
let mut cursor = self.buffer.layout_cursor(&self.cursor);
if self.cursor_x_opt.is_none() {
self.cursor_x_opt = Some(
cursor.glyph as i32 //TODO: glyph x position
cursor.glyph as i32, //TODO: glyph x position
);
}
@ -338,16 +363,20 @@ impl<'a> Edit<'a> for Editor<'a> {
}
self.set_layout_cursor(cursor);
},
}
Action::Down => {
//TODO: make this preserve X as best as possible!
let mut cursor = self.buffer.layout_cursor(&self.cursor);
let layout_len = self.buffer.line_layout(cursor.line).expect("layout not found").len();
let layout_len = self
.buffer
.line_layout(cursor.line)
.expect("layout not found")
.len();
if self.cursor_x_opt.is_none() {
self.cursor_x_opt = Some(
cursor.glyph as i32 //TODO: glyph x position
cursor.glyph as i32, //TODO: glyph x position
);
}
@ -363,13 +392,13 @@ impl<'a> Edit<'a> for Editor<'a> {
}
self.set_layout_cursor(cursor);
},
}
Action::Home => {
let mut cursor = self.buffer.layout_cursor(&self.cursor);
cursor.glyph = 0;
self.set_layout_cursor(cursor);
self.cursor_x_opt = None;
},
}
Action::End => {
let mut cursor = self.buffer.layout_cursor(&self.cursor);
cursor.glyph = usize::max_value();
@ -388,10 +417,10 @@ impl<'a> Edit<'a> for Editor<'a> {
}
Action::PageUp => {
self.action(Action::Vertical(-self.buffer.size().1));
},
}
Action::PageDown => {
self.action(Action::Vertical(self.buffer.size().1));
},
}
Action::Vertical(px) => {
// TODO more efficient
let lines = px / self.buffer.metrics().line_height;
@ -404,16 +433,14 @@ impl<'a> Edit<'a> for Editor<'a> {
self.action(Action::Down);
}
}
},
}
Action::Escape => {
if self.select_opt.take().is_some() {
self.buffer.set_redraw(true);
}
},
}
Action::Insert(character) => {
if character.is_control()
&& !['\t', '\n', '\u{92}'].contains(&character)
{
if character.is_control() && !['\t', '\n', '\u{92}'].contains(&character) {
// Filter out special chars (except for tab), use Action instead
log::debug!("Refusing to insert control character {:?}", character);
} else if character == '\n' {
@ -423,7 +450,7 @@ impl<'a> Edit<'a> for Editor<'a> {
let str_ref = character.encode_utf8(&mut str_buf);
self.insert_string(str_ref, None);
}
},
}
Action::Enter => {
self.delete_selection();
@ -433,7 +460,7 @@ impl<'a> Edit<'a> for Editor<'a> {
self.cursor.index = 0;
self.buffer.lines.insert(self.cursor.line, new_line);
},
}
Action::Backspace => {
if self.delete_selection() {
// Deleted selection
@ -472,7 +499,7 @@ impl<'a> Edit<'a> for Editor<'a> {
line.append(old_line);
}
},
}
Action::Delete => {
if self.delete_selection() {
// Deleted selection
@ -484,9 +511,7 @@ impl<'a> Edit<'a> for Editor<'a> {
.grapheme_indices(true)
.take_while(|(i, _)| *i <= self.cursor.index)
.last()
.map(|(i, c)| {
i..(i + c.len())
});
.map(|(i, c)| i..(i + c.len()));
if let Some(range) = range_opt {
self.cursor.index = range.start;
@ -504,7 +529,7 @@ impl<'a> Edit<'a> for Editor<'a> {
let old_line = self.buffer.lines.remove(self.cursor.line + 1);
self.buffer.lines[self.cursor.line].append(old_line);
}
},
}
Action::Click { x, y } => {
self.select_opt = None;
@ -514,7 +539,7 @@ impl<'a> Edit<'a> for Editor<'a> {
self.buffer.set_redraw(true);
}
}
},
}
Action::Drag { x, y } => {
if self.select_opt.is_none() {
self.select_opt = Some(self.cursor);
@ -527,7 +552,7 @@ impl<'a> Edit<'a> for Editor<'a> {
self.buffer.set_redraw(true);
}
}
},
}
Action::Scroll { lines } => {
let mut scroll = self.buffer.scroll();
scroll += lines;
@ -573,7 +598,10 @@ impl<'a> Edit<'a> for Editor<'a> {
self.cursor_x_opt = None;
}
Action::LeftWord => {
let rtl_opt = self.buffer.lines[self.cursor.line].shape_opt().as_ref().map(|shape| shape.rtl);
let rtl_opt = self.buffer.lines[self.cursor.line]
.shape_opt()
.as_ref()
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
self.action(Action::NextWord);
@ -581,9 +609,12 @@ impl<'a> Edit<'a> for Editor<'a> {
self.action(Action::PreviousWord);
}
}
},
}
Action::RightWord => {
let rtl_opt = self.buffer.lines[self.cursor.line].shape_opt().as_ref().map(|shape| shape.rtl);
let rtl_opt = self.buffer.lines[self.cursor.line]
.shape_opt()
.as_ref()
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
self.action(Action::PreviousWord);
@ -591,7 +622,7 @@ impl<'a> Edit<'a> for Editor<'a> {
self.action(Action::NextWord);
}
}
},
}
Action::BufferStart => {
self.cursor.line = 0;
self.cursor.index = 0;
@ -628,7 +659,8 @@ impl<'a> Edit<'a> for Editor<'a> {
/// Draw the editor
#[cfg(feature = "swash")]
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F)
where F: FnMut(i32, i32, u32, u32, Color)
where
F: FnMut(i32, i32, u32, u32, Color),
{
let font_size = self.buffer.metrics().font_size;
let line_height = self.buffer.metrics().line_height;
@ -664,7 +696,7 @@ impl<'a> Edit<'a> for Editor<'a> {
if cursor.index == glyph.end {
return Some((run.glyphs.len(), 0.0));
}
},
}
None => {
return Some((0, 0.0));
}
@ -701,16 +733,14 @@ impl<'a> Edit<'a> for Editor<'a> {
let c_start = glyph.start + i;
let c_end = glyph.start + i + c.len();
if (start.line != line_i || c_end > start.index)
&& (end.line != line_i || c_start < end.index) {
&& (end.line != line_i || c_start < end.index)
{
range_opt = match range_opt.take() {
Some((min, max)) => Some((
cmp::min(min, c_x as i32),
cmp::max(max, (c_x + c_w) as i32),
)),
None => Some((
c_x as i32,
(c_x + c_w) as i32,
))
None => Some((c_x as i32, (c_x + c_w) as i32)),
};
} else if let Some((min, max)) = range_opt.take() {
f(
@ -718,14 +748,14 @@ impl<'a> Edit<'a> for Editor<'a> {
line_y - font_size,
cmp::max(0, max - min) as u32,
line_height as u32,
Color::rgba(color.r(), color.g(), color.b(), 0x33)
Color::rgba(color.r(), color.g(), color.b(), 0x33),
);
}
c_x += c_w;
}
}
if run.glyphs.is_empty() && end.line > line_i{
if run.glyphs.is_empty() && end.line > line_i {
// Highlight all of internal empty lines
range_opt = Some((0, self.buffer.size().0));
}
@ -744,7 +774,7 @@ impl<'a> Edit<'a> for Editor<'a> {
line_y - font_size,
cmp::max(0, max - min) as u32,
line_height as u32,
Color::rgba(color.r(), color.g(), color.b(), 0x33)
Color::rgba(color.r(), color.g(), color.b(), 0x33),
);
}
}
@ -760,7 +790,7 @@ impl<'a> Edit<'a> for Editor<'a> {
} else {
(glyph.x + cursor_glyph_offset) as i32
}
},
}
None => match run.glyphs.last() {
Some(glyph) => {
// End of last glyph
@ -769,21 +799,15 @@ impl<'a> Edit<'a> for Editor<'a> {
} else {
(glyph.x + glyph.w) as i32
}
},
}
None => {
// Start of empty line
0
}
}
},
};
f(
x,
line_y - font_size,
1,
line_height as u32,
color,
);
f(x, line_y - font_size, 1, line_height as u32, color);
}
for glyph in run.glyphs.iter() {

View file

@ -1,9 +1,9 @@
#[cfg(not(feature = "std"))]
use alloc::string::String;
use crate::{AttrsList, Buffer, Cursor};
#[cfg(feature = "swash")]
use crate::Color;
use crate::{AttrsList, Buffer, Cursor};
pub use self::editor::*;
mod editor;
@ -114,5 +114,6 @@ pub trait Edit<'a> {
/// Draw the editor
#[cfg(feature = "swash")]
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, f: F)
where F: FnMut(i32, i32, u32, u32, Color);
where
F: FnMut(i32, i32, u32, u32, Color);
}

View file

@ -1,41 +1,13 @@
#[cfg(not(feature = "std"))]
use alloc::{
string::String,
vec::Vec,
};
use alloc::{string::String, vec::Vec};
#[cfg(feature = "std")]
use std::{
fs,
io,
path::Path,
};
use std::{fs, io, path::Path};
use syntect::highlighting::{
FontStyle,
Highlighter,
HighlightState,
RangedHighlightIterator,
Theme,
ThemeSet,
};
use syntect::parsing::{
ParseState,
ScopeStack,
SyntaxReference,
SyntaxSet,
FontStyle, HighlightState, Highlighter, RangedHighlightIterator, Theme, ThemeSet,
};
use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet};
use crate::{
Action,
AttrsList,
Buffer,
Color,
Cursor,
Edit,
Editor,
Style,
Weight,
Wrap,
};
use crate::{Action, AttrsList, Buffer, Color, Cursor, Edit, Editor, Style, Weight, Wrap};
pub struct SyntaxSystem {
pub syntax_set: SyntaxSet,
@ -69,7 +41,11 @@ impl<'a> SyntaxEditor<'a> {
/// A good default theme name is "base16-eighties.dark".
///
/// Returns None if theme not found
pub fn new(buffer: Buffer<'a>, syntax_system: &'a SyntaxSystem, theme_name: &str) -> Option<Self> {
pub fn new(
buffer: Buffer<'a>,
syntax_system: &'a SyntaxSystem,
theme_name: &str,
) -> Option<Self> {
let editor = Editor::new(buffer);
let syntax = syntax_system.syntax_set.find_syntax_plain_text();
let theme = syntax_system.theme_set.themes.get(theme_name)?;
@ -91,7 +67,11 @@ impl<'a> SyntaxEditor<'a> {
///
/// Returns an [`io::Error`] if reading the file fails
#[cfg(feature = "std")]
pub fn load_text<P: AsRef<Path>>(&mut self, path: P, attrs: crate::Attrs<'a>) -> io::Result<()> {
pub fn load_text<P: AsRef<Path>>(
&mut self,
path: P,
attrs: crate::Attrs<'a>,
) -> io::Result<()> {
let path = path.as_ref();
let text = fs::read_to_string(path)?;
@ -119,12 +99,7 @@ impl<'a> SyntaxEditor<'a> {
/// Get the default background color
pub fn background_color(&self) -> Color {
if let Some(background) = self.theme.settings.background {
Color::rgba(
background.r,
background.g,
background.b,
background.a,
)
Color::rgba(background.r, background.g, background.b, background.a)
} else {
Color::rgb(0, 0, 0)
}
@ -133,12 +108,7 @@ impl<'a> SyntaxEditor<'a> {
/// Get the default foreground (text) color
pub fn foreground_color(&self) -> Color {
if let Some(foreground) = self.theme.settings.foreground {
Color::rgba(
foreground.r,
foreground.g,
foreground.b,
foreground.a,
)
Color::rgba(foreground.r, foreground.g, foreground.b, foreground.a)
} else {
Color::rgb(0xFF, 0xFF, 0xFF)
}
@ -175,21 +145,24 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
let mut highlighted = 0;
for line_i in 0..buffer.lines.len() {
let line = &mut buffer.lines[line_i];
if ! line.is_reset() && line_i < self.syntax_cache.len() {
if !line.is_reset() && line_i < self.syntax_cache.len() {
continue;
}
highlighted += 1;
let (mut parse_state, mut highlight_state) = if line_i > 0 && line_i <= self.syntax_cache.len() {
self.syntax_cache[line_i - 1].clone()
} else {
(
ParseState::new(self.syntax),
HighlightState::new(&self.highlighter, ScopeStack::new())
)
};
let (mut parse_state, mut highlight_state) =
if line_i > 0 && line_i <= self.syntax_cache.len() {
self.syntax_cache[line_i - 1].clone()
} else {
(
ParseState::new(self.syntax),
HighlightState::new(&self.highlighter, ScopeStack::new()),
)
};
let ops = parse_state.parse_line(line.text(), &self.syntax_system.syntax_set).expect("failed to parse syntax");
let ops = parse_state
.parse_line(line.text(), &self.syntax_system.syntax_set)
.expect("failed to parse syntax");
let ranges = RangedHighlightIterator::new(
&mut highlight_state,
&ops,
@ -219,8 +192,7 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
Weight::BOLD
} else {
Weight::NORMAL
})
//TODO: underline
}), //TODO: underline
);
}
@ -247,7 +219,11 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
if highlighted > 0 {
buffer.set_redraw(true);
#[cfg(feature = "std")]
log::debug!("Syntax highlighted {} lines in {:?}", highlighted, now.elapsed());
log::debug!(
"Syntax highlighted {} lines in {:?}",
highlighted,
now.elapsed()
);
}
self.editor.shape_as_needed();
@ -272,7 +248,8 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
/// Draw the editor
#[cfg(feature = "swash")]
fn draw<F>(&self, cache: &mut crate::SwashCache, _color: Color, mut f: F)
where F: FnMut(i32, i32, u32, u32, Color)
where
F: FnMut(i32, i32, u32, u32, Color),
{
let size = self.buffer().size();
f(0, 0, size.0 as u32, size.1 as u32, self.background_color());

View file

@ -2,15 +2,7 @@ use alloc::string::String;
use core::cmp;
use unicode_segmentation::UnicodeSegmentation;
use crate::{
Action,
AttrsList,
Buffer,
Color,
Cursor,
Edit,
SyntaxEditor,
};
use crate::{Action, AttrsList, Buffer, Color, Cursor, Edit, SyntaxEditor};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Mode {
@ -36,7 +28,11 @@ impl<'a> ViEditor<'a> {
/// Load text from a file, and also set syntax to the best option
#[cfg(feature = "std")]
pub fn load_text<P: AsRef<std::path::Path>>(&mut self, path: P, attrs: crate::Attrs<'a>) -> std::io::Result<()> {
pub fn load_text<P: AsRef<std::path::Path>>(
&mut self,
path: P,
attrs: crate::Attrs<'a>,
) -> std::io::Result<()> {
self.editor.load_text(path, attrs)
}
@ -98,12 +94,12 @@ impl<'a> Edit<'a> for ViEditor<'a> {
'a' => {
self.editor.action(Action::Right);
self.mode = Mode::Insert;
},
}
// Enter insert mode at end of line
'A' => {
self.editor.action(Action::End);
self.mode = Mode::Insert;
},
}
// Change mode
'c' => {
if self.editor.select_opt().is_some() {
@ -112,7 +108,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
} else {
//TODO: change to next cursor movement
}
},
}
// Delete mode
'd' => {
if self.editor.select_opt().is_some() {
@ -120,11 +116,11 @@ impl<'a> Edit<'a> for ViEditor<'a> {
} else {
//TODO: delete to next cursor movement
}
},
}
// Enter insert mode at cursor
'i' => {
self.mode = Mode::Insert;
},
}
// Enter insert mode at start of line
'I' => {
//TODO: soft home, skip whitespace
@ -136,7 +132,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
self.editor.action(Action::End);
self.editor.action(Action::Enter);
self.mode = Mode::Insert;
},
}
// Create line before and enter insert mode
'O' => {
self.editor.action(Action::Home);
@ -144,7 +140,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
self.editor.shape_as_needed(); // TODO: do not require this?
self.editor.action(Action::Up);
self.mode = Mode::Insert;
},
}
// Left
'h' => self.editor.action(Action::Left),
// Top of screen
@ -166,7 +162,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
} else {
self.editor.set_select_opt(Some(self.editor.cursor()));
}
},
}
// Enter line visual mode
'V' => {
if self.editor.select_opt().is_some() {
@ -177,7 +173,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
//TODO: set cursor_x_opt to max
self.editor.action(Action::End);
}
},
}
// Remove character at cursor
'x' => self.editor.action(Action::Delete),
// Remove character before cursor
@ -192,15 +188,15 @@ impl<'a> Edit<'a> for ViEditor<'a> {
// Enter command mode
':' => {
self.mode = Mode::Command;
},
}
// Enter search mode
'/' => {
self.mode = Mode::Search;
},
}
// Enter search backwards mode
'?' => {
self.mode = Mode::SearchBackwards;
},
}
_ => (),
},
_ => self.editor.action(action),
@ -213,13 +209,13 @@ impl<'a> Edit<'a> for ViEditor<'a> {
self.editor.action(Action::Left);
}
self.mode = Mode::Normal;
},
}
_ => self.editor.action(action),
},
_ => {
//TODO: other modes
self.mode = Mode::Normal;
},
}
}
if self.mode != old_mode {
@ -229,7 +225,8 @@ impl<'a> Edit<'a> for ViEditor<'a> {
#[cfg(feature = "swash")]
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F)
where F: FnMut(i32, i32, u32, u32, Color)
where
F: FnMut(i32, i32, u32, u32, Color),
{
let font_size = self.buffer().metrics().font_size;
let line_height = self.buffer().metrics().line_height;
@ -266,7 +263,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
if cursor.index == glyph.end {
return Some((run.glyphs.len(), 0.0, default_width));
}
},
}
None => {
return Some((0, 0.0, default_width));
}
@ -303,16 +300,14 @@ impl<'a> Edit<'a> for ViEditor<'a> {
let c_start = glyph.start + i;
let c_end = glyph.start + i + c.len();
if (start.line != line_i || c_end > start.index)
&& (end.line != line_i || c_start < end.index) {
&& (end.line != line_i || c_start < end.index)
{
range_opt = match range_opt.take() {
Some((min, max)) => Some((
cmp::min(min, c_x as i32),
cmp::max(max, (c_x + c_w) as i32),
)),
None => Some((
c_x as i32,
(c_x + c_w) as i32,
))
None => Some((c_x as i32, (c_x + c_w) as i32)),
};
} else if let Some((min, max)) = range_opt.take() {
f(
@ -320,14 +315,14 @@ impl<'a> Edit<'a> for ViEditor<'a> {
line_y - font_size,
cmp::max(0, max - min) as u32,
line_height as u32,
Color::rgba(color.r(), color.g(), color.b(), 0x33)
Color::rgba(color.r(), color.g(), color.b(), 0x33),
);
}
c_x += c_w;
}
}
if run.glyphs.is_empty() && end.line > line_i{
if run.glyphs.is_empty() && end.line > line_i {
// Highlight all of internal empty lines
range_opt = Some((0, self.buffer().size().0));
}
@ -346,18 +341,20 @@ impl<'a> Edit<'a> for ViEditor<'a> {
line_y - font_size,
cmp::max(0, max - min) as u32,
line_height as u32,
Color::rgba(color.r(), color.g(), color.b(), 0x33)
Color::rgba(color.r(), color.g(), color.b(), 0x33),
);
}
}
}
// Draw cursor
if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) = cursor_glyph_opt(&self.cursor()) {
if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) =
cursor_glyph_opt(&self.cursor())
{
let block_cursor = match self.mode {
Mode::Normal => true,
Mode::Insert => false,
_ => true /*TODO: determine block cursor in other modes*/
_ => true, /*TODO: determine block cursor in other modes*/
};
let (start_x, end_x) = match run.glyphs.get(cursor_glyph) {
@ -366,38 +363,33 @@ impl<'a> Edit<'a> for ViEditor<'a> {
if glyph.level.is_rtl() {
(
(glyph.x + glyph.w - cursor_glyph_offset) as i32,
(glyph.x + glyph.w - cursor_glyph_offset - cursor_glyph_width) as i32,
(glyph.x + glyph.w - cursor_glyph_offset - cursor_glyph_width)
as i32,
)
} else {
(
(glyph.x + cursor_glyph_offset) as i32,
(glyph.x + cursor_glyph_offset + cursor_glyph_width) as i32
(glyph.x + cursor_glyph_offset + cursor_glyph_width) as i32,
)
}
},
}
None => match run.glyphs.last() {
Some(glyph) => {
// End of last glyph
if glyph.level.is_rtl() {
(
glyph.x as i32,
(glyph.x - cursor_glyph_width) as i32
)
(glyph.x as i32, (glyph.x - cursor_glyph_width) as i32)
} else {
(
(glyph.x + glyph.w) as i32,
(glyph.x + glyph.w + cursor_glyph_width) as i32
(glyph.x + glyph.w + cursor_glyph_width) as i32,
)
}
},
}
None => {
// Start of empty line
(
0,
cursor_glyph_width as i32
)
(0, cursor_glyph_width as i32)
}
}
},
};
if block_cursor {
@ -411,13 +403,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
Color::rgba(color.r(), color.g(), color.b(), 0x33),
);
} else {
f(
start_x,
line_y - font_size,
1,
line_height as u32,
color,
);
f(start_x, line_y - font_size, 1, line_height as u32, color);
}
}

View file

@ -15,9 +15,7 @@ pub fn common_fallback() -> &'static [&'static str] {
// Fallbacks to never use
pub fn forbidden_fallback() -> &'static [&'static str] {
&[
".LastResort",
]
&[".LastResort"]
}
fn han_unification(locale: &str) -> &'static [&'static str] {

View file

@ -9,11 +9,7 @@ use crate::Font;
use self::platform::*;
#[cfg(not(any(
target_os = "linux",
target_os = "macos",
target_os = "windows",
)))]
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows",)))]
#[path = "other.rs"]
mod platform;
@ -46,7 +42,7 @@ impl<'a> FontFallbackIter<'a> {
fonts: &'a [Arc<Font<'a>>],
default_families: &'a [&'a str],
scripts: Vec<Script>,
locale: &'a str
locale: &'a str,
) -> Self {
Self {
fonts,
@ -78,7 +74,7 @@ impl<'a> FontFallbackIter<'a> {
font.info.family,
word
);
} else if ! self.scripts.is_empty() && self.common_i > 0 {
} else if !self.scripts.is_empty() && self.common_i > 0 {
let family = common_fallback()[self.common_i - 1];
log::debug!(
"Failed to find script fallback for {:?} locale '{}', used '{}': '{}'",
@ -117,7 +113,12 @@ impl<'a> Iterator for FontFallbackIter<'a> {
return Some(font);
}
}
log::warn!("failed to find family '{}' for script {:?} and locale '{}'", script_family, script, self.locale);
log::warn!(
"failed to find family '{}' for script {:?} and locale '{}'",
script_family,
script,
self.locale
);
}
self.script_i.0 += 1;
@ -142,7 +143,7 @@ impl<'a> Iterator for FontFallbackIter<'a> {
while self.other_i < self.fonts.len() {
let font = &self.fonts[self.other_i];
self.other_i += 1;
if ! forbidden_families.contains(&font.info.family.as_str()) {
if !forbidden_families.contains(&font.info.family.as_str()) {
return Some(font);
}
}

View file

@ -31,7 +31,7 @@ fn han_unification(locale: &str) -> &'static [&'static str] {
// Taiwan
"zh-TW" => &["Microsoft JhengHei UI"],
// Simplified Chinese is the default (also catches "zh-CN" for China)
_ => &["Microsoft YaHei UI"]
_ => &["Microsoft YaHei UI"],
}
}

View file

@ -2,10 +2,7 @@
use alloc::sync::Arc;
#[cfg(not(feature = "std"))]
use alloc::{
string::String,
vec::Vec,
};
use alloc::{string::String, vec::Vec};
use crate::Font;

View file

@ -9,7 +9,7 @@ use alloc::{
use crate::{Attrs, Font, FontMatches};
/// Access system fonts
pub struct FontSystem{
pub struct FontSystem {
locale: String,
db: fontdb::Database,
}
@ -25,16 +25,13 @@ impl FontSystem {
db.set_serif_family("DejaVu Serif");
}
Self {
locale,
db,
}
Self { locale, db }
}
pub fn new_with_locale_and_db(locale: &str, db: fontdb::Database) -> Self {
Self {
locale: locale.to_string(),
db
db,
}
}
@ -74,7 +71,7 @@ impl FontSystem {
Arc::new(FontMatches {
locale: &self.locale,
default_family: self.db.family_name(&attrs.family).to_string(),
fonts
fonts,
})
}
}

View file

@ -9,7 +9,7 @@ use alloc::{
use crate::{Attrs, Font, FontMatches};
/// Access system fonts
pub struct FontSystem{
pub struct FontSystem {
locale: String,
db: fontdb::Database,
}
@ -46,7 +46,9 @@ impl FontSystem {
//TODO only do this on demand!
for i in 0..db.faces().len() {
let id = db.faces()[i].id;
unsafe { db.make_shared_face_data(id); }
unsafe {
db.make_shared_face_data(id);
}
}
log::info!(
@ -56,10 +58,7 @@ impl FontSystem {
);
}
Self {
locale,
db,
}
Self { locale, db }
}
pub fn locale(&self) -> &str {
@ -98,7 +97,7 @@ impl FontSystem {
Arc::new(FontMatches {
locale: &self.locale,
default_family: self.db.family_name(&attrs.family).to_string(),
fonts
fonts,
})
}
}

View file

@ -59,7 +59,9 @@ impl FontSystem {
//TODO only do this on demand!
for i in 0..db.faces().len() {
let id = db.faces()[i].id;
unsafe { db.make_shared_face_data(id); }
unsafe {
db.make_shared_face_data(id);
}
}
log::info!(
@ -69,12 +71,15 @@ impl FontSystem {
);
}
Self(FontSystemInnerBuilder {
locale,
db,
font_cache_builder: |_| Mutex::new(HashMap::new()),
font_matches_cache_builder: |_, _| Mutex::new(HashMap::new())
}.build())
Self(
FontSystemInnerBuilder {
locale,
db,
font_cache_builder: |_| Mutex::new(HashMap::new()),
font_matches_cache_builder: |_, _| Mutex::new(HashMap::new()),
}
.build(),
)
}
pub fn locale(&self) -> &str {
@ -93,53 +98,66 @@ impl FontSystem {
// Clippy false positive
#[allow(clippy::needless_lifetimes)]
pub fn get_font<'a>(&'a self, id: fontdb::ID) -> Option<Arc<Font<'a>>> {
self.0.with(|fields| {
get_font(&fields, id)
})
self.0.with(|fields| get_font(&fields, id))
}
pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> {
self.0.with(|fields| {
let mut font_matches_cache = fields.font_matches_cache.lock().expect("failed to lock font matches cache");
let mut font_matches_cache = fields
.font_matches_cache
.lock()
.expect("failed to lock font matches cache");
//TODO: do not create AttrsOwned unless entry does not already exist
font_matches_cache.entry(AttrsOwned::new(attrs)).or_insert_with(|| {
let now = std::time::Instant::now();
font_matches_cache
.entry(AttrsOwned::new(attrs))
.or_insert_with(|| {
let now = std::time::Instant::now();
let mut fonts = Vec::new();
for face in fields.db.faces() {
if !attrs.matches(face) {
continue;
let mut fonts = Vec::new();
for face in fields.db.faces() {
if !attrs.matches(face) {
continue;
}
if let Some(font) = get_font(&fields, face.id) {
fonts.push(font);
}
}
if let Some(font) = get_font(&fields, face.id) {
fonts.push(font);
}
}
let font_matches = Arc::new(FontMatches {
locale: fields.locale,
default_family: fields.db.family_name(&attrs.family).to_string(),
fonts,
});
let font_matches = Arc::new(FontMatches {
locale: fields.locale,
default_family: fields.db.family_name(&attrs.family).to_string(),
fonts
});
let elapsed = now.elapsed();
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
let elapsed = now.elapsed();
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
font_matches
}).clone()
font_matches
})
.clone()
})
}
}
fn get_font<'b>(fields: &ouroboros_impl_font_system_inner::BorrowedFields<'_, 'b>, id: fontdb::ID) -> Option<Arc<Font<'b>>> {
fields.font_cache.lock().expect("failed to lock font cache").entry(id).or_insert_with(|| {
let face = fields.db.face(id)?;
match Font::new(face) {
Some(font) => Some(Arc::new(font)),
None => {
log::warn!("failed to load font '{}'", face.post_script_name);
None
fn get_font<'b>(
fields: &ouroboros_impl_font_system_inner::BorrowedFields<'_, 'b>,
id: fontdb::ID,
) -> Option<Arc<Font<'b>>> {
fields
.font_cache
.lock()
.expect("failed to lock font cache")
.entry(id)
.or_insert_with(|| {
let face = fields.db.face(id)?;
match Font::new(face) {
Some(font) => Some(Arc::new(font)),
None => {
log::warn!("failed to load font '{}'", face.post_script_name);
None
}
}
}
}).clone()
})
.clone()
}

View file

@ -56,14 +56,12 @@
// Not interested in these lints
#![allow(clippy::new_without_default)]
// TODO: address ocurrances and then deny
//
// Indexing a slice can cause panics and that is something we always want to avoid
#![allow(clippy::indexing_slicing)]
// Overflows can produce unpredictable results and are only checked in debug builds
#![allow(clippy::integer_arithmetic)]
// Soundness issues
//
// Dereferencing unalinged pointers may be undefined behavior
@ -75,7 +73,6 @@
#![deny(unreachable_patterns)]
// Ensure that all must_use results are used
#![deny(unused_must_use)]
// Style issues
//
// Documentation not ideal
@ -88,7 +85,6 @@
#![warn(clippy::semicolon_if_nothing_returned)]
// Ensure numbers are readable
#![warn(clippy::unreadable_literal)]
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;

View file

@ -2,14 +2,14 @@
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::cmp::{min, max};
use core::cmp::{max, min};
use core::mem;
use core::ops::Range;
use unicode_script::{Script, UnicodeScript};
use unicode_segmentation::UnicodeSegmentation;
use crate::{AttrsList, CacheKey, Color, Font, FontSystem, LayoutGlyph, LayoutLine, Wrap};
use crate::fallback::FontFallbackIter;
use crate::{AttrsList, CacheKey, Color, Font, FontSystem, LayoutGlyph, LayoutLine, Wrap};
fn shape_fallback(
font: &Font,
@ -110,21 +110,16 @@ fn shape_run<'a>(
let mut scripts = Vec::new();
for c in line[start_run..end_run].chars() {
match c.script() {
Script::Common |
Script::Inherited |
Script::Latin |
Script::Unknown => (),
script => if ! scripts.contains(&script) {
scripts.push(script);
},
Script::Common | Script::Inherited | Script::Latin | Script::Unknown => (),
script => {
if !scripts.contains(&script) {
scripts.push(script);
}
}
}
}
log::trace!(
" Run {:?}: '{}'",
scripts,
&line[start_run..end_run],
);
log::trace!(" Run {:?}: '{}'", scripts, &line[start_run..end_run],);
let attrs = attrs_list.get_span(start_run);
@ -135,7 +130,7 @@ fn shape_run<'a>(
&font_matches.fonts,
&default_families,
scripts,
font_matches.locale
font_matches.locale,
);
let (mut glyphs, mut missing) = shape_fallback(
@ -155,14 +150,8 @@ fn shape_run<'a>(
};
log::trace!("Evaluating fallback with font '{}'", font.info.family);
let (mut fb_glyphs, fb_missing) = shape_fallback(
font,
line,
attrs_list,
start_run,
end_run,
span_rtl,
);
let (mut fb_glyphs, fb_missing) =
shape_fallback(font, line, attrs_list, start_run, end_run, span_rtl);
// Insert all matching glyphs
let mut fb_i = 0;
@ -255,7 +244,7 @@ impl ShapeGlyph {
self.font_id,
self.glyph_id,
font_size,
(x + x_offset, y - y_offset)
(x + x_offset, y - y_offset),
);
LayoutGlyph {
start: self.start,
@ -307,7 +296,7 @@ impl ShapeWord {
for (egc_i, _egc) in word.grapheme_indices(true) {
let start_egc = word_range.start + egc_i;
let attrs_egc = attrs_list.get_span(start_egc);
if ! attrs.compatible(&attrs_egc) {
if !attrs.compatible(&attrs_egc) {
//TODO: more efficient
glyphs.append(&mut shape_run(
font_system,
@ -315,7 +304,7 @@ impl ShapeWord {
attrs_list,
start_run,
start_egc,
span_rtl
span_rtl,
));
start_run = start_egc;
@ -330,7 +319,7 @@ impl ShapeWord {
attrs_list,
start_run,
word_range.end,
span_rtl
span_rtl,
));
}
@ -341,7 +330,12 @@ impl ShapeWord {
y_advance += glyph.y_advance;
}
Self { blank, glyphs, x_advance, y_advance}
Self {
blank,
glyphs,
x_advance,
y_advance,
}
}
}
@ -398,7 +392,8 @@ impl ShapeSpan {
font_system,
line,
attrs_list,
(span_range.start + start_lb + i)..(span_range.start + start_lb + i + c.len_utf8()),
(span_range.start + start_lb + i)
..(span_range.start + start_lb + i + c.len_utf8()),
level,
true,
));
@ -419,10 +414,7 @@ impl ShapeSpan {
words.reverse();
}
ShapeSpan {
level,
words,
}
ShapeSpan { level, words }
}
}
@ -436,11 +428,7 @@ pub struct ShapeLine {
type VlRange = (usize, (usize, usize), (usize, usize));
impl ShapeLine {
pub fn new<'a>(
font_system: &'a FontSystem,
line: &str,
attrs_list: &AttrsList
) -> Self {
pub fn new<'a>(font_system: &'a FontSystem, line: &str, attrs_list: &AttrsList) -> Self {
let mut spans = Vec::new();
let bidi = unicode_bidi::BidiInfo::new(line, None);
@ -456,12 +444,17 @@ impl ShapeLine {
let line_range = para_info.range.clone();
let levels = Self::adjust_levels(&unicode_bidi::Paragraph::new(&bidi, para_info));
// Find consecutive level runs. We use this to create Spans.
// Find consecutive level runs. We use this to create Spans.
// Each span is a set of characters with equal levels.
let mut start = line_range.start;
let mut run_level = levels[start];
for (i, &new_level) in levels.iter().enumerate().take(line_range.end).skip(start + 1) {
for (i, &new_level) in levels
.iter()
.enumerate()
.take(line_range.end)
.skip(start + 1)
{
if new_level != run_level {
// End of the previous run, start of a new one.
spans.push(ShapeSpan::new(
@ -487,13 +480,11 @@ impl ShapeLine {
line_rtl
};
Self { rtl, spans}
Self { rtl, spans }
}
// A modified version of first part of unicode_bidi::bidi_info::visual_run
fn adjust_levels(
para: &unicode_bidi::Paragraph,
) -> Vec<unicode_bidi::Level> {
fn adjust_levels(para: &unicode_bidi::Paragraph) -> Vec<unicode_bidi::Level> {
use unicode_bidi::BidiClass::*;
let text = para.info.text;
let levels = &para.info.levels;
@ -547,7 +538,10 @@ impl ShapeLine {
// A modified version of second part of unicode_bidi::bidi_info::visual run
fn reorder(&self, line_range: &[VlRange]) -> Vec<Range<usize>> {
let line : Vec<unicode_bidi::Level> = line_range.iter().map(|(span_index, _, _)| self.spans[*span_index].level).collect();
let line: Vec<unicode_bidi::Level> = line_range
.iter()
.map(|(span_index, _, _)| self.spans[*span_index].level)
.collect();
// Find consecutive level runs.
let mut runs = Vec::new();
let mut start = 0;
@ -605,14 +599,8 @@ impl ShapeLine {
runs
}
pub fn layout(
&self,
font_size: i32,
line_width: i32,
wrap: Wrap,
) -> Vec<LayoutLine> {
pub fn layout(&self, font_size: i32, line_width: i32, wrap: Wrap) -> Vec<LayoutLine> {
let mut layout_lines = Vec::with_capacity(1);
// This is used to create a visual line for empty lines (e.g. lines with only a <CR>)
@ -627,8 +615,6 @@ impl ShapeLine {
let mut x = start_x;
let mut y;
// This would keep the maximum number of spans that would fit on a visual line
// If one span is too large, this variable will hold the range of words inside that span
// that fits on a line.
@ -636,22 +622,22 @@ impl ShapeLine {
if wrap == Wrap::None {
for (span_index, span) in self.spans.iter().enumerate() {
current_visual_line.push((span_index, (0,0), (span.words.len(), 0)));
current_visual_line.push((span_index, (0, 0), (span.words.len(), 0)));
}
}
else {
} else {
let mut fit_x = line_width as f32;
for (span_index, span) in self.spans.iter().enumerate() {
let mut word_ranges = Vec::new();
let mut word_range_width = 0.;
// Create the word ranges that fits in a visual line
if self.rtl != span.level.is_rtl() { // incongruent directions
if self.rtl != span.level.is_rtl() {
// incongruent directions
let mut fitting_start = (span.words.len(), 0);
for (i, word) in span.words.iter().enumerate().rev() {
let word_size = font_size as f32 * word.x_advance;
if fit_x - word_size >= 0. { // fits
if fit_x - word_size >= 0. {
// fits
fit_x -= word_size;
word_range_width += word_size;
continue;
@ -663,33 +649,39 @@ impl ShapeLine {
word_range_width += glyph_size;
continue;
} else {
word_ranges.push(((i, glyph_i+1), fitting_start, word_range_width));
word_ranges.push((
(i, glyph_i + 1),
fitting_start,
word_range_width,
));
fit_x = line_width as f32 - glyph_size;
word_range_width = glyph_size;
fitting_start = (i, glyph_i+1);
fitting_start = (i, glyph_i + 1);
}
}
} else { // Wrap::Word
word_ranges.push(((i+1, 0), fitting_start, word_range_width));
} else {
// Wrap::Word
word_ranges.push(((i + 1, 0), fitting_start, word_range_width));
if word.blank {
fit_x = line_width as f32;
word_range_width = 0.;
fitting_start = (i+1, 0);
fitting_start = (i + 1, 0);
} else {
fit_x = line_width as f32 - word_size;
word_range_width = word_size;
fitting_start = (i+1, 0);
fitting_start = (i + 1, 0);
}
}
}
word_ranges.push(((0,0),fitting_start, word_range_width));
} else { // congruent direction
let mut fitting_start = (0,0);
word_ranges.push(((0, 0), fitting_start, word_range_width));
} else {
// congruent direction
let mut fitting_start = (0, 0);
for (i, word) in span.words.iter().enumerate() {
let word_size = font_size as f32 * word.x_advance;
if fit_x - word_size >= 0. { // fits
if fit_x - word_size >= 0. {
// fits
fit_x -= word_size;
word_range_width += word_size;
continue;
@ -701,19 +693,24 @@ impl ShapeLine {
word_range_width += glyph_size;
continue;
} else {
word_ranges.push((fitting_start, (i, glyph_i), word_range_width));
word_ranges.push((
fitting_start,
(i, glyph_i),
word_range_width,
));
fit_x = line_width as f32 - glyph_size;
word_range_width = glyph_size;
fitting_start = (i, glyph_i);
}
}
} else { // Wrap::Word
word_ranges.push((fitting_start,(i,0), word_range_width));
} else {
// Wrap::Word
word_ranges.push((fitting_start, (i, 0), word_range_width));
if word.blank {
fit_x = line_width as f32;
word_range_width = 0.;
fitting_start = (i+1, 0);
fitting_start = (i + 1, 0);
} else {
fit_x = line_width as f32 - word_size;
word_range_width = word_size;
@ -725,12 +722,16 @@ impl ShapeLine {
}
// Create a visual line
for ((starting_word, starting_glyph), (ending_word, ending_glyph), word_range_width) in word_ranges {
for (
(starting_word, starting_glyph),
(ending_word, ending_glyph),
word_range_width,
) in word_ranges
{
// To simplify the algorithm above, we might push empty ranges but we ignore them here
if ending_word == starting_word && starting_glyph == ending_glyph {
if ending_word == starting_word && starting_glyph == ending_glyph {
continue;
}
let fits = !if self.rtl {
x - word_range_width < end_x
@ -738,27 +739,35 @@ impl ShapeLine {
x + word_range_width > end_x
};
if fits {
current_visual_line.push((span_index, (starting_word, starting_glyph), (ending_word, ending_glyph)));
current_visual_line.push((
span_index,
(starting_word, starting_glyph),
(ending_word, ending_glyph),
));
if self.rtl {
x -= word_range_width;
} else {
x += word_range_width;
}
} else {
if !current_visual_line.is_empty(){
if !current_visual_line.is_empty() {
vl_range_of_spans.push(current_visual_line);
current_visual_line = Vec::with_capacity(1);
x = start_x;
}
current_visual_line.push((span_index, (starting_word, starting_glyph), (ending_word, ending_glyph)));
current_visual_line.push((
span_index,
(starting_word, starting_glyph),
(ending_word, ending_glyph),
));
if self.rtl {
x -= word_range_width;
} else {
x += word_range_width;
}
if word_range_width > line_width as f32 { // single word is bigger than line_width
if word_range_width > line_width as f32 {
// single word is bigger than line_width
vl_range_of_spans.push(current_visual_line);
current_visual_line = Vec::with_capacity(1);
x = start_x;
@ -780,10 +789,18 @@ impl ShapeLine {
y = 0.;
if self.rtl {
for range in new_order.iter().rev() {
for (span_index, (starting_word, starting_glyph), (ending_word, ending_glyph)) in visual_line[range.clone()].iter() {
for (
span_index,
(starting_word, starting_glyph),
(ending_word, ending_glyph),
) in visual_line[range.clone()].iter()
{
let span = &self.spans[*span_index];
if starting_word == ending_word {
for glyph in span.words[*starting_word].glyphs[*starting_glyph..*ending_glyph].iter() {
for glyph in span.words[*starting_word].glyphs
[*starting_glyph..*ending_glyph]
.iter()
{
let x_advance = font_size as f32 * glyph.x_advance;
let y_advance = font_size as f32 * glyph.y_advance;
if self.rtl {
@ -796,7 +813,7 @@ impl ShapeLine {
y += y_advance;
}
} else {
for i in *starting_word..*ending_word+1 {
for i in *starting_word..*ending_word + 1 {
if let Some(word) = span.words.get(i) {
let (g1, g2) = if i == *starting_word {
(*starting_glyph, word.glyphs.len())
@ -825,10 +842,18 @@ impl ShapeLine {
}
} else {
for range in new_order {
for (span_index, (starting_word, starting_glyph), (ending_word, ending_glyph)) in visual_line[range.clone()].iter() {
for (
span_index,
(starting_word, starting_glyph),
(ending_word, ending_glyph),
) in visual_line[range.clone()].iter()
{
let span = &self.spans[*span_index];
if starting_word == ending_word {
for glyph in span.words[*starting_word].glyphs[*starting_glyph..*ending_glyph].iter() {
for glyph in span.words[*starting_word].glyphs
[*starting_glyph..*ending_glyph]
.iter()
{
let x_advance = font_size as f32 * glyph.x_advance;
let y_advance = font_size as f32 * glyph.y_advance;
if self.rtl {
@ -841,7 +866,7 @@ impl ShapeLine {
y += y_advance;
}
} else {
for i in *starting_word..*ending_word+1 {
for i in *starting_word..*ending_word + 1 {
if let Some(word) = span.words.get(i) {
let (g1, g2) = if i == *starting_word {
(*starting_glyph, word.glyphs.len())
@ -871,17 +896,18 @@ impl ShapeLine {
}
let mut glyphs_swap = Vec::new();
mem::swap(&mut glyphs, &mut glyphs_swap);
layout_lines.push(
LayoutLine {
w: if self.rtl { start_x - x } else { x },
glyphs: glyphs_swap,
},
);
layout_lines.push(LayoutLine {
w: if self.rtl { start_x - x } else { x },
glyphs: glyphs_swap,
});
push_line = false;
}
if push_line {
layout_lines.push(LayoutLine { w: 0.0 , glyphs: Default::default() });
layout_lines.push(LayoutLine {
w: 0.0,
glyphs: Default::default(),
});
}
layout_lines

View file

@ -6,22 +6,26 @@ use alloc::collections::BTreeMap as Map;
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::collections::HashMap as Map;
use swash::scale::{ScaleContext, image::Content};
use swash::scale::{image::Content, ScaleContext};
use swash::scale::{Render, Source, StrikeWith};
use swash::zeno::{Format, Vector};
use crate::{CacheKey, Color, FontSystem};
pub use swash::zeno::Command;
pub use swash::scale::image::{Content as SwashContent, Image as SwashImage};
pub use swash::zeno::Command;
fn swash_image(font_system: &FontSystem, context: &mut ScaleContext, cache_key: CacheKey) -> Option<SwashImage> {
fn swash_image(
font_system: &FontSystem,
context: &mut ScaleContext,
cache_key: CacheKey,
) -> Option<SwashImage> {
let font = match font_system.get_font(cache_key.font_id) {
Some(some) => some,
None => {
log::warn!("did not find font {:?}", cache_key.font_id);
return None;
},
}
};
// Build the scaler
@ -33,9 +37,7 @@ fn swash_image(font_system: &FontSystem, context: &mut ScaleContext, cache_key:
// Compute the fractional offset-- you'll likely want to quantize this
// in a real renderer
let offset =
Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float());
let offset = Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float());
// Select our source order
Render::new(&[
@ -54,7 +56,11 @@ fn swash_image(font_system: &FontSystem, context: &mut ScaleContext, cache_key:
.render(&mut scaler, cache_key.glyph_id)
}
fn swash_outline_commands(font_system: &FontSystem, context: &mut ScaleContext, cache_key: CacheKey) -> Option<Vec<swash::zeno::Command>> {
fn swash_outline_commands(
font_system: &FontSystem,
context: &mut ScaleContext,
cache_key: CacheKey,
) -> Option<Vec<swash::zeno::Command>> {
use swash::zeno::PathData as _;
let font = match font_system.get_font(cache_key.font_id) {
@ -62,10 +68,9 @@ fn swash_outline_commands(font_system: &FontSystem, context: &mut ScaleContext,
None => {
log::warn!("did not find font {:?}", cache_key.font_id);
return None;
},
}
};
// Build the scaler
let mut scaler = context
.builder(font.as_swash())
@ -73,7 +78,9 @@ fn swash_outline_commands(font_system: &FontSystem, context: &mut ScaleContext,
.build();
// Scale the outline
let outline = scaler.scale_outline(cache_key.glyph_id).or_else(|| scaler.scale_color_outline(cache_key.glyph_id))?;
let outline = scaler
.scale_outline(cache_key.glyph_id)
.or_else(|| scaler.scale_color_outline(cache_key.glyph_id))?;
// Get the path information of the outline
let path = outline.path();
@ -97,7 +104,7 @@ impl<'a> SwashCache<'a> {
font_system,
context: ScaleContext::new(),
image_cache: Map::new(),
outline_command_cache: Map::new()
outline_command_cache: Map::new(),
}
}
@ -108,15 +115,18 @@ impl<'a> SwashCache<'a> {
/// Create a swash Image from a cache key, caching results
pub fn get_image(&mut self, cache_key: CacheKey) -> &Option<SwashImage> {
self.image_cache.entry(cache_key).or_insert_with(|| {
swash_image(self.font_system, &mut self.context, cache_key)
})
self.image_cache
.entry(cache_key)
.or_insert_with(|| swash_image(self.font_system, &mut self.context, cache_key))
}
pub fn get_outline_commands(&mut self, cache_key: CacheKey) -> Option<&[swash::zeno::Command]> {
self.outline_command_cache.entry(cache_key).or_insert_with(|| {
swash_outline_commands(self.font_system, &mut self.context, cache_key)
}).as_deref()
self.outline_command_cache
.entry(cache_key)
.or_insert_with(|| {
swash_outline_commands(self.font_system, &mut self.context, cache_key)
})
.as_deref()
}
/// Enumerate pixels in an Image, use `with_image` for better performance
@ -124,7 +134,7 @@ impl<'a> SwashCache<'a> {
&mut self,
cache_key: CacheKey,
base: Color,
mut f: F
mut f: F,
) {
if let Some(image) = self.get_image(cache_key) {
let x = image.placement.left;
@ -139,10 +149,7 @@ impl<'a> SwashCache<'a> {
f(
x + off_x,
y + off_y,
Color(
((image.data[i] as u32) << 24) |
base.0 & 0xFF_FF_FF
)
Color(((image.data[i] as u32) << 24) | base.0 & 0xFF_FF_FF),
);
i += 1;
}
@ -160,8 +167,8 @@ impl<'a> SwashCache<'a> {
image.data[i],
image.data[i + 1],
image.data[i + 2],
image.data[i + 3]
)
image.data[i + 3],
),
);
i += 4;
}