Update cosmic-text and resvg (#2416)

* Update `cosmic-text`, `glyphon`, and `resvg`

* Fix slow font fallback with `Shaping::Basic` in `cosmic-text`

* Update `cosmic-text` and `resvg`

* Update `cosmic-text`

* Fix `SelectAll` action in `editor`

* Fix some panics in `graphics::text::editor`

* Remove empty `if` statement in `tiny_skia::vector`

* Update `cosmic-text`, `glyphon`, and `rustc-hash`
This commit is contained in:
Héctor Ramón 2024-07-17 13:00:00 +02:00 committed by GitHub
parent b518e30610
commit 616689ca54
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 187 additions and 222 deletions

View file

@ -138,11 +138,11 @@ async-std = "1.0"
bitflags = "2.0"
bytemuck = { version = "1.0", features = ["derive"] }
bytes = "1.6"
cosmic-text = "0.10"
cosmic-text = "0.12"
dark-light = "1.0"
futures = "0.3"
glam = "0.25"
glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "f07e7bab705e69d39a5e6e52c73039a93c4552f8" }
glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "feef9f5630c2adb3528937e55f7bfad2da561a65" }
guillotiere = "0.6"
half = "2.2"
image = "0.24"
@ -157,8 +157,8 @@ ouroboros = "0.18"
palette = "0.7"
qrcode = { version = "0.13", default-features = false }
raw-window-handle = "0.6"
resvg = "0.36"
rustc-hash = "1.0"
resvg = "0.42"
rustc-hash = "2.0"
smol = "1.0"
smol_str = "0.2"
softbuffer = "0.4"

View file

@ -43,6 +43,7 @@ impl Text {
let mut buffer = cosmic_text::BufferLine::new(
&self.content,
cosmic_text::LineEnding::default(),
cosmic_text::AttrsList::new(text::to_attributes(self.font)),
text::to_shaping(self.shaping),
);
@ -50,8 +51,10 @@ impl Text {
let layout = buffer.layout(
font_system.raw(),
self.size.0,
f32::MAX,
None,
cosmic_text::Wrap::None,
None,
4,
);
let translation_x = match self.horizontal_alignment {

View file

@ -48,8 +48,8 @@ impl Cache {
buffer.set_size(
font_system,
key.bounds.width,
key.bounds.height.max(key.line_height),
Some(key.bounds.width),
Some(key.bounds.height.max(key.line_height)),
);
buffer.set_text(
font_system,

View file

@ -17,7 +17,7 @@ use std::sync::{self, Arc};
pub struct Editor(Option<Arc<Internal>>);
struct Internal {
editor: cosmic_text::Editor,
editor: cosmic_text::Editor<'static>,
font: Font,
bounds: Size,
topmost_line_changed: Option<usize>,
@ -32,7 +32,7 @@ impl Editor {
/// Returns the buffer of the [`Editor`].
pub fn buffer(&self) -> &cosmic_text::Buffer {
self.internal().editor.buffer()
buffer_from_editor(&self.internal().editor)
}
/// Creates a [`Weak`] reference to the [`Editor`].
@ -101,16 +101,10 @@ impl editor::Editor for Editor {
let internal = self.internal();
let cursor = internal.editor.cursor();
let buffer = internal.editor.buffer();
match internal.editor.select_opt() {
Some(selection) => {
let (start, end) = if cursor < selection {
(cursor, selection)
} else {
(selection, cursor)
};
let buffer = buffer_from_editor(&internal.editor);
match internal.editor.selection_bounds() {
Some((start, end)) => {
let line_height = buffer.metrics().line_height;
let selected_lines = end.line - start.line + 1;
@ -142,7 +136,8 @@ impl editor::Editor for Editor {
width,
y: (visual_line as i32 + visual_lines_offset)
as f32
* line_height,
* line_height
- buffer.scroll().vertical,
height: line_height,
})
} else {
@ -224,7 +219,8 @@ impl editor::Editor for Editor {
Cursor::Caret(Point::new(
offset,
(visual_lines_offset + visual_line as i32) as f32
* line_height,
* line_height
- buffer.scroll().vertical,
))
}
}
@ -252,16 +248,8 @@ impl editor::Editor for Editor {
match action {
// Motion events
Action::Move(motion) => {
if let Some(selection) = editor.select_opt() {
let cursor = editor.cursor();
let (left, right) = if cursor < selection {
(cursor, selection)
} else {
(selection, cursor)
};
editor.set_select_opt(None);
if let Some((start, end)) = editor.selection_bounds() {
editor.set_selection(cosmic_text::Selection::None);
match motion {
// These motions are performed as-is even when a selection
@ -272,17 +260,20 @@ impl editor::Editor for Editor {
| Motion::DocumentEnd => {
editor.action(
font_system.raw(),
motion_to_action(motion),
cosmic_text::Action::Motion(to_motion(motion)),
);
}
// Other motions simply move the cursor to one end of the selection
_ => editor.set_cursor(match motion.direction() {
Direction::Left => left,
Direction::Right => right,
Direction::Left => start,
Direction::Right => end,
}),
}
} else {
editor.action(font_system.raw(), motion_to_action(motion));
editor.action(
font_system.raw(),
cosmic_text::Action::Motion(to_motion(motion)),
);
}
}
@ -290,103 +281,36 @@ impl editor::Editor for Editor {
Action::Select(motion) => {
let cursor = editor.cursor();
if editor.select_opt().is_none() {
editor.set_select_opt(Some(cursor));
if editor.selection_bounds().is_none() {
editor
.set_selection(cosmic_text::Selection::Normal(cursor));
}
editor.action(font_system.raw(), motion_to_action(motion));
editor.action(
font_system.raw(),
cosmic_text::Action::Motion(to_motion(motion)),
);
// Deselect if selection matches cursor position
if let Some(selection) = editor.select_opt() {
let cursor = editor.cursor();
if cursor.line == selection.line
&& cursor.index == selection.index
{
editor.set_select_opt(None);
if let Some((start, end)) = editor.selection_bounds() {
if start.line == end.line && start.index == end.index {
editor.set_selection(cosmic_text::Selection::None);
}
}
}
Action::SelectWord => {
use unicode_segmentation::UnicodeSegmentation;
let cursor = editor.cursor();
if let Some(line) = editor.buffer().lines.get(cursor.line) {
let (start, end) =
UnicodeSegmentation::unicode_word_indices(line.text())
// Split words with dots
.flat_map(|(i, word)| {
word.split('.').scan(i, |current, word| {
let start = *current;
*current += word.len() + 1;
Some((start, word))
})
})
// Turn words into ranges
.map(|(i, word)| (i, i + word.len()))
// Find the word at cursor
.find(|&(start, end)| {
start <= cursor.index && cursor.index < end
})
// Cursor is not in a word. Let's select its punctuation cluster.
.unwrap_or_else(|| {
let start = line.text()[..cursor.index]
.char_indices()
.rev()
.take_while(|(_, c)| {
c.is_ascii_punctuation()
})
.map(|(i, _)| i)
.last()
.unwrap_or(cursor.index);
let end = line.text()[cursor.index..]
.char_indices()
.skip_while(|(_, c)| {
c.is_ascii_punctuation()
})
.map(|(i, _)| i + cursor.index)
.next()
.unwrap_or(cursor.index);
(start, end)
});
if start != end {
editor.set_cursor(cosmic_text::Cursor {
index: start,
..cursor
});
editor.set_select_opt(Some(cosmic_text::Cursor {
index: end,
..cursor
}));
}
}
editor.set_selection(cosmic_text::Selection::Word(cursor));
}
Action::SelectLine => {
let cursor = editor.cursor();
if let Some(line_length) = editor
.buffer()
.lines
.get(cursor.line)
.map(|line| line.text().len())
{
editor
.set_cursor(cosmic_text::Cursor { index: 0, ..cursor });
editor.set_select_opt(Some(cosmic_text::Cursor {
index: line_length,
..cursor
}));
}
editor.set_selection(cosmic_text::Selection::Line(cursor));
}
Action::SelectAll => {
let buffer = editor.buffer();
let buffer = buffer_from_editor(editor);
if buffer.lines.len() > 1
|| buffer
.lines
@ -394,15 +318,20 @@ impl editor::Editor for Editor {
.is_some_and(|line| !line.text().is_empty())
{
let cursor = editor.cursor();
editor.set_select_opt(Some(cosmic_text::Cursor {
editor.set_selection(cosmic_text::Selection::Normal(
cosmic_text::Cursor {
line: 0,
index: 0,
..cursor
}));
},
));
editor.action(
font_system.raw(),
motion_to_action(Motion::DocumentEnd),
cosmic_text::Action::Motion(
cosmic_text::Motion::BufferEnd,
),
);
}
}
@ -440,10 +369,12 @@ impl editor::Editor for Editor {
}
let cursor = editor.cursor();
let selection = editor.select_opt().unwrap_or(cursor);
let selection_start = editor
.selection_bounds()
.map(|(start, _)| start)
.unwrap_or(cursor);
internal.topmost_line_changed =
Some(cursor.min(selection).line);
internal.topmost_line_changed = Some(selection_start.line);
}
// Mouse events
@ -466,13 +397,9 @@ impl editor::Editor for Editor {
);
// Deselect if selection matches cursor position
if let Some(selection) = editor.select_opt() {
let cursor = editor.cursor();
if cursor.line == selection.line
&& cursor.index == selection.index
{
editor.set_select_opt(None);
if let Some((start, end)) = editor.selection_bounds() {
if start.line == end.line && start.index == end.index {
editor.set_selection(cosmic_text::Selection::None);
}
}
}
@ -494,7 +421,7 @@ impl editor::Editor for Editor {
fn min_bounds(&self) -> Size {
let internal = self.internal();
text::measure(internal.editor.buffer())
text::measure(buffer_from_editor(&internal.editor))
}
fn update(
@ -517,7 +444,10 @@ impl editor::Editor for Editor {
if font_system.version() != internal.version {
log::trace!("Updating `FontSystem` of `Editor`...");
for line in internal.editor.buffer_mut().lines.iter_mut() {
for line in buffer_mut_from_editor(&mut internal.editor)
.lines
.iter_mut()
{
line.reset();
}
@ -528,7 +458,10 @@ impl editor::Editor for Editor {
if new_font != internal.font {
log::trace!("Updating font of `Editor`...");
for line in internal.editor.buffer_mut().lines.iter_mut() {
for line in buffer_mut_from_editor(&mut internal.editor)
.lines
.iter_mut()
{
let _ = line.set_attrs_list(cosmic_text::AttrsList::new(
text::to_attributes(new_font),
));
@ -538,7 +471,7 @@ impl editor::Editor for Editor {
internal.topmost_line_changed = Some(0);
}
let metrics = internal.editor.buffer().metrics();
let metrics = buffer_from_editor(&internal.editor).metrics();
let new_line_height = new_line_height.to_absolute(new_size);
if new_size.0 != metrics.font_size
@ -546,7 +479,7 @@ impl editor::Editor for Editor {
{
log::trace!("Updating `Metrics` of `Editor`...");
internal.editor.buffer_mut().set_metrics(
buffer_mut_from_editor(&mut internal.editor).set_metrics(
font_system.raw(),
cosmic_text::Metrics::new(new_size.0, new_line_height.0),
);
@ -555,10 +488,10 @@ impl editor::Editor for Editor {
if new_bounds != internal.bounds {
log::trace!("Updating size of `Editor`...");
internal.editor.buffer_mut().set_size(
buffer_mut_from_editor(&mut internal.editor).set_size(
font_system.raw(),
new_bounds.width,
new_bounds.height,
Some(new_bounds.width),
Some(new_bounds.height),
);
internal.bounds = new_bounds;
@ -573,7 +506,7 @@ impl editor::Editor for Editor {
new_highlighter.change_line(topmost_line_changed);
}
internal.editor.shape_as_needed(font_system.raw());
internal.editor.shape_as_needed(font_system.raw(), false);
self.0 = Some(Arc::new(internal));
}
@ -585,12 +518,13 @@ impl editor::Editor for Editor {
format_highlight: impl Fn(&H::Highlight) -> highlighter::Format<Self::Font>,
) {
let internal = self.internal();
let buffer = internal.editor.buffer();
let buffer = buffer_from_editor(&internal.editor);
let mut window = buffer.scroll() + buffer.visible_lines();
let scroll = buffer.scroll();
let mut window = (internal.bounds.height / buffer.metrics().line_height)
.ceil() as i32;
let last_visible_line = buffer
.lines
let last_visible_line = buffer.lines[scroll.line..]
.iter()
.enumerate()
.find_map(|(i, line)| {
@ -604,7 +538,7 @@ impl editor::Editor for Editor {
window -= visible_lines;
None
} else {
Some(i)
Some(scroll.line + i)
}
})
.unwrap_or(buffer.lines.len().saturating_sub(1));
@ -626,7 +560,7 @@ impl editor::Editor for Editor {
let attributes = text::to_attributes(font);
for line in &mut internal.editor.buffer_mut().lines
for line in &mut buffer_mut_from_editor(&mut internal.editor).lines
[current_line..=last_visible_line]
{
let mut list = cosmic_text::AttrsList::new(attributes);
@ -652,7 +586,7 @@ impl editor::Editor for Editor {
let _ = line.set_attrs_list(list);
}
internal.editor.shape_as_needed(font_system.raw());
internal.editor.shape_as_needed(font_system.raw(), false);
self.0 = Some(Arc::new(internal));
}
@ -668,7 +602,8 @@ impl PartialEq for Internal {
fn eq(&self, other: &Self) -> bool {
self.font == other.font
&& self.bounds == other.bounds
&& self.editor.buffer().metrics() == other.editor.buffer().metrics()
&& buffer_from_editor(&self.editor).metrics()
== buffer_from_editor(&other.editor).metrics()
}
}
@ -730,7 +665,8 @@ fn highlight_line(
let layout = line
.layout_opt()
.as_ref()
.expect("Line layout should be cached");
.map(Vec::as_slice)
.unwrap_or_default();
layout.iter().map(move |visual_line| {
let start = visual_line
@ -773,34 +709,61 @@ fn highlight_line(
}
fn visual_lines_offset(line: usize, buffer: &cosmic_text::Buffer) -> i32 {
let visual_lines_before_start: usize = buffer
.lines
let scroll = buffer.scroll();
let start = scroll.line.min(line);
let end = scroll.line.max(line);
let visual_lines_offset: usize = buffer.lines[start..]
.iter()
.take(line)
.take(end - start)
.map(|line| {
line.layout_opt()
.as_ref()
.expect("Line layout should be cached")
.len()
line.layout_opt().as_ref().map(Vec::len).unwrap_or_default()
})
.sum();
visual_lines_before_start as i32 - buffer.scroll()
visual_lines_offset as i32 * if scroll.line < line { 1 } else { -1 }
}
fn motion_to_action(motion: Motion) -> cosmic_text::Action {
fn to_motion(motion: Motion) -> cosmic_text::Motion {
match motion {
Motion::Left => cosmic_text::Action::Left,
Motion::Right => cosmic_text::Action::Right,
Motion::Up => cosmic_text::Action::Up,
Motion::Down => cosmic_text::Action::Down,
Motion::WordLeft => cosmic_text::Action::LeftWord,
Motion::WordRight => cosmic_text::Action::RightWord,
Motion::Home => cosmic_text::Action::Home,
Motion::End => cosmic_text::Action::End,
Motion::PageUp => cosmic_text::Action::PageUp,
Motion::PageDown => cosmic_text::Action::PageDown,
Motion::DocumentStart => cosmic_text::Action::BufferStart,
Motion::DocumentEnd => cosmic_text::Action::BufferEnd,
Motion::Left => cosmic_text::Motion::Left,
Motion::Right => cosmic_text::Motion::Right,
Motion::Up => cosmic_text::Motion::Up,
Motion::Down => cosmic_text::Motion::Down,
Motion::WordLeft => cosmic_text::Motion::LeftWord,
Motion::WordRight => cosmic_text::Motion::RightWord,
Motion::Home => cosmic_text::Motion::Home,
Motion::End => cosmic_text::Motion::End,
Motion::PageUp => cosmic_text::Motion::PageUp,
Motion::PageDown => cosmic_text::Motion::PageDown,
Motion::DocumentStart => cosmic_text::Motion::BufferStart,
Motion::DocumentEnd => cosmic_text::Motion::BufferEnd,
}
}
fn buffer_from_editor<'a, 'b>(
editor: &'a impl cosmic_text::Edit<'b>,
) -> &'a cosmic_text::Buffer
where
'b: 'a,
{
match editor.buffer_ref() {
cosmic_text::BufferRef::Owned(buffer) => buffer,
cosmic_text::BufferRef::Borrowed(buffer) => buffer,
cosmic_text::BufferRef::Arc(buffer) => buffer,
}
}
fn buffer_mut_from_editor<'a, 'b>(
editor: &'a mut impl cosmic_text::Edit<'b>,
) -> &'a mut cosmic_text::Buffer
where
'b: 'a,
{
match editor.buffer_ref_mut() {
cosmic_text::BufferRef::Owned(buffer) => buffer,
cosmic_text::BufferRef::Borrowed(buffer) => buffer,
cosmic_text::BufferRef::Arc(_buffer) => unreachable!(),
}
}

View file

@ -77,8 +77,8 @@ impl core::text::Paragraph for Paragraph {
buffer.set_size(
font_system.raw(),
text.bounds.width,
text.bounds.height,
Some(text.bounds.width),
Some(text.bounds.height),
);
buffer.set_text(
@ -116,8 +116,8 @@ impl core::text::Paragraph for Paragraph {
internal.buffer.set_size(
font_system.raw(),
new_bounds.width,
new_bounds.height,
Some(new_bounds.width),
Some(new_bounds.height),
);
internal.bounds = new_bounds;

View file

@ -439,9 +439,13 @@ impl Engine {
let transformation = transformation * *local_transformation;
let (width, height) = buffer.size();
let physical_bounds =
Rectangle::new(raw.position, Size::new(width, height))
* transformation;
let physical_bounds = Rectangle::new(
raw.position,
Size::new(
width.unwrap_or(clip_bounds.width),
height.unwrap_or(clip_bounds.height),
),
) * transformation;
if !clip_bounds.intersects(&physical_bounds) {
return;

View file

@ -169,7 +169,13 @@ impl Pipeline {
font_system.raw(),
&mut self.glyph_cache,
buffer,
Rectangle::new(position, Size::new(width, height)),
Rectangle::new(
position,
Size::new(
width.unwrap_or(pixels.width() as f32),
height.unwrap_or(pixels.height() as f32),
),
),
color,
alignment::Horizontal::Left,
alignment::Vertical::Top,

View file

@ -1,8 +1,7 @@
use crate::core::svg::{Data, Handle};
use crate::core::{Color, Rectangle, Size};
use crate::graphics::text;
use resvg::usvg::{self, TreeTextToPath};
use resvg::usvg;
use rustc_hash::{FxHashMap, FxHashSet};
use tiny_skia::Transform;
@ -80,35 +79,28 @@ struct RasterKey {
impl Cache {
fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> {
use usvg::TreeParsing;
let id = handle.id();
if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) {
let mut svg = match handle.data() {
let svg = match handle.data() {
Data::Path(path) => {
fs::read_to_string(path).ok().and_then(|contents| {
usvg::Tree::from_str(
&contents,
&usvg::Options::default(),
&usvg::Options::default(), // TODO: Set usvg::Options::fontdb
)
.ok()
})
}
Data::Bytes(bytes) => {
usvg::Tree::from_data(bytes, &usvg::Options::default()).ok()
usvg::Tree::from_data(
bytes,
&usvg::Options::default(), // TODO: Set usvg::Options::fontdb
)
.ok()
}
};
if let Some(svg) = &mut svg {
if svg.has_text_nodes() {
let mut font_system =
text::font_system().write().expect("Write font system");
svg.convert_text(font_system.raw().db_mut());
}
}
let _ = entry.insert(svg);
}
@ -118,11 +110,9 @@ impl Cache {
fn viewport_dimensions(&mut self, handle: &Handle) -> Option<Size<u32>> {
let tree = self.load(handle)?;
let size = tree.size();
Some(Size::new(
tree.size.width() as u32,
tree.size.height() as u32,
))
Some(Size::new(size.width() as u32, size.height() as u32))
}
fn draw(
@ -147,7 +137,7 @@ impl Cache {
let mut image = tiny_skia::Pixmap::new(size.width, size.height)?;
let tree_size = tree.size.to_int_size();
let tree_size = tree.size().to_int_size();
let target_size = if size.width > size.height {
tree_size.scale_to_width(size.width)
@ -167,7 +157,7 @@ impl Cache {
tiny_skia::Transform::default()
};
resvg::Tree::from_usvg(tree).render(transform, &mut image.as_mut());
resvg::render(tree, transform, &mut image.as_mut());
if let Some([r, g, b, _]) = key.color {
// Apply color filter

View file

@ -1,10 +1,9 @@
use crate::core::svg;
use crate::core::{Color, Size};
use crate::graphics::text;
use crate::image::atlas::{self, Atlas};
use resvg::tiny_skia;
use resvg::usvg::{self, TreeTextToPath};
use resvg::usvg;
use rustc_hash::{FxHashMap, FxHashSet};
use std::fs;
@ -21,7 +20,7 @@ impl Svg {
pub fn viewport_dimensions(&self) -> Size<u32> {
match self {
Svg::Loaded(tree) => {
let size = tree.size;
let size = tree.size();
Size::new(size.width() as u32, size.height() as u32)
}
@ -45,38 +44,33 @@ type ColorFilter = Option<[u8; 4]>;
impl Cache {
/// Load svg
pub fn load(&mut self, handle: &svg::Handle) -> &Svg {
use usvg::TreeParsing;
if self.svgs.contains_key(&handle.id()) {
return self.svgs.get(&handle.id()).unwrap();
}
let mut svg = match handle.data() {
let svg = match handle.data() {
svg::Data::Path(path) => fs::read_to_string(path)
.ok()
.and_then(|contents| {
usvg::Tree::from_str(&contents, &usvg::Options::default())
usvg::Tree::from_str(
&contents,
&usvg::Options::default(), // TODO: Set usvg::Options::fontdb
)
.ok()
})
.map(Svg::Loaded)
.unwrap_or(Svg::NotFound),
svg::Data::Bytes(bytes) => {
match usvg::Tree::from_data(bytes, &usvg::Options::default()) {
match usvg::Tree::from_data(
bytes,
&usvg::Options::default(), // TODO: Set usvg::Options::fontdb
) {
Ok(tree) => Svg::Loaded(tree),
Err(_) => Svg::NotFound,
}
}
};
if let Svg::Loaded(svg) = &mut svg {
if svg.has_text_nodes() {
let mut font_system =
text::font_system().write().expect("Write font system");
svg.convert_text(font_system.raw().db_mut());
}
}
self.should_trim = true;
let _ = self.svgs.insert(handle.id(), svg);
@ -127,7 +121,7 @@ impl Cache {
// It would be cool to be able to smooth resize the `svg` example.
let mut img = tiny_skia::Pixmap::new(width, height)?;
let tree_size = tree.size.to_int_size();
let tree_size = tree.size().to_int_size();
let target_size = if width > height {
tree_size.scale_to_width(width)
@ -147,8 +141,7 @@ impl Cache {
tiny_skia::Transform::default()
};
resvg::Tree::from_usvg(tree)
.render(transform, &mut img.as_mut());
resvg::render(tree, transform, &mut img.as_mut());
let mut rgba = img.take();

View file

@ -585,7 +585,13 @@ fn prepare(
(
buffer.as_ref(),
Rectangle::new(raw.position, Size::new(width, height)),
Rectangle::new(
raw.position,
Size::new(
width.unwrap_or(layer_bounds.width),
height.unwrap_or(layer_bounds.height),
),
),
alignment::Horizontal::Left,
alignment::Vertical::Top,
raw.color,