diff --git a/examples/editor-libcosmic/src/text.rs b/examples/editor-libcosmic/src/text.rs index 66a9c81..5986f0e 100644 --- a/examples/editor-libcosmic/src/text.rs +++ b/examples/editor-libcosmic/src/text.rs @@ -100,6 +100,7 @@ where self.metrics.font_size, limits.max().width as i32, self.line.wrap(), + self.line.align(), ); let mut width = 0; @@ -160,7 +161,12 @@ 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(), + self.line.align(), + ); let mut cache = state.cache.lock().unwrap(); diff --git a/src/buffer.rs b/src/buffer.rs index 7bd6554..c89cd33 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -10,7 +10,9 @@ use unicode_segmentation::UnicodeSegmentation; #[cfg(feature = "swash")] use crate::Color; -use crate::{Attrs, AttrsList, BufferLine, FontSystem, LayoutGlyph, LayoutLine, ShapeLine, Wrap}; +use crate::{ + Align, Attrs, AttrsList, BufferLine, FontSystem, LayoutGlyph, LayoutLine, ShapeLine, Wrap, +}; /// Current cursor location #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] @@ -301,6 +303,7 @@ pub struct Buffer<'a> { /// True if a redraw is requires. Set to false after processing redraw: bool, wrap: Wrap, + align: Align, } impl<'a> Buffer<'a> { @@ -317,6 +320,7 @@ impl<'a> Buffer<'a> { scroll: 0, redraw: false, wrap: Wrap::Word, + align: Align::Center, }; buffer.set_text("", Attrs::new()); buffer @@ -334,6 +338,7 @@ impl<'a> Buffer<'a> { self.metrics.font_size, self.width, self.wrap, + self.align, ); } } @@ -364,6 +369,7 @@ impl<'a> Buffer<'a> { self.metrics.font_size, self.width, self.wrap, + self.align, ); total_layout += layout.len() as i32; } @@ -397,6 +403,7 @@ impl<'a> Buffer<'a> { self.metrics.font_size, self.width, self.wrap, + self.align, ); if line_i == cursor.line { let layout_cursor = self.layout_cursor(&cursor); @@ -482,6 +489,7 @@ impl<'a> Buffer<'a> { self.metrics.font_size, self.width, self.wrap, + self.align, )) } diff --git a/src/buffer_line.rs b/src/buffer_line.rs index 428504c..0151b93 100644 --- a/src/buffer_line.rs +++ b/src/buffer_line.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "std"))] use alloc::{string::String, vec::Vec}; -use crate::{AttrsList, FontSystem, LayoutLine, ShapeLine, Wrap}; +use crate::{Align, AttrsList, FontSystem, LayoutLine, ShapeLine, Wrap}; /// A line (or paragraph) of text that is shaped and laid out pub struct BufferLine { @@ -9,6 +9,7 @@ pub struct BufferLine { text: String, attrs_list: AttrsList, wrap: Wrap, + align: Align, shape_opt: Option, layout_opt: Option>, } @@ -22,6 +23,7 @@ impl BufferLine { text: text.into(), attrs_list, wrap: Wrap::Word, + align: Align::Left, shape_opt: None, layout_opt: None, } @@ -94,6 +96,25 @@ impl BufferLine { } } + /// Get the Text alignment + pub fn align(&self) -> Align { + self.align + } + + /// Set the text alignment + /// + /// Will reset shape and layout if it differs from current alignment. + /// Returns true if the line was reset + pub fn set_align(&mut self, align: Align) -> bool { + if align != self.align { + self.align = align; + self.reset(); + true + } else { + false + } + } + /// Append line at end of this line /// /// The wrap setting of the appended line will be lost @@ -165,11 +186,13 @@ impl BufferLine { font_size: i32, width: i32, wrap: Wrap, + align: Align, ) -> &[LayoutLine] { if self.layout_opt.is_none() { self.wrap = wrap; + self.align = align; let shape = self.shape(font_system); - let layout = shape.layout(font_size, width, wrap); + let layout = shape.layout(font_size, width, wrap, align); self.layout_opt = Some(layout); } self.layout_opt.as_ref().expect("layout not found") diff --git a/src/layout.rs b/src/layout.rs index 2625659..9ed6212 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -80,3 +80,23 @@ impl Display for Wrap { } } } + +/// Align or justify +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Align { + Left, + Right, + Center, + Justified, +} + +impl Display for Align { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Left => write!(f, "Left"), + Self::Right => write!(f, "Right"), + Self::Center => write!(f, "Center"), + Self::Justified => write!(f, "Justified"), + } + } +} diff --git a/src/shape.rs b/src/shape.rs index 561224d..a7c49db 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -9,7 +9,7 @@ use unicode_script::{Script, UnicodeScript}; use unicode_segmentation::UnicodeSegmentation; use crate::fallback::FontFallbackIter; -use crate::{AttrsList, CacheKey, Color, Font, FontSystem, LayoutGlyph, LayoutLine, Wrap}; +use crate::{Align, AttrsList, CacheKey, Color, Font, FontSystem, LayoutGlyph, LayoutLine, Wrap}; fn shape_fallback( font: &Font, @@ -600,15 +600,28 @@ impl ShapeLine { runs } - pub fn layout(&self, font_size: i32, line_width: i32, wrap: Wrap) -> Vec { + pub fn layout( + &self, + font_size: i32, + line_width: i32, + wrap: Wrap, + align: Align, + ) -> Vec { 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 ) let mut push_line = true; + #[derive(Default)] + struct VisualLine { + ranges: Vec, + spaces: u32, + w: f32, + } // For each visual line a list of (span index, and range of words in that span) // Note that a BiDi visual line could have multiple spans or parts of them - let mut vl_range_of_spans = Vec::with_capacity(1); + // let mut vl_range_of_spans = Vec::with_capacity(1); + let mut vl_range_of_spans: Vec = Vec::with_capacity(1); let start_x = if self.rtl { line_width as f32 } else { 0.0 }; let end_x = if self.rtl { 0.0 } else { line_width as f32 }; @@ -618,11 +631,14 @@ impl ShapeLine { // 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. - let mut current_visual_line: Vec = Vec::with_capacity(1); + // let mut current_visual_line: Vec = Vec::with_capacity(1); + let mut current_visual_line = VisualLine::default(); 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 + .ranges + .push((span_index, (0, 0), (span.words.len(), 0))); } } else { let mut fit_x = line_width as f32; @@ -740,27 +756,29 @@ impl ShapeLine { }; if fits { - current_visual_line.push(( + current_visual_line.ranges.push(( span_index, (starting_word, starting_glyph), (ending_word, ending_glyph), )); + current_visual_line.w += word_range_width; if self.rtl { x -= word_range_width; } else { x += word_range_width; } } else { - if !current_visual_line.is_empty() { + if !current_visual_line.ranges.is_empty() { vl_range_of_spans.push(current_visual_line); - current_visual_line = Vec::with_capacity(1); + current_visual_line = VisualLine::default(); x = start_x; } - current_visual_line.push(( + current_visual_line.ranges.push(( span_index, (starting_word, starting_glyph), (ending_word, ending_glyph), )); + current_visual_line.w += word_range_width; if self.rtl { x -= word_range_width; } else { @@ -769,7 +787,7 @@ impl ShapeLine { 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); + current_visual_line = VisualLine::default(); x = start_x; } } @@ -777,23 +795,32 @@ impl ShapeLine { } } - if !current_visual_line.is_empty() { + if !current_visual_line.ranges.is_empty() { vl_range_of_spans.push(current_visual_line); } // Create the LayoutLines using the ranges inside visual lines - for visual_line in &vl_range_of_spans { - let new_order = self.reorder(visual_line); + for visual_line in vl_range_of_spans { + let new_order = self.reorder(&visual_line.ranges); let mut glyphs = Vec::with_capacity(1); x = start_x; y = 0.; + let alignment_correction = match (align, self.rtl) { + (Align::Left, true) => line_width as f32 - visual_line.w, + (Align::Left, false) => 0., + (Align::Right, true) => 0., + (Align::Right, false) => line_width as f32 - visual_line.w, + (Align::Center, _) => (line_width as f32 - visual_line.w) / 2.0, + (Align::Justified, _) => unimplemented!("I need to work on the number of spaces"), + }; if self.rtl { + x -= alignment_correction; for range in new_order.iter().rev() { for ( span_index, (starting_word, starting_glyph), (ending_word, ending_glyph), - ) in visual_line[range.clone()].iter() + ) in visual_line.ranges[range.clone()].iter() { let span = &self.spans[*span_index]; if starting_word == ending_word { @@ -803,13 +830,8 @@ impl ShapeLine { { let x_advance = font_size as f32 * glyph.x_advance; let y_advance = font_size as f32 * glyph.y_advance; - if self.rtl { - x -= x_advance; - } + x -= x_advance; glyphs.push(glyph.layout(font_size, x, y, span.level)); - if !self.rtl { - x += x_advance; - } y += y_advance; } } else { @@ -841,12 +863,13 @@ impl ShapeLine { } } } else { + x += alignment_correction; for range in new_order { for ( span_index, (starting_word, starting_glyph), (ending_word, ending_glyph), - ) in visual_line[range.clone()].iter() + ) in visual_line.ranges[range.clone()].iter() { let span = &self.spans[*span_index]; if starting_word == ending_word { @@ -856,13 +879,8 @@ impl ShapeLine { { let x_advance = font_size as f32 * glyph.x_advance; let y_advance = font_size as f32 * glyph.y_advance; - if self.rtl { - x -= x_advance; - } glyphs.push(glyph.layout(font_size, x, y, span.level)); - if !self.rtl { - x += x_advance; - } + x += x_advance; y += y_advance; } } else {