term_grid/
lib.rs

1#![warn(future_incompatible)]
2#![warn(missing_copy_implementations)]
3#![warn(missing_docs)]
4#![warn(nonstandard_style)]
5#![warn(rust_2018_compatibility)]
6#![warn(rust_2018_idioms)]
7#![warn(trivial_casts, trivial_numeric_casts)]
8#![warn(unused)]
9
10#![deny(unsafe_code)]
11
12
13//! This library arranges textual data in a grid format suitable for
14//! fixed-width fonts, using an algorithm to minimise the amount of space
15//! needed. For example:
16//!
17//! ```rust
18//! use term_grid::{Grid, GridOptions, Direction, Filling, Cell};
19//!
20//! let mut grid = Grid::new(GridOptions {
21//!     filling:    Filling::Spaces(1),
22//!     direction:  Direction::LeftToRight,
23//! });
24//!
25//! for s in &["one", "two", "three", "four", "five", "six", "seven",
26//!            "eight", "nine", "ten", "eleven", "twelve"]
27//! {
28//!     grid.add(Cell::from(*s));
29//! }
30//!
31//! println!("{}", grid.fit_into_width(24).unwrap());
32//! ```
33//!
34//! Produces the following tabular result:
35//!
36//! ```text
37//! one  two three  four
38//! five six seven  eight
39//! nine ten eleven twelve
40//! ```
41//!
42//!
43//! ## Creating a grid
44//!
45//! To add data to a grid, first create a new [`Grid`] value, and then add
46//! cells to them with the `add` function.
47//!
48//! There are two options that must be specified in the [`GridOptions`] value
49//! that dictate how the grid is formatted:
50//!
51//! - `filling`: what to put in between two columns — either a number of
52//!    spaces, or a text string;
53//! - `direction`, which specifies whether the cells should go along
54//!    rows, or columns:
55//!     - `Direction::LeftToRight` starts them in the top left and
56//!        moves *rightwards*, going to the start of a new row after reaching the
57//!        final column;
58//!     - `Direction::TopToBottom` starts them in the top left and moves
59//!        *downwards*, going to the top of a new column after reaching the final
60//!        row.
61//!
62//!
63//! ## Displaying a grid
64//!
65//! When display a grid, you can either specify the number of columns in advance,
66//! or try to find the maximum number of columns that can fit in an area of a
67//! given width.
68//!
69//! Splitting a series of cells into columns — or, in other words, starting a new
70//! row every <var>n</var> cells — is achieved with the [`fit_into_columns`] function
71//! on a `Grid` value. It takes as its argument the number of columns.
72//!
73//! Trying to fit as much data onto one screen as possible is the main use case
74//! for specifying a maximum width instead. This is achieved with the
75//! [`fit_into_width`] function. It takes the maximum allowed width, including
76//! separators, as its argument. However, it returns an *optional* [`Display`]
77//! value, depending on whether any of the cells actually had a width greater than
78//! the maximum width! If this is the case, your best bet is to just output the
79//! cells with one per line.
80//!
81//!
82//! ## Cells and data
83//!
84//! Grids to not take `String`s or `&str`s — they take [`Cell`] values.
85//!
86//! A **Cell** is a struct containing an individual cell’s contents, as a string,
87//! and its pre-computed length, which gets used when calculating a grid’s final
88//! dimensions. Usually, you want the *Unicode width* of the string to be used for
89//! this, so you can turn a `String` into a `Cell` with the `.into()` function.
90//!
91//! However, you may also want to supply your own width: when you already know the
92//! width in advance, or when you want to change the measurement, such as skipping
93//! over terminal control characters. For cases like these, the fields on the
94//! `Cell` values are public, meaning you can construct your own instances as
95//! necessary.
96//!
97//! [`Cell`]: ./struct.Cell.html
98//! [`Display`]: ./struct.Display.html
99//! [`Grid`]: ./struct.Grid.html
100//! [`fit_into_columns`]: ./struct.Grid.html#method.fit_into_columns
101//! [`fit_into_width`]: ./struct.Grid.html#method.fit_into_width
102//! [`GridOptions`]: ./struct.GridOptions.html
103
104
105use std::cmp::max;
106use std::fmt;
107use std::iter::repeat;
108
109extern crate unicode_width;
110use unicode_width::UnicodeWidthStr;
111
112
113/// Alignment indicate on which side the content should stick if some filling
114/// is required.
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum Alignment {
117
118    /// The content will stick to the left.
119    Left,
120
121    /// The content will stick to the right.
122    Right,
123}
124
125
126/// A **Cell** is the combination of a string and its pre-computed length.
127///
128/// The easiest way to create a Cell is just by using `string.into()`, which
129/// uses the **unicode width** of the string (see the `unicode_width` crate).
130/// However, the fields are public, if you wish to provide your own length.
131#[derive(PartialEq, Debug, Clone)]
132pub struct Cell {
133
134    /// The string to display when this cell gets rendered.
135    pub contents: String,
136
137    /// The pre-computed length of the string.
138    pub width: Width,
139
140    /// The side (left/right) to align the content if some filling is required.
141    pub alignment: Alignment,
142}
143
144impl From<String> for Cell {
145    fn from(string: String) -> Self {
146        Self {
147            width: UnicodeWidthStr::width(&*string),
148            contents: string,
149            alignment: Alignment::Left,
150        }
151    }
152}
153
154impl<'a> From<&'a str> for Cell {
155    fn from(string: &'a str) -> Self {
156        Self {
157            width: UnicodeWidthStr::width(&*string),
158            contents: string.into(),
159            alignment: Alignment::Left,
160        }
161    }
162}
163
164
165/// Direction cells should be written in — either across, or downwards.
166#[derive(PartialEq, Debug, Copy, Clone)]
167pub enum Direction {
168
169    /// Starts at the top left and moves rightwards, going back to the first
170    /// column for a new row, like a typewriter.
171    LeftToRight,
172
173    /// Starts at the top left and moves downwards, going back to the first
174    /// row for a new column, like how `ls` lists files by default.
175    TopToBottom,
176}
177
178
179/// The width of a cell, in columns.
180pub type Width = usize;
181
182
183/// The text to put in between each pair of columns.
184/// This does not include any spaces used when aligning cells.
185#[derive(PartialEq, Debug)]
186pub enum Filling {
187
188    /// A certain number of spaces should be used as the separator.
189    Spaces(Width),
190
191    /// An arbitrary string.
192    /// `"|"` is a common choice.
193    Text(String),
194}
195
196impl Filling {
197    fn width(&self) -> Width {
198        match *self {
199            Filling::Spaces(w)   => w,
200            Filling::Text(ref t) => UnicodeWidthStr::width(&t[..]),
201        }
202    }
203}
204
205/// The user-assignable options for a grid view that should be passed to
206/// [`Grid::new()`](struct.Grid.html#method.new).
207#[derive(PartialEq, Debug)]
208pub struct GridOptions {
209
210    /// The direction that the cells should be written in — either
211    /// across, or downwards.
212    pub direction: Direction,
213
214    /// The number of spaces to put in between each column of cells.
215    pub filling: Filling,
216}
217
218
219#[derive(PartialEq, Debug)]
220struct Dimensions {
221
222    /// The number of lines in the grid.
223    num_lines: Width,
224
225    /// The width of each column in the grid. The length of this vector serves
226    /// as the number of columns.
227    widths: Vec<Width>,
228}
229
230impl Dimensions {
231    fn total_width(&self, separator_width: Width) -> Width {
232        if self.widths.is_empty() {
233            0
234        }
235        else {
236            let values = self.widths.iter().sum::<Width>();
237            let separators = separator_width * (self.widths.len() - 1);
238            values + separators
239        }
240    }
241}
242
243
244/// Everything needed to format the cells with the grid options.
245///
246/// For more information, see the [`term_grid` crate documentation](index.html).
247#[derive(PartialEq, Debug)]
248pub struct Grid {
249    options: GridOptions,
250    cells: Vec<Cell>,
251    widest_cell_length: Width,
252    width_sum: Width,
253    cell_count: usize,
254}
255
256impl Grid {
257
258    /// Creates a new grid view with the given options.
259    pub fn new(options: GridOptions) -> Self {
260        let cells = Vec::new();
261        Self { options, cells, widest_cell_length: 0,
262               width_sum: 0, cell_count: 0 }
263    }
264
265    /// Reserves space in the vector for the given number of additional cells
266    /// to be added. (See the `Vec::reserve` function.)
267    pub fn reserve(&mut self, additional: usize) {
268        self.cells.reserve(additional)
269    }
270
271    /// Adds another cell onto the vector.
272    pub fn add(&mut self, cell: Cell) {
273        if cell.width > self.widest_cell_length {
274            self.widest_cell_length = cell.width;
275        }
276        self.width_sum += cell.width;
277        self.cell_count += 1;
278        self.cells.push(cell)
279    }
280
281    /// Returns a displayable grid that’s been packed to fit into the given
282    /// width in the fewest number of rows.
283    ///
284    /// Returns `None` if any of the cells has a width greater than the
285    /// maximum width.
286    pub fn fit_into_width(&self, maximum_width: Width) -> Option<Display<'_>> {
287        self.width_dimensions(maximum_width)
288            .map(|dims| Display {
289                grid:       self,
290                dimensions: dims,
291            })
292    }
293
294    /// Returns a displayable grid with the given number of columns, and no
295    /// maximum width.
296    pub fn fit_into_columns(&self, num_columns: usize) -> Display<'_> {
297        Display {
298            grid:       self,
299            dimensions: self.columns_dimensions(num_columns),
300        }
301    }
302
303    fn columns_dimensions(&self, num_columns: usize) -> Dimensions {
304        let mut num_lines = self.cells.len() / num_columns;
305        if self.cells.len() % num_columns != 0 {
306            num_lines += 1;
307        }
308
309        self.column_widths(num_lines, num_columns)
310    }
311
312    fn column_widths(&self, num_lines: usize, num_columns: usize) -> Dimensions {
313        let mut widths: Vec<Width> = repeat(0).take(num_columns).collect();
314        for (index, cell) in self.cells.iter().enumerate() {
315            let index = match self.options.direction {
316                Direction::LeftToRight  => index % num_columns,
317                Direction::TopToBottom  => index / num_lines,
318            };
319            widths[index] = max(widths[index], cell.width);
320        }
321
322        Dimensions { num_lines, widths }
323    }
324
325    fn theoretical_max_num_lines(&self, maximum_width: usize) -> usize {
326        // TODO: Make code readable / efficient.
327        let mut theoretical_min_num_cols = 0;
328        let mut col_total_width_so_far = 0;
329
330        let mut cells = self.cells.clone();
331        cells.sort_unstable_by(|a, b| b.width.cmp(&a.width)); // Sort in reverse order
332
333        for cell in &cells {
334            if cell.width + col_total_width_so_far <= maximum_width {
335                theoretical_min_num_cols += 1;
336                col_total_width_so_far += cell.width;
337            } else {
338                let mut theoretical_max_num_lines = self.cell_count / theoretical_min_num_cols;
339                if self.cell_count % theoretical_min_num_cols != 0 {
340                    theoretical_max_num_lines += 1;
341                }
342                return theoretical_max_num_lines;
343            }
344            col_total_width_so_far += self.options.filling.width()
345        }
346
347        // If we make it to this point, we have exhausted all cells before
348        // reaching the maximum width; the theoretical max number of lines
349        // needed to display all cells is 1.
350        1
351    }
352
353    fn width_dimensions(&self, maximum_width: Width) -> Option<Dimensions> {
354        if self.widest_cell_length > maximum_width {
355            // Largest cell is wider than maximum width; it is impossible to fit.
356            return None;
357        }
358
359        if self.cell_count == 0 {
360            return Some(Dimensions { num_lines: 0, widths: Vec::new() });
361        }
362
363        if self.cell_count == 1 {
364            let the_cell = &self.cells[0];
365            return Some(Dimensions { num_lines: 1, widths: vec![ the_cell.width ] });
366        }
367
368        let theoretical_max_num_lines = self.theoretical_max_num_lines(maximum_width);
369        if theoretical_max_num_lines == 1 {
370            // This if—statement is neccesary for the function to work correctly
371            // for small inputs.
372            return Some(Dimensions {
373                num_lines: 1,
374                // I clone self.cells twice. Once here, and once in
375                // self.theoretical_max_num_lines. Perhaps not the best for
376                // performance?
377                widths: self.cells.clone().into_iter().map(|cell| cell.width).collect()
378            });
379        }
380        // Instead of numbers of columns, try to find the fewest number of *lines*
381        // that the output will fit in.
382        let mut smallest_dimensions_yet = None;
383        for num_lines in (1 .. theoretical_max_num_lines).rev() {
384
385            // The number of columns is the number of cells divided by the number
386            // of lines, *rounded up*.
387            let mut num_columns = self.cell_count / num_lines;
388            if self.cell_count % num_lines != 0 {
389                num_columns += 1;
390            }
391
392            // Early abort: if there are so many columns that the width of the
393            // *column separators* is bigger than the width of the screen, then
394            // don’t even try to tabulate it.
395            // This is actually a necessary check, because the width is stored as
396            // a usize, and making it go negative makes it huge instead, but it
397            // also serves as a speed-up.
398            let total_separator_width = (num_columns - 1) * self.options.filling.width();
399            if maximum_width < total_separator_width {
400                continue;
401            }
402
403            // Remove the separator width from the available space.
404            let adjusted_width = maximum_width - total_separator_width;
405
406            let potential_dimensions = self.column_widths(num_lines, num_columns);
407            if potential_dimensions.widths.iter().sum::<Width>() < adjusted_width {
408                smallest_dimensions_yet = Some(potential_dimensions);
409            } else {
410                return smallest_dimensions_yet;
411            }
412        }
413
414        None
415    }
416}
417
418
419/// A displayable representation of a [`Grid`](struct.Grid.html).
420///
421/// This type implements `Display`, so you can get the textual version
422/// of the grid by calling `.to_string()`.
423#[derive(PartialEq, Debug)]
424pub struct Display<'grid> {
425
426    /// The grid to display.
427    grid: &'grid Grid,
428
429    /// The pre-computed column widths for this grid.
430    dimensions: Dimensions,
431}
432
433impl Display<'_> {
434
435    /// Returns how many columns this display takes up, based on the separator
436    /// width and the number and width of the columns.
437    pub fn width(&self) -> Width {
438        self.dimensions.total_width(self.grid.options.filling.width())
439    }
440
441    /// Returns how many rows this display takes up.
442    pub fn row_count(&self) -> usize {
443        self.dimensions.num_lines
444    }
445
446    /// Returns whether this display takes up as many columns as were allotted
447    /// to it.
448    ///
449    /// It’s possible to construct tables that don’t actually use up all the
450    /// columns that they could, such as when there are more columns than
451    /// cells! In this case, a column would have a width of zero. This just
452    /// checks for that.
453    pub fn is_complete(&self) -> bool {
454        self.dimensions.widths.iter().all(|&x| x > 0)
455    }
456}
457
458impl fmt::Display for Display<'_> {
459    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
460        for y in 0 .. self.dimensions.num_lines {
461            for x in 0 .. self.dimensions.widths.len() {
462                let num = match self.grid.options.direction {
463                    Direction::LeftToRight  => y * self.dimensions.widths.len() + x,
464                    Direction::TopToBottom  => y + self.dimensions.num_lines * x,
465                };
466
467                // Abandon a line mid-way through if that’s where the cells end
468                if num >= self.grid.cells.len() {
469                    continue;
470                }
471
472                let cell = &self.grid.cells[num];
473                if x == self.dimensions.widths.len() - 1 {
474                    match cell.alignment {
475                        Alignment::Left => {
476                            // The final column doesn’t need to have trailing spaces,
477                            // as long as it’s left-aligned.
478                            write!(f, "{}", cell.contents)?;
479                        },
480                        Alignment::Right => {
481                            let extra_spaces = self.dimensions.widths[x] - cell.width;
482                            write!(f, "{}", pad_string(&cell.contents, extra_spaces, Alignment::Right))?;
483                        }
484                    }
485                }
486                else {
487                    assert!(self.dimensions.widths[x] >= cell.width);
488                    match (&self.grid.options.filling, cell.alignment) {
489                        (Filling::Spaces(n), Alignment::Left) => {
490                            let extra_spaces = self.dimensions.widths[x] - cell.width + n;
491                            write!(f, "{}", pad_string(&cell.contents, extra_spaces, cell.alignment))?;
492                        },
493                        (Filling::Spaces(n), Alignment::Right) => {
494                            let s = spaces(*n);
495                            let extra_spaces = self.dimensions.widths[x] - cell.width;
496                            write!(f, "{}{}", pad_string(&cell.contents, extra_spaces, cell.alignment), s)?;
497                        },
498                        (Filling::Text(ref t), _) => {
499                            let extra_spaces = self.dimensions.widths[x] - cell.width;
500                            write!(f, "{}{}", pad_string(&cell.contents, extra_spaces, cell.alignment), t)?;
501                        },
502                    }
503                }
504            }
505
506            writeln!(f)?;
507        }
508
509        Ok(())
510    }
511}
512
513
514/// Pad a string with the given number of spaces.
515fn spaces(length: usize) -> String {
516    repeat(" ").take(length).collect()
517}
518
519/// Pad a string with the given alignment and number of spaces.
520///
521/// This doesn’t take the width the string *should* be, rather the number
522/// of spaces to add.
523fn pad_string(string: &str, padding: usize, alignment: Alignment) -> String {
524    if alignment == Alignment::Left {
525        format!("{}{}", string, spaces(padding))
526    }
527    else {
528        format!("{}{}", spaces(padding), string)
529    }
530}
531
532
533#[cfg(test)]
534mod test {
535    use super::*;
536
537    #[test]
538    fn no_items() {
539        let grid = Grid::new(GridOptions {
540            direction:  Direction::TopToBottom,
541            filling:    Filling::Spaces(2),
542        });
543
544        let display = grid.fit_into_width(40).unwrap();
545
546        assert_eq!(display.dimensions.num_lines, 0);
547        assert!(display.dimensions.widths.is_empty());
548
549        assert_eq!(display.width(), 0);
550    }
551
552    #[test]
553    fn one_item() {
554        let mut grid = Grid::new(GridOptions {
555            direction:  Direction::TopToBottom,
556            filling:    Filling::Spaces(2),
557        });
558
559        grid.add(Cell::from("1"));
560
561        let display = grid.fit_into_width(40).unwrap();
562
563        assert_eq!(display.dimensions.num_lines, 1);
564        assert_eq!(display.dimensions.widths, vec![ 1 ]);
565
566        assert_eq!(display.width(), 1);
567    }
568
569    #[test]
570    fn one_item_exact_width() {
571        let mut grid = Grid::new(GridOptions {
572            direction:  Direction::TopToBottom,
573            filling:    Filling::Spaces(2),
574        });
575
576        grid.add(Cell::from("1234567890"));
577
578        let display = grid.fit_into_width(10).unwrap();
579
580        assert_eq!(display.dimensions.num_lines, 1);
581        assert_eq!(display.dimensions.widths, vec![ 10 ]);
582
583        assert_eq!(display.width(), 10);
584    }
585
586    #[test]
587    fn one_item_just_over() {
588        let mut grid = Grid::new(GridOptions {
589            direction:  Direction::TopToBottom,
590            filling:    Filling::Spaces(2),
591        });
592
593        grid.add(Cell::from("1234567890!"));
594
595        assert_eq!(grid.fit_into_width(10), None);
596    }
597
598    #[test]
599    fn two_small_items() {
600        let mut grid = Grid::new(GridOptions {
601            direction:  Direction::TopToBottom,
602            filling:    Filling::Spaces(2),
603        });
604
605        grid.add(Cell::from("1"));
606        grid.add(Cell::from("2"));
607
608        let display = grid.fit_into_width(40).unwrap();
609
610        assert_eq!(display.dimensions.num_lines, 1);
611        assert_eq!(display.dimensions.widths, vec![ 1, 1 ]);
612
613        assert_eq!(display.width(), 1 + 2 + 1);
614    }
615
616    #[test]
617    fn two_medium_size_items() {
618        let mut grid = Grid::new(GridOptions {
619            direction:  Direction::TopToBottom,
620            filling:    Filling::Spaces(2),
621        });
622
623        grid.add(Cell::from("hello there"));
624        grid.add(Cell::from("how are you today?"));
625
626        let display = grid.fit_into_width(40).unwrap();
627
628        assert_eq!(display.dimensions.num_lines, 1);
629        assert_eq!(display.dimensions.widths, vec![ 11, 18 ]);
630
631        assert_eq!(display.width(), 11 + 2 + 18);
632    }
633
634    #[test]
635    fn two_big_items() {
636        let mut grid = Grid::new(GridOptions {
637            direction:  Direction::TopToBottom,
638            filling:    Filling::Spaces(2),
639        });
640
641        grid.add(Cell::from("nuihuneihsoenhisenouiuteinhdauisdonhuisudoiosadiuohnteihaosdinhteuieudi"));
642        grid.add(Cell::from("oudisnuthasuouneohbueobaugceoduhbsauglcobeuhnaeouosbubaoecgueoubeohubeo"));
643
644        assert_eq!(grid.fit_into_width(40), None);
645    }
646
647    #[test]
648    fn that_example_from_earlier() {
649        let mut grid = Grid::new(GridOptions {
650            filling:    Filling::Spaces(1),
651            direction:  Direction::LeftToRight,
652        });
653
654        for s in &["one", "two", "three", "four", "five", "six", "seven",
655                   "eight", "nine", "ten", "eleven", "twelve"]
656        {
657            grid.add(Cell::from(*s));
658        }
659
660        let bits = "one  two three  four\nfive six seven  eight\nnine ten eleven twelve\n";
661        assert_eq!(grid.fit_into_width(24).unwrap().to_string(), bits);
662        assert_eq!(grid.fit_into_width(24).unwrap().row_count(), 3);
663    }
664
665    #[test]
666    fn number_grid_with_pipe() {
667        let mut grid = Grid::new(GridOptions {
668            filling:    Filling::Text("|".into()),
669            direction:  Direction::LeftToRight,
670        });
671
672        for s in &["one", "two", "three", "four", "five", "six", "seven",
673                   "eight", "nine", "ten", "eleven", "twelve"]
674        {
675            grid.add(Cell::from(*s));
676        }
677
678        let bits = "one |two|three |four\nfive|six|seven |eight\nnine|ten|eleven|twelve\n";
679        assert_eq!(grid.fit_into_width(24).unwrap().to_string(), bits);
680        assert_eq!(grid.fit_into_width(24).unwrap().row_count(), 3);
681    }
682
683    #[test]
684    fn numbers_right() {
685        let mut grid = Grid::new(GridOptions {
686            filling:    Filling::Spaces(1),
687            direction:  Direction::LeftToRight,
688        });
689
690        for s in &["one", "two", "three", "four", "five", "six", "seven",
691                   "eight", "nine", "ten", "eleven", "twelve"]
692        {
693            let mut cell = Cell::from(*s);
694            cell.alignment = Alignment::Right;
695            grid.add(cell);
696        }
697
698        let bits = " one two  three   four\nfive six  seven  eight\nnine ten eleven twelve\n";
699        assert_eq!(grid.fit_into_width(24).unwrap().to_string(), bits);
700        assert_eq!(grid.fit_into_width(24).unwrap().row_count(), 3);
701    }
702
703    #[test]
704    fn numbers_right_pipe() {
705        let mut grid = Grid::new(GridOptions {
706            filling:    Filling::Text("|".into()),
707            direction:  Direction::LeftToRight,
708        });
709
710        for s in &["one", "two", "three", "four", "five", "six", "seven",
711                   "eight", "nine", "ten", "eleven", "twelve"]
712        {
713            let mut cell = Cell::from(*s);
714            cell.alignment = Alignment::Right;
715            grid.add(cell);
716        }
717
718        let bits = " one|two| three|  four\nfive|six| seven| eight\nnine|ten|eleven|twelve\n";
719        assert_eq!(grid.fit_into_width(24).unwrap().to_string(), bits);
720        assert_eq!(grid.fit_into_width(24).unwrap().row_count(), 3);
721    }
722
723    #[test]
724    fn huge_separator() {
725        let mut grid = Grid::new(GridOptions {
726            filling:    Filling::Spaces(100),
727            direction:  Direction::LeftToRight,
728        });
729
730        grid.add("a".into());
731        grid.add("b".into());
732
733        assert_eq!(grid.fit_into_width(99), None);
734    }
735
736    #[test]
737    fn huge_yet_unused_separator() {
738        let mut grid = Grid::new(GridOptions {
739            filling:    Filling::Spaces(100),
740            direction:  Direction::LeftToRight,
741        });
742
743        grid.add("abcd".into());
744
745        let display = grid.fit_into_width(99).unwrap();
746
747        assert_eq!(display.dimensions.num_lines, 1);
748        assert_eq!(display.dimensions.widths, vec![ 4 ]);
749
750        assert_eq!(display.width(), 4);
751    }
752}