pub struct Penalties {
    pub nline_penalty: usize,
    pub overflow_penalty: usize,
    pub short_last_line_fraction: usize,
    pub short_last_line_penalty: usize,
    pub hyphen_penalty: usize,
}
Expand description

Penalties for WrapAlgorithm::OptimalFit and wrap_optimal_fit.

This wrapping algorithm in wrap_optimal_fit considers the entire paragraph to find optimal line breaks. When wrapping text, “penalties” are assigned to line breaks based on the gaps left at the end of lines. The penalties are given by this struct, with Penalties::default assigning penalties that work well for monospace text.

If you are wrapping proportional text, you are advised to assign your own penalties according to your font size. See the individual penalties below for details.

Note: Only available when the smawk Cargo feature is enabled.

Fields§

§nline_penalty: usize

Per-line penalty. This is added for every line, which makes it expensive to output more lines than the minimum required.

§overflow_penalty: usize

Per-character cost for lines that overflow the target line width.

With a default value of 50², every single character costs as much as leaving a gap of 50 characters behind. This is because we assign as cost of gap * gap to a short line. When wrapping monospace text, we can overflow the line by 1 character in extreme cases:

use textwrap::core::Word;
use textwrap::wrap_algorithms::{wrap_optimal_fit, Penalties};

let short = "foo ";
let long = "x".repeat(50);
let length = (short.len() + long.len()) as f64;
let fragments = vec![Word::from(short), Word::from(&long)];
let penalties = Penalties::new();

// Perfect fit, both words are on a single line with no overflow.
let wrapped = wrap_optimal_fit(&fragments, &[length], &penalties).unwrap();
assert_eq!(wrapped, vec![&[Word::from(short), Word::from(&long)]]);

// The words no longer fit, yet we get a single line back. While
// the cost of overflow (`1 * 2500`) is the same as the cost of the
// gap (`50 * 50 = 2500`), the tie is broken by `nline_penalty`
// which makes it cheaper to overflow than to use two lines.
let wrapped = wrap_optimal_fit(&fragments, &[length - 1.0], &penalties).unwrap();
assert_eq!(wrapped, vec![&[Word::from(short), Word::from(&long)]]);

// The cost of overflow would be 2 * 2500, whereas the cost of
// the gap is only `49 * 49 + nline_penalty = 2401 + 1000 =
// 3401`. We therefore get two lines.
let wrapped = wrap_optimal_fit(&fragments, &[length - 2.0], &penalties).unwrap();
assert_eq!(wrapped, vec![&[Word::from(short)],
                         &[Word::from(&long)]]);

This only happens if the overflowing word is 50 characters long and if the word overflows the line by exactly one character. If it overflows by more than one character, the overflow penalty will quickly outgrow the cost of the gap, as seen above.

§short_last_line_fraction: usize

When should the a single word on the last line be considered “too short”?

If the last line of the text consist of a single word and if this word is shorter than 1 / short_last_line_fraction of the line width, then the final line will be considered “short” and short_last_line_penalty is added as an extra penalty.

The effect of this is to avoid a final line consisting of a single small word. For example, with a short_last_line_penalty of 25 (the default), a gap of up to 5 columns will be seen as more desirable than having a final short line.

§Examples

use textwrap::{wrap, wrap_algorithms, Options, WrapAlgorithm};

let text = "This is a demo of the short last line penalty.";

// The first-fit algorithm leaves a single short word on the last line:
assert_eq!(wrap(text, Options::new(37).wrap_algorithm(WrapAlgorithm::FirstFit)),
           vec!["This is a demo of the short last line",
                "penalty."]);

#[cfg(feature = "smawk")] {
let mut penalties = wrap_algorithms::Penalties::new();

// Since "penalty." is shorter than 25% of the line width, the
// optimal-fit algorithm adds a penalty of 25. This is enough
// to move "line " down:
assert_eq!(wrap(text, Options::new(37).wrap_algorithm(WrapAlgorithm::OptimalFit(penalties))),
           vec!["This is a demo of the short last",
                "line penalty."]);

// We can change the meaning of "short" lines. Here, only words
// shorter than 1/10th of the line width will be considered short:
penalties.short_last_line_fraction = 10;
assert_eq!(wrap(text, Options::new(37).wrap_algorithm(WrapAlgorithm::OptimalFit(penalties))),
           vec!["This is a demo of the short last line",
                "penalty."]);

// If desired, the penalty can also be disabled:
penalties.short_last_line_fraction = 4;
penalties.short_last_line_penalty = 0;
assert_eq!(wrap(text, Options::new(37).wrap_algorithm(WrapAlgorithm::OptimalFit(penalties))),
           vec!["This is a demo of the short last line",
                "penalty."]);
}
§short_last_line_penalty: usize

Penalty for a last line with a single short word.

Set this to zero if you do not want to penalize short last lines.

§hyphen_penalty: usize

Penalty for lines ending with a hyphen.

Implementations§

source§

impl Penalties

source

pub const fn new() -> Self

Default penalties for monospace text.

The penalties here work well for monospace text. This is because they expect the gaps at the end of lines to be roughly in the range 0..100. If the gaps are larger, the overflow_penalty and hyphen_penalty become insignificant.

Trait Implementations§

source§

impl Clone for Penalties

source§

fn clone(&self) -> Penalties

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Penalties

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for Penalties

source§

fn default() -> Self

Returns the “default value” for a type. Read more
source§

impl PartialEq for Penalties

source§

fn eq(&self, other: &Penalties) -> bool

This method tests for self and other values to be equal, and is used by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
source§

impl Copy for Penalties

source§

impl Eq for Penalties

source§

impl StructuralPartialEq for Penalties

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> ToOwned for T
where T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.