1 //! Word wrapping algorithms.
2 //!
3 //! After a text has been broken into words (or [`Fragment`]s), one
4 //! now has to decide how to break the fragments into lines. The
5 //! simplest algorithm for this is implemented by [`wrap_first_fit`]:
6 //! it uses no look-ahead and simply adds fragments to the line as
7 //! long as they fit. However, this can lead to poor line breaks if a
8 //! large fragment almost-but-not-quite fits on a line. When that
9 //! happens, the fragment is moved to the next line and it will leave
10 //! behind a large gap. A more advanced algorithm, implemented by
11 //! [`wrap_optimal_fit`], will take this into account. The optimal-fit
12 //! algorithm considers all possible line breaks and will attempt to
13 //! minimize the gaps left behind by overly short lines.
14 //!
15 //! While both algorithms run in linear time, the first-fit algorithm
16 //! is about 4 times faster than the optimal-fit algorithm.
17 
18 #[cfg(feature = "smawk")]
19 mod optimal_fit;
20 #[cfg(feature = "smawk")]
21 pub use optimal_fit::{wrap_optimal_fit, OptimalFit};
22 
23 use crate::core::{Fragment, Word};
24 
25 /// Describes how to wrap words into lines.
26 ///
27 /// The simplest approach is to wrap words one word at a time. This is
28 /// implemented by [`FirstFit`]. If the `smawk` Cargo feature is
29 /// enabled, a more complex algorithm is available, implemented by
30 /// [`OptimalFit`], which will look at an entire paragraph at a time
31 /// in order to find optimal line breaks.
32 pub trait WrapAlgorithm: WrapAlgorithmClone + std::fmt::Debug {
33     /// Wrap words according to line widths.
34     ///
35     /// The `line_widths` slice gives the target line width for each
36     /// line (the last slice element is repeated as necessary). This
37     /// can be used to implement hanging indentation.
38     ///
39     /// Please see the implementors of the trait for examples.
wrap<'a, 'b>(&self, words: &'b [Word<'a>], line_widths: &'b [usize]) -> Vec<&'b [Word<'a>]>40     fn wrap<'a, 'b>(&self, words: &'b [Word<'a>], line_widths: &'b [usize]) -> Vec<&'b [Word<'a>]>;
41 }
42 
43 // The internal `WrapAlgorithmClone` trait is allows us to implement
44 // `Clone` for `Box<dyn WrapAlgorithm>`. This in used in the
45 // `From<&Options<'_, WrapAlgo, WordSep, WordSplit>> for Options<'a,
46 // WrapAlgo, WordSep, WordSplit>` implementation.
47 #[doc(hidden)]
48 pub trait WrapAlgorithmClone {
clone_box(&self) -> Box<dyn WrapAlgorithm>49     fn clone_box(&self) -> Box<dyn WrapAlgorithm>;
50 }
51 
52 impl<T: WrapAlgorithm + Clone + 'static> WrapAlgorithmClone for T {
clone_box(&self) -> Box<dyn WrapAlgorithm>53     fn clone_box(&self) -> Box<dyn WrapAlgorithm> {
54         Box::new(self.clone())
55     }
56 }
57 
58 impl Clone for Box<dyn WrapAlgorithm> {
clone(&self) -> Box<dyn WrapAlgorithm>59     fn clone(&self) -> Box<dyn WrapAlgorithm> {
60         use std::ops::Deref;
61         self.deref().clone_box()
62     }
63 }
64 
65 impl WrapAlgorithm for Box<dyn WrapAlgorithm> {
wrap<'a, 'b>(&self, words: &'b [Word<'a>], line_widths: &'b [usize]) -> Vec<&'b [Word<'a>]>66     fn wrap<'a, 'b>(&self, words: &'b [Word<'a>], line_widths: &'b [usize]) -> Vec<&'b [Word<'a>]> {
67         use std::ops::Deref;
68         self.deref().wrap(words, line_widths)
69     }
70 }
71 
72 /// Wrap words using a fast and simple algorithm.
73 ///
74 /// This algorithm uses no look-ahead when finding line breaks.
75 /// Implemented by [`wrap_first_fit`], please see that function for
76 /// details and examples.
77 #[derive(Clone, Copy, Debug, Default)]
78 pub struct FirstFit;
79 
80 impl WrapAlgorithm for FirstFit {
81     #[inline]
wrap<'a, 'b>(&self, words: &'b [Word<'a>], line_widths: &'b [usize]) -> Vec<&'b [Word<'a>]>82     fn wrap<'a, 'b>(&self, words: &'b [Word<'a>], line_widths: &'b [usize]) -> Vec<&'b [Word<'a>]> {
83         wrap_first_fit(words, line_widths)
84     }
85 }
86 
87 /// Wrap abstract fragments into lines with a first-fit algorithm.
88 ///
89 /// The `line_widths` slice gives the target line width for each line
90 /// (the last slice element is repeated as necessary). This can be
91 /// used to implement hanging indentation.
92 ///
93 /// The fragments must already have been split into the desired
94 /// widths, this function will not (and cannot) attempt to split them
95 /// further when arranging them into lines.
96 ///
97 /// # First-Fit Algorithm
98 ///
99 /// This implements a simple “greedy” algorithm: accumulate fragments
100 /// one by one and when a fragment no longer fits, start a new line.
101 /// There is no look-ahead, we simply take first fit of the fragments
102 /// we find.
103 ///
104 /// While fast and predictable, this algorithm can produce poor line
105 /// breaks when a long fragment is moved to a new line, leaving behind
106 /// a large gap:
107 ///
108 /// ```
109 /// use textwrap::core::Word;
110 /// use textwrap::wrap_algorithms;
111 /// use textwrap::word_separators::{AsciiSpace, WordSeparator};
112 ///
113 /// // Helper to convert wrapped lines to a Vec<String>.
114 /// fn lines_to_strings(lines: Vec<&[Word<'_>]>) -> Vec<String> {
115 ///     lines.iter().map(|line| {
116 ///         line.iter().map(|word| &**word).collect::<Vec<_>>().join(" ")
117 ///     }).collect::<Vec<_>>()
118 /// }
119 ///
120 /// let text = "These few words will unfortunately not wrap nicely.";
121 /// let words = AsciiSpace.find_words(text).collect::<Vec<_>>();
122 /// assert_eq!(lines_to_strings(wrap_algorithms::wrap_first_fit(&words, &[15])),
123 ///            vec!["These few words",
124 ///                 "will",  // <-- short line
125 ///                 "unfortunately",
126 ///                 "not wrap",
127 ///                 "nicely."]);
128 ///
129 /// // We can avoid the short line if we look ahead:
130 /// #[cfg(feature = "smawk")]
131 /// assert_eq!(lines_to_strings(wrap_algorithms::wrap_optimal_fit(&words, &[15])),
132 ///            vec!["These few",
133 ///                 "words will",
134 ///                 "unfortunately",
135 ///                 "not wrap",
136 ///                 "nicely."]);
137 /// ```
138 ///
139 /// The [`wrap_optimal_fit`] function was used above to get better
140 /// line breaks. It uses an advanced algorithm which tries to avoid
141 /// short lines. This function is about 4 times faster than
142 /// [`wrap_optimal_fit`].
143 ///
144 /// # Examples
145 ///
146 /// Imagine you're building a house site and you have a number of
147 /// tasks you need to execute. Things like pour foundation, complete
148 /// framing, install plumbing, electric cabling, install insulation.
149 ///
150 /// The construction workers can only work during daytime, so they
151 /// need to pack up everything at night. Because they need to secure
152 /// their tools and move machines back to the garage, this process
153 /// takes much more time than the time it would take them to simply
154 /// switch to another task.
155 ///
156 /// You would like to make a list of tasks to execute every day based
157 /// on your estimates. You can model this with a program like this:
158 ///
159 /// ```
160 /// use textwrap::wrap_algorithms::wrap_first_fit;
161 /// use textwrap::core::{Fragment, Word};
162 ///
163 /// #[derive(Debug)]
164 /// struct Task<'a> {
165 ///     name: &'a str,
166 ///     hours: usize,   // Time needed to complete task.
167 ///     sweep: usize,   // Time needed for a quick sweep after task during the day.
168 ///     cleanup: usize, // Time needed for full cleanup if day ends with this task.
169 /// }
170 ///
171 /// impl Fragment for Task<'_> {
172 ///     fn width(&self) -> usize { self.hours }
173 ///     fn whitespace_width(&self) -> usize { self.sweep }
174 ///     fn penalty_width(&self) -> usize { self.cleanup }
175 /// }
176 ///
177 /// // The morning tasks
178 /// let tasks = vec![
179 ///     Task { name: "Foundation",  hours: 4, sweep: 2, cleanup: 3 },
180 ///     Task { name: "Framing",     hours: 3, sweep: 1, cleanup: 2 },
181 ///     Task { name: "Plumbing",    hours: 2, sweep: 2, cleanup: 2 },
182 ///     Task { name: "Electrical",  hours: 2, sweep: 1, cleanup: 2 },
183 ///     Task { name: "Insulation",  hours: 2, sweep: 1, cleanup: 2 },
184 ///     Task { name: "Drywall",     hours: 3, sweep: 1, cleanup: 2 },
185 ///     Task { name: "Floors",      hours: 3, sweep: 1, cleanup: 2 },
186 ///     Task { name: "Countertops", hours: 1, sweep: 1, cleanup: 2 },
187 ///     Task { name: "Bathrooms",   hours: 2, sweep: 1, cleanup: 2 },
188 /// ];
189 ///
190 /// // Fill tasks into days, taking `day_length` into account. The
191 /// // output shows the hours worked per day along with the names of
192 /// // the tasks for that day.
193 /// fn assign_days<'a>(tasks: &[Task<'a>], day_length: usize) -> Vec<(usize, Vec<&'a str>)> {
194 ///     let mut days = Vec::new();
195 ///     // Assign tasks to days. The assignment is a vector of slices,
196 ///     // with a slice per day.
197 ///     let assigned_days: Vec<&[Task<'a>]> = wrap_first_fit(&tasks, &[day_length]);
198 ///     for day in assigned_days.iter() {
199 ///         let last = day.last().unwrap();
200 ///         let work_hours: usize = day.iter().map(|t| t.hours + t.sweep).sum();
201 ///         let names = day.iter().map(|t| t.name).collect::<Vec<_>>();
202 ///         days.push((work_hours - last.sweep + last.cleanup, names));
203 ///     }
204 ///     days
205 /// }
206 ///
207 /// // With a single crew working 8 hours a day:
208 /// assert_eq!(
209 ///     assign_days(&tasks, 8),
210 ///     [
211 ///         (7, vec!["Foundation"]),
212 ///         (8, vec!["Framing", "Plumbing"]),
213 ///         (7, vec!["Electrical", "Insulation"]),
214 ///         (5, vec!["Drywall"]),
215 ///         (7, vec!["Floors", "Countertops"]),
216 ///         (4, vec!["Bathrooms"]),
217 ///     ]
218 /// );
219 ///
220 /// // With two crews working in shifts, 16 hours a day:
221 /// assert_eq!(
222 ///     assign_days(&tasks, 16),
223 ///     [
224 ///         (14, vec!["Foundation", "Framing", "Plumbing"]),
225 ///         (15, vec!["Electrical", "Insulation", "Drywall", "Floors"]),
226 ///         (6, vec!["Countertops", "Bathrooms"]),
227 ///     ]
228 /// );
229 /// ```
230 ///
231 /// Apologies to anyone who actually knows how to build a house and
232 /// knows how long each step takes :-)
wrap_first_fit<'a, 'b, T: Fragment>( fragments: &'a [T], line_widths: &'b [usize], ) -> Vec<&'a [T]>233 pub fn wrap_first_fit<'a, 'b, T: Fragment>(
234     fragments: &'a [T],
235     line_widths: &'b [usize],
236 ) -> Vec<&'a [T]> {
237     // The final line width is used for all remaining lines.
238     let default_line_width = line_widths.last().copied().unwrap_or(0);
239     let mut lines = Vec::new();
240     let mut start = 0;
241     let mut width = 0;
242 
243     for (idx, fragment) in fragments.iter().enumerate() {
244         let line_width = line_widths
245             .get(lines.len())
246             .copied()
247             .unwrap_or(default_line_width);
248         if width + fragment.width() + fragment.penalty_width() > line_width && idx > start {
249             lines.push(&fragments[start..idx]);
250             start = idx;
251             width = 0;
252         }
253         width += fragment.width() + fragment.whitespace_width();
254     }
255     lines.push(&fragments[start..]);
256     lines
257 }
258