Skip to content

Commit

Permalink
first line indent
Browse files Browse the repository at this point in the history
  • Loading branch information
Jack Nichols committed Jun 21, 2024
1 parent a03ec6b commit 167d0d8
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 20 deletions.
5 changes: 5 additions & 0 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ pub struct Buffer {
metrics: Metrics,
width_opt: Option<f32>,
height_opt: Option<f32>,
first_line_indent: Option<f32>,
scroll: Scroll,
/// True if a redraw is requires. Set to false after processing
redraw: bool,
Expand All @@ -225,6 +226,7 @@ impl Clone for Buffer {
metrics: self.metrics,
width_opt: self.width_opt,
height_opt: self.height_opt,
first_line_indent: self.first_line_indent,
scroll: self.scroll,
redraw: self.redraw,
wrap: self.wrap,
Expand Down Expand Up @@ -254,6 +256,7 @@ impl Buffer {
metrics,
width_opt: None,
height_opt: None,
first_line_indent: None,
scroll: Scroll::default(),
redraw: false,
wrap: Wrap::WordOrGlyph,
Expand Down Expand Up @@ -297,6 +300,7 @@ impl Buffer {
font_system,
self.metrics.font_size,
self.width_opt,
self.first_line_indent,
self.wrap,
self.monospace_width,
self.tab_width,
Expand Down Expand Up @@ -536,6 +540,7 @@ impl Buffer {
font_system,
self.metrics.font_size,
self.width_opt,
self.first_line_indent,
self.wrap,
self.monospace_width,
self.tab_width,
Expand Down
4 changes: 4 additions & 0 deletions src/buffer_line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ impl BufferLine {
font_system: &mut FontSystem,
font_size: f32,
width_opt: Option<f32>,
first_line_indent: Option<f32>,
wrap: Wrap,
match_mono_width: Option<f32>,
tab_width: u16,
Expand All @@ -227,6 +228,7 @@ impl BufferLine {
font_system,
font_size,
width_opt,
first_line_indent,
wrap,
match_mono_width,
tab_width,
Expand All @@ -240,6 +242,7 @@ impl BufferLine {
font_system: &mut FontSystem,
font_size: f32,
width_opt: Option<f32>,
first_line_indent: Option<f32>,
wrap: Wrap,
match_mono_width: Option<f32>,
tab_width: u16,
Expand All @@ -254,6 +257,7 @@ impl BufferLine {
width_opt,
wrap,
align,
first_line_indent,
&mut layout,
match_mono_width,
);
Expand Down
85 changes: 67 additions & 18 deletions src/shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,7 @@ impl ShapeLine {
width_opt: Option<f32>,
wrap: Wrap,
align: Option<Align>,
first_line_indent: Option<f32>,
match_mono_width: Option<f32>,
) -> Vec<LayoutLine> {
let mut lines = Vec::with_capacity(1);
Expand All @@ -1010,6 +1011,7 @@ impl ShapeLine {
width_opt,
wrap,
align,
first_line_indent,
&mut lines,
match_mono_width,
);
Expand All @@ -1023,6 +1025,7 @@ impl ShapeLine {
width_opt: Option<f32>,
wrap: Wrap,
align: Option<Align>,
first_line_head_indent: Option<f32>,
layout_lines: &mut Vec<LayoutLine>,
match_mono_width: Option<f32>,
) {
Expand Down Expand Up @@ -1052,11 +1055,19 @@ impl ShapeLine {
vl.spaces += number_of_blanks;
}

let first_line_indent = first_line_head_indent
.unwrap_or_default()
.min(width_opt.unwrap_or(f32::INFINITY));

// 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<VlRange> = Vec::with_capacity(1);
let mut current_visual_line = VisualLine::default();
let mut current_visual_line = VisualLine {
// The first line gets initialized with the head indent.
w: first_line_indent,
..Default::default()
};

if wrap == Wrap::None {
for (span_index, span) in self.spans.iter().enumerate() {
Expand Down Expand Up @@ -1108,14 +1119,25 @@ impl ShapeLine {
}
word_range_width += word_width;
continue;
} else if wrap == Wrap::Glyph
// Make sure that the word is able to fit on it's own line, if not, fall back to Glyph wrapping.
}

let on_first_line = visual_lines.is_empty();
let word_fits_on_current_line = current_visual_line.w + word_width
<= width_opt.unwrap_or(f32::INFINITY);

if wrap == Wrap::Glyph
// Make sure that the word is able to fit on its own line, if not, fall back to Glyph wrapping.
|| (wrap == Wrap::WordOrGlyph && word_width > width_opt.unwrap_or(f32::INFINITY))
// If we're on the first line and can't fit the word on its own
|| (wrap == Wrap::WordOrGlyph && on_first_line && !word_fits_on_current_line)
{
// Commit the current line so that the word starts on the next line.
if word_range_width > 0.
&& wrap == Wrap::WordOrGlyph
&& word_width > width_opt.unwrap_or(f32::INFINITY)
&& ((wrap == Wrap::WordOrGlyph
&& word_width > width_opt.unwrap_or(f32::INFINITY))
|| (wrap == Wrap::WordOrGlyph
&& on_first_line
&& !word_fits_on_current_line))
{
add_to_visual_line(
&mut current_visual_line,
Expand Down Expand Up @@ -1232,14 +1254,25 @@ impl ShapeLine {
}
word_range_width += word_width;
continue;
} else if wrap == Wrap::Glyph
}

let on_first_line = visual_lines.is_empty();
let word_fits_on_current_line = current_visual_line.w + word_width
<= width_opt.unwrap_or(f32::INFINITY);

if wrap == Wrap::Glyph
// Make sure that the word is able to fit on it's own line, if not, fall back to Glyph wrapping.
|| (wrap == Wrap::WordOrGlyph && word_width > width_opt.unwrap_or(f32::INFINITY))
// If we're on the first line and can't fit the word on its own
|| (wrap == Wrap::WordOrGlyph && on_first_line && !word_fits_on_current_line)
{
// Commit the current line so that the word starts on the next line.
if word_range_width > 0.
&& wrap == Wrap::WordOrGlyph
&& word_width > width_opt.unwrap_or(f32::INFINITY)
&& ((wrap == Wrap::WordOrGlyph
&& word_width > width_opt.unwrap_or(f32::INFINITY))
|| (wrap == Wrap::WordOrGlyph
&& on_first_line
&& !word_fits_on_current_line))
{
add_to_visual_line(
&mut current_visual_line,
Expand Down Expand Up @@ -1285,7 +1318,6 @@ impl ShapeLine {
}
} else {
// Wrap::Word, Wrap::WordOrGlyph

// If we had a previous range, commit that line before the next word.
if word_range_width > 0. {
// Current word causing a wrap is not whitespace, so we ignore the
Expand Down Expand Up @@ -1367,9 +1399,19 @@ impl ShapeLine {

let number_of_visual_lines = visual_lines.len();
for (index, visual_line) in visual_lines.iter().enumerate() {
// This empty line check accounts for the case in which a word can't fit on the first
// line with an indent, but could otherwise fit on a full line by itself.
if visual_line.ranges.is_empty() {
layout_lines.push(LayoutLine {
w: 0.0,
max_ascent: 0.0,
max_descent: 0.0,
line_height_opt: None,
glyphs: Default::default(),
});
continue;
}
let first_line = index == 0;
let new_order = self.reorder(&visual_line.ranges);
let mut glyphs = Vec::with_capacity(1);
let mut x = start_x;
Expand Down Expand Up @@ -1406,13 +1448,18 @@ impl ShapeLine {
// (also some spaces aren't followed by potential linebreaks but they could
// still be expanded)

let current_line_width = if first_line {
line_width - first_line_indent
} else {
line_width
};
// Amount of extra width added to each blank space within a line.
let justification_expansion = if matches!(align, Align::Justified)
&& visual_line.spaces > 0
// Don't justify the last line in a paragraph.
&& index != number_of_visual_lines - 1
{
(line_width - visual_line.w) / visual_line.spaces as f32
(current_line_width - visual_line.w) / visual_line.spaces as f32
} else {
0.
};
Expand Down Expand Up @@ -1505,17 +1552,19 @@ impl ShapeLine {
};
}
}

layout_lines.push(LayoutLine {
w: if align != Align::Justified {
visual_line.w
} else if self.rtl {
let current_line_width = if align != Align::Justified {
visual_line.w - if first_line { first_line_indent } else { 0. }
} else {
if self.rtl {
start_x - x
} else {
x
},
max_ascent,
max_descent,
}
};

Check warning on line 1563 in src/shape.rs

View workflow job for this annotation

GitHub Actions / clippy

this `else { if .. }` block can be collapsed

warning: this `else { if .. }` block can be collapsed --> src/shape.rs:1557:20 | 1557 | } else { | ____________________^ 1558 | | if self.rtl { 1559 | | start_x - x 1560 | | } else { 1561 | | x 1562 | | } 1563 | | }; | |_____________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_else_if = note: `#[warn(clippy::collapsible_else_if)]` on by default help: collapse nested if block | 1557 ~ } else if self.rtl { 1558 + start_x - x 1559 + } else { 1560 + x 1561 ~ }; |
layout_lines.push(LayoutLine {
w: current_line_width,
max_ascent: max_ascent * font_size,
max_descent: max_descent * font_size,
line_height_opt,
glyphs,
});
Expand Down
18 changes: 16 additions & 2 deletions tests/wrap_stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,28 @@ fn stable_wrap() {
let mut check_wrap = |text: &_, wrap, align_opt, start_width_opt| {
let line = ShapeLine::new(&mut font_system, text, &attrs, Shaping::Advanced, 8);

let layout_unbounded = line.layout(font_size, start_width_opt, wrap, align_opt, None);
let layout_unbounded = line.layout(
font_size,
start_width_opt,
wrap,
Some(Align::Left),
/* first_line_indent */ None,
/* match_mono_width */ None,
);
let max_width = layout_unbounded.iter().map(|l| l.w).fold(0.0, f32::max);
let new_limit = match start_width_opt {
Some(start_width) => f32::min(start_width, max_width),
None => max_width,
};

let layout_bounded = line.layout(font_size, Some(new_limit), wrap, align_opt, None);
let layout_bounded = line.layout(
font_size,
Some(new_limit),
wrap,
Some(Align::Left),
/* first_line_indent */ None,
/* match_mono_width */ None,
);
let bounded_max_width = layout_bounded.iter().map(|l| l.w).fold(0.0, f32::max);

// For debugging:
Expand Down

0 comments on commit 167d0d8

Please sign in to comment.