diff --git a/Cargo.toml b/Cargo.toml index b85900cf61..bc566bf671 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/graphics/src/geometry/text.rs b/graphics/src/geometry/text.rs index d314e85ec9..90147f8796 100644 --- a/graphics/src/geometry/text.rs +++ b/graphics/src/geometry/text.rs @@ -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 { diff --git a/graphics/src/text/cache.rs b/graphics/src/text/cache.rs index 822b61c47c..e64d93f166 100644 --- a/graphics/src/text/cache.rs +++ b/graphics/src/text/cache.rs @@ -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, diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs index 36b4ca6e16..3e6ef70cf3 100644 --- a/graphics/src/text/editor.rs +++ b/graphics/src/text/editor.rs @@ -17,7 +17,7 @@ use std::sync::{self, Arc}; pub struct Editor(Option>); struct Internal { - editor: cosmic_text::Editor, + editor: cosmic_text::Editor<'static>, font: Font, bounds: Size, topmost_line_changed: Option, @@ -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 { - line: 0, - index: 0, - ..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, ) { 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!(), } } diff --git a/graphics/src/text/paragraph.rs b/graphics/src/text/paragraph.rs index 31a323ac90..a5fefe8fed 100644 --- a/graphics/src/text/paragraph.rs +++ b/graphics/src/text/paragraph.rs @@ -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; diff --git a/tiny_skia/src/engine.rs b/tiny_skia/src/engine.rs index 028b304fb3..898657c8ee 100644 --- a/tiny_skia/src/engine.rs +++ b/tiny_skia/src/engine.rs @@ -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; diff --git a/tiny_skia/src/text.rs b/tiny_skia/src/text.rs index c71deb105f..0fc3d1f71b 100644 --- a/tiny_skia/src/text.rs +++ b/tiny_skia/src/text.rs @@ -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, diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index bbe08cb806..8a15f47f95 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -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> { 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 diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index c6d829afba..74e9924dd6 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -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 { 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()) - .ok() + 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(); diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index 05db5f8069..bf7eae180f 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -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,