papergrid/records/vec_records/
text.rs

1use core::fmt::Display;
2use std::{borrow::Cow, cmp::max};
3
4use crate::{
5    records::vec_records::Cell,
6    util::string::{self, count_lines, get_line_width, get_lines},
7};
8
9/// The struct is a [Cell] implementation which keeps width information pre allocated.
10#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub struct Text<S> {
12    text: S,
13    width: usize,
14    lines: Vec<StrWithWidth<'static>>,
15}
16
17impl<S> Text<S> {
18    /// Creates a new instance of the structure.
19    pub fn new(text: S) -> Self
20    where
21        S: AsRef<str>,
22    {
23        create_text(text)
24    }
25
26    /// Creates a new instance of the structure with a single line.
27    pub fn exact(text: S, width: usize, lines: Vec<StrWithWidth<'static>>) -> Self {
28        Self { text, width, lines }
29    }
30
31    /// Return a original text value.
32    pub fn into_inner(self) -> S {
33        self.text
34    }
35}
36
37impl<S> AsRef<str> for Text<S>
38where
39    S: AsRef<str>,
40{
41    fn as_ref(&self) -> &str {
42        self.text()
43    }
44}
45
46impl<S> Display for Text<S>
47where
48    S: Display,
49{
50    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
51        self.text.fmt(f)
52    }
53}
54
55impl<S> Cell for Text<S>
56where
57    S: AsRef<str>,
58{
59    fn text(&self) -> &str {
60        self.text.as_ref()
61    }
62
63    fn line(&self, i: usize) -> &str {
64        if i == 0 && self.lines.is_empty() {
65            return self.text.as_ref();
66        }
67
68        &self.lines[i].text
69    }
70
71    fn count_lines(&self) -> usize {
72        std::cmp::max(1, self.lines.len())
73    }
74
75    fn width(&self) -> usize {
76        self.width
77    }
78
79    fn line_width(&self, i: usize) -> usize {
80        if i == 0 && self.lines.is_empty() {
81            return self.width;
82        }
83
84        self.lines[i].width
85    }
86}
87
88impl<S> Clone for Text<S>
89where
90    S: Clone + AsRef<str>,
91{
92    fn clone(&self) -> Self {
93        let mut cell = Self {
94            text: self.text.clone(),
95            width: self.width,
96            lines: Vec::with_capacity(self.lines.len()),
97        };
98
99        for line in self.lines.iter() {
100            // We need to redirect pointers to the original string.
101            //
102            // # Safety
103            //
104            // It must be safe because the referenced string and the references are dropped at the same time.
105            // And the referenced String is guaranteed to not be changed.
106            let text = unsafe {
107                let text_ptr = self.text.as_ref().as_ptr();
108                let line_ptr = line.text.as_ptr();
109                let text_shift = line_ptr as isize - text_ptr as isize;
110
111                let new_text_shifted_ptr = cell.text.as_ref().as_ptr().offset(text_shift);
112
113                std::str::from_utf8_unchecked(std::slice::from_raw_parts(
114                    new_text_shifted_ptr,
115                    line.text.len(),
116                ))
117            };
118
119            let newline = StrWithWidth::new(Cow::Borrowed(text), line.width);
120            cell.lines.push(newline);
121        }
122
123        cell
124    }
125}
126
127/// StrWithWidth is a structure is responsible for a string and it's width.
128#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
129pub struct StrWithWidth<'a> {
130    text: Cow<'a, str>,
131    width: usize,
132}
133
134impl<'a> StrWithWidth<'a> {
135    /// Creates a new object.
136    pub fn new(text: Cow<'a, str>, width: usize) -> Self {
137        Self { text, width }
138    }
139}
140
141// TODO: compare with 'get_text_dimension'
142fn create_text<S: AsRef<str>>(text: S) -> Text<S> {
143    let mut info = Text {
144        text,
145        lines: vec![],
146        width: 0,
147    };
148
149    // Here we do a small optimization.
150    // We check if there's only 1 line in which case we don't allocate lines Vec
151    let count_lines = count_lines(info.text.as_ref());
152    if count_lines < 2 {
153        info.width = string::get_line_width(info.text.as_ref());
154        return info;
155    }
156
157    // In case `Cow::Borrowed` we want to not allocate a String.
158    // It's currerently not possible due to a lifetime issues. (It's known as self-referential struct)
159    //
160    // Here we change the lifetime of text.
161    //
162    // # Safety
163    //
164    // It must be safe because the referenced string and the references are dropped at the same time.
165    // And the referenced String is guaranteed to not be changed.
166    let text = unsafe {
167        std::str::from_utf8_unchecked(std::slice::from_raw_parts(
168            info.text.as_ref().as_ptr(),
169            info.text.as_ref().len(),
170        ))
171    };
172
173    info.lines = Vec::with_capacity(count_lines);
174    for line in get_lines(text) {
175        let line_width = get_line_width(&line);
176        let line = StrWithWidth::new(line, line_width);
177        info.width = max(info.width, line_width);
178        info.lines.push(line);
179    }
180
181    info
182}