text_minimap/
mod.rs

1extern crate itertools;
2extern crate unicode_segmentation;
3
4use std::io::BufReader;
5use std::{io};
6
7use itertools::Itertools;
8use std::io::BufRead;
9use unicode_segmentation::UnicodeSegmentation;
10
11const LOOKUP_TABLE : [char; 256] =
12[
13    '⠀', '⠁', '⠂', '⠃', '⠄', '⠅', '⠆', '⠇', '⡀', '⡁', '⡂', '⡃', '⡄', '⡅', '⡆', '⡇',
14'⠈', '⠉', '⠊', '⠋', '⠌', '⠍', '⠎', '⠏', '⡈', '⡉', '⡊', '⡋', '⡌', '⡍', '⡎', '⡏',
15'⠐', '⠑', '⠒', '⠓', '⠔', '⠕', '⠖', '⠗', '⡐', '⡑', '⡒', '⡓', '⡔', '⡕', '⡖', '⡗',
16'⠘', '⠙', '⠚', '⠛', '⠜', '⠝', '⠞', '⠟', '⡘', '⡙', '⡚', '⡛', '⡜', '⡝', '⡞', '⡟',
17'⠠', '⠡', '⠢', '⠣', '⠤', '⠥', '⠦', '⠧', '⡠', '⡡', '⡢', '⡣', '⡤', '⡥', '⡦', '⡧',
18'⠨', '⠩', '⠪', '⠫', '⠬', '⠭', '⠮', '⠯', '⡨', '⡩', '⡪', '⡫', '⡬', '⡭', '⡮', '⡯',
19'⠰', '⠱', '⠲', '⠳', '⠴', '⠵', '⠶', '⠷', '⡰', '⡱', '⡲', '⡳', '⡴', '⡵', '⡶', '⡷',
20'⠸', '⠹', '⠺', '⠻', '⠼', '⠽', '⠾', '⠿', '⡸', '⡹', '⡺', '⡻', '⡼', '⡽', '⡾', '⡿',
21'⢀', '⢁', '⢂', '⢃', '⢄', '⢅', '⢆', '⢇', '⣀', '⣁', '⣂', '⣃', '⣄', '⣅', '⣆', '⣇',
22'⢈', '⢉', '⢊', '⢋', '⢌', '⢍', '⢎', '⢏', '⣈', '⣉', '⣊', '⣋', '⣌', '⣍', '⣎', '⣏',
23'⢐', '⢑', '⢒', '⢓', '⢔', '⢕', '⢖', '⢗', '⣐', '⣑', '⣒', '⣓', '⣔', '⣕', '⣖', '⣗',
24'⢘', '⢙', '⢚', '⢛', '⢜', '⢝', '⢞', '⢟', '⣘', '⣙', '⣚', '⣛', '⣜', '⣝', '⣞', '⣟',
25'⢠', '⢡', '⢢', '⢣', '⢤', '⢥', '⢦', '⢧', '⣠', '⣡', '⣢', '⣣', '⣤', '⣥', '⣦', '⣧',
26'⢨', '⢩', '⢪', '⢫', '⢬', '⢭', '⢮', '⢯', '⣨', '⣩', '⣪', '⣫', '⣬', '⣭', '⣮', '⣯',
27'⢰', '⢱', '⢲', '⢳', '⢴', '⢵', '⢶', '⢷', '⣰', '⣱', '⣲', '⣳', '⣴', '⣵', '⣶', '⣷',
28'⢸', '⢹', '⢺', '⢻', '⢼', '⢽', '⢾', '⢿', '⣸', '⣹', '⣺', '⣻', '⣼', '⣽', '⣾', '⣿',
29];
30
31/// Convert simple binary representation that this crate
32/// uses to unicode character, by calculating the right unicode
33///
34/// Unicode offset format
35///
36/// ```norust
37/// 03
38/// 14
39/// 25
40/// 67
41/// ```
42///
43/// This crate format:
44/// ```norust
45/// 04
46/// 15
47/// 26
48/// 37
49/// ```
50fn byte_to_braillechar(nb : u8) -> char {
51
52    let nb = nb as u32;
53    let mut ub = 0u32;
54    ub |= ((nb >> 0) & 0x7) << 0;
55    ub |= ((nb >> 4) & 0x7) << 3;
56    ub |= ((nb >> 3) & 0x1) << 6;
57    ub |= ((nb >> 7) & 0x1) << 7;
58
59
60
61    std::char::from_u32(0x2800u32 + ub).unwrap()
62}
63
64#[allow(unused)]
65/// This is the script that generated `LOOKUP_TABLE`
66fn generate_lookup_table() {
67    for bin2 in 0..16 {
68        println!(
69            "'{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}',",
70            byte_to_braillechar(bin2 * 16 + 0),
71            byte_to_braillechar(bin2 * 16 + 1),
72            byte_to_braillechar(bin2 * 16 + 2),
73            byte_to_braillechar(bin2 * 16 + 3),
74            byte_to_braillechar(bin2 * 16 + 4),
75            byte_to_braillechar(bin2 * 16 + 5),
76            byte_to_braillechar(bin2 * 16 + 6),
77            byte_to_braillechar(bin2 * 16 + 7),
78            byte_to_braillechar(bin2 * 16 + 8),
79            byte_to_braillechar(bin2 * 16 + 9),
80            byte_to_braillechar(bin2 * 16 + 10),
81            byte_to_braillechar(bin2 * 16 + 11),
82            byte_to_braillechar(bin2 * 16 + 12),
83            byte_to_braillechar(bin2 * 16 + 13),
84            byte_to_braillechar(bin2 * 16 + 14),
85            byte_to_braillechar(bin2 * 16 + 15),
86            )
87    }
88}
89
90const BRAILLE_Y :usize = 4;
91#[allow(unused)]
92const BRAILLE_X :usize = 2;
93
94/// Generate minimap/preview for the text read from `reader`j
95pub fn to_minimap<'a, R>(reader: R, settings: Settings) -> Box<Iterator<Item = String> + 'a>
96    where R: io::Read + 'a
97{
98
99    let i = to_minimap_bool(reader, settings)
100        .batching(move |i| {
101            let mut group : Vec<Vec<bool>> = Vec::new();
102            for _ in 0..BRAILLE_Y {
103                if let Some(line) = i.next() {
104                    group.push(line);
105                } else {
106                    break;
107                }
108            }
109            if group.is_empty() {
110                None
111            } else {
112                Some(group)
113            }
114        })
115        // map to binary representations of 4 bits in every column
116        .map(move |group| {
117            let mut line_iters : Vec<_> = group
118                .iter()
119                .map(|line| line.iter()).collect();
120
121            let mut binary_column : Vec<u8> = Vec::new();
122
123
124            loop {
125                // reset to false if anything was found
126                let mut finished = true;
127
128                let mut binary = 0;
129
130                for (row_i, mut line_iter) in line_iters.iter_mut().enumerate() {
131                    if let Some(&printable) = line_iter.next() {
132                        finished = false;
133                        if printable {
134                            binary |= 1 << row_i;
135                        }
136                    }
137                }
138
139                if finished {
140                    break;
141                }
142
143                binary_column.push(binary)
144            }
145
146            binary_column
147        })
148        .map(|line| {
149            let mut s = String::new();
150
151            let mut iter = line.iter();
152            loop {
153                let bin1 = iter.next();
154                let bin2 = iter.next();
155
156                if bin1.is_none() && bin2.is_none() {
157                    break;
158                }
159
160                let bin1 : u8 = *bin1.unwrap_or(&0);
161                let bin2 : u8 = *bin2.unwrap_or(&0);
162
163                debug_assert!(bin1 < 16);
164                debug_assert!(bin2 < 16);
165
166                let ch = LOOKUP_TABLE[(bin2 * 16 + bin1) as usize];
167                debug_assert_eq!(ch, byte_to_braillechar((bin2 * 16 + bin1)));
168                s.push(ch)
169            }
170
171            s
172        });
173
174    Box::new(i)
175}
176
177#[allow(unused)]
178fn to_minimap_stars<'a, R>(reader: R, settings: Settings) -> Box<Iterator<Item = String> + 'a>
179    where R: io::Read + 'a
180{
181
182    let i = to_minimap_bool(reader, settings)
183        .map(|dots| {
184            let v : Vec<_> = dots.iter().map(|&b| if b { "*" } else { " " }).collect();
185            let line : String = v.join("");
186            line
187        });
188
189    Box::new(i)
190}
191
192fn to_minimap_bool<'a, R>(reader: R,
193                          settings: Settings)
194                          -> Box<Iterator<Item = Vec<bool>> + 'a>
195    where R: io::Read + 'a
196{
197
198    let buf_reader = BufReader::new(reader);
199
200    let xscale = settings.xscale;
201    let yscale = settings.yscale;
202
203    let i = buf_reader
204        .lines()
205        .take_while(|b| b.is_ok())
206        .map(|b| b.unwrap())
207        // iterate over groups of n lines
208        .batching(move |i| {
209            let mut group : Vec<String> = Vec::new();
210            for _ in 0..yscale {
211                if let Some(line) = i.next() {
212                    group.push(line);
213                } else {
214                    break;
215                }
216            }
217            if group.is_empty() {
218                None
219            } else {
220                Some(group)
221            }
222        })
223        .map(move |group| {
224            let mut line_iters : Vec<_> = group
225                .iter()
226                .map(|line| UnicodeSegmentation::graphemes(line.as_str(), true)).collect();
227
228            let mut dots : Vec<bool> = Vec::new();
229            loop {
230                let mut in_this_column : Vec<bool> = Vec::new();
231                for line_iter in &mut line_iters {
232                    let mut contains_nonwhite = None;
233
234                    for _ in 0..xscale {
235                        if let Some(glyph) = line_iter.next() {
236                            if contains_nonwhite != Some(true) {
237                                if glyph.chars().any(|ch| !ch.is_whitespace()) {
238                                    contains_nonwhite = Some(true);
239                                } else {
240                                    contains_nonwhite = Some(false);
241                                }
242                            }
243                        }
244                    }
245
246                    if let Some(nw) = contains_nonwhite {
247                        in_this_column.push(nw)
248                    }
249                }
250
251                if in_this_column.is_empty() {
252                    break;
253                }
254
255                dots.push(in_this_column.iter().any(|&g| g ))
256            }
257            dots
258        });
259
260    Box::new(i)
261}
262
263#[derive(Clone)]
264pub struct Settings {
265    pub xscale: usize,
266    pub yscale: usize,
267}
268
269impl Settings {
270    pub fn new() -> Self {
271        Settings {
272            xscale: 1,
273            yscale: 1,
274        }
275    }
276}
277