1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
use std::convert::TryInto;

mod glyphsets;
mod image;
mod int;
pub use self::{glyphsets::*, image::*};

/// A set of consecutive pixels of a constant length.
///
/// This is currently `u16` but may change to a different unsigned integer type.
pub type Span = u16;

/// The unsigned integer type twice as wide as `Span`.
type Span2 = u32;

// FIXME: Waiting for `T::BITS` (https://github.com/rust-lang/rust/issues/76904)
const SPAN_BITS: usize = <Span as int::BinInteger>::BITS as usize;

/// A small bitmap image, whose dimensions are specified implciitly (e.g., by
/// `GlyphSet::mask_dims`).
pub type Fragment = u64;

#[derive(Clone)]
#[non_exhaustive]
pub struct Bmp2textOpts<'a> {
    pub glyph_set: &'a dyn GlyphSet,
}

impl Default for Bmp2textOpts<'_> {
    fn default() -> Self {
        Self::new()
    }
}

impl Bmp2textOpts<'_> {
    pub fn new() -> Self {
        Self {
            glyph_set: GLYPH_SET_SLC,
        }
    }
}

/// The working area for bitmap-to-text conversion.
#[derive(Default, Debug)]
pub struct Bmp2text {
    row_group: Vec<Span>,
}

impl Bmp2text {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn transform_and_write(
        &mut self,
        image: &impl ImageRead,
        opts: &Bmp2textOpts,
        out: &mut impl std::fmt::Write,
    ) -> std::fmt::Result {
        use int::BinInteger;

        let glyph_set = opts.glyph_set;
        let mask_dims = glyph_set.mask_dims();
        let mask_overlap = glyph_set.mask_overlap();

        let [img_w, img_h] = image.dims();
        let num_spans_per_line = (img_w + SPAN_BITS - 1) / SPAN_BITS;
        let [out_w, out_h] = [
            num_glyphs_for_image_width(img_w, opts),
            num_lines_for_image_height(img_h, opts),
        ];

        let num_spans_per_line_extra = num_spans_per_line + 1;
        self.row_group
            .resize(num_spans_per_line_extra * mask_dims[1], 0);
        let mut row_group: Vec<&mut [Span]> = self
            .row_group
            .chunks_exact_mut(num_spans_per_line_extra)
            .collect();
        let row_group: &mut [&mut [Span]] = &mut row_group[..mask_dims[1]];

        // The scanning state of each row in `row_group`
        #[derive(Clone, Copy)]
        struct RowState {
            bits: Span2,
        }
        let mut row_states = [RowState { bits: 0 }; 16];
        let row_states = &mut row_states[0..mask_dims[1]];

        for out_y in 0..out_h {
            // Read a row group from the input image
            for (y, row) in row_group.iter_mut().enumerate() {
                image.copy_line_as_spans_to(out_y * (mask_dims[1] - mask_overlap[1]) + y, row);
            }

            let mut num_valid_bits = 0; // .. in `RowState::bits`
            let mut span_i = 0;

            for _ in 0..out_w {
                if num_valid_bits < mask_dims[0] {
                    for (row_state, row) in row_states.iter_mut().zip(row_group.iter_mut()) {
                        row_state.bits |= (row[span_i] as Span2) << num_valid_bits;
                    }
                    span_i += 1;
                    num_valid_bits += SPAN_BITS;
                }

                // Collect an input fragment of dimensions `mask_dims`
                let mut fragment: Fragment = 0;
                for (i, row_state) in row_states.iter_mut().enumerate() {
                    fragment |= (row_state.bits as Fragment & Fragment::ones(0..mask_dims[0] as _))
                        << (i * mask_dims[0]);

                    row_state.bits >>= mask_dims[0] - mask_overlap[0];
                }
                num_valid_bits -= mask_dims[0] - mask_overlap[0];

                debug_assert!(fragment < (1 << (mask_dims[0] * mask_dims[1])));

                // Find the glyph
                let glyph = glyph_set.fragment_to_glyph(fragment);
                out.write_str(glyph)?;
            }
            out.write_str("\n")?;
        }

        Ok(())
    }
}

pub fn num_glyphs_for_image_width(width: usize, opts: &Bmp2textOpts) -> usize {
    let mask_dims = opts.glyph_set.mask_dims();
    let mask_overlap = opts.glyph_set.mask_overlap();
    width.saturating_sub(mask_overlap[0]) / (mask_dims[0] - mask_overlap[0])
}

pub fn num_lines_for_image_height(height: usize, opts: &Bmp2textOpts) -> usize {
    let mask_dims = opts.glyph_set.mask_dims();
    let mask_overlap = opts.glyph_set.mask_overlap();
    height.saturating_sub(mask_overlap[1]) / (mask_dims[1] - mask_overlap[1])
}

/// Calculate the maximum number of bytes possibly outputted by
/// [`Bmp2text::transform_and_write`].
pub fn max_output_len_for_image_dims(
    [width, height]: [usize; 2],
    opts: &Bmp2textOpts,
) -> Option<usize> {
    let glyph_set = opts.glyph_set;
    num_glyphs_for_image_width(width, opts)
        .checked_mul(glyph_set.max_glyph_len())
        .and_then(|x| x.checked_add(1)) // line termination
        .and_then(|x| x.checked_mul(num_lines_for_image_height(height, opts)))
}

#[doc(hidden)]
#[cfg(feature = "log")]
pub fn adjust_image_size_for_output_size_preserving_aspect_ratio(
    image_dims: [usize; 2],
    output_dims: [usize; 2],
    can_scale_up: bool,
    cover: bool,
    cell_width: f64,
    opts: &Bmp2textOpts,
) -> Option<[usize; 2]> {
    let mask_dims = opts.glyph_set.mask_dims();
    let mask_overlap = opts.glyph_set.mask_overlap();

    // Calculate the "natural" size
    let [nat_out_w, nat_out_h] = [
        num_glyphs_for_image_width(image_dims[0], opts),
        num_lines_for_image_height(image_dims[1], opts),
    ];
    let aspect = (mask_dims[1] - mask_overlap[1]) as f64 / (mask_dims[0] - mask_overlap[0]) as f64
        * cell_width;

    let [img_w, img_h] = [
        nat_out_w as f64 / aspect.max(1.0),
        nat_out_h as f64 * aspect.min(1.0),
    ];
    log::debug!("'natural' output size = {:?}", [img_w, img_h]);
    let scale_x = output_dims[0] as f64 / img_w;
    let scale_y = output_dims[1] as f64 / img_h;

    let mut scale = if cover {
        f64::max(scale_x, scale_y)
    } else {
        f64::min(scale_x, scale_y)
    };
    if !can_scale_up {
        scale = scale.min(1.0);
    }
    log::debug!("scaling the 'natural' output size by {}...", scale);

    let output_dims = [
        (img_w * scale).round() as usize,
        (img_h * scale).round() as usize,
    ];

    adjust_image_size_for_output_size(output_dims, opts)
}

#[doc(hidden)]
pub fn adjust_image_size_for_output_size(
    output_dims: [usize; 2],
    opts: &Bmp2textOpts,
) -> Option<[usize; 2]> {
    let mask_dims = opts.glyph_set.mask_dims();
    let mask_overlap = opts.glyph_set.mask_overlap();

    Some([
        output_dims[0]
            .checked_mul(mask_dims[0] - mask_overlap[0])?
            .checked_add(mask_overlap[0])?
            .try_into()
            .ok()?,
        output_dims[1]
            .checked_mul(mask_dims[1] - mask_overlap[1])?
            .checked_add(mask_overlap[1])?
            .try_into()
            .ok()?,
    ])
}