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

View file

@ -2,26 +2,16 @@
use cosmic::{ use cosmic::{
iced_native::{ iced_native::{
{Color, Element, Length, Point, Rectangle, Size},
image, image,
layout::{self, Layout}, layout::{self, Layout},
renderer, renderer,
widget::{self, tree, Widget}, widget::{self, tree, Widget},
{Color, Element, Length, Point, Rectangle, Size},
}, },
theme::Theme, theme::Theme,
}; };
use cosmic_text::{ use cosmic_text::{Attrs, AttrsList, BufferLine, Metrics, SwashCache};
Attrs, use std::{cmp, sync::Mutex, time::Instant};
AttrsList,
SwashCache,
BufferLine,
Metrics,
};
use std::{
cmp,
sync::Mutex,
time::Instant,
};
pub struct Appearance { pub struct Appearance {
background_color: Option<Color>, background_color: Option<Color>,
@ -57,10 +47,7 @@ impl Text {
let instant = Instant::now(); let instant = Instant::now();
//TODO: make it possible to set attrs //TODO: make it possible to set attrs
let mut line = BufferLine::new( let mut line = BufferLine::new(string, AttrsList::new(Attrs::new()));
string,
AttrsList::new(Attrs::new())
);
//TODO: do we have to immediately shape? //TODO: do we have to immediately shape?
line.shape(&crate::FONT_SYSTEM); line.shape(&crate::FONT_SYSTEM);
@ -101,11 +88,7 @@ where
Length::Shrink Length::Shrink
} }
fn layout( fn layout(&self, _renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
&self,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let instant = Instant::now(); let instant = Instant::now();
let limits = limits.width(Length::Shrink).height(Length::Shrink); let limits = limits.width(Length::Shrink).height(Length::Shrink);
@ -116,7 +99,7 @@ where
let layout_lines = shape.layout( let layout_lines = shape.layout(
self.metrics.font_size, self.metrics.font_size,
limits.max().width as i32, limits.max().width as i32,
self.line.wrap() self.line.wrap(),
); );
let mut width = 0; let mut width = 0;
@ -160,7 +143,7 @@ where
border_width: 0.0, border_width: 0.0,
border_color: Color::TRANSPARENT, border_color: Color::TRANSPARENT,
}, },
background_color background_color,
); );
} }
@ -177,11 +160,7 @@ where
let shape = self.line.shape_opt().as_ref().unwrap(); let shape = self.line.shape_opt().as_ref().unwrap();
//TODO: can we cache this? //TODO: can we cache this?
let layout_lines = shape.layout( let layout_lines = shape.layout(self.metrics.font_size, layout_w, self.line.wrap());
self.metrics.font_size,
layout_w,
self.line.wrap()
);
let mut cache = state.cache.lock().unwrap(); let mut cache = state.cache.lock().unwrap();
@ -219,7 +198,7 @@ pub fn draw_pixel(
height: i32, height: i32,
x: i32, x: i32,
y: i32, y: i32,
color: cosmic_text::Color color: cosmic_text::Color,
) { ) {
let alpha = (color.0 >> 24) & 0xFF; let alpha = (color.0 >> 24) & 0xFF;
if alpha == 0 { if alpha == 0 {
@ -239,11 +218,10 @@ pub fn draw_pixel(
let offset = (y as usize * width as usize + x as usize) * 4; let offset = (y as usize * width as usize + x as usize) * 4;
let mut current = let mut current = buffer[offset + 2] as u32
buffer[offset + 2] as u32 | | (buffer[offset + 1] as u32) << 8
(buffer[offset + 1] as u32) << 8 | | (buffer[offset + 0] as u32) << 16
(buffer[offset + 0] as u32) << 16 | | (buffer[offset + 3] as u32) << 24;
(buffer[offset + 3] as u32) << 24;
if alpha >= 255 || current == 0 { if alpha >= 255 || current == 0 {
// Alpha is 100% or current is null, replace with no blending // 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 // SPDX-License-Identifier: MIT OR Apache-2.0
use super::text;
use cosmic::{ use cosmic::{
iced_native::{ iced_native::{
{Color, Element, Length, Point, Rectangle, Shell, Size},
clipboard::Clipboard, clipboard::Clipboard,
event::{Event, Status}, event::{Event, Status},
image, image,
@ -11,21 +11,12 @@ use cosmic::{
mouse::{self, Button, Event as MouseEvent, ScrollDelta}, mouse::{self, Button, Event as MouseEvent, ScrollDelta},
renderer, renderer,
widget::{self, tree, Widget}, widget::{self, tree, Widget},
Padding Padding, {Color, Element, Length, Point, Rectangle, Shell, Size},
}, },
theme::Theme, theme::Theme,
}; };
use cosmic_text::{ use cosmic_text::{Action, Edit, SwashCache};
Action, use std::{cmp, sync::Mutex, time::Instant};
Edit,
SwashCache,
};
use std::{
cmp,
sync::Mutex,
time::Instant,
};
use super::text;
pub struct Appearance { pub struct Appearance {
background_color: Option<Color>, background_color: Option<Color>,
@ -68,7 +59,6 @@ impl<'a, Editor> TextBox<'a, Editor> {
self.padding = padding.into(); self.padding = padding.into();
self self
} }
} }
pub fn text_box<'a, Editor>(editor: &'a Mutex<Editor>) -> TextBox<'a, Editor> { pub fn text_box<'a, Editor>(editor: &'a Mutex<Editor>) -> TextBox<'a, Editor> {
@ -97,11 +87,7 @@ where
Length::Fill Length::Fill
} }
fn layout( fn layout(&self, _renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
&self,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(Length::Fill).height(Length::Fill); let limits = limits.width(Length::Fill).height(Length::Fill);
//TODO: allow lazy shape //TODO: allow lazy shape
@ -160,7 +146,7 @@ where
border_width: 0.0, border_width: 0.0,
border_color: Color::TRANSPARENT, border_color: Color::TRANSPARENT,
}, },
background_color background_color,
); );
} }
@ -173,8 +159,10 @@ where
let mut editor = self.editor.lock().unwrap(); 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_w = cmp::min(viewport.width as i32, layout.bounds().width as i32)
let view_h = cmp::min(viewport.height as i32, layout.bounds().height as i32) - self.padding.vertical() 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.buffer_mut().set_size(view_w, view_h);
editor.shape_as_needed(); editor.shape_as_needed();
@ -183,41 +171,51 @@ where
let mut pixels = vec![0; view_w as usize * view_h as usize * 4]; 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| { editor.draw(
if w <= 0 || h <= 0 { &mut state.cache.lock().unwrap(),
// Do not draw invalid sized rectangles text_color,
return; |x, y, w, h, color| {
} if w <= 0 || h <= 0 {
// Do not draw invalid sized rectangles
return;
}
if w > 1 || h > 1 { if w > 1 || h > 1 {
// Draw rectangles with optimized quad renderer // Draw rectangles with optimized quad renderer
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds: Rectangle::new( bounds: Rectangle::new(
layout.position() + [x as f32, y as f32].into() + [self.padding.left as f32, self.padding.top as f32].into(), layout.position()
Size::new(w as f32 , h as f32) + [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, } else {
border_color: Color::TRANSPARENT, text::draw_pixel(&mut pixels, view_w, view_h, x, y, color);
}, }
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);
}
});
let handle = image::Handle::from_pixels(view_w as u32, view_h as u32, pixels); let handle = image::Handle::from_pixels(view_w as u32, view_h as u32, pixels);
image::Renderer::draw(renderer, handle, Rectangle::new( image::Renderer::draw(
layout.position() + [self.padding.left as f32, self.padding.top as f32].into(), renderer,
Size::new(view_w as f32, view_h as f32) 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(); let duration = instant.elapsed();
log::debug!("redraw {}, {}: {:?}", view_w, view_h, duration); log::debug!("redraw {}, {}: {:?}", view_w, view_h, duration);
@ -238,101 +236,107 @@ where
let mut status = Status::Ignored; let mut status = Status::Ignored;
match event { match event {
Event::Keyboard(KeyEvent::KeyPressed { key_code, modifiers }) => match key_code { Event::Keyboard(KeyEvent::KeyPressed {
key_code,
modifiers,
}) => match key_code {
KeyCode::Left => { KeyCode::Left => {
editor.action(Action::Left); editor.action(Action::Left);
status = Status::Captured; status = Status::Captured;
}, }
KeyCode::Right => { KeyCode::Right => {
editor.action(Action::Right); editor.action(Action::Right);
status = Status::Captured; status = Status::Captured;
}, }
KeyCode::Up => { KeyCode::Up => {
editor.action(Action::Up); editor.action(Action::Up);
status = Status::Captured; status = Status::Captured;
}, }
KeyCode::Down => { KeyCode::Down => {
editor.action(Action::Down); editor.action(Action::Down);
status = Status::Captured; status = Status::Captured;
}, }
KeyCode::Home => { KeyCode::Home => {
editor.action(Action::Home); editor.action(Action::Home);
status = Status::Captured; status = Status::Captured;
}, }
KeyCode::End => { KeyCode::End => {
editor.action(Action::End); editor.action(Action::End);
status = Status::Captured; status = Status::Captured;
}, }
KeyCode::PageUp => { KeyCode::PageUp => {
editor.action(Action::PageUp); editor.action(Action::PageUp);
status = Status::Captured; status = Status::Captured;
}, }
KeyCode::PageDown => { KeyCode::PageDown => {
editor.action(Action::PageDown); editor.action(Action::PageDown);
status = Status::Captured; status = Status::Captured;
}, }
KeyCode::Escape => { KeyCode::Escape => {
editor.action(Action::Escape); editor.action(Action::Escape);
status = Status::Captured; status = Status::Captured;
}, }
KeyCode::Enter => { KeyCode::Enter => {
editor.action(Action::Enter); editor.action(Action::Enter);
status = Status::Captured; status = Status::Captured;
}, }
KeyCode::Backspace => { KeyCode::Backspace => {
editor.action(Action::Backspace); editor.action(Action::Backspace);
status = Status::Captured; status = Status::Captured;
}, }
KeyCode::Delete => { KeyCode::Delete => {
editor.action(Action::Delete); editor.action(Action::Delete);
status = Status::Captured; status = Status::Captured;
}, }
_ => () _ => (),
}, },
Event::Keyboard(KeyEvent::CharacterReceived(character)) => { Event::Keyboard(KeyEvent::CharacterReceived(character)) => {
editor.action(Action::Insert(character)); editor.action(Action::Insert(character));
status = Status::Captured; status = Status::Captured;
}, }
Event::Mouse(MouseEvent::ButtonPressed(Button::Left)) => { Event::Mouse(MouseEvent::ButtonPressed(Button::Left)) => {
if layout.bounds().contains(cursor_position) { if layout.bounds().contains(cursor_position) {
editor.action(Action::Click { 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, y: (cursor_position.y - layout.bounds().y) as i32 - self.padding.top as i32,
}); });
state.is_dragging = true; state.is_dragging = true;
status = Status::Captured; status = Status::Captured;
} }
}, }
Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => { Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => {
state.is_dragging = false; state.is_dragging = false;
status = Status::Captured; status = Status::Captured;
}, }
Event::Mouse(MouseEvent::CursorMoved { .. }) => { Event::Mouse(MouseEvent::CursorMoved { .. }) => {
if state.is_dragging { if state.is_dragging {
editor.action(Action::Drag { 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, y: (cursor_position.y - layout.bounds().y) as i32 - self.padding.top as i32,
}); });
status = Status::Captured; status = Status::Captured;
} }
}, }
Event::Mouse(MouseEvent::WheelScrolled { delta }) => match delta { Event::Mouse(MouseEvent::WheelScrolled { delta }) => match delta {
ScrollDelta::Lines { x, y } => { ScrollDelta::Lines { x, y } => {
editor.action(Action::Scroll { editor.action(Action::Scroll {
lines: (-y * 6.0) as i32, lines: (-y * 6.0) as i32,
}); });
status = Status::Captured; status = Status::Captured;
}, }
_ => (), _ => (),
}, },
_ => () _ => (),
} }
status 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 where
Renderer: renderer::Renderer + image::Renderer<Handle = image::Handle>, Renderer: renderer::Renderer + image::Renderer<Handle = image::Handle>,
Renderer::Theme: StyleSheet, Renderer::Theme: StyleSheet,

View file

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

View file

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

View file

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

View file

@ -1,11 +1,8 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // 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 std::cmp;
use termion::{ use termion::{color, cursor};
color,
cursor,
};
fn main() { fn main() {
// A FontSystem provides access to detected system fonts, create one per application // 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) // Scale by alpha (mimics blending with black)
let scale = |c: u8| { let scale = |c: u8| cmp::max(0, cmp::min(255, ((c as i32) * (a as i32)) / 255)) as u8;
cmp::max(0, cmp::min(255,
((c as i32) * (a as i32)) / 255
)) as u8
};
// Navigate to x coordinate // Navigate to x coordinate
if x > last_x { 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 /// Create new color with red, green, blue, and alpha components
#[inline] #[inline]
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self { pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self( Self(((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32))
((a as u32) << 24) |
((r as u32) << 16) |
((g as u32) << 8) |
(b as u32)
)
} }
/// Get the red component /// Get the red component
@ -165,22 +160,20 @@ impl<'a> Attrs<'a> {
/// Check if font matches /// Check if font matches
pub fn matches(&self, face: &fontdb::FaceInfo) -> bool { pub fn matches(&self, face: &fontdb::FaceInfo) -> bool {
//TODO: smarter way of including emoji //TODO: smarter way of including emoji
face.post_script_name.contains("Emoji") || face.post_script_name.contains("Emoji")
( || (face.style == self.style
face.style == self.style && && face.weight == self.weight
face.weight == self.weight && && face.stretch == self.stretch
face.stretch == self.stretch && && face.monospaced == self.monospaced)
face.monospaced == self.monospaced
)
} }
/// Check if this set of attributes can be shaped with another /// Check if this set of attributes can be shaped with another
pub fn compatible(&self, other: &Self) -> bool { pub fn compatible(&self, other: &Self) -> bool {
self.family == other.family self.family == other.family
&& self.monospaced == other.monospaced && self.monospaced == other.monospaced
&& self.stretch == other.stretch && self.stretch == other.stretch
&& self.style == other.style && self.style == other.style
&& self.weight == other.weight && self.weight == other.weight
} }
} }
@ -268,7 +261,10 @@ impl AttrsList {
/// ///
/// This returns a span that contains the index /// This returns a span that contains the index
pub fn get_span(&self, index: usize) -> Attrs { 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 /// Split attributes list at an offset
@ -288,20 +284,19 @@ impl AttrsList {
} }
for (key, resize) in removes { 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); self.spans.remove(key);
if resize { if resize {
new.spans.insert( new.spans.insert(0..range.end - index, attrs.clone());
0..range.end - index,
attrs.clone()
);
self.spans.insert(range.start..index, attrs); self.spans.insert(range.start..index, attrs);
} else { } else {
new.spans.insert( new.spans
range.start - index..range.end - index, .insert(range.start - index..range.end - index, attrs);
attrs
);
} }
} }
new new

View file

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

View file

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

View file

@ -1,9 +1,9 @@
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::string::String; use alloc::string::String;
use crate::{AttrsList, Buffer, Cursor};
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
use crate::Color; use crate::Color;
use crate::{AttrsList, Buffer, Cursor};
pub use self::editor::*; pub use self::editor::*;
mod editor; mod editor;
@ -114,5 +114,6 @@ pub trait Edit<'a> {
/// Draw the editor /// Draw the editor
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, f: F) 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"))] #[cfg(not(feature = "std"))]
use alloc::{ use alloc::{string::String, vec::Vec};
string::String,
vec::Vec,
};
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::{ use std::{fs, io, path::Path};
fs,
io,
path::Path,
};
use syntect::highlighting::{ use syntect::highlighting::{
FontStyle, FontStyle, HighlightState, Highlighter, RangedHighlightIterator, Theme, ThemeSet,
Highlighter,
HighlightState,
RangedHighlightIterator,
Theme,
ThemeSet,
};
use syntect::parsing::{
ParseState,
ScopeStack,
SyntaxReference,
SyntaxSet,
}; };
use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet};
use crate::{ use crate::{Action, AttrsList, Buffer, Color, Cursor, Edit, Editor, Style, Weight, Wrap};
Action,
AttrsList,
Buffer,
Color,
Cursor,
Edit,
Editor,
Style,
Weight,
Wrap,
};
pub struct SyntaxSystem { pub struct SyntaxSystem {
pub syntax_set: SyntaxSet, pub syntax_set: SyntaxSet,
@ -69,7 +41,11 @@ impl<'a> SyntaxEditor<'a> {
/// A good default theme name is "base16-eighties.dark". /// A good default theme name is "base16-eighties.dark".
/// ///
/// Returns None if theme not found /// 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 editor = Editor::new(buffer);
let syntax = syntax_system.syntax_set.find_syntax_plain_text(); let syntax = syntax_system.syntax_set.find_syntax_plain_text();
let theme = syntax_system.theme_set.themes.get(theme_name)?; 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 /// Returns an [`io::Error`] if reading the file fails
#[cfg(feature = "std")] #[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 path = path.as_ref();
let text = fs::read_to_string(path)?; let text = fs::read_to_string(path)?;
@ -119,12 +99,7 @@ impl<'a> SyntaxEditor<'a> {
/// Get the default background color /// Get the default background color
pub fn background_color(&self) -> Color { pub fn background_color(&self) -> Color {
if let Some(background) = self.theme.settings.background { if let Some(background) = self.theme.settings.background {
Color::rgba( Color::rgba(background.r, background.g, background.b, background.a)
background.r,
background.g,
background.b,
background.a,
)
} else { } else {
Color::rgb(0, 0, 0) Color::rgb(0, 0, 0)
} }
@ -133,12 +108,7 @@ impl<'a> SyntaxEditor<'a> {
/// Get the default foreground (text) color /// Get the default foreground (text) color
pub fn foreground_color(&self) -> Color { pub fn foreground_color(&self) -> Color {
if let Some(foreground) = self.theme.settings.foreground { if let Some(foreground) = self.theme.settings.foreground {
Color::rgba( Color::rgba(foreground.r, foreground.g, foreground.b, foreground.a)
foreground.r,
foreground.g,
foreground.b,
foreground.a,
)
} else { } else {
Color::rgb(0xFF, 0xFF, 0xFF) Color::rgb(0xFF, 0xFF, 0xFF)
} }
@ -175,21 +145,24 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
let mut highlighted = 0; let mut highlighted = 0;
for line_i in 0..buffer.lines.len() { for line_i in 0..buffer.lines.len() {
let line = &mut buffer.lines[line_i]; 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; continue;
} }
highlighted += 1; highlighted += 1;
let (mut parse_state, mut highlight_state) = if line_i > 0 && line_i <= self.syntax_cache.len() { let (mut parse_state, mut highlight_state) =
self.syntax_cache[line_i - 1].clone() if line_i > 0 && line_i <= self.syntax_cache.len() {
} else { self.syntax_cache[line_i - 1].clone()
( } else {
ParseState::new(self.syntax), (
HighlightState::new(&self.highlighter, ScopeStack::new()) 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( let ranges = RangedHighlightIterator::new(
&mut highlight_state, &mut highlight_state,
&ops, &ops,
@ -219,8 +192,7 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
Weight::BOLD Weight::BOLD
} else { } else {
Weight::NORMAL Weight::NORMAL
}) }), //TODO: underline
//TODO: underline
); );
} }
@ -247,7 +219,11 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
if highlighted > 0 { if highlighted > 0 {
buffer.set_redraw(true); buffer.set_redraw(true);
#[cfg(feature = "std")] #[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(); self.editor.shape_as_needed();
@ -272,7 +248,8 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
/// Draw the editor /// Draw the editor
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
fn draw<F>(&self, cache: &mut crate::SwashCache, _color: Color, mut f: F) 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(); let size = self.buffer().size();
f(0, 0, size.0 as u32, size.1 as u32, self.background_color()); 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 core::cmp;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use crate::{ use crate::{Action, AttrsList, Buffer, Color, Cursor, Edit, SyntaxEditor};
Action,
AttrsList,
Buffer,
Color,
Cursor,
Edit,
SyntaxEditor,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Mode { enum Mode {
@ -36,7 +28,11 @@ impl<'a> ViEditor<'a> {
/// Load text from a file, and also set syntax to the best option /// Load text from a file, and also set syntax to the best option
#[cfg(feature = "std")] #[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) self.editor.load_text(path, attrs)
} }
@ -98,12 +94,12 @@ impl<'a> Edit<'a> for ViEditor<'a> {
'a' => { 'a' => {
self.editor.action(Action::Right); self.editor.action(Action::Right);
self.mode = Mode::Insert; self.mode = Mode::Insert;
}, }
// Enter insert mode at end of line // Enter insert mode at end of line
'A' => { 'A' => {
self.editor.action(Action::End); self.editor.action(Action::End);
self.mode = Mode::Insert; self.mode = Mode::Insert;
}, }
// Change mode // Change mode
'c' => { 'c' => {
if self.editor.select_opt().is_some() { if self.editor.select_opt().is_some() {
@ -112,7 +108,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
} else { } else {
//TODO: change to next cursor movement //TODO: change to next cursor movement
} }
}, }
// Delete mode // Delete mode
'd' => { 'd' => {
if self.editor.select_opt().is_some() { if self.editor.select_opt().is_some() {
@ -120,11 +116,11 @@ impl<'a> Edit<'a> for ViEditor<'a> {
} else { } else {
//TODO: delete to next cursor movement //TODO: delete to next cursor movement
} }
}, }
// Enter insert mode at cursor // Enter insert mode at cursor
'i' => { 'i' => {
self.mode = Mode::Insert; self.mode = Mode::Insert;
}, }
// Enter insert mode at start of line // Enter insert mode at start of line
'I' => { 'I' => {
//TODO: soft home, skip whitespace //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::End);
self.editor.action(Action::Enter); self.editor.action(Action::Enter);
self.mode = Mode::Insert; self.mode = Mode::Insert;
}, }
// Create line before and enter insert mode // Create line before and enter insert mode
'O' => { 'O' => {
self.editor.action(Action::Home); 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.shape_as_needed(); // TODO: do not require this?
self.editor.action(Action::Up); self.editor.action(Action::Up);
self.mode = Mode::Insert; self.mode = Mode::Insert;
}, }
// Left // Left
'h' => self.editor.action(Action::Left), 'h' => self.editor.action(Action::Left),
// Top of screen // Top of screen
@ -166,7 +162,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
} else { } else {
self.editor.set_select_opt(Some(self.editor.cursor())); self.editor.set_select_opt(Some(self.editor.cursor()));
} }
}, }
// Enter line visual mode // Enter line visual mode
'V' => { 'V' => {
if self.editor.select_opt().is_some() { 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 //TODO: set cursor_x_opt to max
self.editor.action(Action::End); self.editor.action(Action::End);
} }
}, }
// Remove character at cursor // Remove character at cursor
'x' => self.editor.action(Action::Delete), 'x' => self.editor.action(Action::Delete),
// Remove character before cursor // Remove character before cursor
@ -192,15 +188,15 @@ impl<'a> Edit<'a> for ViEditor<'a> {
// Enter command mode // Enter command mode
':' => { ':' => {
self.mode = Mode::Command; self.mode = Mode::Command;
}, }
// Enter search mode // Enter search mode
'/' => { '/' => {
self.mode = Mode::Search; self.mode = Mode::Search;
}, }
// Enter search backwards mode // Enter search backwards mode
'?' => { '?' => {
self.mode = Mode::SearchBackwards; self.mode = Mode::SearchBackwards;
}, }
_ => (), _ => (),
}, },
_ => self.editor.action(action), _ => self.editor.action(action),
@ -213,13 +209,13 @@ impl<'a> Edit<'a> for ViEditor<'a> {
self.editor.action(Action::Left); self.editor.action(Action::Left);
} }
self.mode = Mode::Normal; self.mode = Mode::Normal;
}, }
_ => self.editor.action(action), _ => self.editor.action(action),
}, },
_ => { _ => {
//TODO: other modes //TODO: other modes
self.mode = Mode::Normal; self.mode = Mode::Normal;
}, }
} }
if self.mode != old_mode { if self.mode != old_mode {
@ -229,7 +225,8 @@ impl<'a> Edit<'a> for ViEditor<'a> {
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F) 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 font_size = self.buffer().metrics().font_size;
let line_height = self.buffer().metrics().line_height; let line_height = self.buffer().metrics().line_height;
@ -266,7 +263,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
if cursor.index == glyph.end { if cursor.index == glyph.end {
return Some((run.glyphs.len(), 0.0, default_width)); return Some((run.glyphs.len(), 0.0, default_width));
} }
}, }
None => { None => {
return Some((0, 0.0, default_width)); 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_start = glyph.start + i;
let c_end = glyph.start + i + c.len(); let c_end = glyph.start + i + c.len();
if (start.line != line_i || c_end > start.index) 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() { range_opt = match range_opt.take() {
Some((min, max)) => Some(( Some((min, max)) => Some((
cmp::min(min, c_x as i32), cmp::min(min, c_x as i32),
cmp::max(max, (c_x + c_w) as i32), cmp::max(max, (c_x + c_w) as i32),
)), )),
None => Some(( None => Some((c_x as i32, (c_x + c_w) as i32)),
c_x as i32,
(c_x + c_w) as i32,
))
}; };
} else if let Some((min, max)) = range_opt.take() { } else if let Some((min, max)) = range_opt.take() {
f( f(
@ -320,14 +315,14 @@ impl<'a> Edit<'a> for ViEditor<'a> {
line_y - font_size, line_y - font_size,
cmp::max(0, max - min) as u32, cmp::max(0, max - min) as u32,
line_height 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; 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 // Highlight all of internal empty lines
range_opt = Some((0, self.buffer().size().0)); range_opt = Some((0, self.buffer().size().0));
} }
@ -346,18 +341,20 @@ impl<'a> Edit<'a> for ViEditor<'a> {
line_y - font_size, line_y - font_size,
cmp::max(0, max - min) as u32, cmp::max(0, max - min) as u32,
line_height 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 // 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 { let block_cursor = match self.mode {
Mode::Normal => true, Mode::Normal => true,
Mode::Insert => false, 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) { 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() { if glyph.level.is_rtl() {
( (
(glyph.x + glyph.w - cursor_glyph_offset) as i32, (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 { } else {
( (
(glyph.x + cursor_glyph_offset) as i32, (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() { None => match run.glyphs.last() {
Some(glyph) => { Some(glyph) => {
// End of last glyph // End of last glyph
if glyph.level.is_rtl() { 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 { } else {
( (
(glyph.x + glyph.w) as i32, (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 => { None => {
// Start of empty line // Start of empty line
( (0, cursor_glyph_width as i32)
0,
cursor_glyph_width as i32
)
} }
} },
}; };
if block_cursor { if block_cursor {
@ -411,13 +403,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
Color::rgba(color.r(), color.g(), color.b(), 0x33), Color::rgba(color.r(), color.g(), color.b(), 0x33),
); );
} else { } else {
f( f(start_x, line_y - font_size, 1, line_height as u32, color);
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 // Fallbacks to never use
pub fn forbidden_fallback() -> &'static [&'static str] { pub fn forbidden_fallback() -> &'static [&'static str] {
&[ &[".LastResort"]
".LastResort",
]
} }
fn han_unification(locale: &str) -> &'static [&'static str] { fn han_unification(locale: &str) -> &'static [&'static str] {

View file

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

View file

@ -31,7 +31,7 @@ fn han_unification(locale: &str) -> &'static [&'static str] {
// Taiwan // Taiwan
"zh-TW" => &["Microsoft JhengHei UI"], "zh-TW" => &["Microsoft JhengHei UI"],
// Simplified Chinese is the default (also catches "zh-CN" for China) // 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; use alloc::sync::Arc;
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::{ use alloc::{string::String, vec::Vec};
string::String,
vec::Vec,
};
use crate::Font; use crate::Font;

View file

@ -9,7 +9,7 @@ use alloc::{
use crate::{Attrs, Font, FontMatches}; use crate::{Attrs, Font, FontMatches};
/// Access system fonts /// Access system fonts
pub struct FontSystem{ pub struct FontSystem {
locale: String, locale: String,
db: fontdb::Database, db: fontdb::Database,
} }
@ -25,16 +25,13 @@ impl FontSystem {
db.set_serif_family("DejaVu Serif"); db.set_serif_family("DejaVu Serif");
} }
Self { Self { locale, db }
locale,
db,
}
} }
pub fn new_with_locale_and_db(locale: &str, db: fontdb::Database) -> Self { pub fn new_with_locale_and_db(locale: &str, db: fontdb::Database) -> Self {
Self { Self {
locale: locale.to_string(), locale: locale.to_string(),
db db,
} }
} }
@ -74,7 +71,7 @@ impl FontSystem {
Arc::new(FontMatches { Arc::new(FontMatches {
locale: &self.locale, locale: &self.locale,
default_family: self.db.family_name(&attrs.family).to_string(), 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}; use crate::{Attrs, Font, FontMatches};
/// Access system fonts /// Access system fonts
pub struct FontSystem{ pub struct FontSystem {
locale: String, locale: String,
db: fontdb::Database, db: fontdb::Database,
} }
@ -46,7 +46,9 @@ impl FontSystem {
//TODO only do this on demand! //TODO only do this on demand!
for i in 0..db.faces().len() { for i in 0..db.faces().len() {
let id = db.faces()[i].id; let id = db.faces()[i].id;
unsafe { db.make_shared_face_data(id); } unsafe {
db.make_shared_face_data(id);
}
} }
log::info!( log::info!(
@ -56,10 +58,7 @@ impl FontSystem {
); );
} }
Self { Self { locale, db }
locale,
db,
}
} }
pub fn locale(&self) -> &str { pub fn locale(&self) -> &str {
@ -98,7 +97,7 @@ impl FontSystem {
Arc::new(FontMatches { Arc::new(FontMatches {
locale: &self.locale, locale: &self.locale,
default_family: self.db.family_name(&attrs.family).to_string(), 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! //TODO only do this on demand!
for i in 0..db.faces().len() { for i in 0..db.faces().len() {
let id = db.faces()[i].id; let id = db.faces()[i].id;
unsafe { db.make_shared_face_data(id); } unsafe {
db.make_shared_face_data(id);
}
} }
log::info!( log::info!(
@ -69,12 +71,15 @@ impl FontSystem {
); );
} }
Self(FontSystemInnerBuilder { Self(
locale, FontSystemInnerBuilder {
db, locale,
font_cache_builder: |_| Mutex::new(HashMap::new()), db,
font_matches_cache_builder: |_, _| Mutex::new(HashMap::new()) font_cache_builder: |_| Mutex::new(HashMap::new()),
}.build()) font_matches_cache_builder: |_, _| Mutex::new(HashMap::new()),
}
.build(),
)
} }
pub fn locale(&self) -> &str { pub fn locale(&self) -> &str {
@ -93,53 +98,66 @@ impl FontSystem {
// Clippy false positive // Clippy false positive
#[allow(clippy::needless_lifetimes)] #[allow(clippy::needless_lifetimes)]
pub fn get_font<'a>(&'a self, id: fontdb::ID) -> Option<Arc<Font<'a>>> { pub fn get_font<'a>(&'a self, id: fontdb::ID) -> Option<Arc<Font<'a>>> {
self.0.with(|fields| { self.0.with(|fields| get_font(&fields, id))
get_font(&fields, id)
})
} }
pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> { pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> {
self.0.with(|fields| { 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 //TODO: do not create AttrsOwned unless entry does not already exist
font_matches_cache.entry(AttrsOwned::new(attrs)).or_insert_with(|| { font_matches_cache
let now = std::time::Instant::now(); .entry(AttrsOwned::new(attrs))
.or_insert_with(|| {
let now = std::time::Instant::now();
let mut fonts = Vec::new(); let mut fonts = Vec::new();
for face in fields.db.faces() { for face in fields.db.faces() {
if !attrs.matches(face) { if !attrs.matches(face) {
continue; continue;
}
if let Some(font) = get_font(&fields, face.id) {
fonts.push(font);
}
} }
if let Some(font) = get_font(&fields, face.id) { let font_matches = Arc::new(FontMatches {
fonts.push(font); locale: fields.locale,
} default_family: fields.db.family_name(&attrs.family).to_string(),
} fonts,
});
let font_matches = Arc::new(FontMatches { let elapsed = now.elapsed();
locale: fields.locale, log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
default_family: fields.db.family_name(&attrs.family).to_string(),
fonts
});
let elapsed = now.elapsed(); font_matches
log::debug!("font matches for {:?} in {:?}", attrs, elapsed); })
.clone()
font_matches
}).clone()
}) })
} }
} }
fn get_font<'b>(fields: &ouroboros_impl_font_system_inner::BorrowedFields<'_, 'b>, id: fontdb::ID) -> Option<Arc<Font<'b>>> { fn get_font<'b>(
fields.font_cache.lock().expect("failed to lock font cache").entry(id).or_insert_with(|| { fields: &ouroboros_impl_font_system_inner::BorrowedFields<'_, 'b>,
let face = fields.db.face(id)?; id: fontdb::ID,
match Font::new(face) { ) -> Option<Arc<Font<'b>>> {
Some(font) => Some(Arc::new(font)), fields
None => { .font_cache
log::warn!("failed to load font '{}'", face.post_script_name); .lock()
None .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 // Not interested in these lints
#![allow(clippy::new_without_default)] #![allow(clippy::new_without_default)]
// TODO: address ocurrances and then deny // TODO: address ocurrances and then deny
// //
// Indexing a slice can cause panics and that is something we always want to avoid // Indexing a slice can cause panics and that is something we always want to avoid
#![allow(clippy::indexing_slicing)] #![allow(clippy::indexing_slicing)]
// Overflows can produce unpredictable results and are only checked in debug builds // Overflows can produce unpredictable results and are only checked in debug builds
#![allow(clippy::integer_arithmetic)] #![allow(clippy::integer_arithmetic)]
// Soundness issues // Soundness issues
// //
// Dereferencing unalinged pointers may be undefined behavior // Dereferencing unalinged pointers may be undefined behavior
@ -75,7 +73,6 @@
#![deny(unreachable_patterns)] #![deny(unreachable_patterns)]
// Ensure that all must_use results are used // Ensure that all must_use results are used
#![deny(unused_must_use)] #![deny(unused_must_use)]
// Style issues // Style issues
// //
// Documentation not ideal // Documentation not ideal
@ -88,7 +85,6 @@
#![warn(clippy::semicolon_if_nothing_returned)] #![warn(clippy::semicolon_if_nothing_returned)]
// Ensure numbers are readable // Ensure numbers are readable
#![warn(clippy::unreadable_literal)] #![warn(clippy::unreadable_literal)]
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc; extern crate alloc;

View file

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

View file

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