From d2af19986539520466b3cfa6fe57e0e4aa65b71f Mon Sep 17 00:00:00 2001 From: Jack Nichols Date: Fri, 21 Jun 2024 16:47:35 -0400 Subject: [PATCH 1/4] first line indent --- src/buffer.rs | 5 +++ src/buffer_line.rs | 2 + src/shape.rs | 85 ++++++++++++++++++++++++++++++++--------- tests/wrap_stability.rs | 18 ++++++++- 4 files changed, 90 insertions(+), 20 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 0292e6bd3a..b6968762cd 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -208,6 +208,7 @@ pub struct Buffer { metrics: Metrics, width_opt: Option, height_opt: Option, + first_line_indent: Option, scroll: Scroll, /// True if a redraw is requires. Set to false after processing redraw: bool, @@ -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, @@ -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, @@ -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, @@ -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, diff --git a/src/buffer_line.rs b/src/buffer_line.rs index 6929e03a55..f0356064b7 100644 --- a/src/buffer_line.rs +++ b/src/buffer_line.rs @@ -235,6 +235,7 @@ impl BufferLine { font_system: &mut FontSystem, font_size: f32, width_opt: Option, + first_line_indent: Option, wrap: Wrap, match_mono_width: Option, tab_width: u16, @@ -252,6 +253,7 @@ impl BufferLine { width_opt, wrap, align, + first_line_indent, &mut layout, match_mono_width, ); diff --git a/src/shape.rs b/src/shape.rs index 98eb730bb9..53e97a2d26 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -1086,6 +1086,7 @@ impl ShapeLine { width_opt: Option, wrap: Wrap, align: Option, + first_line_indent: Option, match_mono_width: Option, ) -> Vec { let mut lines = Vec::with_capacity(1); @@ -1095,6 +1096,7 @@ impl ShapeLine { width_opt, wrap, align, + first_line_indent, &mut lines, match_mono_width, ); @@ -1108,6 +1110,7 @@ impl ShapeLine { width_opt: Option, wrap: Wrap, align: Option, + first_line_head_indent: Option, layout_lines: &mut Vec, match_mono_width: Option, ) { @@ -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 = 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() { @@ -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, @@ -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, @@ -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 @@ -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() @@ -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. }; @@ -1608,17 +1655,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, + } + }; + layout_lines.push(LayoutLine { + w: current_line_width, + max_ascent: max_ascent * font_size, + max_descent: max_descent * font_size, line_height_opt, glyphs, }); diff --git a/tests/wrap_stability.rs b/tests/wrap_stability.rs index 3bcdf29f5d..bd8bd1173d 100644 --- a/tests/wrap_stability.rs +++ b/tests/wrap_stability.rs @@ -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: From 2a459ec88c5216235c35e94e0268d529d00ba008 Mon Sep 17 00:00:00 2001 From: David Stern Date: Fri, 11 Oct 2024 14:46:27 -0400 Subject: [PATCH 2/4] Bump dependencies so we can standardize on single (and newer) versions at the app level. (#4) Bump dependencies so we can standardize on single versions at the app level. --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ffa1eae815..e5b459022e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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.22", 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.18", 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.24.1", default-features = false, features = ["opentype-layout"] } unicode-linebreak = "0.1.5" unicode-script = "0.5.5" unicode-segmentation = "1.10.1" @@ -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 = [ From 3c1f02842403a33543ac3901714a5de32c0c63a2 Mon Sep 17 00:00:00 2001 From: David Stern Date: Tue, 11 Mar 2025 09:41:09 -0400 Subject: [PATCH 3/4] Bump version for a handful of dependencies. (#5) --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e5b459022e..de8a192d95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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.22", 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.18", default-features = false } +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.24.1", default-features = false, features = ["opentype-layout"] } +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" From 9f1f88ed210d0fe4914e291b444561604412786f Mon Sep 17 00:00:00 2001 From: David Stern Date: Thu, 8 May 2025 11:22:44 -0400 Subject: [PATCH 4/4] Fix incorrect computation of ascent and descent. (#6) --- src/shape.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shape.rs b/src/shape.rs index 53e97a2d26..3f5d2d6c5d 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -1666,8 +1666,8 @@ impl ShapeLine { }; layout_lines.push(LayoutLine { w: current_line_width, - max_ascent: max_ascent * font_size, - max_descent: max_descent * font_size, + max_ascent, + max_descent, line_height_opt, glyphs, });