Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tab_width support #490

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions benchmarks/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ name = "unfill"
harness = false
path = "unfill.rs"

[[bench]]
name = "display_width"
harness = false
path = "display_width.rs"

[dependencies]
textwrap = { path = "../", features = ["hyphenation"] }

Expand Down
26 changes: 26 additions & 0 deletions benchmarks/display_width.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use criterion::{criterion_group, criterion_main, Criterion};

pub fn benchmark(c: &mut Criterion) {
let words_per_line = [
5, 10, 15, 5, 5, 10, 5, 5, 5, 10, // 10 lines
10, 10, 5, 5, 5, 5, 15, 10, 5, 5, // 20 lines
10, 5, 5, 5, 15, 10, 10, 5, 5, 5, // 30 lines
15, 5, 5, 10, 5, 5, 5, 15, 5, 10, // 40 lines
5, 15, 5, 5, 15, 5, 10, 10, 5, 5, // 50 lines
];
let mut text = String::new();
for (line_no, word_count) in words_per_line.iter().enumerate() {
text.push_str("\t\t\t");
text.push_str(&lipsum::lipsum_words_from_seed(*word_count, line_no as u64));
text.push('\n');
}
text.push_str("\n\n\n\n");
assert_eq!(text.len(), 2800); // The size for reference.

c.bench_function("display_width", |b| {
b.iter(|| textwrap::core::display_width(&text, 2))
});
}

criterion_group!(benches, benchmark);
criterion_main!(benches);
8 changes: 4 additions & 4 deletions examples/wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ impl textwrap::core::Fragment for CanvasWord<'_> {
}

#[inline]
fn whitespace_width(&self) -> f64 {
fn whitespace_width(&self, _: u8) -> f64 {
self.whitespace_width
}

Expand Down Expand Up @@ -351,7 +351,7 @@ pub fn draw_wrapped_text(
let mut lineno = 0;
for line in text.split('\n') {
let words = word_separator.find_words(line);
let split_words = split_words(words, &word_splitter);
let split_words = split_words(words, &word_splitter, 0);

let canvas_words = split_words
.flat_map(|word| {
Expand All @@ -366,10 +366,10 @@ pub fn draw_wrapped_text(

let line_lengths = [options.width];
let wrapped_words = match options.wrap_algorithm {
WasmWrapAlgorithm::FirstFit => wrap_first_fit(&canvas_words, &line_lengths),
WasmWrapAlgorithm::FirstFit => wrap_first_fit(&canvas_words, &line_lengths, 0),
WasmWrapAlgorithm::OptimalFit => {
let penalties = options.penalties.into();
wrap_optimal_fit(&canvas_words, &line_lengths, &penalties).unwrap()
wrap_optimal_fit(&canvas_words, &line_lengths, 0, &penalties).unwrap()
}
_ => Err("WasmOptions has an invalid wrap_algorithm field")?,
};
Expand Down
4 changes: 2 additions & 2 deletions fuzz/fuzz_targets/wrap_first_fit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ struct Word {
#[rustfmt::skip]
impl core::Fragment for Word {
fn width(&self) -> f64 { self.width }
fn whitespace_width(&self) -> f64 { self.whitespace_width }
fn whitespace_width(&self, _: u8) -> f64 { self.whitespace_width }
fn penalty_width(&self) -> f64 { self.penalty_width }
}

fuzz_target!(|input: (f64, Vec<Word>)| {
let width = input.0;
let words = input.1;
let _ = wrap_first_fit(&words, &[width]);
let _ = wrap_first_fit(&words, &[width], 0);
});
4 changes: 2 additions & 2 deletions fuzz/fuzz_targets/wrap_optimal_fit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct Word {
#[rustfmt::skip]
impl core::Fragment for Word {
fn width(&self) -> f64 { self.width }
fn whitespace_width(&self) -> f64 { self.whitespace_width }
fn whitespace_width(&self, _: u8) -> f64 { self.whitespace_width }
fn penalty_width(&self) -> f64 { self.penalty_width }
}

Expand All @@ -57,5 +57,5 @@ fuzz_target!(|input: (usize, Vec<Word>, Penalties)| {
}
}

let _ = wrap_optimal_fit(&words, &[width as f64], &penalties);
let _ = wrap_optimal_fit(&words, &[width as f64], 0, &penalties);
});
4 changes: 2 additions & 2 deletions fuzz/fuzz_targets/wrap_optimal_fit_usize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct Word {
#[rustfmt::skip]
impl core::Fragment for Word {
fn width(&self) -> f64 { self.width as f64 }
fn whitespace_width(&self) -> f64 { self.whitespace_width as f64 }
fn whitespace_width(&self, _: u8) -> f64 { self.whitespace_width as f64 }
fn penalty_width(&self) -> f64 { self.penalty_width as f64 }
}

Expand All @@ -45,5 +45,5 @@ fuzz_target!(|input: (usize, Vec<Word>, Penalties)| {
let width = input.0;
let words = input.1;
let penalties = input.2.into();
let _ = wrap_optimal_fit(&words, &[width as f64], &penalties);
let _ = wrap_optimal_fit(&words, &[width as f64], 0, &penalties);
});
28 changes: 23 additions & 5 deletions src/columns.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! Functionality for wrapping text into columns.

use crate::core::display_width;
use crate::{wrap, Options};
use crate::{core, wrap, Options};

/// Wrap text into columns with a given total width.
///
Expand All @@ -23,9 +22,9 @@ use crate::{wrap, Options};
/// # let columns = 2;
/// # let options = textwrap::Options::new(80);
/// let inner_width = options.width
/// - textwrap::core::display_width(left_gap)
/// - textwrap::core::display_width(right_gap)
/// - textwrap::core::display_width(middle_gap) * (columns - 1);
/// - textwrap::core::display_width(left_gap, options.tab_width)
/// - textwrap::core::display_width(right_gap, options.tab_width)
/// - textwrap::core::display_width(middle_gap, options.tab_width) * (columns - 1);
/// let column_width = inner_width / columns;
/// ```
///
Expand Down Expand Up @@ -74,6 +73,8 @@ where
assert!(columns > 0);

let mut options: Options = total_width_or_options.into();
let tab_width = options.tab_width;
let display_width = |text| core::display_width(text, tab_width);

let inner_width = options
.width
Expand Down Expand Up @@ -190,4 +191,21 @@ mod tests {
fn wrap_columns_panic_with_zero_columns() {
wrap_columns("", 0, 10, "", "", "");
}

#[test]
fn wrap_columns_with_tabs() {
let options = Options::new(23).tab_width(4);

#[cfg(feature = "smawk")]
let expected = vec!["|hello |is\tlong|", "|this |yeah |"];
#[cfg(all(not(feature = "smawk"), feature = "unicode-linebreak"))]
let expected = vec!["|hello |long |", "|this\tis|yeah |"];
#[cfg(not(any(feature = "smawk", feature = "unicode-linebreak")))]
let expected = vec!["|hello\tt|\tlong |", "|his\tis |\tyeah |"];

assert_eq!(
wrap_columns("hello\tthis\tis\tlong\tyeah", 2, options, "|", "|", "|"),
expected
)
}
}