bitmap2ttf 0.1.1

Convert bitmap font glyphs into TrueType (.ttf) vector fonts
Documentation
use std::collections::{HashMap, HashSet};

fn pixel_index(width: u32, x: u32, y: u32) -> Option<usize> {
    let offset = y.checked_mul(width)?.checked_add(x)?;
    usize::try_from(offset).ok()
}

pub fn collect_pixel_rects(
    width: u32,
    height: u32,
    pixels: &[u8],
    merge_rects: bool,
) -> Vec<(u32, u32, u32, u32)> {
    if !merge_rects {
        let mut rects = Vec::new();
        for y in 0..height {
            for x in 0..width {
                let filled = pixel_index(width, x, y)
                    .and_then(|idx| pixels.get(idx))
                    .copied()
                    .unwrap_or(0)
                    != 0;
                if filled {
                    rects.push((x, y, 1, 1));
                }
            }
        }
        return rects;
    }

    let mut active: HashMap<(u32, u32), (u32, u32)> = HashMap::new();
    let mut rects = Vec::new();

    for y in 0..height {
        let mut runs = Vec::new();
        let mut x = 0_u32;
        while x < width {
            let filled = pixel_index(width, x, y)
                .and_then(|idx| pixels.get(idx))
                .copied()
                .unwrap_or(0)
                != 0;
            if !filled {
                x = x.saturating_add(1);
                continue;
            }

            let start = x;
            x = x.saturating_add(1);
            while x < width {
                let filled = pixel_index(width, x, y)
                    .and_then(|idx| pixels.get(idx))
                    .copied()
                    .unwrap_or(0)
                    != 0;
                if !filled {
                    break;
                }
                x = x.saturating_add(1);
            }
            runs.push((start, x.saturating_sub(start)));
        }

        let mut seen = HashSet::new();
        for run in runs {
            seen.insert(run);
            if let Some((_, run_h)) = active.get_mut(&run) {
                *run_h = run_h.saturating_add(1);
            } else {
                active.insert(run, (y, 1));
            }
        }

        let stale = active
            .keys()
            .filter(|key| !seen.contains(*key))
            .copied()
            .collect::<Vec<_>>();
        for key in stale {
            if let Some((start_y, run_h)) = active.remove(&key) {
                rects.push((key.0, start_y, key.1, run_h));
            }
        }
    }

    for (key, (start_y, run_h)) in active {
        rects.push((key.0, start_y, key.1, run_h));
    }

    rects
}