Merge pull request #78 from hojjatabdollahi/main
Alignment and Justification
This commit is contained in:
commit
f4b14f1210
7 changed files with 258 additions and 49 deletions
|
|
@ -15,7 +15,7 @@ log = "0.4"
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
git = "https://github.com/pop-os/libcosmic"
|
git = "https://github.com/pop-os/libcosmic"
|
||||||
rev = "5fe44511"
|
rev = "2dde95ee"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["wgpu", "winit"]
|
features = ["wgpu", "winit"]
|
||||||
#path = "../../../libcosmic"
|
#path = "../../../libcosmic"
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use cosmic::{
|
||||||
Element,
|
Element,
|
||||||
};
|
};
|
||||||
use cosmic_text::{
|
use cosmic_text::{
|
||||||
Attrs, AttrsList, Buffer, Edit, FontSystem, Metrics, SyntaxEditor, SyntaxSystem, Wrap,
|
Align, Attrs, AttrsList, Buffer, Edit, FontSystem, Metrics, SyntaxEditor, SyntaxSystem, Wrap,
|
||||||
};
|
};
|
||||||
use std::{env, fs, path::PathBuf, sync::Mutex};
|
use std::{env, fs, path::PathBuf, sync::Mutex};
|
||||||
|
|
||||||
|
|
@ -66,6 +66,7 @@ pub enum Message {
|
||||||
Monospaced(bool),
|
Monospaced(bool),
|
||||||
MetricsChanged(Metrics),
|
MetricsChanged(Metrics),
|
||||||
WrapChanged(Wrap),
|
WrapChanged(Wrap),
|
||||||
|
AlignmentChanged(Align),
|
||||||
ThemeChanged(&'static str),
|
ThemeChanged(&'static str),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,6 +203,10 @@ impl Application for Window {
|
||||||
let mut editor = self.editor.lock().unwrap();
|
let mut editor = self.editor.lock().unwrap();
|
||||||
editor.buffer_mut().set_wrap(wrap);
|
editor.buffer_mut().set_wrap(wrap);
|
||||||
}
|
}
|
||||||
|
Message::AlignmentChanged(align) => {
|
||||||
|
let mut editor = self.editor.lock().unwrap();
|
||||||
|
update_alignment(&mut *editor, align);
|
||||||
|
}
|
||||||
Message::ThemeChanged(theme) => {
|
Message::ThemeChanged(theme) => {
|
||||||
self.theme = match theme {
|
self.theme = match theme {
|
||||||
"Dark" => Theme::Dark,
|
"Dark" => Theme::Dark,
|
||||||
|
|
@ -282,8 +287,24 @@ impl Application for Window {
|
||||||
theme_picker,
|
theme_picker,
|
||||||
text("Font Size:"),
|
text("Font Size:"),
|
||||||
font_size_picker,
|
font_size_picker,
|
||||||
|
]
|
||||||
|
.align_items(Alignment::Center)
|
||||||
|
.spacing(8),
|
||||||
|
row![
|
||||||
text("Wrap:"),
|
text("Wrap:"),
|
||||||
wrap_picker,
|
wrap_picker,
|
||||||
|
button(theme::Button::Text)
|
||||||
|
.icon(theme::Svg::Default, "format-justify-left", 20)
|
||||||
|
.on_press(Message::AlignmentChanged(Align::Left)),
|
||||||
|
button(theme::Button::Text)
|
||||||
|
.icon(theme::Svg::Symbolic, "format-justify-center", 20)
|
||||||
|
.on_press(Message::AlignmentChanged(Align::Center)),
|
||||||
|
button(theme::Button::Text)
|
||||||
|
.icon(theme::Svg::Symbolic, "format-justify-right", 20)
|
||||||
|
.on_press(Message::AlignmentChanged(Align::Right)),
|
||||||
|
button(theme::Button::Text)
|
||||||
|
.icon(theme::Svg::SymbolicLink, "format-justify-fill", 20)
|
||||||
|
.on_press(Message::AlignmentChanged(Align::Justified)),
|
||||||
]
|
]
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.spacing(8),
|
.spacing(8),
|
||||||
|
|
@ -303,3 +324,21 @@ fn update_attrs<'a, T: Edit<'a>>(editor: &mut T, attrs: Attrs<'a>) {
|
||||||
line.set_attrs_list(AttrsList::new(attrs));
|
line.set_attrs_list(AttrsList::new(attrs));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_alignment<'a, T: Edit<'a>>(editor: &mut T, align: Align) {
|
||||||
|
let current_line = editor.cursor().line;
|
||||||
|
if let Some(select) = editor.select_opt() {
|
||||||
|
let (start, end) = match select.line.cmp(¤t_line) {
|
||||||
|
std::cmp::Ordering::Greater => (current_line, select.line),
|
||||||
|
std::cmp::Ordering::Less => (select.line, current_line),
|
||||||
|
std::cmp::Ordering::Equal => (current_line, current_line),
|
||||||
|
};
|
||||||
|
editor.buffer_mut().lines.get_mut(start..=end).map(|lines| {
|
||||||
|
for line in lines.iter_mut() {
|
||||||
|
line.set_align(Some(align));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if let Some(line) = editor.buffer_mut().lines.get_mut(current_line) {
|
||||||
|
line.set_align(Some(align));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use cosmic::{
|
||||||
widget::{self, tree, Widget},
|
widget::{self, tree, Widget},
|
||||||
{Color, Element, Length, Point, Rectangle, Size},
|
{Color, Element, Length, Point, Rectangle, Size},
|
||||||
},
|
},
|
||||||
|
iced_winit::renderer::BorderRadius,
|
||||||
theme::Theme,
|
theme::Theme,
|
||||||
};
|
};
|
||||||
use cosmic_text::{Attrs, AttrsList, BufferLine, Metrics, SwashCache};
|
use cosmic_text::{Attrs, AttrsList, BufferLine, Metrics, SwashCache};
|
||||||
|
|
@ -100,6 +101,7 @@ where
|
||||||
self.metrics.font_size,
|
self.metrics.font_size,
|
||||||
limits.max().width as i32,
|
limits.max().width as i32,
|
||||||
self.line.wrap(),
|
self.line.wrap(),
|
||||||
|
self.line.align(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut width = 0;
|
let mut width = 0;
|
||||||
|
|
@ -139,7 +141,7 @@ where
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: layout.bounds(),
|
bounds: layout.bounds(),
|
||||||
border_radius: 0.0,
|
border_radius: BorderRadius::default(),
|
||||||
border_width: 0.0,
|
border_width: 0.0,
|
||||||
border_color: Color::TRANSPARENT,
|
border_color: Color::TRANSPARENT,
|
||||||
},
|
},
|
||||||
|
|
@ -160,7 +162,12 @@ where
|
||||||
let shape = self.line.shape_opt().as_ref().unwrap();
|
let shape = self.line.shape_opt().as_ref().unwrap();
|
||||||
|
|
||||||
//TODO: can we cache this?
|
//TODO: can we cache this?
|
||||||
let layout_lines = shape.layout(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();
|
let mut cache = state.cache.lock().unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use cosmic::{
|
||||||
widget::{self, tree, Widget},
|
widget::{self, tree, Widget},
|
||||||
Padding, {Color, Element, Length, Point, Rectangle, Shell, Size},
|
Padding, {Color, Element, Length, Point, Rectangle, Shell, Size},
|
||||||
},
|
},
|
||||||
|
iced_winit::renderer::BorderRadius,
|
||||||
theme::Theme,
|
theme::Theme,
|
||||||
};
|
};
|
||||||
use cosmic_text::{Action, Edit, SwashCache};
|
use cosmic_text::{Action, Edit, SwashCache};
|
||||||
|
|
@ -142,7 +143,7 @@ where
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: layout.bounds(),
|
bounds: layout.bounds(),
|
||||||
border_radius: 0.0,
|
border_radius: BorderRadius::default(),
|
||||||
border_width: 0.0,
|
border_width: 0.0,
|
||||||
border_color: Color::TRANSPARENT,
|
border_color: Color::TRANSPARENT,
|
||||||
},
|
},
|
||||||
|
|
@ -190,7 +191,7 @@ where
|
||||||
+ [self.padding.left as f32, self.padding.top as f32].into(),
|
+ [self.padding.left as f32, self.padding.top as f32].into(),
|
||||||
Size::new(w as f32, h as f32),
|
Size::new(w as f32, h as f32),
|
||||||
),
|
),
|
||||||
border_radius: 0.0,
|
border_radius: BorderRadius::default(),
|
||||||
border_width: 0.0,
|
border_width: 0.0,
|
||||||
border_color: Color::TRANSPARENT,
|
border_color: Color::TRANSPARENT,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::{string::String, vec::Vec};
|
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
|
/// A line (or paragraph) of text that is shaped and laid out
|
||||||
pub struct BufferLine {
|
pub struct BufferLine {
|
||||||
|
|
@ -9,6 +9,7 @@ pub struct BufferLine {
|
||||||
text: String,
|
text: String,
|
||||||
attrs_list: AttrsList,
|
attrs_list: AttrsList,
|
||||||
wrap: Wrap,
|
wrap: Wrap,
|
||||||
|
align: Option<Align>,
|
||||||
shape_opt: Option<ShapeLine>,
|
shape_opt: Option<ShapeLine>,
|
||||||
layout_opt: Option<Vec<LayoutLine>>,
|
layout_opt: Option<Vec<LayoutLine>>,
|
||||||
}
|
}
|
||||||
|
|
@ -22,6 +23,7 @@ impl BufferLine {
|
||||||
text: text.into(),
|
text: text.into(),
|
||||||
attrs_list,
|
attrs_list,
|
||||||
wrap: Wrap::Word,
|
wrap: Wrap::Word,
|
||||||
|
align: None,
|
||||||
shape_opt: None,
|
shape_opt: None,
|
||||||
layout_opt: None,
|
layout_opt: None,
|
||||||
}
|
}
|
||||||
|
|
@ -87,7 +89,27 @@ impl BufferLine {
|
||||||
pub fn set_wrap(&mut self, wrap: Wrap) -> bool {
|
pub fn set_wrap(&mut self, wrap: Wrap) -> bool {
|
||||||
if wrap != self.wrap {
|
if wrap != self.wrap {
|
||||||
self.wrap = wrap;
|
self.wrap = wrap;
|
||||||
self.reset();
|
self.reset_layout();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the Text alignment
|
||||||
|
pub fn align(&self) -> Option<Align> {
|
||||||
|
self.align
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the text alignment
|
||||||
|
///
|
||||||
|
/// Will reset shape and layout if it differs from current alignment.
|
||||||
|
/// Setting to None will use `Align::Right` for RTL lines, and `Align::Left` for LTR lines.
|
||||||
|
/// Returns true if the line was reset
|
||||||
|
pub fn set_align(&mut self, align: Option<Align>) -> bool {
|
||||||
|
if align != self.align {
|
||||||
|
self.align = align;
|
||||||
|
self.reset_layout();
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
@ -168,8 +190,9 @@ impl BufferLine {
|
||||||
) -> &[LayoutLine] {
|
) -> &[LayoutLine] {
|
||||||
if self.layout_opt.is_none() {
|
if self.layout_opt.is_none() {
|
||||||
self.wrap = wrap;
|
self.wrap = wrap;
|
||||||
|
let align = self.align;
|
||||||
let shape = self.shape(font_system);
|
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 = Some(layout);
|
||||||
}
|
}
|
||||||
self.layout_opt.as_ref().expect("layout not found")
|
self.layout_opt.as_ref().expect("layout not found")
|
||||||
|
|
|
||||||
|
|
@ -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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
199
src/shape.rs
199
src/shape.rs
|
|
@ -9,7 +9,7 @@ use unicode_script::{Script, UnicodeScript};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::fallback::FontFallbackIter;
|
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(
|
fn shape_fallback(
|
||||||
font: &Font,
|
font: &Font,
|
||||||
|
|
@ -600,15 +600,36 @@ impl ShapeLine {
|
||||||
runs
|
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,
|
||||||
|
align: Option<Align>,
|
||||||
|
) -> Vec<LayoutLine> {
|
||||||
let mut layout_lines = Vec::with_capacity(1);
|
let mut layout_lines = Vec::with_capacity(1);
|
||||||
|
|
||||||
|
let align = align.unwrap_or({
|
||||||
|
if self.rtl {
|
||||||
|
Align::Right
|
||||||
|
} else {
|
||||||
|
Align::Left
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// This is used to create a visual line for empty lines (e.g. lines with only a <CR>)
|
// This is used to create a visual line for empty lines (e.g. lines with only a <CR>)
|
||||||
let mut push_line = true;
|
let mut push_line = true;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct VisualLine {
|
||||||
|
ranges: Vec<VlRange>,
|
||||||
|
spaces: u32,
|
||||||
|
w: f32,
|
||||||
|
}
|
||||||
// For each visual line a list of (span index, and range of words in that span)
|
// 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
|
// 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<VisualLine> = Vec::with_capacity(1);
|
||||||
|
|
||||||
let start_x = if self.rtl { line_width as f32 } else { 0.0 };
|
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 };
|
let end_x = if self.rtl { 0.0 } else { line_width as f32 };
|
||||||
|
|
@ -618,17 +639,21 @@ impl ShapeLine {
|
||||||
// This would keep the maximum number of spans that would fit on a visual line
|
// This would keep the maximum number of spans that would fit on a visual line
|
||||||
// If one span is too large, this variable will hold the range of words inside that span
|
// If one span is too large, this variable will hold the range of words inside that span
|
||||||
// that fits on a line.
|
// that fits on a line.
|
||||||
let mut current_visual_line: Vec<VlRange> = Vec::with_capacity(1);
|
// let mut current_visual_line: Vec<VlRange> = Vec::with_capacity(1);
|
||||||
|
let mut current_visual_line = VisualLine::default();
|
||||||
|
|
||||||
if wrap == Wrap::None {
|
if wrap == Wrap::None {
|
||||||
for (span_index, span) in self.spans.iter().enumerate() {
|
for (span_index, span) in self.spans.iter().enumerate() {
|
||||||
current_visual_line.push((span_index, (0, 0), (span.words.len(), 0)));
|
current_visual_line
|
||||||
|
.ranges
|
||||||
|
.push((span_index, (0, 0), (span.words.len(), 0)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut fit_x = line_width as f32;
|
let mut fit_x = line_width as f32;
|
||||||
for (span_index, span) in self.spans.iter().enumerate() {
|
for (span_index, span) in self.spans.iter().enumerate() {
|
||||||
let mut word_ranges = Vec::new();
|
let mut word_ranges = Vec::new();
|
||||||
let mut word_range_width = 0.;
|
let mut word_range_width = 0.;
|
||||||
|
let mut number_of_blanks = 0;
|
||||||
|
|
||||||
// Create the word ranges that fits in a visual line
|
// Create the word ranges that fits in a visual line
|
||||||
if self.rtl != span.level.is_rtl() {
|
if self.rtl != span.level.is_rtl() {
|
||||||
|
|
@ -640,6 +665,9 @@ impl ShapeLine {
|
||||||
// fits
|
// fits
|
||||||
fit_x -= word_size;
|
fit_x -= word_size;
|
||||||
word_range_width += word_size;
|
word_range_width += word_size;
|
||||||
|
if word.blank {
|
||||||
|
number_of_blanks += 1;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
} else if wrap == Wrap::Glyph {
|
} else if wrap == Wrap::Glyph {
|
||||||
for (glyph_i, glyph) in word.glyphs.iter().enumerate().rev() {
|
for (glyph_i, glyph) in word.glyphs.iter().enumerate().rev() {
|
||||||
|
|
@ -653,7 +681,9 @@ impl ShapeLine {
|
||||||
(i, glyph_i + 1),
|
(i, glyph_i + 1),
|
||||||
fitting_start,
|
fitting_start,
|
||||||
word_range_width,
|
word_range_width,
|
||||||
|
number_of_blanks,
|
||||||
));
|
));
|
||||||
|
number_of_blanks = 0;
|
||||||
fit_x = line_width as f32 - glyph_size;
|
fit_x = line_width as f32 - glyph_size;
|
||||||
word_range_width = glyph_size;
|
word_range_width = glyph_size;
|
||||||
fitting_start = (i, glyph_i + 1);
|
fitting_start = (i, glyph_i + 1);
|
||||||
|
|
@ -661,8 +691,35 @@ impl ShapeLine {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Wrap::Word
|
// Wrap::Word
|
||||||
word_ranges.push(((i + 1, 0), fitting_start, word_range_width));
|
let mut prev_word_width = None;
|
||||||
|
if word.blank && number_of_blanks > 0 {
|
||||||
|
// current word causing a wrap is a space so we ignore it
|
||||||
|
number_of_blanks -= 1;
|
||||||
|
} else if let Some(previous_word) = span.words.get(i - 1) {
|
||||||
|
// Current word causing a wrap is not whitespace, so we ignore the
|
||||||
|
// previous word if it's a whitespace
|
||||||
|
if previous_word.blank {
|
||||||
|
number_of_blanks -= 1;
|
||||||
|
prev_word_width =
|
||||||
|
Some(previous_word.x_advance * font_size as f32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(width) = prev_word_width {
|
||||||
|
word_ranges.push((
|
||||||
|
(i, 0),
|
||||||
|
fitting_start,
|
||||||
|
word_range_width - width,
|
||||||
|
number_of_blanks,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
word_ranges.push((
|
||||||
|
(i + 1, 0),
|
||||||
|
fitting_start,
|
||||||
|
word_range_width,
|
||||||
|
number_of_blanks,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
number_of_blanks = 0;
|
||||||
if word.blank {
|
if word.blank {
|
||||||
fit_x = line_width as f32;
|
fit_x = line_width as f32;
|
||||||
word_range_width = 0.;
|
word_range_width = 0.;
|
||||||
|
|
@ -674,7 +731,7 @@ impl ShapeLine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
word_ranges.push(((0, 0), fitting_start, word_range_width));
|
word_ranges.push(((0, 0), fitting_start, word_range_width, number_of_blanks));
|
||||||
} else {
|
} else {
|
||||||
// congruent direction
|
// congruent direction
|
||||||
let mut fitting_start = (0, 0);
|
let mut fitting_start = (0, 0);
|
||||||
|
|
@ -684,6 +741,9 @@ impl ShapeLine {
|
||||||
// fits
|
// fits
|
||||||
fit_x -= word_size;
|
fit_x -= word_size;
|
||||||
word_range_width += word_size;
|
word_range_width += word_size;
|
||||||
|
if word.blank {
|
||||||
|
number_of_blanks += 1;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
} else if wrap == Wrap::Glyph {
|
} else if wrap == Wrap::Glyph {
|
||||||
for (glyph_i, glyph) in word.glyphs.iter().enumerate() {
|
for (glyph_i, glyph) in word.glyphs.iter().enumerate() {
|
||||||
|
|
@ -697,7 +757,9 @@ impl ShapeLine {
|
||||||
fitting_start,
|
fitting_start,
|
||||||
(i, glyph_i),
|
(i, glyph_i),
|
||||||
word_range_width,
|
word_range_width,
|
||||||
|
number_of_blanks,
|
||||||
));
|
));
|
||||||
|
number_of_blanks = 0;
|
||||||
fit_x = line_width as f32 - glyph_size;
|
fit_x = line_width as f32 - glyph_size;
|
||||||
word_range_width = glyph_size;
|
word_range_width = glyph_size;
|
||||||
fitting_start = (i, glyph_i);
|
fitting_start = (i, glyph_i);
|
||||||
|
|
@ -705,7 +767,35 @@ impl ShapeLine {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Wrap::Word
|
// Wrap::Word
|
||||||
word_ranges.push((fitting_start, (i, 0), word_range_width));
|
let mut prev_word_width = None;
|
||||||
|
if word.blank && number_of_blanks > 0 {
|
||||||
|
// current word causing a wrap is a space so we ignore it
|
||||||
|
number_of_blanks -= 1;
|
||||||
|
} else if let Some(previous_word) = span.words.get(i - 1) {
|
||||||
|
// Current word causing a wrap is not whitespace, so we ignore the
|
||||||
|
// previous word if it's a whitespace
|
||||||
|
if previous_word.blank {
|
||||||
|
number_of_blanks -= 1;
|
||||||
|
prev_word_width =
|
||||||
|
Some(previous_word.x_advance * font_size as f32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(width) = prev_word_width {
|
||||||
|
word_ranges.push((
|
||||||
|
fitting_start,
|
||||||
|
(i - 1, 0),
|
||||||
|
word_range_width - width,
|
||||||
|
number_of_blanks,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
word_ranges.push((
|
||||||
|
fitting_start,
|
||||||
|
(i, 0),
|
||||||
|
word_range_width,
|
||||||
|
number_of_blanks,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
number_of_blanks = 0;
|
||||||
|
|
||||||
if word.blank {
|
if word.blank {
|
||||||
fit_x = line_width as f32;
|
fit_x = line_width as f32;
|
||||||
|
|
@ -718,7 +808,12 @@ impl ShapeLine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
word_ranges.push((fitting_start, (span.words.len(), 0), word_range_width));
|
word_ranges.push((
|
||||||
|
fitting_start,
|
||||||
|
(span.words.len(), 0),
|
||||||
|
word_range_width,
|
||||||
|
number_of_blanks,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a visual line
|
// Create a visual line
|
||||||
|
|
@ -726,6 +821,7 @@ impl ShapeLine {
|
||||||
(starting_word, starting_glyph),
|
(starting_word, starting_glyph),
|
||||||
(ending_word, ending_glyph),
|
(ending_word, ending_glyph),
|
||||||
word_range_width,
|
word_range_width,
|
||||||
|
number_of_blanks,
|
||||||
) in word_ranges
|
) in word_ranges
|
||||||
{
|
{
|
||||||
// To simplify the algorithm above, we might push empty ranges but we ignore them here
|
// To simplify the algorithm above, we might push empty ranges but we ignore them here
|
||||||
|
|
@ -740,27 +836,31 @@ impl ShapeLine {
|
||||||
};
|
};
|
||||||
|
|
||||||
if fits {
|
if fits {
|
||||||
current_visual_line.push((
|
current_visual_line.ranges.push((
|
||||||
span_index,
|
span_index,
|
||||||
(starting_word, starting_glyph),
|
(starting_word, starting_glyph),
|
||||||
(ending_word, ending_glyph),
|
(ending_word, ending_glyph),
|
||||||
));
|
));
|
||||||
|
current_visual_line.w += word_range_width;
|
||||||
|
current_visual_line.spaces += number_of_blanks;
|
||||||
if self.rtl {
|
if self.rtl {
|
||||||
x -= word_range_width;
|
x -= word_range_width;
|
||||||
} else {
|
} else {
|
||||||
x += word_range_width;
|
x += word_range_width;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !current_visual_line.is_empty() {
|
if !current_visual_line.ranges.is_empty() {
|
||||||
vl_range_of_spans.push(current_visual_line);
|
vl_range_of_spans.push(current_visual_line);
|
||||||
current_visual_line = Vec::with_capacity(1);
|
current_visual_line = VisualLine::default();
|
||||||
x = start_x;
|
x = start_x;
|
||||||
}
|
}
|
||||||
current_visual_line.push((
|
current_visual_line.ranges.push((
|
||||||
span_index,
|
span_index,
|
||||||
(starting_word, starting_glyph),
|
(starting_word, starting_glyph),
|
||||||
(ending_word, ending_glyph),
|
(ending_word, ending_glyph),
|
||||||
));
|
));
|
||||||
|
current_visual_line.w += word_range_width;
|
||||||
|
current_visual_line.spaces += number_of_blanks;
|
||||||
if self.rtl {
|
if self.rtl {
|
||||||
x -= word_range_width;
|
x -= word_range_width;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -769,7 +869,7 @@ impl ShapeLine {
|
||||||
if word_range_width > line_width as f32 {
|
if word_range_width > line_width as f32 {
|
||||||
// single word is bigger than line_width
|
// single word is bigger than line_width
|
||||||
vl_range_of_spans.push(current_visual_line);
|
vl_range_of_spans.push(current_visual_line);
|
||||||
current_visual_line = Vec::with_capacity(1);
|
current_visual_line = VisualLine::default();
|
||||||
x = start_x;
|
x = start_x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -777,39 +877,57 @@ impl ShapeLine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !current_visual_line.is_empty() {
|
if !current_visual_line.ranges.is_empty() {
|
||||||
vl_range_of_spans.push(current_visual_line);
|
vl_range_of_spans.push(current_visual_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the LayoutLines using the ranges inside visual lines
|
// Create the LayoutLines using the ranges inside visual lines
|
||||||
for visual_line in &vl_range_of_spans {
|
let number_of_visual_lines = vl_range_of_spans.len();
|
||||||
let new_order = self.reorder(visual_line);
|
for (index, visual_line) in vl_range_of_spans.iter().enumerate() {
|
||||||
|
let new_order = self.reorder(&visual_line.ranges);
|
||||||
let mut glyphs = Vec::with_capacity(1);
|
let mut glyphs = Vec::with_capacity(1);
|
||||||
x = start_x;
|
x = start_x;
|
||||||
y = 0.;
|
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, _) => {
|
||||||
|
// Don't justify the last line in a paragraph.
|
||||||
|
if visual_line.spaces > 0 && index != number_of_visual_lines - 1 {
|
||||||
|
(line_width as f32 - visual_line.w) / visual_line.spaces as f32
|
||||||
|
} else {
|
||||||
|
0.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
if self.rtl {
|
if self.rtl {
|
||||||
|
if align != Align::Justified {
|
||||||
|
x -= alignment_correction;
|
||||||
|
}
|
||||||
for range in new_order.iter().rev() {
|
for range in new_order.iter().rev() {
|
||||||
for (
|
for (
|
||||||
span_index,
|
span_index,
|
||||||
(starting_word, starting_glyph),
|
(starting_word, starting_glyph),
|
||||||
(ending_word, ending_glyph),
|
(ending_word, ending_glyph),
|
||||||
) in visual_line[range.clone()].iter()
|
) in visual_line.ranges[range.clone()].iter()
|
||||||
{
|
{
|
||||||
let span = &self.spans[*span_index];
|
let span = &self.spans[*span_index];
|
||||||
if starting_word == ending_word {
|
if starting_word == ending_word {
|
||||||
|
let word_blank = span.words[*starting_word].blank;
|
||||||
for glyph in span.words[*starting_word].glyphs
|
for glyph in span.words[*starting_word].glyphs
|
||||||
[*starting_glyph..*ending_glyph]
|
[*starting_glyph..*ending_glyph]
|
||||||
.iter()
|
.iter()
|
||||||
{
|
{
|
||||||
let x_advance = font_size as f32 * glyph.x_advance;
|
let x_advance = font_size as f32 * glyph.x_advance;
|
||||||
let y_advance = font_size as f32 * glyph.y_advance;
|
let y_advance = font_size as f32 * glyph.y_advance;
|
||||||
if self.rtl {
|
x -= x_advance;
|
||||||
x -= x_advance;
|
if word_blank && align == Align::Justified {
|
||||||
|
x -= alignment_correction;
|
||||||
}
|
}
|
||||||
glyphs.push(glyph.layout(font_size, x, y, span.level));
|
glyphs.push(glyph.layout(font_size, x, y, span.level));
|
||||||
if !self.rtl {
|
|
||||||
x += x_advance;
|
|
||||||
}
|
|
||||||
y += y_advance;
|
y += y_advance;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -823,16 +941,15 @@ impl ShapeLine {
|
||||||
(0, word.glyphs.len())
|
(0, word.glyphs.len())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let word_blank = word.blank;
|
||||||
for glyph in &word.glyphs[g1..g2] {
|
for glyph in &word.glyphs[g1..g2] {
|
||||||
let x_advance = font_size as f32 * glyph.x_advance;
|
let x_advance = font_size as f32 * glyph.x_advance;
|
||||||
let y_advance = font_size as f32 * glyph.y_advance;
|
let y_advance = font_size as f32 * glyph.y_advance;
|
||||||
if self.rtl {
|
if word_blank && align == Align::Justified {
|
||||||
x -= x_advance;
|
x -= alignment_correction;
|
||||||
}
|
}
|
||||||
|
x -= x_advance;
|
||||||
glyphs.push(glyph.layout(font_size, x, y, span.level));
|
glyphs.push(glyph.layout(font_size, x, y, span.level));
|
||||||
if !self.rtl {
|
|
||||||
x += x_advance;
|
|
||||||
}
|
|
||||||
y += y_advance;
|
y += y_advance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -841,27 +958,30 @@ impl ShapeLine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
/* LTR */
|
||||||
|
if align != Align::Justified {
|
||||||
|
x += alignment_correction;
|
||||||
|
}
|
||||||
for range in new_order {
|
for range in new_order {
|
||||||
for (
|
for (
|
||||||
span_index,
|
span_index,
|
||||||
(starting_word, starting_glyph),
|
(starting_word, starting_glyph),
|
||||||
(ending_word, ending_glyph),
|
(ending_word, ending_glyph),
|
||||||
) in visual_line[range.clone()].iter()
|
) in visual_line.ranges[range.clone()].iter()
|
||||||
{
|
{
|
||||||
let span = &self.spans[*span_index];
|
let span = &self.spans[*span_index];
|
||||||
if starting_word == ending_word {
|
if starting_word == ending_word {
|
||||||
|
let word_blank = span.words[*starting_word].blank;
|
||||||
for glyph in span.words[*starting_word].glyphs
|
for glyph in span.words[*starting_word].glyphs
|
||||||
[*starting_glyph..*ending_glyph]
|
[*starting_glyph..*ending_glyph]
|
||||||
.iter()
|
.iter()
|
||||||
{
|
{
|
||||||
let x_advance = font_size as f32 * glyph.x_advance;
|
let x_advance = font_size as f32 * glyph.x_advance;
|
||||||
let y_advance = font_size as f32 * glyph.y_advance;
|
let y_advance = font_size as f32 * glyph.y_advance;
|
||||||
if self.rtl {
|
|
||||||
x -= x_advance;
|
|
||||||
}
|
|
||||||
glyphs.push(glyph.layout(font_size, x, y, span.level));
|
glyphs.push(glyph.layout(font_size, x, y, span.level));
|
||||||
if !self.rtl {
|
x += x_advance;
|
||||||
x += x_advance;
|
if word_blank && align == Align::Justified {
|
||||||
|
x += alignment_correction;
|
||||||
}
|
}
|
||||||
y += y_advance;
|
y += y_advance;
|
||||||
}
|
}
|
||||||
|
|
@ -876,16 +996,15 @@ impl ShapeLine {
|
||||||
(0, word.glyphs.len())
|
(0, word.glyphs.len())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let word_blank = word.blank;
|
||||||
for glyph in &word.glyphs[g1..g2] {
|
for glyph in &word.glyphs[g1..g2] {
|
||||||
let x_advance = font_size as f32 * glyph.x_advance;
|
let x_advance = font_size as f32 * glyph.x_advance;
|
||||||
let y_advance = font_size as f32 * glyph.y_advance;
|
let y_advance = font_size as f32 * glyph.y_advance;
|
||||||
if self.rtl {
|
|
||||||
x -= x_advance;
|
|
||||||
}
|
|
||||||
glyphs.push(glyph.layout(font_size, x, y, span.level));
|
glyphs.push(glyph.layout(font_size, x, y, span.level));
|
||||||
if !self.rtl {
|
if word_blank && align == Align::Justified {
|
||||||
x += x_advance;
|
x += alignment_correction;
|
||||||
}
|
}
|
||||||
|
x += x_advance;
|
||||||
y += y_advance;
|
y += y_advance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue