Run cargo fmt
This commit is contained in:
parent
00bc4d1e88
commit
8cc988d374
25 changed files with 732 additions and 731 deletions
|
|
@ -1,45 +1,20 @@
|
|||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use cosmic::{
|
||||
Element,
|
||||
iced::{
|
||||
self,
|
||||
Color,
|
||||
Alignment,
|
||||
Application,
|
||||
Command,
|
||||
Length,
|
||||
widget::{
|
||||
column,
|
||||
horizontal_space,
|
||||
pick_list,
|
||||
row,
|
||||
},
|
||||
widget::{column, horizontal_space, pick_list, row},
|
||||
Alignment, Application, Color, Command, Length,
|
||||
},
|
||||
settings,
|
||||
theme::{self, Theme},
|
||||
widget::{
|
||||
button,
|
||||
toggler,
|
||||
},
|
||||
widget::{button, toggler},
|
||||
Element,
|
||||
};
|
||||
use cosmic_text::{
|
||||
Attrs,
|
||||
AttrsList,
|
||||
Buffer,
|
||||
Edit,
|
||||
FontSystem,
|
||||
Metrics,
|
||||
SyntaxEditor,
|
||||
SyntaxSystem,
|
||||
Wrap,
|
||||
};
|
||||
use std::{
|
||||
env,
|
||||
fs,
|
||||
path::PathBuf,
|
||||
sync::Mutex,
|
||||
Attrs, AttrsList, Buffer, Edit, FontSystem, Metrics, SyntaxEditor, SyntaxSystem, Wrap,
|
||||
};
|
||||
use std::{env, fs, path::PathBuf, sync::Mutex};
|
||||
|
||||
use self::text::text;
|
||||
mod text;
|
||||
|
|
@ -61,11 +36,7 @@ static FONT_SIZES: &'static [Metrics] = &[
|
|||
Metrics::new(32, 44), // Title 1
|
||||
];
|
||||
|
||||
static WRAP_MODE: &'static [Wrap] = & [
|
||||
Wrap::None,
|
||||
Wrap::Glyph,
|
||||
Wrap::Word,
|
||||
];
|
||||
static WRAP_MODE: &'static [Wrap] = &[Wrap::None, Wrap::Glyph, Wrap::Word];
|
||||
|
||||
fn main() -> cosmic::iced::Result {
|
||||
env_logger::init();
|
||||
|
|
@ -105,7 +76,7 @@ impl Window {
|
|||
Ok(()) => {
|
||||
log::info!("opened '{}'", path.display());
|
||||
self.path_opt = Some(path);
|
||||
},
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("failed to open '{}': {}", path.display(), err);
|
||||
self.path_opt = None;
|
||||
|
|
@ -128,8 +99,9 @@ impl Application for Window {
|
|||
let mut editor = SyntaxEditor::new(
|
||||
Buffer::new(&FONT_SYSTEM, FONT_SIZES[1 /* Body */]),
|
||||
&SYNTAX_SYSTEM,
|
||||
"base16-eighties.dark"
|
||||
).unwrap();
|
||||
"base16-eighties.dark",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
#[cfg(feature = "vi")]
|
||||
let mut editor = cosmic_text::ViEditor::new(editor);
|
||||
|
|
@ -154,7 +126,11 @@ impl Application for Window {
|
|||
|
||||
fn title(&self) -> String {
|
||||
if let Some(path) = &self.path_opt {
|
||||
format!("COSMIC Text - {} - {}", FONT_SYSTEM.locale(), path.display())
|
||||
format!(
|
||||
"COSMIC Text - {} - {}",
|
||||
FONT_SYSTEM.locale(),
|
||||
path.display()
|
||||
)
|
||||
} else {
|
||||
format!("COSMIC Text - {}", FONT_SYSTEM.locale())
|
||||
}
|
||||
|
|
@ -166,7 +142,7 @@ impl Application for Window {
|
|||
if let Some(path) = rfd::FileDialog::new().pick_file() {
|
||||
self.open(path);
|
||||
}
|
||||
},
|
||||
}
|
||||
Message::Save => {
|
||||
if let Some(path) = &self.path_opt {
|
||||
let editor = self.editor.lock().unwrap();
|
||||
|
|
@ -178,13 +154,13 @@ impl Application for Window {
|
|||
match fs::write(path, text) {
|
||||
Ok(()) => {
|
||||
log::info!("saved '{}'", path.display());
|
||||
},
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("failed to save '{}': {}", path.display(), err);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Message::Bold(bold) => {
|
||||
self.attrs = self.attrs.weight(if bold {
|
||||
cosmic_text::Weight::BOLD
|
||||
|
|
@ -194,7 +170,7 @@ impl Application for Window {
|
|||
|
||||
let mut editor = self.editor.lock().unwrap();
|
||||
update_attrs(&mut *editor, self.attrs);
|
||||
},
|
||||
}
|
||||
Message::Italic(italic) => {
|
||||
self.attrs = self.attrs.style(if italic {
|
||||
cosmic_text::Style::Italic
|
||||
|
|
@ -204,9 +180,10 @@ impl Application for Window {
|
|||
|
||||
let mut editor = self.editor.lock().unwrap();
|
||||
update_attrs(&mut *editor, self.attrs);
|
||||
},
|
||||
}
|
||||
Message::Monospaced(monospaced) => {
|
||||
self.attrs = self.attrs
|
||||
self.attrs = self
|
||||
.attrs
|
||||
.family(if monospaced {
|
||||
cosmic_text::Family::Monospace
|
||||
} else {
|
||||
|
|
@ -216,15 +193,15 @@ impl Application for Window {
|
|||
|
||||
let mut editor = self.editor.lock().unwrap();
|
||||
update_attrs(&mut *editor, self.attrs);
|
||||
},
|
||||
}
|
||||
Message::MetricsChanged(metrics) => {
|
||||
let mut editor = self.editor.lock().unwrap();
|
||||
editor.buffer_mut().set_metrics(metrics);
|
||||
},
|
||||
}
|
||||
Message::WrapChanged(wrap) => {
|
||||
let mut editor = self.editor.lock().unwrap();
|
||||
editor.buffer_mut().set_wrap(wrap);
|
||||
},
|
||||
}
|
||||
Message::ThemeChanged(theme) => {
|
||||
self.theme = match theme {
|
||||
"Dark" => Theme::Dark,
|
||||
|
|
@ -234,11 +211,16 @@ impl Application for Window {
|
|||
|
||||
let Color { r, g, b, a } = self.theme.palette().text;
|
||||
let as_u8 = |component: f32| (component * 255.0) as u8;
|
||||
self.attrs = self.attrs.color(cosmic_text::Color::rgba(as_u8(r), as_u8(g), as_u8(b), as_u8(a)));
|
||||
self.attrs = self.attrs.color(cosmic_text::Color::rgba(
|
||||
as_u8(r),
|
||||
as_u8(g),
|
||||
as_u8(b),
|
||||
as_u8(a),
|
||||
));
|
||||
|
||||
let mut editor = self.editor.lock().unwrap();
|
||||
update_attrs(&mut *editor, self.attrs);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
|
|
@ -252,7 +234,7 @@ impl Application for Window {
|
|||
Theme::Dark => THEMES[0],
|
||||
Theme::Light => THEMES[1],
|
||||
}),
|
||||
Message::ThemeChanged
|
||||
Message::ThemeChanged,
|
||||
);
|
||||
|
||||
let font_size_picker = {
|
||||
|
|
@ -260,7 +242,7 @@ impl Application for Window {
|
|||
pick_list(
|
||||
FONT_SIZES,
|
||||
Some(editor.buffer().metrics()),
|
||||
Message::MetricsChanged
|
||||
Message::MetricsChanged,
|
||||
)
|
||||
};
|
||||
|
||||
|
|
@ -283,9 +265,17 @@ impl Application for Window {
|
|||
.on_press(Message::Save),
|
||||
horizontal_space(Length::Fill),
|
||||
text("Bold:"),
|
||||
toggler(None, self.attrs.weight == cosmic_text::Weight::BOLD, Message::Bold),
|
||||
toggler(
|
||||
None,
|
||||
self.attrs.weight == cosmic_text::Weight::BOLD,
|
||||
Message::Bold
|
||||
),
|
||||
text("Italic:"),
|
||||
toggler(None, self.attrs.style == cosmic_text::Style::Italic, Message::Italic),
|
||||
toggler(
|
||||
None,
|
||||
self.attrs.style == cosmic_text::Style::Italic,
|
||||
Message::Italic
|
||||
),
|
||||
text("Monospaced:"),
|
||||
toggler(None, self.attrs.monospaced, Message::Monospaced),
|
||||
text("Theme:"),
|
||||
|
|
@ -296,8 +286,7 @@ impl Application for Window {
|
|||
wrap_picker,
|
||||
]
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(8)
|
||||
,
|
||||
.spacing(8),
|
||||
text_box(&self.editor)
|
||||
]
|
||||
.spacing(8)
|
||||
|
|
|
|||
|
|
@ -2,26 +2,16 @@
|
|||
|
||||
use cosmic::{
|
||||
iced_native::{
|
||||
{Color, Element, Length, Point, Rectangle, Size},
|
||||
image,
|
||||
layout::{self, Layout},
|
||||
renderer,
|
||||
widget::{self, tree, Widget},
|
||||
{Color, Element, Length, Point, Rectangle, Size},
|
||||
},
|
||||
theme::Theme,
|
||||
};
|
||||
use cosmic_text::{
|
||||
Attrs,
|
||||
AttrsList,
|
||||
SwashCache,
|
||||
BufferLine,
|
||||
Metrics,
|
||||
};
|
||||
use std::{
|
||||
cmp,
|
||||
sync::Mutex,
|
||||
time::Instant,
|
||||
};
|
||||
use cosmic_text::{Attrs, AttrsList, BufferLine, Metrics, SwashCache};
|
||||
use std::{cmp, sync::Mutex, time::Instant};
|
||||
|
||||
pub struct Appearance {
|
||||
background_color: Option<Color>,
|
||||
|
|
@ -57,10 +47,7 @@ impl Text {
|
|||
let instant = Instant::now();
|
||||
|
||||
//TODO: make it possible to set attrs
|
||||
let mut line = BufferLine::new(
|
||||
string,
|
||||
AttrsList::new(Attrs::new())
|
||||
);
|
||||
let mut line = BufferLine::new(string, AttrsList::new(Attrs::new()));
|
||||
|
||||
//TODO: do we have to immediately shape?
|
||||
line.shape(&crate::FONT_SYSTEM);
|
||||
|
|
@ -101,11 +88,7 @@ where
|
|||
Length::Shrink
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
fn layout(&self, _renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
|
||||
let instant = Instant::now();
|
||||
|
||||
let limits = limits.width(Length::Shrink).height(Length::Shrink);
|
||||
|
|
@ -116,7 +99,7 @@ where
|
|||
let layout_lines = shape.layout(
|
||||
self.metrics.font_size,
|
||||
limits.max().width as i32,
|
||||
self.line.wrap()
|
||||
self.line.wrap(),
|
||||
);
|
||||
|
||||
let mut width = 0;
|
||||
|
|
@ -160,7 +143,7 @@ where
|
|||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
background_color
|
||||
background_color,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -177,11 +160,7 @@ where
|
|||
let shape = self.line.shape_opt().as_ref().unwrap();
|
||||
|
||||
//TODO: can we cache this?
|
||||
let layout_lines = shape.layout(
|
||||
self.metrics.font_size,
|
||||
layout_w,
|
||||
self.line.wrap()
|
||||
);
|
||||
let layout_lines = shape.layout(self.metrics.font_size, layout_w, self.line.wrap());
|
||||
|
||||
let mut cache = state.cache.lock().unwrap();
|
||||
|
||||
|
|
@ -219,7 +198,7 @@ pub fn draw_pixel(
|
|||
height: i32,
|
||||
x: i32,
|
||||
y: i32,
|
||||
color: cosmic_text::Color
|
||||
color: cosmic_text::Color,
|
||||
) {
|
||||
let alpha = (color.0 >> 24) & 0xFF;
|
||||
if alpha == 0 {
|
||||
|
|
@ -239,11 +218,10 @@ pub fn draw_pixel(
|
|||
|
||||
let offset = (y as usize * width as usize + x as usize) * 4;
|
||||
|
||||
let mut current =
|
||||
buffer[offset + 2] as u32 |
|
||||
(buffer[offset + 1] as u32) << 8 |
|
||||
(buffer[offset + 0] as u32) << 16 |
|
||||
(buffer[offset + 3] as u32) << 24;
|
||||
let mut current = buffer[offset + 2] as u32
|
||||
| (buffer[offset + 1] as u32) << 8
|
||||
| (buffer[offset + 0] as u32) << 16
|
||||
| (buffer[offset + 3] as u32) << 24;
|
||||
|
||||
if alpha >= 255 || current == 0 {
|
||||
// Alpha is 100% or current is null, replace with no blending
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use super::text;
|
||||
use cosmic::{
|
||||
iced_native::{
|
||||
{Color, Element, Length, Point, Rectangle, Shell, Size},
|
||||
clipboard::Clipboard,
|
||||
event::{Event, Status},
|
||||
image,
|
||||
|
|
@ -11,21 +11,12 @@ use cosmic::{
|
|||
mouse::{self, Button, Event as MouseEvent, ScrollDelta},
|
||||
renderer,
|
||||
widget::{self, tree, Widget},
|
||||
Padding
|
||||
Padding, {Color, Element, Length, Point, Rectangle, Shell, Size},
|
||||
},
|
||||
theme::Theme,
|
||||
};
|
||||
use cosmic_text::{
|
||||
Action,
|
||||
Edit,
|
||||
SwashCache,
|
||||
};
|
||||
use std::{
|
||||
cmp,
|
||||
sync::Mutex,
|
||||
time::Instant,
|
||||
};
|
||||
use super::text;
|
||||
use cosmic_text::{Action, Edit, SwashCache};
|
||||
use std::{cmp, sync::Mutex, time::Instant};
|
||||
|
||||
pub struct Appearance {
|
||||
background_color: Option<Color>,
|
||||
|
|
@ -68,7 +59,6 @@ impl<'a, Editor> TextBox<'a, Editor> {
|
|||
self.padding = padding.into();
|
||||
self
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn text_box<'a, Editor>(editor: &'a Mutex<Editor>) -> TextBox<'a, Editor> {
|
||||
|
|
@ -97,11 +87,7 @@ where
|
|||
Length::Fill
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
fn layout(&self, _renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
|
||||
let limits = limits.width(Length::Fill).height(Length::Fill);
|
||||
|
||||
//TODO: allow lazy shape
|
||||
|
|
@ -160,7 +146,7 @@ where
|
|||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
background_color
|
||||
background_color,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -173,8 +159,10 @@ where
|
|||
|
||||
let mut editor = self.editor.lock().unwrap();
|
||||
|
||||
let view_w = cmp::min(viewport.width as i32, layout.bounds().width as i32) - self.padding.horizontal() as i32;
|
||||
let view_h = cmp::min(viewport.height as i32, layout.bounds().height as i32) - self.padding.vertical() as i32;
|
||||
let view_w = cmp::min(viewport.width as i32, layout.bounds().width as i32)
|
||||
- self.padding.horizontal() as i32;
|
||||
let view_h = cmp::min(viewport.height as i32, layout.bounds().height as i32)
|
||||
- self.padding.vertical() as i32;
|
||||
editor.buffer_mut().set_size(view_w, view_h);
|
||||
|
||||
editor.shape_as_needed();
|
||||
|
|
@ -183,41 +171,51 @@ where
|
|||
|
||||
let mut pixels = vec![0; view_w as usize * view_h as usize * 4];
|
||||
|
||||
editor.draw(&mut state.cache.lock().unwrap(), text_color, |x, y, w, h, color| {
|
||||
if w <= 0 || h <= 0 {
|
||||
// Do not draw invalid sized rectangles
|
||||
return;
|
||||
}
|
||||
editor.draw(
|
||||
&mut state.cache.lock().unwrap(),
|
||||
text_color,
|
||||
|x, y, w, h, color| {
|
||||
if w <= 0 || h <= 0 {
|
||||
// Do not draw invalid sized rectangles
|
||||
return;
|
||||
}
|
||||
|
||||
if w > 1 || h > 1 {
|
||||
// Draw rectangles with optimized quad renderer
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle::new(
|
||||
layout.position() + [x as f32, y as f32].into() + [self.padding.left as f32, self.padding.top as f32].into(),
|
||||
Size::new(w as f32 , h as f32)
|
||||
if w > 1 || h > 1 {
|
||||
// Draw rectangles with optimized quad renderer
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle::new(
|
||||
layout.position()
|
||||
+ [x as f32, y as f32].into()
|
||||
+ [self.padding.left as f32, self.padding.top as f32].into(),
|
||||
Size::new(w as f32, h as f32),
|
||||
),
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
Color::from_rgba8(
|
||||
color.r(),
|
||||
color.g(),
|
||||
color.b(),
|
||||
(color.a() as f32) / 255.0,
|
||||
),
|
||||
border_radius: 0.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
Color::from_rgba8(
|
||||
color.r(),
|
||||
color.g(),
|
||||
color.b(),
|
||||
(color.a() as f32) / 255.0
|
||||
)
|
||||
);
|
||||
} else {
|
||||
text::draw_pixel(&mut pixels, view_w, view_h, x, y, color);
|
||||
}
|
||||
});
|
||||
);
|
||||
} else {
|
||||
text::draw_pixel(&mut pixels, view_w, view_h, x, y, color);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let handle = image::Handle::from_pixels(view_w as u32, view_h as u32, pixels);
|
||||
image::Renderer::draw(renderer, handle, Rectangle::new(
|
||||
layout.position() + [self.padding.left as f32, self.padding.top as f32].into(),
|
||||
Size::new(view_w as f32, view_h as f32)
|
||||
));
|
||||
image::Renderer::draw(
|
||||
renderer,
|
||||
handle,
|
||||
Rectangle::new(
|
||||
layout.position() + [self.padding.left as f32, self.padding.top as f32].into(),
|
||||
Size::new(view_w as f32, view_h as f32),
|
||||
),
|
||||
);
|
||||
|
||||
let duration = instant.elapsed();
|
||||
log::debug!("redraw {}, {}: {:?}", view_w, view_h, duration);
|
||||
|
|
@ -238,101 +236,107 @@ where
|
|||
|
||||
let mut status = Status::Ignored;
|
||||
match event {
|
||||
Event::Keyboard(KeyEvent::KeyPressed { key_code, modifiers }) => match key_code {
|
||||
Event::Keyboard(KeyEvent::KeyPressed {
|
||||
key_code,
|
||||
modifiers,
|
||||
}) => match key_code {
|
||||
KeyCode::Left => {
|
||||
editor.action(Action::Left);
|
||||
status = Status::Captured;
|
||||
},
|
||||
}
|
||||
KeyCode::Right => {
|
||||
editor.action(Action::Right);
|
||||
status = Status::Captured;
|
||||
},
|
||||
}
|
||||
KeyCode::Up => {
|
||||
editor.action(Action::Up);
|
||||
status = Status::Captured;
|
||||
},
|
||||
}
|
||||
KeyCode::Down => {
|
||||
editor.action(Action::Down);
|
||||
status = Status::Captured;
|
||||
},
|
||||
}
|
||||
KeyCode::Home => {
|
||||
editor.action(Action::Home);
|
||||
status = Status::Captured;
|
||||
},
|
||||
}
|
||||
KeyCode::End => {
|
||||
editor.action(Action::End);
|
||||
status = Status::Captured;
|
||||
},
|
||||
}
|
||||
KeyCode::PageUp => {
|
||||
editor.action(Action::PageUp);
|
||||
status = Status::Captured;
|
||||
},
|
||||
}
|
||||
KeyCode::PageDown => {
|
||||
editor.action(Action::PageDown);
|
||||
status = Status::Captured;
|
||||
},
|
||||
}
|
||||
KeyCode::Escape => {
|
||||
editor.action(Action::Escape);
|
||||
status = Status::Captured;
|
||||
},
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
editor.action(Action::Enter);
|
||||
status = Status::Captured;
|
||||
},
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
editor.action(Action::Backspace);
|
||||
status = Status::Captured;
|
||||
},
|
||||
}
|
||||
KeyCode::Delete => {
|
||||
editor.action(Action::Delete);
|
||||
status = Status::Captured;
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::Keyboard(KeyEvent::CharacterReceived(character)) => {
|
||||
editor.action(Action::Insert(character));
|
||||
status = Status::Captured;
|
||||
},
|
||||
}
|
||||
Event::Mouse(MouseEvent::ButtonPressed(Button::Left)) => {
|
||||
if layout.bounds().contains(cursor_position) {
|
||||
editor.action(Action::Click {
|
||||
x: (cursor_position.x - layout.bounds().x) as i32 - self.padding.left as i32,
|
||||
x: (cursor_position.x - layout.bounds().x) as i32
|
||||
- self.padding.left as i32,
|
||||
y: (cursor_position.y - layout.bounds().y) as i32 - self.padding.top as i32,
|
||||
});
|
||||
state.is_dragging = true;
|
||||
status = Status::Captured;
|
||||
}
|
||||
},
|
||||
}
|
||||
Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => {
|
||||
state.is_dragging = false;
|
||||
status = Status::Captured;
|
||||
},
|
||||
}
|
||||
Event::Mouse(MouseEvent::CursorMoved { .. }) => {
|
||||
if state.is_dragging {
|
||||
editor.action(Action::Drag {
|
||||
x: (cursor_position.x - layout.bounds().x) as i32 - self.padding.left as i32,
|
||||
x: (cursor_position.x - layout.bounds().x) as i32
|
||||
- self.padding.left as i32,
|
||||
y: (cursor_position.y - layout.bounds().y) as i32 - self.padding.top as i32,
|
||||
});
|
||||
status = Status::Captured;
|
||||
}
|
||||
},
|
||||
}
|
||||
Event::Mouse(MouseEvent::WheelScrolled { delta }) => match delta {
|
||||
ScrollDelta::Lines { x, y } => {
|
||||
editor.action(Action::Scroll {
|
||||
lines: (-y * 6.0) as i32,
|
||||
});
|
||||
status = Status::Captured;
|
||||
},
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => ()
|
||||
_ => (),
|
||||
}
|
||||
|
||||
status
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'editor, Editor, Message, Renderer> From<TextBox<'a, Editor>> for Element<'a, Message, Renderer>
|
||||
impl<'a, 'editor, Editor, Message, Renderer> From<TextBox<'a, Editor>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: renderer::Renderer + image::Renderer<Handle = image::Handle>,
|
||||
Renderer::Theme: StyleSheet,
|
||||
|
|
|
|||
|
|
@ -1,19 +1,14 @@
|
|||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use cosmic_text::{
|
||||
Action,
|
||||
Attrs,
|
||||
Buffer,
|
||||
Edit,
|
||||
Family,
|
||||
FontSystem,
|
||||
Metrics,
|
||||
SwashCache,
|
||||
SyntaxEditor,
|
||||
Action, Attrs, Buffer, Edit, Family, FontSystem, Metrics, SwashCache, SyntaxEditor,
|
||||
SyntaxSystem,
|
||||
};
|
||||
use orbclient::{EventOption, Renderer, Window, WindowFlag};
|
||||
use std::{env, thread, time::{Duration, Instant}};
|
||||
use std::{
|
||||
env, thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
|
@ -65,20 +60,18 @@ fn main() {
|
|||
let mut editor = SyntaxEditor::new(
|
||||
Buffer::new(&font_system, font_sizes[font_size_i]),
|
||||
&syntax_system,
|
||||
"base16-eighties.dark"
|
||||
).unwrap();
|
||||
"base16-eighties.dark",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
#[cfg(feature = "vi")]
|
||||
let mut editor = cosmic_text::ViEditor::new(editor);
|
||||
|
||||
editor.buffer_mut().set_size(
|
||||
window.width() as i32 - line_x * 2,
|
||||
window.height() as i32
|
||||
);
|
||||
editor
|
||||
.buffer_mut()
|
||||
.set_size(window.width() as i32 - line_x * 2, window.height() as i32);
|
||||
|
||||
let attrs = Attrs::new()
|
||||
.monospaced(true)
|
||||
.family(Family::Monospace);
|
||||
let attrs = Attrs::new().monospaced(true).family(Family::Monospace);
|
||||
match editor.load_text(&path, attrs) {
|
||||
Ok(()) => (),
|
||||
Err(err) => {
|
||||
|
|
@ -212,7 +205,9 @@ fn main() {
|
|||
}
|
||||
}
|
||||
EventOption::Resize(event) => {
|
||||
editor.buffer_mut().set_size(event.width as i32 - line_x * 2, event.height as i32);
|
||||
editor
|
||||
.buffer_mut()
|
||||
.set_size(event.width as i32 - line_x * 2, event.height as i32);
|
||||
}
|
||||
EventOption::Scroll(event) => {
|
||||
editor.action(Action::Scroll {
|
||||
|
|
@ -243,7 +238,7 @@ fn main() {
|
|||
window.set_async(window_async);
|
||||
}
|
||||
|
||||
if window_async && ! found_event {
|
||||
if window_async && !found_event {
|
||||
// In async mode and no event found, sleep
|
||||
thread::sleep(Duration::from_millis(5));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,14 +54,8 @@ fn main() {
|
|||
];
|
||||
let font_size_default = 1; // Body
|
||||
|
||||
let mut buffer = Buffer::new(
|
||||
&font_system,
|
||||
font_sizes[font_size_default]
|
||||
);
|
||||
buffer.set_size(
|
||||
window.width() as i32,
|
||||
window.height() as i32
|
||||
);
|
||||
let mut buffer = Buffer::new(&font_system, font_sizes[font_size_default]);
|
||||
buffer.set_size(window.width() as i32, window.height() as i32);
|
||||
|
||||
let mut editor = Editor::new(buffer);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,14 @@
|
|||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use cosmic_text::{
|
||||
Action,
|
||||
Attrs,
|
||||
AttrsList,
|
||||
Buffer,
|
||||
BufferLine,
|
||||
Color,
|
||||
Edit,
|
||||
Editor,
|
||||
Family,
|
||||
FontSystem,
|
||||
Metrics,
|
||||
Style,
|
||||
SwashCache,
|
||||
Weight,
|
||||
Action, Attrs, AttrsList, Buffer, BufferLine, Color, Edit, Editor, Family, FontSystem, Metrics,
|
||||
Style, SwashCache, Weight,
|
||||
};
|
||||
use orbclient::{EventOption, Renderer, Window, WindowFlag};
|
||||
use std::{process, thread, time::{Duration, Instant}};
|
||||
use std::{
|
||||
process, thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
|
@ -47,13 +38,12 @@ fn main() {
|
|||
|
||||
let mut editor = Editor::new(Buffer::new(
|
||||
&font_system,
|
||||
Metrics::new(32, 44).scale(display_scale)
|
||||
Metrics::new(32, 44).scale(display_scale),
|
||||
));
|
||||
|
||||
editor.buffer_mut().set_size(
|
||||
window.width() as i32,
|
||||
window.height() as i32
|
||||
);
|
||||
editor
|
||||
.buffer_mut()
|
||||
.set_size(window.width() as i32, window.height() as i32);
|
||||
|
||||
let attrs = Attrs::new();
|
||||
let serif_attrs = attrs.family(Family::Serif);
|
||||
|
|
@ -79,25 +69,37 @@ fn main() {
|
|||
("Sans-Serif Normal ", attrs),
|
||||
("Sans-Serif Bold ", attrs.weight(Weight::BOLD)),
|
||||
("Sans-Serif Italic ", attrs.style(Style::Italic)),
|
||||
("Sans-Serif Bold Italic", attrs.weight(Weight::BOLD).style(Style::Italic)),
|
||||
(
|
||||
"Sans-Serif Bold Italic",
|
||||
attrs.weight(Weight::BOLD).style(Style::Italic),
|
||||
),
|
||||
],
|
||||
&[
|
||||
("Serif Normal ", serif_attrs),
|
||||
("Serif Bold ", serif_attrs.weight(Weight::BOLD)),
|
||||
("Serif Italic ", serif_attrs.style(Style::Italic)),
|
||||
("Serif Bold Italic", serif_attrs.weight(Weight::BOLD).style(Style::Italic)),
|
||||
(
|
||||
"Serif Bold Italic",
|
||||
serif_attrs.weight(Weight::BOLD).style(Style::Italic),
|
||||
),
|
||||
],
|
||||
&[
|
||||
("Mono Normal ", mono_attrs),
|
||||
("Mono Bold ", mono_attrs.weight(Weight::BOLD)),
|
||||
("Mono Italic ", mono_attrs.style(Style::Italic)),
|
||||
("Mono Bold Italic", mono_attrs.weight(Weight::BOLD).style(Style::Italic)),
|
||||
(
|
||||
"Mono Bold Italic",
|
||||
mono_attrs.weight(Weight::BOLD).style(Style::Italic),
|
||||
),
|
||||
],
|
||||
&[
|
||||
("Comic Normal ", comic_attrs),
|
||||
("Comic Bold ", comic_attrs.weight(Weight::BOLD)),
|
||||
("Comic Italic ", comic_attrs.style(Style::Italic)),
|
||||
("Comic Bold Italic", comic_attrs.weight(Weight::BOLD).style(Style::Italic)),
|
||||
(
|
||||
"Comic Bold Italic",
|
||||
comic_attrs.weight(Weight::BOLD).style(Style::Italic),
|
||||
),
|
||||
],
|
||||
&[
|
||||
("R", attrs.color(Color::rgb(0xFF, 0x00, 0x00))),
|
||||
|
|
@ -121,10 +123,11 @@ fn main() {
|
|||
("O", attrs.color(Color::rgb(0xFF, 0xFF, 0x00))),
|
||||
("R", attrs.color(Color::rgb(0xFF, 0x7F, 0x00))),
|
||||
("N", attrs.color(Color::rgb(0xFF, 0x00, 0x00))),
|
||||
],
|
||||
&[
|
||||
("生活,삶,जिंदगी 😀 FPS", attrs.color(Color::rgb(0xFF, 0x00, 0x00)))
|
||||
]
|
||||
],
|
||||
&[(
|
||||
"生活,삶,जिंदगी 😀 FPS",
|
||||
attrs.color(Color::rgb(0xFF, 0x00, 0x00)),
|
||||
)],
|
||||
];
|
||||
for &line in lines {
|
||||
let mut line_text = String::new();
|
||||
|
|
@ -135,7 +138,10 @@ fn main() {
|
|||
let end = line_text.len();
|
||||
attrs_list.add_span(start..end, attrs);
|
||||
}
|
||||
editor.buffer_mut().lines.push(BufferLine::new(line_text, attrs_list));
|
||||
editor
|
||||
.buffer_mut()
|
||||
.lines
|
||||
.push(BufferLine::new(line_text, attrs_list));
|
||||
}
|
||||
|
||||
let mut swash_cache = SwashCache::new(&font_system);
|
||||
|
|
@ -187,18 +193,26 @@ fn main() {
|
|||
mouse_x = mouse.x;
|
||||
mouse_y = mouse.y;
|
||||
if mouse_left {
|
||||
editor.action(Action::Drag { x: mouse_x, y: mouse_y });
|
||||
editor.action(Action::Drag {
|
||||
x: mouse_x,
|
||||
y: mouse_y,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
EventOption::Button(button) => {
|
||||
mouse_left = button.left;
|
||||
if mouse_left {
|
||||
editor.action(Action::Click { x: mouse_x, y: mouse_y });
|
||||
editor.action(Action::Click {
|
||||
x: mouse_x,
|
||||
y: mouse_y,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
EventOption::Resize(resize) => {
|
||||
editor.buffer_mut().set_size(resize.width as i32, resize.height as i32);
|
||||
},
|
||||
editor
|
||||
.buffer_mut()
|
||||
.set_size(resize.width as i32, resize.height as i32);
|
||||
}
|
||||
EventOption::Quit(_) => process::exit(0),
|
||||
_ => (),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use cosmic_text::{Attrs, Color, FontSystem, SwashCache, Buffer, Metrics};
|
||||
use cosmic_text::{Attrs, Buffer, Color, FontSystem, Metrics, SwashCache};
|
||||
use std::cmp;
|
||||
use termion::{
|
||||
color,
|
||||
cursor,
|
||||
};
|
||||
use termion::{color, cursor};
|
||||
|
||||
fn main() {
|
||||
// A FontSystem provides access to detected system fonts, create one per application
|
||||
|
|
@ -66,11 +63,7 @@ fn main() {
|
|||
}
|
||||
|
||||
// Scale by alpha (mimics blending with black)
|
||||
let scale = |c: u8| {
|
||||
cmp::max(0, cmp::min(255,
|
||||
((c as i32) * (a as i32)) / 255
|
||||
)) as u8
|
||||
};
|
||||
let scale = |c: u8| cmp::max(0, cmp::min(255, ((c as i32) * (a as i32)) / 255)) as u8;
|
||||
|
||||
// Navigate to x coordinate
|
||||
if x > last_x {
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
disable_all_formatting = true
|
||||
49
src/attrs.rs
49
src/attrs.rs
|
|
@ -24,12 +24,7 @@ impl Color {
|
|||
/// Create new color with red, green, blue, and alpha components
|
||||
#[inline]
|
||||
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
Self(
|
||||
((a as u32) << 24) |
|
||||
((r as u32) << 16) |
|
||||
((g as u32) << 8) |
|
||||
(b as u32)
|
||||
)
|
||||
Self(((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32))
|
||||
}
|
||||
|
||||
/// Get the red component
|
||||
|
|
@ -165,22 +160,20 @@ impl<'a> Attrs<'a> {
|
|||
/// Check if font matches
|
||||
pub fn matches(&self, face: &fontdb::FaceInfo) -> bool {
|
||||
//TODO: smarter way of including emoji
|
||||
face.post_script_name.contains("Emoji") ||
|
||||
(
|
||||
face.style == self.style &&
|
||||
face.weight == self.weight &&
|
||||
face.stretch == self.stretch &&
|
||||
face.monospaced == self.monospaced
|
||||
)
|
||||
face.post_script_name.contains("Emoji")
|
||||
|| (face.style == self.style
|
||||
&& face.weight == self.weight
|
||||
&& face.stretch == self.stretch
|
||||
&& face.monospaced == self.monospaced)
|
||||
}
|
||||
|
||||
/// Check if this set of attributes can be shaped with another
|
||||
pub fn compatible(&self, other: &Self) -> bool {
|
||||
self.family == other.family
|
||||
&& self.monospaced == other.monospaced
|
||||
&& self.stretch == other.stretch
|
||||
&& self.style == other.style
|
||||
&& self.weight == other.weight
|
||||
&& self.monospaced == other.monospaced
|
||||
&& self.stretch == other.stretch
|
||||
&& self.style == other.style
|
||||
&& self.weight == other.weight
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -268,7 +261,10 @@ impl AttrsList {
|
|||
///
|
||||
/// This returns a span that contains the index
|
||||
pub fn get_span(&self, index: usize) -> Attrs {
|
||||
self.spans.get(&index).map(|v| v.as_attrs()).unwrap_or(self.defaults.as_attrs())
|
||||
self.spans
|
||||
.get(&index)
|
||||
.map(|v| v.as_attrs())
|
||||
.unwrap_or(self.defaults.as_attrs())
|
||||
}
|
||||
|
||||
/// Split attributes list at an offset
|
||||
|
|
@ -288,20 +284,19 @@ impl AttrsList {
|
|||
}
|
||||
|
||||
for (key, resize) in removes {
|
||||
let (range, attrs) = self.spans.get_key_value(&key.start).map(|v| (v.0.clone(), v.1.clone())).expect("attrs span not found");
|
||||
let (range, attrs) = self
|
||||
.spans
|
||||
.get_key_value(&key.start)
|
||||
.map(|v| (v.0.clone(), v.1.clone()))
|
||||
.expect("attrs span not found");
|
||||
self.spans.remove(key);
|
||||
|
||||
if resize {
|
||||
new.spans.insert(
|
||||
0..range.end - index,
|
||||
attrs.clone()
|
||||
);
|
||||
new.spans.insert(0..range.end - index, attrs.clone());
|
||||
self.spans.insert(range.start..index, attrs);
|
||||
} else {
|
||||
new.spans.insert(
|
||||
range.start - index..range.end - index,
|
||||
attrs
|
||||
);
|
||||
new.spans
|
||||
.insert(range.start - index..range.end - index, attrs);
|
||||
}
|
||||
}
|
||||
new
|
||||
|
|
|
|||
153
src/buffer.rs
153
src/buffer.rs
|
|
@ -5,15 +5,12 @@ use alloc::{
|
|||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use core::{
|
||||
cmp,
|
||||
fmt,
|
||||
};
|
||||
use core::{cmp, fmt};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::{Attrs, AttrsList, BufferLine, FontSystem, LayoutGlyph, LayoutLine, ShapeLine, Wrap};
|
||||
#[cfg(feature = "swash")]
|
||||
use crate::Color;
|
||||
use crate::{Attrs, AttrsList, BufferLine, FontSystem, LayoutGlyph, LayoutLine, ShapeLine, Wrap};
|
||||
|
||||
/// Current cursor location
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
|
||||
|
|
@ -39,7 +36,11 @@ pub struct LayoutCursor {
|
|||
|
||||
impl LayoutCursor {
|
||||
pub fn new(line: usize, layout: usize, glyph: usize) -> Self {
|
||||
Self { line, layout, glyph }
|
||||
Self {
|
||||
line,
|
||||
layout,
|
||||
glyph,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -81,13 +82,25 @@ impl<'a> LayoutRun<'a> {
|
|||
let cursor = Cursor::new(self.line_i, self.glyphs.last().map_or(0, |glyph| glyph.end));
|
||||
if cursor >= cursor_start && cursor <= cursor_end {
|
||||
if x_start.is_none() {
|
||||
x_start = Some(self.glyphs.last().map_or(0., |glyph| glyph.x + glyph.w * ltr_factor));
|
||||
x_start = Some(
|
||||
self.glyphs
|
||||
.last()
|
||||
.map_or(0., |glyph| glyph.x + glyph.w * ltr_factor),
|
||||
);
|
||||
}
|
||||
x_end = Some(self.glyphs.last().map_or(0., |glyph| glyph.x + glyph.w * ltr_factor));
|
||||
x_end = Some(
|
||||
self.glyphs
|
||||
.last()
|
||||
.map_or(0., |glyph| glyph.x + glyph.w * ltr_factor),
|
||||
);
|
||||
}
|
||||
if let Some(x_start) = x_start {
|
||||
let x_end = x_end.expect("end of cursor not found");
|
||||
let (x_start, x_end) = if x_start < x_end { (x_start, x_end) } else { (x_end, x_start) };
|
||||
let (x_start, x_end) = if x_start < x_end {
|
||||
(x_start, x_end)
|
||||
} else {
|
||||
(x_end, x_start)
|
||||
};
|
||||
Some((x_start, x_end - x_start))
|
||||
} else {
|
||||
None
|
||||
|
|
@ -107,14 +120,28 @@ pub struct LayoutRunIter<'a, 'b> {
|
|||
|
||||
impl<'a, 'b> LayoutRunIter<'a, 'b> {
|
||||
pub fn new(buffer: &'b Buffer<'a>) -> Self {
|
||||
let total_layout_lines: usize = buffer.lines.iter().map(|line| line.layout_opt().as_ref().map(|layout| layout.len()).unwrap_or_default()).sum();
|
||||
let top_cropped_layout_lines = total_layout_lines.saturating_sub(buffer.scroll.try_into().unwrap_or_default());
|
||||
let maximum_lines = buffer.height.checked_div(buffer.metrics.line_height).unwrap_or_default();
|
||||
let bottom_cropped_layout_lines = if top_cropped_layout_lines > maximum_lines.try_into().unwrap_or_default() {
|
||||
maximum_lines.try_into().unwrap_or_default()
|
||||
} else {
|
||||
top_cropped_layout_lines
|
||||
};
|
||||
let total_layout_lines: usize = buffer
|
||||
.lines
|
||||
.iter()
|
||||
.map(|line| {
|
||||
line.layout_opt()
|
||||
.as_ref()
|
||||
.map(|layout| layout.len())
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.sum();
|
||||
let top_cropped_layout_lines =
|
||||
total_layout_lines.saturating_sub(buffer.scroll.try_into().unwrap_or_default());
|
||||
let maximum_lines = buffer
|
||||
.height
|
||||
.checked_div(buffer.metrics.line_height)
|
||||
.unwrap_or_default();
|
||||
let bottom_cropped_layout_lines =
|
||||
if top_cropped_layout_lines > maximum_lines.try_into().unwrap_or_default() {
|
||||
maximum_lines.try_into().unwrap_or_default()
|
||||
} else {
|
||||
top_cropped_layout_lines
|
||||
};
|
||||
Self {
|
||||
buffer,
|
||||
line_i: 0,
|
||||
|
|
@ -169,7 +196,7 @@ impl<'a, 'b> Iterator for LayoutRunIter<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> ExactSizeIterator for LayoutRunIter<'a, 'b> { }
|
||||
impl<'a, 'b> ExactSizeIterator for LayoutRunIter<'a, 'b> {}
|
||||
|
||||
/// Metrics of text
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
|
|
@ -182,7 +209,10 @@ pub struct Metrics {
|
|||
|
||||
impl Metrics {
|
||||
pub const fn new(font_size: i32, line_height: i32) -> Self {
|
||||
Self { font_size, line_height }
|
||||
Self {
|
||||
font_size,
|
||||
line_height,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn scale(self, scale: i32) -> Self {
|
||||
|
|
@ -215,10 +245,7 @@ pub struct Buffer<'a> {
|
|||
|
||||
impl<'a> Buffer<'a> {
|
||||
/// Create a new [`Buffer`] with the provided [`FontSystem`] and [`Metrics`]
|
||||
pub fn new(
|
||||
font_system: &'a FontSystem,
|
||||
metrics: Metrics,
|
||||
) -> Self {
|
||||
pub fn new(font_system: &'a FontSystem, metrics: Metrics) -> Self {
|
||||
let mut buffer = Self {
|
||||
font_system,
|
||||
lines: Vec::new(),
|
||||
|
|
@ -244,7 +271,7 @@ impl<'a> Buffer<'a> {
|
|||
self.font_system,
|
||||
self.metrics.font_size,
|
||||
self.width,
|
||||
self.wrap
|
||||
self.wrap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -274,7 +301,7 @@ impl<'a> Buffer<'a> {
|
|||
self.font_system,
|
||||
self.metrics.font_size,
|
||||
self.width,
|
||||
self.wrap
|
||||
self.wrap,
|
||||
);
|
||||
total_layout += layout.len() as i32;
|
||||
}
|
||||
|
|
@ -307,7 +334,7 @@ impl<'a> Buffer<'a> {
|
|||
self.font_system,
|
||||
self.metrics.font_size,
|
||||
self.width,
|
||||
self.wrap
|
||||
self.wrap,
|
||||
);
|
||||
if line_i == cursor.line {
|
||||
let layout_cursor = self.layout_cursor(&cursor);
|
||||
|
|
@ -341,13 +368,7 @@ impl<'a> Buffer<'a> {
|
|||
let scroll_end = self.scroll + lines;
|
||||
let total_layout = self.shape_until(scroll_end);
|
||||
|
||||
self.scroll = cmp::max(
|
||||
0,
|
||||
cmp::min(
|
||||
total_layout - (lines - 1),
|
||||
self.scroll,
|
||||
),
|
||||
);
|
||||
self.scroll = cmp::max(0, cmp::min(total_layout - (lines - 1), self.scroll));
|
||||
}
|
||||
|
||||
pub fn layout_cursor(&self, cursor: &Cursor) -> LayoutCursor {
|
||||
|
|
@ -358,40 +379,24 @@ impl<'a> Buffer<'a> {
|
|||
for (layout_i, layout_line) in layout.iter().enumerate() {
|
||||
for (glyph_i, glyph) in layout_line.glyphs.iter().enumerate() {
|
||||
if cursor.index == glyph.start {
|
||||
return LayoutCursor::new(
|
||||
cursor.line,
|
||||
layout_i,
|
||||
glyph_i
|
||||
);
|
||||
return LayoutCursor::new(cursor.line, layout_i, glyph_i);
|
||||
}
|
||||
}
|
||||
match layout_line.glyphs.last() {
|
||||
Some(glyph) => {
|
||||
if cursor.index == glyph.end {
|
||||
return LayoutCursor::new(
|
||||
cursor.line,
|
||||
layout_i,
|
||||
layout_line.glyphs.len()
|
||||
);
|
||||
return LayoutCursor::new(cursor.line, layout_i, layout_line.glyphs.len());
|
||||
}
|
||||
},
|
||||
}
|
||||
None => {
|
||||
return LayoutCursor::new(
|
||||
cursor.line,
|
||||
layout_i,
|
||||
0
|
||||
);
|
||||
return LayoutCursor::new(cursor.line, layout_i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to start of line
|
||||
//TODO: should this be the end of the line?
|
||||
LayoutCursor::new(
|
||||
cursor.line,
|
||||
0,
|
||||
0
|
||||
)
|
||||
LayoutCursor::new(cursor.line, 0, 0)
|
||||
}
|
||||
|
||||
/// Get [`FontSystem`] used by this [`Buffer`]
|
||||
|
|
@ -408,7 +413,12 @@ impl<'a> Buffer<'a> {
|
|||
/// Lay out the provided line index and return the result
|
||||
pub fn line_layout(&mut self, line_i: usize) -> Option<&[LayoutLine]> {
|
||||
let line = self.lines.get_mut(line_i)?;
|
||||
Some(line.layout(self.font_system, self.metrics.font_size, self.width, self.wrap))
|
||||
Some(line.layout(
|
||||
self.font_system,
|
||||
self.metrics.font_size,
|
||||
self.width,
|
||||
self.wrap,
|
||||
))
|
||||
}
|
||||
|
||||
/// Get the current [`Metrics`]
|
||||
|
|
@ -476,11 +486,13 @@ impl<'a> Buffer<'a> {
|
|||
pub fn set_text(&mut self, text: &str, attrs: Attrs<'a>) {
|
||||
self.lines.clear();
|
||||
for line in text.lines() {
|
||||
self.lines.push(BufferLine::new(line.to_string(), AttrsList::new(attrs)));
|
||||
self.lines
|
||||
.push(BufferLine::new(line.to_string(), AttrsList::new(attrs)));
|
||||
}
|
||||
// Make sure there is always one line
|
||||
if self.lines.is_empty() {
|
||||
self.lines.push(BufferLine::new(String::new(), AttrsList::new(attrs)));
|
||||
self.lines
|
||||
.push(BufferLine::new(String::new(), AttrsList::new(attrs)));
|
||||
}
|
||||
|
||||
self.scroll = 0;
|
||||
|
|
@ -522,9 +534,7 @@ impl<'a> Buffer<'a> {
|
|||
first_run = false;
|
||||
let new_cursor = Cursor::new(run.line_i, 0);
|
||||
new_cursor_opt = Some(new_cursor);
|
||||
} else if y >= line_y - font_size
|
||||
&& y < line_y - font_size + line_height
|
||||
{
|
||||
} else if y >= line_y - font_size && y < line_y - font_size + line_height {
|
||||
let mut new_cursor_glyph = run.glyphs.len();
|
||||
let mut new_cursor_char = 0;
|
||||
|
||||
|
|
@ -538,9 +548,7 @@ impl<'a> Buffer<'a> {
|
|||
new_cursor_char = 0;
|
||||
}
|
||||
}
|
||||
if x >= glyph.x as i32
|
||||
&& x <= (glyph.x + glyph.w) as i32
|
||||
{
|
||||
if x >= glyph.x as i32 && x <= (glyph.x + glyph.w) as i32 {
|
||||
new_cursor_glyph = glyph_i;
|
||||
|
||||
let cluster = &run.text[glyph.start..glyph.end];
|
||||
|
|
@ -548,9 +556,7 @@ impl<'a> Buffer<'a> {
|
|||
let mut egc_x = glyph.x;
|
||||
let egc_w = glyph.w / (total as f32);
|
||||
for (egc_i, egc) in cluster.grapheme_indices(true) {
|
||||
if x >= egc_x as i32
|
||||
&& x <= (egc_x + egc_w) as i32
|
||||
{
|
||||
if x >= egc_x as i32 && x <= (egc_x + egc_w) as i32 {
|
||||
new_cursor_char = egc_i;
|
||||
|
||||
let right_half = x >= (egc_x + egc_w / 2.0) as i32;
|
||||
|
|
@ -578,11 +584,13 @@ impl<'a> Buffer<'a> {
|
|||
Some(glyph) => {
|
||||
// Position at glyph
|
||||
new_cursor.index = glyph.start + new_cursor_char;
|
||||
},
|
||||
None => if let Some(glyph) = run.glyphs.last() {
|
||||
// Position at end of line
|
||||
new_cursor.index = glyph.end;
|
||||
},
|
||||
}
|
||||
None => {
|
||||
if let Some(glyph) = run.glyphs.last() {
|
||||
// Position at end of line
|
||||
new_cursor.index = glyph.end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_cursor_opt = Some(new_cursor);
|
||||
|
|
@ -606,7 +614,8 @@ impl<'a> Buffer<'a> {
|
|||
/// Draw the buffer
|
||||
#[cfg(feature = "swash")]
|
||||
pub fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F)
|
||||
where F: FnMut(i32, i32, u32, u32, Color)
|
||||
where
|
||||
F: FnMut(i32, i32, u32, u32, Color),
|
||||
{
|
||||
for run in self.layout_runs() {
|
||||
for glyph in run.glyphs.iter() {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{
|
||||
string::String,
|
||||
vec::Vec,
|
||||
};
|
||||
use alloc::{string::String, vec::Vec};
|
||||
|
||||
use crate::{AttrsList, FontSystem, LayoutLine, ShapeLine, Wrap};
|
||||
|
||||
|
|
@ -39,7 +36,11 @@ impl BufferLine {
|
|||
///
|
||||
/// Will reset shape and layout if it differs from current text and attributes list.
|
||||
/// Returns true if the line was reset
|
||||
pub fn set_text<T: AsRef<str> + Into<String>>(&mut self, text: T, attrs_list: AttrsList) -> bool {
|
||||
pub fn set_text<T: AsRef<str> + Into<String>>(
|
||||
&mut self,
|
||||
text: T,
|
||||
attrs_list: AttrsList,
|
||||
) -> bool {
|
||||
if text.as_ref() != self.text || attrs_list != self.attrs_list {
|
||||
self.text = text.into();
|
||||
self.attrs_list = attrs_list;
|
||||
|
|
@ -102,7 +103,8 @@ impl BufferLine {
|
|||
|
||||
if other.attrs_list.defaults() != self.attrs_list.defaults() {
|
||||
// If default formatting does not match, make a new span for it
|
||||
self.attrs_list.add_span(len..len + other.text().len(), other.attrs_list.defaults());
|
||||
self.attrs_list
|
||||
.add_span(len..len + other.text().len(), other.attrs_list.defaults());
|
||||
}
|
||||
|
||||
for (other_range, attrs) in other.attrs_list.spans() {
|
||||
|
|
@ -157,15 +159,17 @@ impl BufferLine {
|
|||
}
|
||||
|
||||
/// Layout line, will cache results
|
||||
pub fn layout(&mut self, font_system: &FontSystem, font_size: i32, width: i32, wrap: Wrap) -> &[LayoutLine] {
|
||||
pub fn layout(
|
||||
&mut self,
|
||||
font_system: &FontSystem,
|
||||
font_size: i32,
|
||||
width: i32,
|
||||
wrap: Wrap,
|
||||
) -> &[LayoutLine] {
|
||||
if self.layout_opt.is_none() {
|
||||
self.wrap = wrap;
|
||||
let shape = self.shape(font_system);
|
||||
let layout = shape.layout(
|
||||
font_size,
|
||||
width,
|
||||
wrap
|
||||
);
|
||||
let layout = shape.layout(font_size, width, wrap);
|
||||
self.layout_opt = Some(layout);
|
||||
}
|
||||
self.layout_opt.as_ref().expect("layout not found")
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ use alloc::string::String;
|
|||
use core::{cmp, iter::once};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::{Action, AttrsList, Buffer, BufferLine, Cursor, Edit, LayoutCursor};
|
||||
#[cfg(feature = "swash")]
|
||||
use crate::Color;
|
||||
use crate::{Action, AttrsList, Buffer, BufferLine, Cursor, Edit, LayoutCursor};
|
||||
|
||||
/// A wrapper of [`Buffer`] for easy editing
|
||||
pub struct Editor<'a> {
|
||||
|
|
@ -31,14 +31,17 @@ impl<'a> Editor<'a> {
|
|||
}
|
||||
|
||||
fn set_layout_cursor(&mut self, cursor: LayoutCursor) {
|
||||
let layout = self.buffer.line_layout(cursor.line).expect("layout not found");
|
||||
let layout = self
|
||||
.buffer
|
||||
.line_layout(cursor.line)
|
||||
.expect("layout not found");
|
||||
|
||||
let layout_line = match layout.get(cursor.layout) {
|
||||
Some(some) => some,
|
||||
None => match layout.last() {
|
||||
Some(some) => some,
|
||||
None => todo!("layout cursor in line with no layouts"),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let new_index = match layout_line.glyphs.get(cursor.glyph) {
|
||||
|
|
@ -47,7 +50,7 @@ impl<'a> Editor<'a> {
|
|||
Some(glyph) => glyph.end,
|
||||
//TODO: is this correct?
|
||||
None => 0,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if self.cursor.line != cursor.line || self.cursor.index != new_index {
|
||||
|
|
@ -217,7 +220,8 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
let after_len = after.text().len();
|
||||
|
||||
// Collect attributes
|
||||
let mut final_attrs = attrs_list.unwrap_or_else(|| AttrsList::new(line.attrs_list().get_span(line.text().len())));
|
||||
let mut final_attrs = attrs_list
|
||||
.unwrap_or_else(|| AttrsList::new(line.attrs_list().get_span(line.text().len())));
|
||||
|
||||
// Append the inserted text, line by line
|
||||
// we want to see a blank entry if the string ends with a newline
|
||||
|
|
@ -227,13 +231,23 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
let mut these_attrs = final_attrs.split_off(data_line.len());
|
||||
remaining_split_len -= data_line.len();
|
||||
core::mem::swap(&mut these_attrs, &mut final_attrs);
|
||||
line.append(BufferLine::new(data_line.strip_suffix(char::is_control).unwrap_or(data_line), these_attrs));
|
||||
line.append(BufferLine::new(
|
||||
data_line
|
||||
.strip_suffix(char::is_control)
|
||||
.unwrap_or(data_line),
|
||||
these_attrs,
|
||||
));
|
||||
} else {
|
||||
panic!("str::lines() did not yield any elements");
|
||||
}
|
||||
if let Some(data_line) = lines_iter.next_back() {
|
||||
remaining_split_len -= data_line.len();
|
||||
let mut tmp = BufferLine::new(data_line.strip_suffix(char::is_control).unwrap_or(data_line), final_attrs.split_off(remaining_split_len));
|
||||
let mut tmp = BufferLine::new(
|
||||
data_line
|
||||
.strip_suffix(char::is_control)
|
||||
.unwrap_or(data_line),
|
||||
final_attrs.split_off(remaining_split_len),
|
||||
);
|
||||
tmp.append(after);
|
||||
self.buffer.lines.insert(insert_line, tmp);
|
||||
self.cursor.line += 1;
|
||||
|
|
@ -242,7 +256,12 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
}
|
||||
for data_line in lines_iter.rev() {
|
||||
remaining_split_len -= data_line.len();
|
||||
let tmp = BufferLine::new(data_line.strip_suffix(char::is_control).unwrap_or(data_line), final_attrs.split_off(remaining_split_len));
|
||||
let tmp = BufferLine::new(
|
||||
data_line
|
||||
.strip_suffix(char::is_control)
|
||||
.unwrap_or(data_line),
|
||||
final_attrs.split_off(remaining_split_len),
|
||||
);
|
||||
self.buffer.lines.insert(insert_line, tmp);
|
||||
self.cursor.line += 1;
|
||||
}
|
||||
|
|
@ -278,7 +297,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
self.buffer.set_redraw(true);
|
||||
}
|
||||
self.cursor_x_opt = None;
|
||||
},
|
||||
}
|
||||
Action::Next => {
|
||||
let line = &mut self.buffer.lines[self.cursor.line];
|
||||
if self.cursor.index < line.text().len() {
|
||||
|
|
@ -295,9 +314,12 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
self.buffer.set_redraw(true);
|
||||
}
|
||||
self.cursor_x_opt = None;
|
||||
},
|
||||
}
|
||||
Action::Left => {
|
||||
let rtl_opt = self.buffer.lines[self.cursor.line].shape_opt().as_ref().map(|shape| shape.rtl);
|
||||
let rtl_opt = self.buffer.lines[self.cursor.line]
|
||||
.shape_opt()
|
||||
.as_ref()
|
||||
.map(|shape| shape.rtl);
|
||||
if let Some(rtl) = rtl_opt {
|
||||
if rtl {
|
||||
self.action(Action::Next);
|
||||
|
|
@ -305,9 +327,12 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
self.action(Action::Previous);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Action::Right => {
|
||||
let rtl_opt = self.buffer.lines[self.cursor.line].shape_opt().as_ref().map(|shape| shape.rtl);
|
||||
let rtl_opt = self.buffer.lines[self.cursor.line]
|
||||
.shape_opt()
|
||||
.as_ref()
|
||||
.map(|shape| shape.rtl);
|
||||
if let Some(rtl) = rtl_opt {
|
||||
if rtl {
|
||||
self.action(Action::Previous);
|
||||
|
|
@ -315,14 +340,14 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
self.action(Action::Next);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Action::Up => {
|
||||
//TODO: make this preserve X as best as possible!
|
||||
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
||||
|
||||
if self.cursor_x_opt.is_none() {
|
||||
self.cursor_x_opt = Some(
|
||||
cursor.glyph as i32 //TODO: glyph x position
|
||||
cursor.glyph as i32, //TODO: glyph x position
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -338,16 +363,20 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
}
|
||||
|
||||
self.set_layout_cursor(cursor);
|
||||
},
|
||||
}
|
||||
Action::Down => {
|
||||
//TODO: make this preserve X as best as possible!
|
||||
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
||||
|
||||
let layout_len = self.buffer.line_layout(cursor.line).expect("layout not found").len();
|
||||
let layout_len = self
|
||||
.buffer
|
||||
.line_layout(cursor.line)
|
||||
.expect("layout not found")
|
||||
.len();
|
||||
|
||||
if self.cursor_x_opt.is_none() {
|
||||
self.cursor_x_opt = Some(
|
||||
cursor.glyph as i32 //TODO: glyph x position
|
||||
cursor.glyph as i32, //TODO: glyph x position
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -363,13 +392,13 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
}
|
||||
|
||||
self.set_layout_cursor(cursor);
|
||||
},
|
||||
}
|
||||
Action::Home => {
|
||||
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
||||
cursor.glyph = 0;
|
||||
self.set_layout_cursor(cursor);
|
||||
self.cursor_x_opt = None;
|
||||
},
|
||||
}
|
||||
Action::End => {
|
||||
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
||||
cursor.glyph = usize::max_value();
|
||||
|
|
@ -388,10 +417,10 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
}
|
||||
Action::PageUp => {
|
||||
self.action(Action::Vertical(-self.buffer.size().1));
|
||||
},
|
||||
}
|
||||
Action::PageDown => {
|
||||
self.action(Action::Vertical(self.buffer.size().1));
|
||||
},
|
||||
}
|
||||
Action::Vertical(px) => {
|
||||
// TODO more efficient
|
||||
let lines = px / self.buffer.metrics().line_height;
|
||||
|
|
@ -404,16 +433,14 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
self.action(Action::Down);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Action::Escape => {
|
||||
if self.select_opt.take().is_some() {
|
||||
self.buffer.set_redraw(true);
|
||||
}
|
||||
},
|
||||
}
|
||||
Action::Insert(character) => {
|
||||
if character.is_control()
|
||||
&& !['\t', '\n', '\u{92}'].contains(&character)
|
||||
{
|
||||
if character.is_control() && !['\t', '\n', '\u{92}'].contains(&character) {
|
||||
// Filter out special chars (except for tab), use Action instead
|
||||
log::debug!("Refusing to insert control character {:?}", character);
|
||||
} else if character == '\n' {
|
||||
|
|
@ -423,7 +450,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
let str_ref = character.encode_utf8(&mut str_buf);
|
||||
self.insert_string(str_ref, None);
|
||||
}
|
||||
},
|
||||
}
|
||||
Action::Enter => {
|
||||
self.delete_selection();
|
||||
|
||||
|
|
@ -433,7 +460,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
self.cursor.index = 0;
|
||||
|
||||
self.buffer.lines.insert(self.cursor.line, new_line);
|
||||
},
|
||||
}
|
||||
Action::Backspace => {
|
||||
if self.delete_selection() {
|
||||
// Deleted selection
|
||||
|
|
@ -472,7 +499,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
|
||||
line.append(old_line);
|
||||
}
|
||||
},
|
||||
}
|
||||
Action::Delete => {
|
||||
if self.delete_selection() {
|
||||
// Deleted selection
|
||||
|
|
@ -484,9 +511,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
.grapheme_indices(true)
|
||||
.take_while(|(i, _)| *i <= self.cursor.index)
|
||||
.last()
|
||||
.map(|(i, c)| {
|
||||
i..(i + c.len())
|
||||
});
|
||||
.map(|(i, c)| i..(i + c.len()));
|
||||
|
||||
if let Some(range) = range_opt {
|
||||
self.cursor.index = range.start;
|
||||
|
|
@ -504,7 +529,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
let old_line = self.buffer.lines.remove(self.cursor.line + 1);
|
||||
self.buffer.lines[self.cursor.line].append(old_line);
|
||||
}
|
||||
},
|
||||
}
|
||||
Action::Click { x, y } => {
|
||||
self.select_opt = None;
|
||||
|
||||
|
|
@ -514,7 +539,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
self.buffer.set_redraw(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Action::Drag { x, y } => {
|
||||
if self.select_opt.is_none() {
|
||||
self.select_opt = Some(self.cursor);
|
||||
|
|
@ -527,7 +552,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
self.buffer.set_redraw(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Action::Scroll { lines } => {
|
||||
let mut scroll = self.buffer.scroll();
|
||||
scroll += lines;
|
||||
|
|
@ -573,7 +598,10 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
self.cursor_x_opt = None;
|
||||
}
|
||||
Action::LeftWord => {
|
||||
let rtl_opt = self.buffer.lines[self.cursor.line].shape_opt().as_ref().map(|shape| shape.rtl);
|
||||
let rtl_opt = self.buffer.lines[self.cursor.line]
|
||||
.shape_opt()
|
||||
.as_ref()
|
||||
.map(|shape| shape.rtl);
|
||||
if let Some(rtl) = rtl_opt {
|
||||
if rtl {
|
||||
self.action(Action::NextWord);
|
||||
|
|
@ -581,9 +609,12 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
self.action(Action::PreviousWord);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Action::RightWord => {
|
||||
let rtl_opt = self.buffer.lines[self.cursor.line].shape_opt().as_ref().map(|shape| shape.rtl);
|
||||
let rtl_opt = self.buffer.lines[self.cursor.line]
|
||||
.shape_opt()
|
||||
.as_ref()
|
||||
.map(|shape| shape.rtl);
|
||||
if let Some(rtl) = rtl_opt {
|
||||
if rtl {
|
||||
self.action(Action::PreviousWord);
|
||||
|
|
@ -591,7 +622,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
self.action(Action::NextWord);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Action::BufferStart => {
|
||||
self.cursor.line = 0;
|
||||
self.cursor.index = 0;
|
||||
|
|
@ -628,7 +659,8 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
/// Draw the editor
|
||||
#[cfg(feature = "swash")]
|
||||
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F)
|
||||
where F: FnMut(i32, i32, u32, u32, Color)
|
||||
where
|
||||
F: FnMut(i32, i32, u32, u32, Color),
|
||||
{
|
||||
let font_size = self.buffer.metrics().font_size;
|
||||
let line_height = self.buffer.metrics().line_height;
|
||||
|
|
@ -664,7 +696,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
if cursor.index == glyph.end {
|
||||
return Some((run.glyphs.len(), 0.0));
|
||||
}
|
||||
},
|
||||
}
|
||||
None => {
|
||||
return Some((0, 0.0));
|
||||
}
|
||||
|
|
@ -701,16 +733,14 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
let c_start = glyph.start + i;
|
||||
let c_end = glyph.start + i + c.len();
|
||||
if (start.line != line_i || c_end > start.index)
|
||||
&& (end.line != line_i || c_start < end.index) {
|
||||
&& (end.line != line_i || c_start < end.index)
|
||||
{
|
||||
range_opt = match range_opt.take() {
|
||||
Some((min, max)) => Some((
|
||||
cmp::min(min, c_x as i32),
|
||||
cmp::max(max, (c_x + c_w) as i32),
|
||||
)),
|
||||
None => Some((
|
||||
c_x as i32,
|
||||
(c_x + c_w) as i32,
|
||||
))
|
||||
None => Some((c_x as i32, (c_x + c_w) as i32)),
|
||||
};
|
||||
} else if let Some((min, max)) = range_opt.take() {
|
||||
f(
|
||||
|
|
@ -718,14 +748,14 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
line_y - font_size,
|
||||
cmp::max(0, max - min) as u32,
|
||||
line_height as u32,
|
||||
Color::rgba(color.r(), color.g(), color.b(), 0x33)
|
||||
Color::rgba(color.r(), color.g(), color.b(), 0x33),
|
||||
);
|
||||
}
|
||||
c_x += c_w;
|
||||
}
|
||||
}
|
||||
|
||||
if run.glyphs.is_empty() && end.line > line_i{
|
||||
if run.glyphs.is_empty() && end.line > line_i {
|
||||
// Highlight all of internal empty lines
|
||||
range_opt = Some((0, self.buffer.size().0));
|
||||
}
|
||||
|
|
@ -744,7 +774,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
line_y - font_size,
|
||||
cmp::max(0, max - min) as u32,
|
||||
line_height as u32,
|
||||
Color::rgba(color.r(), color.g(), color.b(), 0x33)
|
||||
Color::rgba(color.r(), color.g(), color.b(), 0x33),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -760,7 +790,7 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
} else {
|
||||
(glyph.x + cursor_glyph_offset) as i32
|
||||
}
|
||||
},
|
||||
}
|
||||
None => match run.glyphs.last() {
|
||||
Some(glyph) => {
|
||||
// End of last glyph
|
||||
|
|
@ -769,21 +799,15 @@ impl<'a> Edit<'a> for Editor<'a> {
|
|||
} else {
|
||||
(glyph.x + glyph.w) as i32
|
||||
}
|
||||
},
|
||||
}
|
||||
None => {
|
||||
// Start of empty line
|
||||
0
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
f(
|
||||
x,
|
||||
line_y - font_size,
|
||||
1,
|
||||
line_height as u32,
|
||||
color,
|
||||
);
|
||||
f(x, line_y - font_size, 1, line_height as u32, color);
|
||||
}
|
||||
|
||||
for glyph in run.glyphs.iter() {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
#[cfg(not(feature = "std"))]
|
||||
use alloc::string::String;
|
||||
|
||||
use crate::{AttrsList, Buffer, Cursor};
|
||||
#[cfg(feature = "swash")]
|
||||
use crate::Color;
|
||||
use crate::{AttrsList, Buffer, Cursor};
|
||||
|
||||
pub use self::editor::*;
|
||||
mod editor;
|
||||
|
|
@ -114,5 +114,6 @@ pub trait Edit<'a> {
|
|||
/// Draw the editor
|
||||
#[cfg(feature = "swash")]
|
||||
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, f: F)
|
||||
where F: FnMut(i32, i32, u32, u32, Color);
|
||||
where
|
||||
F: FnMut(i32, i32, u32, u32, Color);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +1,13 @@
|
|||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{
|
||||
string::String,
|
||||
vec::Vec,
|
||||
};
|
||||
use alloc::{string::String, vec::Vec};
|
||||
#[cfg(feature = "std")]
|
||||
use std::{
|
||||
fs,
|
||||
io,
|
||||
path::Path,
|
||||
};
|
||||
use std::{fs, io, path::Path};
|
||||
use syntect::highlighting::{
|
||||
FontStyle,
|
||||
Highlighter,
|
||||
HighlightState,
|
||||
RangedHighlightIterator,
|
||||
Theme,
|
||||
ThemeSet,
|
||||
};
|
||||
use syntect::parsing::{
|
||||
ParseState,
|
||||
ScopeStack,
|
||||
SyntaxReference,
|
||||
SyntaxSet,
|
||||
FontStyle, HighlightState, Highlighter, RangedHighlightIterator, Theme, ThemeSet,
|
||||
};
|
||||
use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet};
|
||||
|
||||
use crate::{
|
||||
Action,
|
||||
AttrsList,
|
||||
Buffer,
|
||||
Color,
|
||||
Cursor,
|
||||
Edit,
|
||||
Editor,
|
||||
Style,
|
||||
Weight,
|
||||
Wrap,
|
||||
};
|
||||
use crate::{Action, AttrsList, Buffer, Color, Cursor, Edit, Editor, Style, Weight, Wrap};
|
||||
|
||||
pub struct SyntaxSystem {
|
||||
pub syntax_set: SyntaxSet,
|
||||
|
|
@ -69,7 +41,11 @@ impl<'a> SyntaxEditor<'a> {
|
|||
/// A good default theme name is "base16-eighties.dark".
|
||||
///
|
||||
/// Returns None if theme not found
|
||||
pub fn new(buffer: Buffer<'a>, syntax_system: &'a SyntaxSystem, theme_name: &str) -> Option<Self> {
|
||||
pub fn new(
|
||||
buffer: Buffer<'a>,
|
||||
syntax_system: &'a SyntaxSystem,
|
||||
theme_name: &str,
|
||||
) -> Option<Self> {
|
||||
let editor = Editor::new(buffer);
|
||||
let syntax = syntax_system.syntax_set.find_syntax_plain_text();
|
||||
let theme = syntax_system.theme_set.themes.get(theme_name)?;
|
||||
|
|
@ -91,7 +67,11 @@ impl<'a> SyntaxEditor<'a> {
|
|||
///
|
||||
/// Returns an [`io::Error`] if reading the file fails
|
||||
#[cfg(feature = "std")]
|
||||
pub fn load_text<P: AsRef<Path>>(&mut self, path: P, attrs: crate::Attrs<'a>) -> io::Result<()> {
|
||||
pub fn load_text<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
path: P,
|
||||
attrs: crate::Attrs<'a>,
|
||||
) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
|
||||
let text = fs::read_to_string(path)?;
|
||||
|
|
@ -119,12 +99,7 @@ impl<'a> SyntaxEditor<'a> {
|
|||
/// Get the default background color
|
||||
pub fn background_color(&self) -> Color {
|
||||
if let Some(background) = self.theme.settings.background {
|
||||
Color::rgba(
|
||||
background.r,
|
||||
background.g,
|
||||
background.b,
|
||||
background.a,
|
||||
)
|
||||
Color::rgba(background.r, background.g, background.b, background.a)
|
||||
} else {
|
||||
Color::rgb(0, 0, 0)
|
||||
}
|
||||
|
|
@ -133,12 +108,7 @@ impl<'a> SyntaxEditor<'a> {
|
|||
/// Get the default foreground (text) color
|
||||
pub fn foreground_color(&self) -> Color {
|
||||
if let Some(foreground) = self.theme.settings.foreground {
|
||||
Color::rgba(
|
||||
foreground.r,
|
||||
foreground.g,
|
||||
foreground.b,
|
||||
foreground.a,
|
||||
)
|
||||
Color::rgba(foreground.r, foreground.g, foreground.b, foreground.a)
|
||||
} else {
|
||||
Color::rgb(0xFF, 0xFF, 0xFF)
|
||||
}
|
||||
|
|
@ -175,21 +145,24 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
|
|||
let mut highlighted = 0;
|
||||
for line_i in 0..buffer.lines.len() {
|
||||
let line = &mut buffer.lines[line_i];
|
||||
if ! line.is_reset() && line_i < self.syntax_cache.len() {
|
||||
if !line.is_reset() && line_i < self.syntax_cache.len() {
|
||||
continue;
|
||||
}
|
||||
highlighted += 1;
|
||||
|
||||
let (mut parse_state, mut highlight_state) = if line_i > 0 && line_i <= self.syntax_cache.len() {
|
||||
self.syntax_cache[line_i - 1].clone()
|
||||
} else {
|
||||
(
|
||||
ParseState::new(self.syntax),
|
||||
HighlightState::new(&self.highlighter, ScopeStack::new())
|
||||
)
|
||||
};
|
||||
let (mut parse_state, mut highlight_state) =
|
||||
if line_i > 0 && line_i <= self.syntax_cache.len() {
|
||||
self.syntax_cache[line_i - 1].clone()
|
||||
} else {
|
||||
(
|
||||
ParseState::new(self.syntax),
|
||||
HighlightState::new(&self.highlighter, ScopeStack::new()),
|
||||
)
|
||||
};
|
||||
|
||||
let ops = parse_state.parse_line(line.text(), &self.syntax_system.syntax_set).expect("failed to parse syntax");
|
||||
let ops = parse_state
|
||||
.parse_line(line.text(), &self.syntax_system.syntax_set)
|
||||
.expect("failed to parse syntax");
|
||||
let ranges = RangedHighlightIterator::new(
|
||||
&mut highlight_state,
|
||||
&ops,
|
||||
|
|
@ -219,8 +192,7 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
|
|||
Weight::BOLD
|
||||
} else {
|
||||
Weight::NORMAL
|
||||
})
|
||||
//TODO: underline
|
||||
}), //TODO: underline
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -247,7 +219,11 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
|
|||
if highlighted > 0 {
|
||||
buffer.set_redraw(true);
|
||||
#[cfg(feature = "std")]
|
||||
log::debug!("Syntax highlighted {} lines in {:?}", highlighted, now.elapsed());
|
||||
log::debug!(
|
||||
"Syntax highlighted {} lines in {:?}",
|
||||
highlighted,
|
||||
now.elapsed()
|
||||
);
|
||||
}
|
||||
|
||||
self.editor.shape_as_needed();
|
||||
|
|
@ -272,7 +248,8 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
|
|||
/// Draw the editor
|
||||
#[cfg(feature = "swash")]
|
||||
fn draw<F>(&self, cache: &mut crate::SwashCache, _color: Color, mut f: F)
|
||||
where F: FnMut(i32, i32, u32, u32, Color)
|
||||
where
|
||||
F: FnMut(i32, i32, u32, u32, Color),
|
||||
{
|
||||
let size = self.buffer().size();
|
||||
f(0, 0, size.0 as u32, size.1 as u32, self.background_color());
|
||||
|
|
|
|||
100
src/edit/vi.rs
100
src/edit/vi.rs
|
|
@ -2,15 +2,7 @@ use alloc::string::String;
|
|||
use core::cmp;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::{
|
||||
Action,
|
||||
AttrsList,
|
||||
Buffer,
|
||||
Color,
|
||||
Cursor,
|
||||
Edit,
|
||||
SyntaxEditor,
|
||||
};
|
||||
use crate::{Action, AttrsList, Buffer, Color, Cursor, Edit, SyntaxEditor};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum Mode {
|
||||
|
|
@ -36,7 +28,11 @@ impl<'a> ViEditor<'a> {
|
|||
|
||||
/// Load text from a file, and also set syntax to the best option
|
||||
#[cfg(feature = "std")]
|
||||
pub fn load_text<P: AsRef<std::path::Path>>(&mut self, path: P, attrs: crate::Attrs<'a>) -> std::io::Result<()> {
|
||||
pub fn load_text<P: AsRef<std::path::Path>>(
|
||||
&mut self,
|
||||
path: P,
|
||||
attrs: crate::Attrs<'a>,
|
||||
) -> std::io::Result<()> {
|
||||
self.editor.load_text(path, attrs)
|
||||
}
|
||||
|
||||
|
|
@ -98,12 +94,12 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
|||
'a' => {
|
||||
self.editor.action(Action::Right);
|
||||
self.mode = Mode::Insert;
|
||||
},
|
||||
}
|
||||
// Enter insert mode at end of line
|
||||
'A' => {
|
||||
self.editor.action(Action::End);
|
||||
self.mode = Mode::Insert;
|
||||
},
|
||||
}
|
||||
// Change mode
|
||||
'c' => {
|
||||
if self.editor.select_opt().is_some() {
|
||||
|
|
@ -112,7 +108,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
|||
} else {
|
||||
//TODO: change to next cursor movement
|
||||
}
|
||||
},
|
||||
}
|
||||
// Delete mode
|
||||
'd' => {
|
||||
if self.editor.select_opt().is_some() {
|
||||
|
|
@ -120,11 +116,11 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
|||
} else {
|
||||
//TODO: delete to next cursor movement
|
||||
}
|
||||
},
|
||||
}
|
||||
// Enter insert mode at cursor
|
||||
'i' => {
|
||||
self.mode = Mode::Insert;
|
||||
},
|
||||
}
|
||||
// Enter insert mode at start of line
|
||||
'I' => {
|
||||
//TODO: soft home, skip whitespace
|
||||
|
|
@ -136,7 +132,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
|||
self.editor.action(Action::End);
|
||||
self.editor.action(Action::Enter);
|
||||
self.mode = Mode::Insert;
|
||||
},
|
||||
}
|
||||
// Create line before and enter insert mode
|
||||
'O' => {
|
||||
self.editor.action(Action::Home);
|
||||
|
|
@ -144,7 +140,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
|||
self.editor.shape_as_needed(); // TODO: do not require this?
|
||||
self.editor.action(Action::Up);
|
||||
self.mode = Mode::Insert;
|
||||
},
|
||||
}
|
||||
// Left
|
||||
'h' => self.editor.action(Action::Left),
|
||||
// Top of screen
|
||||
|
|
@ -166,7 +162,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
|||
} else {
|
||||
self.editor.set_select_opt(Some(self.editor.cursor()));
|
||||
}
|
||||
},
|
||||
}
|
||||
// Enter line visual mode
|
||||
'V' => {
|
||||
if self.editor.select_opt().is_some() {
|
||||
|
|
@ -177,7 +173,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
|||
//TODO: set cursor_x_opt to max
|
||||
self.editor.action(Action::End);
|
||||
}
|
||||
},
|
||||
}
|
||||
// Remove character at cursor
|
||||
'x' => self.editor.action(Action::Delete),
|
||||
// Remove character before cursor
|
||||
|
|
@ -192,15 +188,15 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
|||
// Enter command mode
|
||||
':' => {
|
||||
self.mode = Mode::Command;
|
||||
},
|
||||
}
|
||||
// Enter search mode
|
||||
'/' => {
|
||||
self.mode = Mode::Search;
|
||||
},
|
||||
}
|
||||
// Enter search backwards mode
|
||||
'?' => {
|
||||
self.mode = Mode::SearchBackwards;
|
||||
},
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
_ => self.editor.action(action),
|
||||
|
|
@ -213,13 +209,13 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
|||
self.editor.action(Action::Left);
|
||||
}
|
||||
self.mode = Mode::Normal;
|
||||
},
|
||||
}
|
||||
_ => self.editor.action(action),
|
||||
},
|
||||
_ => {
|
||||
//TODO: other modes
|
||||
self.mode = Mode::Normal;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if self.mode != old_mode {
|
||||
|
|
@ -229,7 +225,8 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
|||
|
||||
#[cfg(feature = "swash")]
|
||||
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F)
|
||||
where F: FnMut(i32, i32, u32, u32, Color)
|
||||
where
|
||||
F: FnMut(i32, i32, u32, u32, Color),
|
||||
{
|
||||
let font_size = self.buffer().metrics().font_size;
|
||||
let line_height = self.buffer().metrics().line_height;
|
||||
|
|
@ -266,7 +263,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
|||
if cursor.index == glyph.end {
|
||||
return Some((run.glyphs.len(), 0.0, default_width));
|
||||
}
|
||||
},
|
||||
}
|
||||
None => {
|
||||
return Some((0, 0.0, default_width));
|
||||
}
|
||||
|
|
@ -303,16 +300,14 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
|||
let c_start = glyph.start + i;
|
||||
let c_end = glyph.start + i + c.len();
|
||||
if (start.line != line_i || c_end > start.index)
|
||||
&& (end.line != line_i || c_start < end.index) {
|
||||
&& (end.line != line_i || c_start < end.index)
|
||||
{
|
||||
range_opt = match range_opt.take() {
|
||||
Some((min, max)) => Some((
|
||||
cmp::min(min, c_x as i32),
|
||||
cmp::max(max, (c_x + c_w) as i32),
|
||||
)),
|
||||
None => Some((
|
||||
c_x as i32,
|
||||
(c_x + c_w) as i32,
|
||||
))
|
||||
None => Some((c_x as i32, (c_x + c_w) as i32)),
|
||||
};
|
||||
} else if let Some((min, max)) = range_opt.take() {
|
||||
f(
|
||||
|
|
@ -320,14 +315,14 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
|||
line_y - font_size,
|
||||
cmp::max(0, max - min) as u32,
|
||||
line_height as u32,
|
||||
Color::rgba(color.r(), color.g(), color.b(), 0x33)
|
||||
Color::rgba(color.r(), color.g(), color.b(), 0x33),
|
||||
);
|
||||
}
|
||||
c_x += c_w;
|
||||
}
|
||||
}
|
||||
|
||||
if run.glyphs.is_empty() && end.line > line_i{
|
||||
if run.glyphs.is_empty() && end.line > line_i {
|
||||
// Highlight all of internal empty lines
|
||||
range_opt = Some((0, self.buffer().size().0));
|
||||
}
|
||||
|
|
@ -346,18 +341,20 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
|||
line_y - font_size,
|
||||
cmp::max(0, max - min) as u32,
|
||||
line_height as u32,
|
||||
Color::rgba(color.r(), color.g(), color.b(), 0x33)
|
||||
Color::rgba(color.r(), color.g(), color.b(), 0x33),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw cursor
|
||||
if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) = cursor_glyph_opt(&self.cursor()) {
|
||||
if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) =
|
||||
cursor_glyph_opt(&self.cursor())
|
||||
{
|
||||
let block_cursor = match self.mode {
|
||||
Mode::Normal => true,
|
||||
Mode::Insert => false,
|
||||
_ => true /*TODO: determine block cursor in other modes*/
|
||||
_ => true, /*TODO: determine block cursor in other modes*/
|
||||
};
|
||||
|
||||
let (start_x, end_x) = match run.glyphs.get(cursor_glyph) {
|
||||
|
|
@ -366,38 +363,33 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
|||
if glyph.level.is_rtl() {
|
||||
(
|
||||
(glyph.x + glyph.w - cursor_glyph_offset) as i32,
|
||||
(glyph.x + glyph.w - cursor_glyph_offset - cursor_glyph_width) as i32,
|
||||
(glyph.x + glyph.w - cursor_glyph_offset - cursor_glyph_width)
|
||||
as i32,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
(glyph.x + cursor_glyph_offset) as i32,
|
||||
(glyph.x + cursor_glyph_offset + cursor_glyph_width) as i32
|
||||
(glyph.x + cursor_glyph_offset + cursor_glyph_width) as i32,
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
None => match run.glyphs.last() {
|
||||
Some(glyph) => {
|
||||
// End of last glyph
|
||||
if glyph.level.is_rtl() {
|
||||
(
|
||||
glyph.x as i32,
|
||||
(glyph.x - cursor_glyph_width) as i32
|
||||
)
|
||||
(glyph.x as i32, (glyph.x - cursor_glyph_width) as i32)
|
||||
} else {
|
||||
(
|
||||
(glyph.x + glyph.w) as i32,
|
||||
(glyph.x + glyph.w + cursor_glyph_width) as i32
|
||||
(glyph.x + glyph.w + cursor_glyph_width) as i32,
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
None => {
|
||||
// Start of empty line
|
||||
(
|
||||
0,
|
||||
cursor_glyph_width as i32
|
||||
)
|
||||
(0, cursor_glyph_width as i32)
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if block_cursor {
|
||||
|
|
@ -411,13 +403,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
|
|||
Color::rgba(color.r(), color.g(), color.b(), 0x33),
|
||||
);
|
||||
} else {
|
||||
f(
|
||||
start_x,
|
||||
line_y - font_size,
|
||||
1,
|
||||
line_height as u32,
|
||||
color,
|
||||
);
|
||||
f(start_x, line_y - font_size, 1, line_height as u32, color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ pub fn common_fallback() -> &'static [&'static str] {
|
|||
|
||||
// Fallbacks to never use
|
||||
pub fn forbidden_fallback() -> &'static [&'static str] {
|
||||
&[
|
||||
".LastResort",
|
||||
]
|
||||
&[".LastResort"]
|
||||
}
|
||||
|
||||
fn han_unification(locale: &str) -> &'static [&'static str] {
|
||||
|
|
|
|||
|
|
@ -9,11 +9,7 @@ use crate::Font;
|
|||
|
||||
use self::platform::*;
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "linux",
|
||||
target_os = "macos",
|
||||
target_os = "windows",
|
||||
)))]
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows",)))]
|
||||
#[path = "other.rs"]
|
||||
mod platform;
|
||||
|
||||
|
|
@ -46,7 +42,7 @@ impl<'a> FontFallbackIter<'a> {
|
|||
fonts: &'a [Arc<Font<'a>>],
|
||||
default_families: &'a [&'a str],
|
||||
scripts: Vec<Script>,
|
||||
locale: &'a str
|
||||
locale: &'a str,
|
||||
) -> Self {
|
||||
Self {
|
||||
fonts,
|
||||
|
|
@ -78,7 +74,7 @@ impl<'a> FontFallbackIter<'a> {
|
|||
font.info.family,
|
||||
word
|
||||
);
|
||||
} else if ! self.scripts.is_empty() && self.common_i > 0 {
|
||||
} else if !self.scripts.is_empty() && self.common_i > 0 {
|
||||
let family = common_fallback()[self.common_i - 1];
|
||||
log::debug!(
|
||||
"Failed to find script fallback for {:?} locale '{}', used '{}': '{}'",
|
||||
|
|
@ -117,7 +113,12 @@ impl<'a> Iterator for FontFallbackIter<'a> {
|
|||
return Some(font);
|
||||
}
|
||||
}
|
||||
log::warn!("failed to find family '{}' for script {:?} and locale '{}'", script_family, script, self.locale);
|
||||
log::warn!(
|
||||
"failed to find family '{}' for script {:?} and locale '{}'",
|
||||
script_family,
|
||||
script,
|
||||
self.locale
|
||||
);
|
||||
}
|
||||
|
||||
self.script_i.0 += 1;
|
||||
|
|
@ -142,7 +143,7 @@ impl<'a> Iterator for FontFallbackIter<'a> {
|
|||
while self.other_i < self.fonts.len() {
|
||||
let font = &self.fonts[self.other_i];
|
||||
self.other_i += 1;
|
||||
if ! forbidden_families.contains(&font.info.family.as_str()) {
|
||||
if !forbidden_families.contains(&font.info.family.as_str()) {
|
||||
return Some(font);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ fn han_unification(locale: &str) -> &'static [&'static str] {
|
|||
// Taiwan
|
||||
"zh-TW" => &["Microsoft JhengHei UI"],
|
||||
// Simplified Chinese is the default (also catches "zh-CN" for China)
|
||||
_ => &["Microsoft YaHei UI"]
|
||||
_ => &["Microsoft YaHei UI"],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@
|
|||
|
||||
use alloc::sync::Arc;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{
|
||||
string::String,
|
||||
vec::Vec,
|
||||
};
|
||||
use alloc::{string::String, vec::Vec};
|
||||
|
||||
use crate::Font;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use alloc::{
|
|||
use crate::{Attrs, Font, FontMatches};
|
||||
|
||||
/// Access system fonts
|
||||
pub struct FontSystem{
|
||||
pub struct FontSystem {
|
||||
locale: String,
|
||||
db: fontdb::Database,
|
||||
}
|
||||
|
|
@ -25,16 +25,13 @@ impl FontSystem {
|
|||
db.set_serif_family("DejaVu Serif");
|
||||
}
|
||||
|
||||
Self {
|
||||
locale,
|
||||
db,
|
||||
}
|
||||
Self { locale, db }
|
||||
}
|
||||
|
||||
pub fn new_with_locale_and_db(locale: &str, db: fontdb::Database) -> Self {
|
||||
Self {
|
||||
locale: locale.to_string(),
|
||||
db
|
||||
db,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +71,7 @@ impl FontSystem {
|
|||
Arc::new(FontMatches {
|
||||
locale: &self.locale,
|
||||
default_family: self.db.family_name(&attrs.family).to_string(),
|
||||
fonts
|
||||
fonts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use alloc::{
|
|||
use crate::{Attrs, Font, FontMatches};
|
||||
|
||||
/// Access system fonts
|
||||
pub struct FontSystem{
|
||||
pub struct FontSystem {
|
||||
locale: String,
|
||||
db: fontdb::Database,
|
||||
}
|
||||
|
|
@ -46,7 +46,9 @@ impl FontSystem {
|
|||
//TODO only do this on demand!
|
||||
for i in 0..db.faces().len() {
|
||||
let id = db.faces()[i].id;
|
||||
unsafe { db.make_shared_face_data(id); }
|
||||
unsafe {
|
||||
db.make_shared_face_data(id);
|
||||
}
|
||||
}
|
||||
|
||||
log::info!(
|
||||
|
|
@ -56,10 +58,7 @@ impl FontSystem {
|
|||
);
|
||||
}
|
||||
|
||||
Self {
|
||||
locale,
|
||||
db,
|
||||
}
|
||||
Self { locale, db }
|
||||
}
|
||||
|
||||
pub fn locale(&self) -> &str {
|
||||
|
|
@ -98,7 +97,7 @@ impl FontSystem {
|
|||
Arc::new(FontMatches {
|
||||
locale: &self.locale,
|
||||
default_family: self.db.family_name(&attrs.family).to_string(),
|
||||
fonts
|
||||
fonts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,9 @@ impl FontSystem {
|
|||
//TODO only do this on demand!
|
||||
for i in 0..db.faces().len() {
|
||||
let id = db.faces()[i].id;
|
||||
unsafe { db.make_shared_face_data(id); }
|
||||
unsafe {
|
||||
db.make_shared_face_data(id);
|
||||
}
|
||||
}
|
||||
|
||||
log::info!(
|
||||
|
|
@ -69,12 +71,15 @@ impl FontSystem {
|
|||
);
|
||||
}
|
||||
|
||||
Self(FontSystemInnerBuilder {
|
||||
locale,
|
||||
db,
|
||||
font_cache_builder: |_| Mutex::new(HashMap::new()),
|
||||
font_matches_cache_builder: |_, _| Mutex::new(HashMap::new())
|
||||
}.build())
|
||||
Self(
|
||||
FontSystemInnerBuilder {
|
||||
locale,
|
||||
db,
|
||||
font_cache_builder: |_| Mutex::new(HashMap::new()),
|
||||
font_matches_cache_builder: |_, _| Mutex::new(HashMap::new()),
|
||||
}
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn locale(&self) -> &str {
|
||||
|
|
@ -93,53 +98,66 @@ impl FontSystem {
|
|||
// Clippy false positive
|
||||
#[allow(clippy::needless_lifetimes)]
|
||||
pub fn get_font<'a>(&'a self, id: fontdb::ID) -> Option<Arc<Font<'a>>> {
|
||||
self.0.with(|fields| {
|
||||
get_font(&fields, id)
|
||||
})
|
||||
self.0.with(|fields| get_font(&fields, id))
|
||||
}
|
||||
|
||||
pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> {
|
||||
self.0.with(|fields| {
|
||||
let mut font_matches_cache = fields.font_matches_cache.lock().expect("failed to lock font matches cache");
|
||||
let mut font_matches_cache = fields
|
||||
.font_matches_cache
|
||||
.lock()
|
||||
.expect("failed to lock font matches cache");
|
||||
//TODO: do not create AttrsOwned unless entry does not already exist
|
||||
font_matches_cache.entry(AttrsOwned::new(attrs)).or_insert_with(|| {
|
||||
let now = std::time::Instant::now();
|
||||
font_matches_cache
|
||||
.entry(AttrsOwned::new(attrs))
|
||||
.or_insert_with(|| {
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
let mut fonts = Vec::new();
|
||||
for face in fields.db.faces() {
|
||||
if !attrs.matches(face) {
|
||||
continue;
|
||||
let mut fonts = Vec::new();
|
||||
for face in fields.db.faces() {
|
||||
if !attrs.matches(face) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(font) = get_font(&fields, face.id) {
|
||||
fonts.push(font);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(font) = get_font(&fields, face.id) {
|
||||
fonts.push(font);
|
||||
}
|
||||
}
|
||||
let font_matches = Arc::new(FontMatches {
|
||||
locale: fields.locale,
|
||||
default_family: fields.db.family_name(&attrs.family).to_string(),
|
||||
fonts,
|
||||
});
|
||||
|
||||
let font_matches = Arc::new(FontMatches {
|
||||
locale: fields.locale,
|
||||
default_family: fields.db.family_name(&attrs.family).to_string(),
|
||||
fonts
|
||||
});
|
||||
let elapsed = now.elapsed();
|
||||
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
|
||||
|
||||
let elapsed = now.elapsed();
|
||||
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
|
||||
|
||||
font_matches
|
||||
}).clone()
|
||||
font_matches
|
||||
})
|
||||
.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_font<'b>(fields: &ouroboros_impl_font_system_inner::BorrowedFields<'_, 'b>, id: fontdb::ID) -> Option<Arc<Font<'b>>> {
|
||||
fields.font_cache.lock().expect("failed to lock font cache").entry(id).or_insert_with(|| {
|
||||
let face = fields.db.face(id)?;
|
||||
match Font::new(face) {
|
||||
Some(font) => Some(Arc::new(font)),
|
||||
None => {
|
||||
log::warn!("failed to load font '{}'", face.post_script_name);
|
||||
None
|
||||
fn get_font<'b>(
|
||||
fields: &ouroboros_impl_font_system_inner::BorrowedFields<'_, 'b>,
|
||||
id: fontdb::ID,
|
||||
) -> Option<Arc<Font<'b>>> {
|
||||
fields
|
||||
.font_cache
|
||||
.lock()
|
||||
.expect("failed to lock font cache")
|
||||
.entry(id)
|
||||
.or_insert_with(|| {
|
||||
let face = fields.db.face(id)?;
|
||||
match Font::new(face) {
|
||||
Some(font) => Some(Arc::new(font)),
|
||||
None => {
|
||||
log::warn!("failed to load font '{}'", face.post_script_name);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}).clone()
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,14 +56,12 @@
|
|||
|
||||
// Not interested in these lints
|
||||
#![allow(clippy::new_without_default)]
|
||||
|
||||
// TODO: address ocurrances and then deny
|
||||
//
|
||||
// Indexing a slice can cause panics and that is something we always want to avoid
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
// Overflows can produce unpredictable results and are only checked in debug builds
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
|
||||
// Soundness issues
|
||||
//
|
||||
// Dereferencing unalinged pointers may be undefined behavior
|
||||
|
|
@ -75,7 +73,6 @@
|
|||
#![deny(unreachable_patterns)]
|
||||
// Ensure that all must_use results are used
|
||||
#![deny(unused_must_use)]
|
||||
|
||||
// Style issues
|
||||
//
|
||||
// Documentation not ideal
|
||||
|
|
@ -88,7 +85,6 @@
|
|||
#![warn(clippy::semicolon_if_nothing_returned)]
|
||||
// Ensure numbers are readable
|
||||
#![warn(clippy::unreadable_literal)]
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
|
|
|
|||
218
src/shape.rs
218
src/shape.rs
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::vec::Vec;
|
||||
use core::cmp::{min, max};
|
||||
use core::cmp::{max, min};
|
||||
use core::mem;
|
||||
use core::ops::Range;
|
||||
use unicode_script::{Script, UnicodeScript};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::{AttrsList, CacheKey, Color, Font, FontSystem, LayoutGlyph, LayoutLine, Wrap};
|
||||
use crate::fallback::FontFallbackIter;
|
||||
use crate::{AttrsList, CacheKey, Color, Font, FontSystem, LayoutGlyph, LayoutLine, Wrap};
|
||||
|
||||
fn shape_fallback(
|
||||
font: &Font,
|
||||
|
|
@ -110,21 +110,16 @@ fn shape_run<'a>(
|
|||
let mut scripts = Vec::new();
|
||||
for c in line[start_run..end_run].chars() {
|
||||
match c.script() {
|
||||
Script::Common |
|
||||
Script::Inherited |
|
||||
Script::Latin |
|
||||
Script::Unknown => (),
|
||||
script => if ! scripts.contains(&script) {
|
||||
scripts.push(script);
|
||||
},
|
||||
Script::Common | Script::Inherited | Script::Latin | Script::Unknown => (),
|
||||
script => {
|
||||
if !scripts.contains(&script) {
|
||||
scripts.push(script);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::trace!(
|
||||
" Run {:?}: '{}'",
|
||||
scripts,
|
||||
&line[start_run..end_run],
|
||||
);
|
||||
log::trace!(" Run {:?}: '{}'", scripts, &line[start_run..end_run],);
|
||||
|
||||
let attrs = attrs_list.get_span(start_run);
|
||||
|
||||
|
|
@ -135,7 +130,7 @@ fn shape_run<'a>(
|
|||
&font_matches.fonts,
|
||||
&default_families,
|
||||
scripts,
|
||||
font_matches.locale
|
||||
font_matches.locale,
|
||||
);
|
||||
|
||||
let (mut glyphs, mut missing) = shape_fallback(
|
||||
|
|
@ -155,14 +150,8 @@ fn shape_run<'a>(
|
|||
};
|
||||
|
||||
log::trace!("Evaluating fallback with font '{}'", font.info.family);
|
||||
let (mut fb_glyphs, fb_missing) = shape_fallback(
|
||||
font,
|
||||
line,
|
||||
attrs_list,
|
||||
start_run,
|
||||
end_run,
|
||||
span_rtl,
|
||||
);
|
||||
let (mut fb_glyphs, fb_missing) =
|
||||
shape_fallback(font, line, attrs_list, start_run, end_run, span_rtl);
|
||||
|
||||
// Insert all matching glyphs
|
||||
let mut fb_i = 0;
|
||||
|
|
@ -255,7 +244,7 @@ impl ShapeGlyph {
|
|||
self.font_id,
|
||||
self.glyph_id,
|
||||
font_size,
|
||||
(x + x_offset, y - y_offset)
|
||||
(x + x_offset, y - y_offset),
|
||||
);
|
||||
LayoutGlyph {
|
||||
start: self.start,
|
||||
|
|
@ -307,7 +296,7 @@ impl ShapeWord {
|
|||
for (egc_i, _egc) in word.grapheme_indices(true) {
|
||||
let start_egc = word_range.start + egc_i;
|
||||
let attrs_egc = attrs_list.get_span(start_egc);
|
||||
if ! attrs.compatible(&attrs_egc) {
|
||||
if !attrs.compatible(&attrs_egc) {
|
||||
//TODO: more efficient
|
||||
glyphs.append(&mut shape_run(
|
||||
font_system,
|
||||
|
|
@ -315,7 +304,7 @@ impl ShapeWord {
|
|||
attrs_list,
|
||||
start_run,
|
||||
start_egc,
|
||||
span_rtl
|
||||
span_rtl,
|
||||
));
|
||||
|
||||
start_run = start_egc;
|
||||
|
|
@ -330,7 +319,7 @@ impl ShapeWord {
|
|||
attrs_list,
|
||||
start_run,
|
||||
word_range.end,
|
||||
span_rtl
|
||||
span_rtl,
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -341,7 +330,12 @@ impl ShapeWord {
|
|||
y_advance += glyph.y_advance;
|
||||
}
|
||||
|
||||
Self { blank, glyphs, x_advance, y_advance}
|
||||
Self {
|
||||
blank,
|
||||
glyphs,
|
||||
x_advance,
|
||||
y_advance,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -398,7 +392,8 @@ impl ShapeSpan {
|
|||
font_system,
|
||||
line,
|
||||
attrs_list,
|
||||
(span_range.start + start_lb + i)..(span_range.start + start_lb + i + c.len_utf8()),
|
||||
(span_range.start + start_lb + i)
|
||||
..(span_range.start + start_lb + i + c.len_utf8()),
|
||||
level,
|
||||
true,
|
||||
));
|
||||
|
|
@ -419,10 +414,7 @@ impl ShapeSpan {
|
|||
words.reverse();
|
||||
}
|
||||
|
||||
ShapeSpan {
|
||||
level,
|
||||
words,
|
||||
}
|
||||
ShapeSpan { level, words }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -436,11 +428,7 @@ pub struct ShapeLine {
|
|||
type VlRange = (usize, (usize, usize), (usize, usize));
|
||||
|
||||
impl ShapeLine {
|
||||
pub fn new<'a>(
|
||||
font_system: &'a FontSystem,
|
||||
line: &str,
|
||||
attrs_list: &AttrsList
|
||||
) -> Self {
|
||||
pub fn new<'a>(font_system: &'a FontSystem, line: &str, attrs_list: &AttrsList) -> Self {
|
||||
let mut spans = Vec::new();
|
||||
|
||||
let bidi = unicode_bidi::BidiInfo::new(line, None);
|
||||
|
|
@ -456,12 +444,17 @@ impl ShapeLine {
|
|||
let line_range = para_info.range.clone();
|
||||
let levels = Self::adjust_levels(&unicode_bidi::Paragraph::new(&bidi, para_info));
|
||||
|
||||
// Find consecutive level runs. We use this to create Spans.
|
||||
// Find consecutive level runs. We use this to create Spans.
|
||||
// Each span is a set of characters with equal levels.
|
||||
let mut start = line_range.start;
|
||||
let mut run_level = levels[start];
|
||||
|
||||
for (i, &new_level) in levels.iter().enumerate().take(line_range.end).skip(start + 1) {
|
||||
for (i, &new_level) in levels
|
||||
.iter()
|
||||
.enumerate()
|
||||
.take(line_range.end)
|
||||
.skip(start + 1)
|
||||
{
|
||||
if new_level != run_level {
|
||||
// End of the previous run, start of a new one.
|
||||
spans.push(ShapeSpan::new(
|
||||
|
|
@ -487,13 +480,11 @@ impl ShapeLine {
|
|||
line_rtl
|
||||
};
|
||||
|
||||
Self { rtl, spans}
|
||||
Self { rtl, spans }
|
||||
}
|
||||
|
||||
// A modified version of first part of unicode_bidi::bidi_info::visual_run
|
||||
fn adjust_levels(
|
||||
para: &unicode_bidi::Paragraph,
|
||||
) -> Vec<unicode_bidi::Level> {
|
||||
fn adjust_levels(para: &unicode_bidi::Paragraph) -> Vec<unicode_bidi::Level> {
|
||||
use unicode_bidi::BidiClass::*;
|
||||
let text = para.info.text;
|
||||
let levels = ¶.info.levels;
|
||||
|
|
@ -547,7 +538,10 @@ impl ShapeLine {
|
|||
|
||||
// A modified version of second part of unicode_bidi::bidi_info::visual run
|
||||
fn reorder(&self, line_range: &[VlRange]) -> Vec<Range<usize>> {
|
||||
let line : Vec<unicode_bidi::Level> = line_range.iter().map(|(span_index, _, _)| self.spans[*span_index].level).collect();
|
||||
let line: Vec<unicode_bidi::Level> = line_range
|
||||
.iter()
|
||||
.map(|(span_index, _, _)| self.spans[*span_index].level)
|
||||
.collect();
|
||||
// Find consecutive level runs.
|
||||
let mut runs = Vec::new();
|
||||
let mut start = 0;
|
||||
|
|
@ -605,14 +599,8 @@ impl ShapeLine {
|
|||
|
||||
runs
|
||||
}
|
||||
|
||||
|
||||
pub fn layout(
|
||||
&self,
|
||||
font_size: i32,
|
||||
line_width: i32,
|
||||
wrap: Wrap,
|
||||
) -> Vec<LayoutLine> {
|
||||
pub fn layout(&self, font_size: i32, line_width: i32, wrap: Wrap) -> Vec<LayoutLine> {
|
||||
let mut layout_lines = Vec::with_capacity(1);
|
||||
|
||||
// This is used to create a visual line for empty lines (e.g. lines with only a <CR>)
|
||||
|
|
@ -627,8 +615,6 @@ impl ShapeLine {
|
|||
let mut x = start_x;
|
||||
let mut y;
|
||||
|
||||
|
||||
|
||||
// This would keep the maximum number of spans that would fit on a visual line
|
||||
// If one span is too large, this variable will hold the range of words inside that span
|
||||
// that fits on a line.
|
||||
|
|
@ -636,22 +622,22 @@ impl ShapeLine {
|
|||
|
||||
if wrap == Wrap::None {
|
||||
for (span_index, span) in self.spans.iter().enumerate() {
|
||||
current_visual_line.push((span_index, (0,0), (span.words.len(), 0)));
|
||||
current_visual_line.push((span_index, (0, 0), (span.words.len(), 0)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
let mut fit_x = line_width as f32;
|
||||
for (span_index, span) in self.spans.iter().enumerate() {
|
||||
|
||||
let mut word_ranges = Vec::new();
|
||||
let mut word_range_width = 0.;
|
||||
|
||||
// Create the word ranges that fits in a visual line
|
||||
if self.rtl != span.level.is_rtl() { // incongruent directions
|
||||
if self.rtl != span.level.is_rtl() {
|
||||
// incongruent directions
|
||||
let mut fitting_start = (span.words.len(), 0);
|
||||
for (i, word) in span.words.iter().enumerate().rev() {
|
||||
let word_size = font_size as f32 * word.x_advance;
|
||||
if fit_x - word_size >= 0. { // fits
|
||||
if fit_x - word_size >= 0. {
|
||||
// fits
|
||||
fit_x -= word_size;
|
||||
word_range_width += word_size;
|
||||
continue;
|
||||
|
|
@ -663,33 +649,39 @@ impl ShapeLine {
|
|||
word_range_width += glyph_size;
|
||||
continue;
|
||||
} else {
|
||||
word_ranges.push(((i, glyph_i+1), fitting_start, word_range_width));
|
||||
word_ranges.push((
|
||||
(i, glyph_i + 1),
|
||||
fitting_start,
|
||||
word_range_width,
|
||||
));
|
||||
fit_x = line_width as f32 - glyph_size;
|
||||
word_range_width = glyph_size;
|
||||
fitting_start = (i, glyph_i+1);
|
||||
fitting_start = (i, glyph_i + 1);
|
||||
}
|
||||
}
|
||||
} else { // Wrap::Word
|
||||
word_ranges.push(((i+1, 0), fitting_start, word_range_width));
|
||||
} else {
|
||||
// Wrap::Word
|
||||
word_ranges.push(((i + 1, 0), fitting_start, word_range_width));
|
||||
|
||||
if word.blank {
|
||||
fit_x = line_width as f32;
|
||||
word_range_width = 0.;
|
||||
fitting_start = (i+1, 0);
|
||||
fitting_start = (i + 1, 0);
|
||||
} else {
|
||||
fit_x = line_width as f32 - word_size;
|
||||
word_range_width = word_size;
|
||||
fitting_start = (i+1, 0);
|
||||
fitting_start = (i + 1, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
word_ranges.push(((0,0),fitting_start, word_range_width));
|
||||
|
||||
} else { // congruent direction
|
||||
let mut fitting_start = (0,0);
|
||||
word_ranges.push(((0, 0), fitting_start, word_range_width));
|
||||
} else {
|
||||
// congruent direction
|
||||
let mut fitting_start = (0, 0);
|
||||
for (i, word) in span.words.iter().enumerate() {
|
||||
let word_size = font_size as f32 * word.x_advance;
|
||||
if fit_x - word_size >= 0. { // fits
|
||||
if fit_x - word_size >= 0. {
|
||||
// fits
|
||||
fit_x -= word_size;
|
||||
word_range_width += word_size;
|
||||
continue;
|
||||
|
|
@ -701,19 +693,24 @@ impl ShapeLine {
|
|||
word_range_width += glyph_size;
|
||||
continue;
|
||||
} else {
|
||||
word_ranges.push((fitting_start, (i, glyph_i), word_range_width));
|
||||
word_ranges.push((
|
||||
fitting_start,
|
||||
(i, glyph_i),
|
||||
word_range_width,
|
||||
));
|
||||
fit_x = line_width as f32 - glyph_size;
|
||||
word_range_width = glyph_size;
|
||||
fitting_start = (i, glyph_i);
|
||||
}
|
||||
}
|
||||
} else { // Wrap::Word
|
||||
word_ranges.push((fitting_start,(i,0), word_range_width));
|
||||
} else {
|
||||
// Wrap::Word
|
||||
word_ranges.push((fitting_start, (i, 0), word_range_width));
|
||||
|
||||
if word.blank {
|
||||
fit_x = line_width as f32;
|
||||
word_range_width = 0.;
|
||||
fitting_start = (i+1, 0);
|
||||
fitting_start = (i + 1, 0);
|
||||
} else {
|
||||
fit_x = line_width as f32 - word_size;
|
||||
word_range_width = word_size;
|
||||
|
|
@ -725,12 +722,16 @@ impl ShapeLine {
|
|||
}
|
||||
|
||||
// Create a visual line
|
||||
for ((starting_word, starting_glyph), (ending_word, ending_glyph), word_range_width) in word_ranges {
|
||||
for (
|
||||
(starting_word, starting_glyph),
|
||||
(ending_word, ending_glyph),
|
||||
word_range_width,
|
||||
) in word_ranges
|
||||
{
|
||||
// To simplify the algorithm above, we might push empty ranges but we ignore them here
|
||||
if ending_word == starting_word && starting_glyph == ending_glyph {
|
||||
if ending_word == starting_word && starting_glyph == ending_glyph {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
let fits = !if self.rtl {
|
||||
x - word_range_width < end_x
|
||||
|
|
@ -738,27 +739,35 @@ impl ShapeLine {
|
|||
x + word_range_width > end_x
|
||||
};
|
||||
|
||||
|
||||
if fits {
|
||||
current_visual_line.push((span_index, (starting_word, starting_glyph), (ending_word, ending_glyph)));
|
||||
current_visual_line.push((
|
||||
span_index,
|
||||
(starting_word, starting_glyph),
|
||||
(ending_word, ending_glyph),
|
||||
));
|
||||
if self.rtl {
|
||||
x -= word_range_width;
|
||||
} else {
|
||||
x += word_range_width;
|
||||
}
|
||||
} else {
|
||||
if !current_visual_line.is_empty(){
|
||||
if !current_visual_line.is_empty() {
|
||||
vl_range_of_spans.push(current_visual_line);
|
||||
current_visual_line = Vec::with_capacity(1);
|
||||
x = start_x;
|
||||
}
|
||||
current_visual_line.push((span_index, (starting_word, starting_glyph), (ending_word, ending_glyph)));
|
||||
current_visual_line.push((
|
||||
span_index,
|
||||
(starting_word, starting_glyph),
|
||||
(ending_word, ending_glyph),
|
||||
));
|
||||
if self.rtl {
|
||||
x -= word_range_width;
|
||||
} else {
|
||||
x += word_range_width;
|
||||
}
|
||||
if word_range_width > line_width as f32 { // single word is bigger than line_width
|
||||
if word_range_width > line_width as f32 {
|
||||
// single word is bigger than line_width
|
||||
vl_range_of_spans.push(current_visual_line);
|
||||
current_visual_line = Vec::with_capacity(1);
|
||||
x = start_x;
|
||||
|
|
@ -780,10 +789,18 @@ impl ShapeLine {
|
|||
y = 0.;
|
||||
if self.rtl {
|
||||
for range in new_order.iter().rev() {
|
||||
for (span_index, (starting_word, starting_glyph), (ending_word, ending_glyph)) in visual_line[range.clone()].iter() {
|
||||
for (
|
||||
span_index,
|
||||
(starting_word, starting_glyph),
|
||||
(ending_word, ending_glyph),
|
||||
) in visual_line[range.clone()].iter()
|
||||
{
|
||||
let span = &self.spans[*span_index];
|
||||
if starting_word == ending_word {
|
||||
for glyph in span.words[*starting_word].glyphs[*starting_glyph..*ending_glyph].iter() {
|
||||
for glyph in span.words[*starting_word].glyphs
|
||||
[*starting_glyph..*ending_glyph]
|
||||
.iter()
|
||||
{
|
||||
let x_advance = font_size as f32 * glyph.x_advance;
|
||||
let y_advance = font_size as f32 * glyph.y_advance;
|
||||
if self.rtl {
|
||||
|
|
@ -796,7 +813,7 @@ impl ShapeLine {
|
|||
y += y_advance;
|
||||
}
|
||||
} else {
|
||||
for i in *starting_word..*ending_word+1 {
|
||||
for i in *starting_word..*ending_word + 1 {
|
||||
if let Some(word) = span.words.get(i) {
|
||||
let (g1, g2) = if i == *starting_word {
|
||||
(*starting_glyph, word.glyphs.len())
|
||||
|
|
@ -825,10 +842,18 @@ impl ShapeLine {
|
|||
}
|
||||
} else {
|
||||
for range in new_order {
|
||||
for (span_index, (starting_word, starting_glyph), (ending_word, ending_glyph)) in visual_line[range.clone()].iter() {
|
||||
for (
|
||||
span_index,
|
||||
(starting_word, starting_glyph),
|
||||
(ending_word, ending_glyph),
|
||||
) in visual_line[range.clone()].iter()
|
||||
{
|
||||
let span = &self.spans[*span_index];
|
||||
if starting_word == ending_word {
|
||||
for glyph in span.words[*starting_word].glyphs[*starting_glyph..*ending_glyph].iter() {
|
||||
for glyph in span.words[*starting_word].glyphs
|
||||
[*starting_glyph..*ending_glyph]
|
||||
.iter()
|
||||
{
|
||||
let x_advance = font_size as f32 * glyph.x_advance;
|
||||
let y_advance = font_size as f32 * glyph.y_advance;
|
||||
if self.rtl {
|
||||
|
|
@ -841,7 +866,7 @@ impl ShapeLine {
|
|||
y += y_advance;
|
||||
}
|
||||
} else {
|
||||
for i in *starting_word..*ending_word+1 {
|
||||
for i in *starting_word..*ending_word + 1 {
|
||||
if let Some(word) = span.words.get(i) {
|
||||
let (g1, g2) = if i == *starting_word {
|
||||
(*starting_glyph, word.glyphs.len())
|
||||
|
|
@ -871,17 +896,18 @@ impl ShapeLine {
|
|||
}
|
||||
let mut glyphs_swap = Vec::new();
|
||||
mem::swap(&mut glyphs, &mut glyphs_swap);
|
||||
layout_lines.push(
|
||||
LayoutLine {
|
||||
w: if self.rtl { start_x - x } else { x },
|
||||
glyphs: glyphs_swap,
|
||||
},
|
||||
);
|
||||
layout_lines.push(LayoutLine {
|
||||
w: if self.rtl { start_x - x } else { x },
|
||||
glyphs: glyphs_swap,
|
||||
});
|
||||
push_line = false;
|
||||
}
|
||||
|
||||
if push_line {
|
||||
layout_lines.push(LayoutLine { w: 0.0 , glyphs: Default::default() });
|
||||
layout_lines.push(LayoutLine {
|
||||
w: 0.0,
|
||||
glyphs: Default::default(),
|
||||
});
|
||||
}
|
||||
|
||||
layout_lines
|
||||
|
|
|
|||
57
src/swash.rs
57
src/swash.rs
|
|
@ -6,22 +6,26 @@ use alloc::collections::BTreeMap as Map;
|
|||
use alloc::vec::Vec;
|
||||
#[cfg(feature = "std")]
|
||||
use std::collections::HashMap as Map;
|
||||
use swash::scale::{ScaleContext, image::Content};
|
||||
use swash::scale::{image::Content, ScaleContext};
|
||||
use swash::scale::{Render, Source, StrikeWith};
|
||||
use swash::zeno::{Format, Vector};
|
||||
|
||||
use crate::{CacheKey, Color, FontSystem};
|
||||
|
||||
pub use swash::zeno::Command;
|
||||
pub use swash::scale::image::{Content as SwashContent, Image as SwashImage};
|
||||
pub use swash::zeno::Command;
|
||||
|
||||
fn swash_image(font_system: &FontSystem, context: &mut ScaleContext, cache_key: CacheKey) -> Option<SwashImage> {
|
||||
fn swash_image(
|
||||
font_system: &FontSystem,
|
||||
context: &mut ScaleContext,
|
||||
cache_key: CacheKey,
|
||||
) -> Option<SwashImage> {
|
||||
let font = match font_system.get_font(cache_key.font_id) {
|
||||
Some(some) => some,
|
||||
None => {
|
||||
log::warn!("did not find font {:?}", cache_key.font_id);
|
||||
return None;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// Build the scaler
|
||||
|
|
@ -33,9 +37,7 @@ fn swash_image(font_system: &FontSystem, context: &mut ScaleContext, cache_key:
|
|||
|
||||
// Compute the fractional offset-- you'll likely want to quantize this
|
||||
// in a real renderer
|
||||
let offset =
|
||||
Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float());
|
||||
|
||||
let offset = Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float());
|
||||
|
||||
// Select our source order
|
||||
Render::new(&[
|
||||
|
|
@ -54,7 +56,11 @@ fn swash_image(font_system: &FontSystem, context: &mut ScaleContext, cache_key:
|
|||
.render(&mut scaler, cache_key.glyph_id)
|
||||
}
|
||||
|
||||
fn swash_outline_commands(font_system: &FontSystem, context: &mut ScaleContext, cache_key: CacheKey) -> Option<Vec<swash::zeno::Command>> {
|
||||
fn swash_outline_commands(
|
||||
font_system: &FontSystem,
|
||||
context: &mut ScaleContext,
|
||||
cache_key: CacheKey,
|
||||
) -> Option<Vec<swash::zeno::Command>> {
|
||||
use swash::zeno::PathData as _;
|
||||
|
||||
let font = match font_system.get_font(cache_key.font_id) {
|
||||
|
|
@ -62,10 +68,9 @@ fn swash_outline_commands(font_system: &FontSystem, context: &mut ScaleContext,
|
|||
None => {
|
||||
log::warn!("did not find font {:?}", cache_key.font_id);
|
||||
return None;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Build the scaler
|
||||
let mut scaler = context
|
||||
.builder(font.as_swash())
|
||||
|
|
@ -73,7 +78,9 @@ fn swash_outline_commands(font_system: &FontSystem, context: &mut ScaleContext,
|
|||
.build();
|
||||
|
||||
// Scale the outline
|
||||
let outline = scaler.scale_outline(cache_key.glyph_id).or_else(|| scaler.scale_color_outline(cache_key.glyph_id))?;
|
||||
let outline = scaler
|
||||
.scale_outline(cache_key.glyph_id)
|
||||
.or_else(|| scaler.scale_color_outline(cache_key.glyph_id))?;
|
||||
|
||||
// Get the path information of the outline
|
||||
let path = outline.path();
|
||||
|
|
@ -97,7 +104,7 @@ impl<'a> SwashCache<'a> {
|
|||
font_system,
|
||||
context: ScaleContext::new(),
|
||||
image_cache: Map::new(),
|
||||
outline_command_cache: Map::new()
|
||||
outline_command_cache: Map::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,15 +115,18 @@ impl<'a> SwashCache<'a> {
|
|||
|
||||
/// Create a swash Image from a cache key, caching results
|
||||
pub fn get_image(&mut self, cache_key: CacheKey) -> &Option<SwashImage> {
|
||||
self.image_cache.entry(cache_key).or_insert_with(|| {
|
||||
swash_image(self.font_system, &mut self.context, cache_key)
|
||||
})
|
||||
self.image_cache
|
||||
.entry(cache_key)
|
||||
.or_insert_with(|| swash_image(self.font_system, &mut self.context, cache_key))
|
||||
}
|
||||
|
||||
pub fn get_outline_commands(&mut self, cache_key: CacheKey) -> Option<&[swash::zeno::Command]> {
|
||||
self.outline_command_cache.entry(cache_key).or_insert_with(|| {
|
||||
swash_outline_commands(self.font_system, &mut self.context, cache_key)
|
||||
}).as_deref()
|
||||
self.outline_command_cache
|
||||
.entry(cache_key)
|
||||
.or_insert_with(|| {
|
||||
swash_outline_commands(self.font_system, &mut self.context, cache_key)
|
||||
})
|
||||
.as_deref()
|
||||
}
|
||||
|
||||
/// Enumerate pixels in an Image, use `with_image` for better performance
|
||||
|
|
@ -124,7 +134,7 @@ impl<'a> SwashCache<'a> {
|
|||
&mut self,
|
||||
cache_key: CacheKey,
|
||||
base: Color,
|
||||
mut f: F
|
||||
mut f: F,
|
||||
) {
|
||||
if let Some(image) = self.get_image(cache_key) {
|
||||
let x = image.placement.left;
|
||||
|
|
@ -139,10 +149,7 @@ impl<'a> SwashCache<'a> {
|
|||
f(
|
||||
x + off_x,
|
||||
y + off_y,
|
||||
Color(
|
||||
((image.data[i] as u32) << 24) |
|
||||
base.0 & 0xFF_FF_FF
|
||||
)
|
||||
Color(((image.data[i] as u32) << 24) | base.0 & 0xFF_FF_FF),
|
||||
);
|
||||
i += 1;
|
||||
}
|
||||
|
|
@ -160,8 +167,8 @@ impl<'a> SwashCache<'a> {
|
|||
image.data[i],
|
||||
image.data[i + 1],
|
||||
image.data[i + 2],
|
||||
image.data[i + 3]
|
||||
)
|
||||
image.data[i + 3],
|
||||
),
|
||||
);
|
||||
i += 4;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue