Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@ rust-version = "1.75"
[dependencies]
bitflags = "2.4.1"
cosmic_undo_2 = { version = "0.2.0", optional = true }
fontdb = { version = "0.16", default-features = false }
fontdb = { version = "0.23", default-features = false }
hashbrown = { version = "0.14.1", optional = true, default-features = false }
libm = { version = "0.2.8", optional = true }
log = "0.4.20"
modit = { version = "0.1.4", optional = true }
rangemap = "1.4.0"
rustc-hash = { version = "1.1.0", default-features = false }
rustybuzz = { version = "0.14", default-features = false, features = ["libm"] }
rustybuzz = { version = "0.20.1", default-features = false }
self_cell = "1.0.1"
smol_str = { version = "0.2.2", default-features = false }
syntect = { version = "5.1.0", optional = true }
sys-locale = { version = "0.3.1", optional = true }
ttf-parser = { version = "0.21", default-features = false }
ttf-parser = { version = "0.25.1", default-features = false, features = ["opentype-layout"] }
unicode-linebreak = "0.1.5"
unicode-script = "0.5.5"
unicode-segmentation = "1.10.1"
Expand All @@ -48,7 +48,7 @@ optional = true
default = ["std", "swash", "fontconfig"]
fontconfig = ["fontdb/fontconfig", "std"]
monospace_fallback = []
no_std = ["rustybuzz/libm", "hashbrown", "dep:libm"]
no_std = ["hashbrown", "dep:libm"]
peniko = ["dep:peniko"]
shape-run-cache = []
std = [
Expand Down
5 changes: 5 additions & 0 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,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 @@ -223,6 +224,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 @@ -251,6 +253,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 @@ -292,6 +295,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 @@ -539,6 +543,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
2 changes: 2 additions & 0 deletions src/buffer_line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,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 @@ -252,6 +253,7 @@ impl BufferLine {
width_opt,
wrap,
align,
first_line_indent,
&mut layout,
match_mono_width,
);
Expand Down
81 changes: 65 additions & 16 deletions src/shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,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 @@ -1095,6 +1096,7 @@ impl ShapeLine {
width_opt,
wrap,
align,
first_line_indent,
&mut lines,
match_mono_width,
);
Expand All @@ -1108,6 +1110,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 @@ -1147,11 +1150,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 = cached_visual_lines.pop().unwrap_or_default();
let mut current_visual_line = cached_visual_lines.pop().unwrap_or_else(|| 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 @@ -1203,14 +1214,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 @@ -1329,14 +1351,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 @@ -1383,7 +1416,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 @@ -1468,9 +1500,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 = cached_glyph_sets
.pop()
Expand Down Expand Up @@ -1509,13 +1551,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 @@ -1608,15 +1655,17 @@ 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
},
}
};
layout_lines.push(LayoutLine {
w: current_line_width,
max_ascent,
max_descent,
line_height_opt,
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