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

@ -1,45 +1,20 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use cosmic::{
Element,
iced::{
self,
Color,
Alignment,
Application,
Command,
Length,
widget::{
column,
horizontal_space,
pick_list,
row,
},
widget::{column, horizontal_space, pick_list, row},
Alignment, Application, Color, Command, Length,
},
settings,
theme::{self, Theme},
widget::{
button,
toggler,
},
widget::{button, toggler},
Element,
};
use cosmic_text::{
Attrs,
AttrsList,
Buffer,
Edit,
FontSystem,
Metrics,
SyntaxEditor,
SyntaxSystem,
Wrap,
};
use std::{
env,
fs,
path::PathBuf,
sync::Mutex,
Attrs, AttrsList, Buffer, Edit, FontSystem, Metrics, SyntaxEditor, SyntaxSystem, Wrap,
};
use std::{env, fs, path::PathBuf, sync::Mutex};
use self::text::text;
mod text;
@ -61,11 +36,7 @@ static FONT_SIZES: &'static [Metrics] = &[
Metrics::new(32, 44), // Title 1
];
static WRAP_MODE: &'static [Wrap] = & [
Wrap::None,
Wrap::Glyph,
Wrap::Word,
];
static WRAP_MODE: &'static [Wrap] = &[Wrap::None, Wrap::Glyph, Wrap::Word];
fn main() -> cosmic::iced::Result {
env_logger::init();
@ -105,7 +76,7 @@ impl Window {
Ok(()) => {
log::info!("opened '{}'", path.display());
self.path_opt = Some(path);
},
}
Err(err) => {
log::error!("failed to open '{}': {}", path.display(), err);
self.path_opt = None;
@ -128,8 +99,9 @@ impl Application for Window {
let mut editor = SyntaxEditor::new(
Buffer::new(&FONT_SYSTEM, FONT_SIZES[1 /* Body */]),
&SYNTAX_SYSTEM,
"base16-eighties.dark"
).unwrap();
"base16-eighties.dark",
)
.unwrap();
#[cfg(feature = "vi")]
let mut editor = cosmic_text::ViEditor::new(editor);
@ -154,7 +126,11 @@ impl Application for Window {
fn title(&self) -> String {
if let Some(path) = &self.path_opt {
format!("COSMIC Text - {} - {}", FONT_SYSTEM.locale(), path.display())
format!(
"COSMIC Text - {} - {}",
FONT_SYSTEM.locale(),
path.display()
)
} else {
format!("COSMIC Text - {}", FONT_SYSTEM.locale())
}
@ -166,7 +142,7 @@ impl Application for Window {
if let Some(path) = rfd::FileDialog::new().pick_file() {
self.open(path);
}
},
}
Message::Save => {
if let Some(path) = &self.path_opt {
let editor = self.editor.lock().unwrap();
@ -178,13 +154,13 @@ impl Application for Window {
match fs::write(path, text) {
Ok(()) => {
log::info!("saved '{}'", path.display());
},
}
Err(err) => {
log::error!("failed to save '{}': {}", path.display(), err);
}
}
}
},
}
Message::Bold(bold) => {
self.attrs = self.attrs.weight(if bold {
cosmic_text::Weight::BOLD
@ -194,7 +170,7 @@ impl Application for Window {
let mut editor = self.editor.lock().unwrap();
update_attrs(&mut *editor, self.attrs);
},
}
Message::Italic(italic) => {
self.attrs = self.attrs.style(if italic {
cosmic_text::Style::Italic
@ -204,9 +180,10 @@ impl Application for Window {
let mut editor = self.editor.lock().unwrap();
update_attrs(&mut *editor, self.attrs);
},
}
Message::Monospaced(monospaced) => {
self.attrs = self.attrs
self.attrs = self
.attrs
.family(if monospaced {
cosmic_text::Family::Monospace
} else {
@ -216,15 +193,15 @@ impl Application for Window {
let mut editor = self.editor.lock().unwrap();
update_attrs(&mut *editor, self.attrs);
},
}
Message::MetricsChanged(metrics) => {
let mut editor = self.editor.lock().unwrap();
editor.buffer_mut().set_metrics(metrics);
},
}
Message::WrapChanged(wrap) => {
let mut editor = self.editor.lock().unwrap();
editor.buffer_mut().set_wrap(wrap);
},
}
Message::ThemeChanged(theme) => {
self.theme = match theme {
"Dark" => Theme::Dark,
@ -234,11 +211,16 @@ impl Application for Window {
let Color { r, g, b, a } = self.theme.palette().text;
let as_u8 = |component: f32| (component * 255.0) as u8;
self.attrs = self.attrs.color(cosmic_text::Color::rgba(as_u8(r), as_u8(g), as_u8(b), as_u8(a)));
self.attrs = self.attrs.color(cosmic_text::Color::rgba(
as_u8(r),
as_u8(g),
as_u8(b),
as_u8(a),
));
let mut editor = self.editor.lock().unwrap();
update_attrs(&mut *editor, self.attrs);
},
}
}
Command::none()
@ -252,7 +234,7 @@ impl Application for Window {
Theme::Dark => THEMES[0],
Theme::Light => THEMES[1],
}),
Message::ThemeChanged
Message::ThemeChanged,
);
let font_size_picker = {
@ -260,7 +242,7 @@ impl Application for Window {
pick_list(
FONT_SIZES,
Some(editor.buffer().metrics()),
Message::MetricsChanged
Message::MetricsChanged,
)
};
@ -283,9 +265,17 @@ impl Application for Window {
.on_press(Message::Save),
horizontal_space(Length::Fill),
text("Bold:"),
toggler(None, self.attrs.weight == cosmic_text::Weight::BOLD, Message::Bold),
toggler(
None,
self.attrs.weight == cosmic_text::Weight::BOLD,
Message::Bold
),
text("Italic:"),
toggler(None, self.attrs.style == cosmic_text::Style::Italic, Message::Italic),
toggler(
None,
self.attrs.style == cosmic_text::Style::Italic,
Message::Italic
),
text("Monospaced:"),
toggler(None, self.attrs.monospaced, Message::Monospaced),
text("Theme:"),
@ -296,8 +286,7 @@ impl Application for Window {
wrap_picker,
]
.align_items(Alignment::Center)
.spacing(8)
,
.spacing(8),
text_box(&self.editor)
]
.spacing(8)

View file

@ -2,26 +2,16 @@
use cosmic::{
iced_native::{
{Color, Element, Length, Point, Rectangle, Size},
image,
layout::{self, Layout},
renderer,
widget::{self, tree, Widget},
{Color, Element, Length, Point, Rectangle, Size},
},
theme::Theme,
};
use cosmic_text::{
Attrs,
AttrsList,
SwashCache,
BufferLine,
Metrics,
};
use std::{
cmp,
sync::Mutex,
time::Instant,
};
use cosmic_text::{Attrs, AttrsList, BufferLine, Metrics, SwashCache};
use std::{cmp, sync::Mutex, time::Instant};
pub struct Appearance {
background_color: Option<Color>,
@ -57,10 +47,7 @@ impl Text {
let instant = Instant::now();
//TODO: make it possible to set attrs
let mut line = BufferLine::new(
string,
AttrsList::new(Attrs::new())
);
let mut line = BufferLine::new(string, AttrsList::new(Attrs::new()));
//TODO: do we have to immediately shape?
line.shape(&crate::FONT_SYSTEM);
@ -101,11 +88,7 @@ where
Length::Shrink
}
fn layout(
&self,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
fn layout(&self, _renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
let instant = Instant::now();
let limits = limits.width(Length::Shrink).height(Length::Shrink);
@ -116,7 +99,7 @@ where
let layout_lines = shape.layout(
self.metrics.font_size,
limits.max().width as i32,
self.line.wrap()
self.line.wrap(),
);
let mut width = 0;
@ -160,7 +143,7 @@ where
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
background_color
background_color,
);
}
@ -177,11 +160,7 @@ where
let shape = self.line.shape_opt().as_ref().unwrap();
//TODO: can we cache this?
let layout_lines = shape.layout(
self.metrics.font_size,
layout_w,
self.line.wrap()
);
let layout_lines = shape.layout(self.metrics.font_size, layout_w, self.line.wrap());
let mut cache = state.cache.lock().unwrap();
@ -219,7 +198,7 @@ pub fn draw_pixel(
height: i32,
x: i32,
y: i32,
color: cosmic_text::Color
color: cosmic_text::Color,
) {
let alpha = (color.0 >> 24) & 0xFF;
if alpha == 0 {
@ -239,11 +218,10 @@ pub fn draw_pixel(
let offset = (y as usize * width as usize + x as usize) * 4;
let mut current =
buffer[offset + 2] as u32 |
(buffer[offset + 1] as u32) << 8 |
(buffer[offset + 0] as u32) << 16 |
(buffer[offset + 3] as u32) << 24;
let mut current = buffer[offset + 2] as u32
| (buffer[offset + 1] as u32) << 8
| (buffer[offset + 0] as u32) << 16
| (buffer[offset + 3] as u32) << 24;
if alpha >= 255 || current == 0 {
// Alpha is 100% or current is null, replace with no blending

View file

@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use super::text;
use cosmic::{
iced_native::{
{Color, Element, Length, Point, Rectangle, Shell, Size},
clipboard::Clipboard,
event::{Event, Status},
image,
@ -11,21 +11,12 @@ use cosmic::{
mouse::{self, Button, Event as MouseEvent, ScrollDelta},
renderer,
widget::{self, tree, Widget},
Padding
Padding, {Color, Element, Length, Point, Rectangle, Shell, Size},
},
theme::Theme,
};
use cosmic_text::{
Action,
Edit,
SwashCache,
};
use std::{
cmp,
sync::Mutex,
time::Instant,
};
use super::text;
use cosmic_text::{Action, Edit, SwashCache};
use std::{cmp, sync::Mutex, time::Instant};
pub struct Appearance {
background_color: Option<Color>,
@ -68,7 +59,6 @@ impl<'a, Editor> TextBox<'a, Editor> {
self.padding = padding.into();
self
}
}
pub fn text_box<'a, Editor>(editor: &'a Mutex<Editor>) -> TextBox<'a, Editor> {
@ -97,11 +87,7 @@ where
Length::Fill
}
fn layout(
&self,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
fn layout(&self, _renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
let limits = limits.width(Length::Fill).height(Length::Fill);
//TODO: allow lazy shape
@ -160,7 +146,7 @@ where
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
background_color
background_color,
);
}
@ -173,8 +159,10 @@ where
let mut editor = self.editor.lock().unwrap();
let view_w = cmp::min(viewport.width as i32, layout.bounds().width as i32) - self.padding.horizontal() as i32;
let view_h = cmp::min(viewport.height as i32, layout.bounds().height as i32) - self.padding.vertical() as i32;
let view_w = cmp::min(viewport.width as i32, layout.bounds().width as i32)
- self.padding.horizontal() as i32;
let view_h = cmp::min(viewport.height as i32, layout.bounds().height as i32)
- self.padding.vertical() as i32;
editor.buffer_mut().set_size(view_w, view_h);
editor.shape_as_needed();
@ -183,41 +171,51 @@ where
let mut pixels = vec![0; view_w as usize * view_h as usize * 4];
editor.draw(&mut state.cache.lock().unwrap(), text_color, |x, y, w, h, color| {
if w <= 0 || h <= 0 {
// Do not draw invalid sized rectangles
return;
}
editor.draw(
&mut state.cache.lock().unwrap(),
text_color,
|x, y, w, h, color| {
if w <= 0 || h <= 0 {
// Do not draw invalid sized rectangles
return;
}
if w > 1 || h > 1 {
// Draw rectangles with optimized quad renderer
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle::new(
layout.position() + [x as f32, y as f32].into() + [self.padding.left as f32, self.padding.top as f32].into(),
Size::new(w as f32 , h as f32)
if w > 1 || h > 1 {
// Draw rectangles with optimized quad renderer
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle::new(
layout.position()
+ [x as f32, y as f32].into()
+ [self.padding.left as f32, self.padding.top as f32].into(),
Size::new(w as f32, h as f32),
),
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Color::from_rgba8(
color.r(),
color.g(),
color.b(),
(color.a() as f32) / 255.0,
),
border_radius: 0.0,
border_width: 0.0,
border_color: Color::TRANSPARENT,
},
Color::from_rgba8(
color.r(),
color.g(),
color.b(),
(color.a() as f32) / 255.0
)
);
} else {
text::draw_pixel(&mut pixels, view_w, view_h, x, y, color);
}
});
);
} else {
text::draw_pixel(&mut pixels, view_w, view_h, x, y, color);
}
},
);
let handle = image::Handle::from_pixels(view_w as u32, view_h as u32, pixels);
image::Renderer::draw(renderer, handle, Rectangle::new(
layout.position() + [self.padding.left as f32, self.padding.top as f32].into(),
Size::new(view_w as f32, view_h as f32)
));
image::Renderer::draw(
renderer,
handle,
Rectangle::new(
layout.position() + [self.padding.left as f32, self.padding.top as f32].into(),
Size::new(view_w as f32, view_h as f32),
),
);
let duration = instant.elapsed();
log::debug!("redraw {}, {}: {:?}", view_w, view_h, duration);
@ -238,101 +236,107 @@ where
let mut status = Status::Ignored;
match event {
Event::Keyboard(KeyEvent::KeyPressed { key_code, modifiers }) => match key_code {
Event::Keyboard(KeyEvent::KeyPressed {
key_code,
modifiers,
}) => match key_code {
KeyCode::Left => {
editor.action(Action::Left);
status = Status::Captured;
},
}
KeyCode::Right => {
editor.action(Action::Right);
status = Status::Captured;
},
}
KeyCode::Up => {
editor.action(Action::Up);
status = Status::Captured;
},
}
KeyCode::Down => {
editor.action(Action::Down);
status = Status::Captured;
},
}
KeyCode::Home => {
editor.action(Action::Home);
status = Status::Captured;
},
}
KeyCode::End => {
editor.action(Action::End);
status = Status::Captured;
},
}
KeyCode::PageUp => {
editor.action(Action::PageUp);
status = Status::Captured;
},
}
KeyCode::PageDown => {
editor.action(Action::PageDown);
status = Status::Captured;
},
}
KeyCode::Escape => {
editor.action(Action::Escape);
status = Status::Captured;
},
}
KeyCode::Enter => {
editor.action(Action::Enter);
status = Status::Captured;
},
}
KeyCode::Backspace => {
editor.action(Action::Backspace);
status = Status::Captured;
},
}
KeyCode::Delete => {
editor.action(Action::Delete);
status = Status::Captured;
},
_ => ()
}
_ => (),
},
Event::Keyboard(KeyEvent::CharacterReceived(character)) => {
editor.action(Action::Insert(character));
status = Status::Captured;
},
}
Event::Mouse(MouseEvent::ButtonPressed(Button::Left)) => {
if layout.bounds().contains(cursor_position) {
editor.action(Action::Click {
x: (cursor_position.x - layout.bounds().x) as i32 - self.padding.left as i32,
x: (cursor_position.x - layout.bounds().x) as i32
- self.padding.left as i32,
y: (cursor_position.y - layout.bounds().y) as i32 - self.padding.top as i32,
});
state.is_dragging = true;
status = Status::Captured;
}
},
}
Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => {
state.is_dragging = false;
status = Status::Captured;
},
}
Event::Mouse(MouseEvent::CursorMoved { .. }) => {
if state.is_dragging {
editor.action(Action::Drag {
x: (cursor_position.x - layout.bounds().x) as i32 - self.padding.left as i32,
x: (cursor_position.x - layout.bounds().x) as i32
- self.padding.left as i32,
y: (cursor_position.y - layout.bounds().y) as i32 - self.padding.top as i32,
});
status = Status::Captured;
}
},
}
Event::Mouse(MouseEvent::WheelScrolled { delta }) => match delta {
ScrollDelta::Lines { x, y } => {
editor.action(Action::Scroll {
lines: (-y * 6.0) as i32,
});
status = Status::Captured;
},
}
_ => (),
},
_ => ()
_ => (),
}
status
}
}
impl<'a, 'editor, Editor, Message, Renderer> From<TextBox<'a, Editor>> for Element<'a, Message, Renderer>
impl<'a, 'editor, Editor, Message, Renderer> From<TextBox<'a, Editor>>
for Element<'a, Message, Renderer>
where
Renderer: renderer::Renderer + image::Renderer<Handle = image::Handle>,
Renderer::Theme: StyleSheet,

View file

@ -1,19 +1,14 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use cosmic_text::{
Action,
Attrs,
Buffer,
Edit,
Family,
FontSystem,
Metrics,
SwashCache,
SyntaxEditor,
Action, Attrs, Buffer, Edit, Family, FontSystem, Metrics, SwashCache, SyntaxEditor,
SyntaxSystem,
};
use orbclient::{EventOption, Renderer, Window, WindowFlag};
use std::{env, thread, time::{Duration, Instant}};
use std::{
env, thread,
time::{Duration, Instant},
};
fn main() {
env_logger::init();
@ -65,20 +60,18 @@ fn main() {
let mut editor = SyntaxEditor::new(
Buffer::new(&font_system, font_sizes[font_size_i]),
&syntax_system,
"base16-eighties.dark"
).unwrap();
"base16-eighties.dark",
)
.unwrap();
#[cfg(feature = "vi")]
let mut editor = cosmic_text::ViEditor::new(editor);
editor.buffer_mut().set_size(
window.width() as i32 - line_x * 2,
window.height() as i32
);
editor
.buffer_mut()
.set_size(window.width() as i32 - line_x * 2, window.height() as i32);
let attrs = Attrs::new()
.monospaced(true)
.family(Family::Monospace);
let attrs = Attrs::new().monospaced(true).family(Family::Monospace);
match editor.load_text(&path, attrs) {
Ok(()) => (),
Err(err) => {
@ -212,7 +205,9 @@ fn main() {
}
}
EventOption::Resize(event) => {
editor.buffer_mut().set_size(event.width as i32 - line_x * 2, event.height as i32);
editor
.buffer_mut()
.set_size(event.width as i32 - line_x * 2, event.height as i32);
}
EventOption::Scroll(event) => {
editor.action(Action::Scroll {
@ -243,7 +238,7 @@ fn main() {
window.set_async(window_async);
}
if window_async && ! found_event {
if window_async && !found_event {
// In async mode and no event found, sleep
thread::sleep(Duration::from_millis(5));
}

View file

@ -54,14 +54,8 @@ fn main() {
];
let font_size_default = 1; // Body
let mut buffer = Buffer::new(
&font_system,
font_sizes[font_size_default]
);
buffer.set_size(
window.width() as i32,
window.height() as i32
);
let mut buffer = Buffer::new(&font_system, font_sizes[font_size_default]);
buffer.set_size(window.width() as i32, window.height() as i32);
let mut editor = Editor::new(buffer);

View file

@ -1,23 +1,14 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use cosmic_text::{
Action,
Attrs,
AttrsList,
Buffer,
BufferLine,
Color,
Edit,
Editor,
Family,
FontSystem,
Metrics,
Style,
SwashCache,
Weight,
Action, Attrs, AttrsList, Buffer, BufferLine, Color, Edit, Editor, Family, FontSystem, Metrics,
Style, SwashCache, Weight,
};
use orbclient::{EventOption, Renderer, Window, WindowFlag};
use std::{process, thread, time::{Duration, Instant}};
use std::{
process, thread,
time::{Duration, Instant},
};
fn main() {
env_logger::init();
@ -47,13 +38,12 @@ fn main() {
let mut editor = Editor::new(Buffer::new(
&font_system,
Metrics::new(32, 44).scale(display_scale)
Metrics::new(32, 44).scale(display_scale),
));
editor.buffer_mut().set_size(
window.width() as i32,
window.height() as i32
);
editor
.buffer_mut()
.set_size(window.width() as i32, window.height() as i32);
let attrs = Attrs::new();
let serif_attrs = attrs.family(Family::Serif);
@ -79,25 +69,37 @@ fn main() {
("Sans-Serif Normal ", attrs),
("Sans-Serif Bold ", attrs.weight(Weight::BOLD)),
("Sans-Serif Italic ", attrs.style(Style::Italic)),
("Sans-Serif Bold Italic", attrs.weight(Weight::BOLD).style(Style::Italic)),
(
"Sans-Serif Bold Italic",
attrs.weight(Weight::BOLD).style(Style::Italic),
),
],
&[
("Serif Normal ", serif_attrs),
("Serif Bold ", serif_attrs.weight(Weight::BOLD)),
("Serif Italic ", serif_attrs.style(Style::Italic)),
("Serif Bold Italic", serif_attrs.weight(Weight::BOLD).style(Style::Italic)),
(
"Serif Bold Italic",
serif_attrs.weight(Weight::BOLD).style(Style::Italic),
),
],
&[
("Mono Normal ", mono_attrs),
("Mono Bold ", mono_attrs.weight(Weight::BOLD)),
("Mono Italic ", mono_attrs.style(Style::Italic)),
("Mono Bold Italic", mono_attrs.weight(Weight::BOLD).style(Style::Italic)),
(
"Mono Bold Italic",
mono_attrs.weight(Weight::BOLD).style(Style::Italic),
),
],
&[
("Comic Normal ", comic_attrs),
("Comic Bold ", comic_attrs.weight(Weight::BOLD)),
("Comic Italic ", comic_attrs.style(Style::Italic)),
("Comic Bold Italic", comic_attrs.weight(Weight::BOLD).style(Style::Italic)),
(
"Comic Bold Italic",
comic_attrs.weight(Weight::BOLD).style(Style::Italic),
),
],
&[
("R", attrs.color(Color::rgb(0xFF, 0x00, 0x00))),
@ -121,10 +123,11 @@ fn main() {
("O", attrs.color(Color::rgb(0xFF, 0xFF, 0x00))),
("R", attrs.color(Color::rgb(0xFF, 0x7F, 0x00))),
("N", attrs.color(Color::rgb(0xFF, 0x00, 0x00))),
],
&[
("生活,삶,जिंदगी 😀 FPS", attrs.color(Color::rgb(0xFF, 0x00, 0x00)))
]
],
&[(
"生活,삶,जिंदगी 😀 FPS",
attrs.color(Color::rgb(0xFF, 0x00, 0x00)),
)],
];
for &line in lines {
let mut line_text = String::new();
@ -135,7 +138,10 @@ fn main() {
let end = line_text.len();
attrs_list.add_span(start..end, attrs);
}
editor.buffer_mut().lines.push(BufferLine::new(line_text, attrs_list));
editor
.buffer_mut()
.lines
.push(BufferLine::new(line_text, attrs_list));
}
let mut swash_cache = SwashCache::new(&font_system);
@ -187,18 +193,26 @@ fn main() {
mouse_x = mouse.x;
mouse_y = mouse.y;
if mouse_left {
editor.action(Action::Drag { x: mouse_x, y: mouse_y });
editor.action(Action::Drag {
x: mouse_x,
y: mouse_y,
});
}
},
}
EventOption::Button(button) => {
mouse_left = button.left;
if mouse_left {
editor.action(Action::Click { x: mouse_x, y: mouse_y });
editor.action(Action::Click {
x: mouse_x,
y: mouse_y,
});
}
},
}
EventOption::Resize(resize) => {
editor.buffer_mut().set_size(resize.width as i32, resize.height as i32);
},
editor
.buffer_mut()
.set_size(resize.width as i32, resize.height as i32);
}
EventOption::Quit(_) => process::exit(0),
_ => (),
}

View file

@ -1,11 +1,8 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use cosmic_text::{Attrs, Color, FontSystem, SwashCache, Buffer, Metrics};
use cosmic_text::{Attrs, Buffer, Color, FontSystem, Metrics, SwashCache};
use std::cmp;
use termion::{
color,
cursor,
};
use termion::{color, cursor};
fn main() {
// A FontSystem provides access to detected system fonts, create one per application
@ -66,11 +63,7 @@ fn main() {
}
// Scale by alpha (mimics blending with black)
let scale = |c: u8| {
cmp::max(0, cmp::min(255,
((c as i32) * (a as i32)) / 255
)) as u8
};
let scale = |c: u8| cmp::max(0, cmp::min(255, ((c as i32) * (a as i32)) / 255)) as u8;
// Navigate to x coordinate
if x > last_x {

View file

@ -1 +0,0 @@
disable_all_formatting = true

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;
}