pprint/
utils.rs

1use std::usize;
2
3/// Text justification algorithm inspired by LaTeX's algorithm.
4///
5/// This function takes a list of document lengths and a maximum line width, and returns a vector
6/// of indices that represent the end of each line in the justified text. The algorithm minimizes
7/// the "badness" of each line, which is defined as the cube of the unused space at the end of the
8/// line.
9///
10/// # Arguments
11///
12/// * `sep_length` - The length of the separator between words.
13/// * `doc_lengths` - A vector of the lengths of each word in the document.
14/// * `max_width` - The maximum line width.
15///
16/// # Returns
17///
18/// A vector of indices that represent the end of each line in the justified text.
19pub fn text_justify(sep_length: usize, doc_lengths: &Vec<usize>, max_width: usize) -> Vec<usize> {
20    // Score struct to hold the badness and the index of the next word
21    #[derive(Clone, Debug)]
22    struct Score {
23        badness: usize,
24        j: usize,
25    }
26
27    // Initialize memoization vector with maximum badness and the index of the next word
28    let n = doc_lengths.len();
29    let mut memo = vec![
30        Score {
31            badness: usize::MAX,
32            j: n
33        };
34        n + 1
35    ];
36    // The last word has no badness and does not point to any next word
37    memo[n] = Score { badness: 0, j: 0 };
38
39    // Iterate over the words in reverse order
40    for i in (0..=n).rev() {
41        let mut line_length = 0;
42
43        // For each word, calculate the line length and badness
44        for j in i..n {
45            // Add the length of the current word to the line length
46            line_length += doc_lengths[j];
47            // Add the separator length if this is not the first word in the line
48            if j > i {
49                line_length += sep_length;
50            }
51            // Ensure that the line length does not exceed the maximum width
52            line_length = line_length.min(max_width);
53
54            // Calculate the badness as the cube of the unused space at the end of the line
55            let badness = (max_width - line_length).pow(3);
56            // Get the score of the next word
57            let next_score = memo[j + 1].clone();
58
59            // If the total badness of this line and the next is less than the current badness,
60            // update the score for this word
61            if badness + next_score.badness < memo[i].badness {
62                memo[i] = Score {
63                    badness: badness + next_score.badness,
64                    j: j + 1,
65                };
66            }
67            // If the line length has reached the maximum width, break the loop
68            if line_length >= max_width {
69                break;
70            }
71        }
72    }
73
74    // Generate the list of line breaks by scanning the memoization vector
75    (0..n)
76        .scan(0, |i, _| {
77            let j = memo[*i].j;
78            *i = j;
79            Some(j)
80        })
81        .collect::<Vec<_>>()
82}