ibm437 0.4.0

IBM437 bitmap font — works with embedded-graphics and raw framebuffers (minifb, softbuffer, SDL2…)
Documentation
use std::fs::File;
use std::io::BufWriter;
use std::io::prelude::*;
use std::path::Path;

use png;

#[path = "../char_offset.rs"]
mod char_offset;
use char_offset::{CHARS_PER_ROW, char_offset_impl};

// #######################################################################
// generic code

fn get_bit(data: &[u8], bit_index: usize) -> u8 {
    let byte = data[bit_index / 8];
    let shift = 7 - (bit_index % 8);
    (byte >> shift) & 1
}

fn set_bit(data: &mut [u8], bit_index: usize, value: u8) {
    let byte = &mut data[bit_index / 8];
    let shift = 7 - (bit_index % 8);
    if value == 1 {
        *byte |= 1 << shift;
    } else {
        *byte &= !(1 << shift);
    }
}

fn save_png(filename: &str, image: &[u8], width: usize, height: usize) -> std::io::Result<()> {
    let path = Path::new(filename);
    let file = File::create(path)?;
    let ref mut w = BufWriter::new(file);

    let mut encoder = png::Encoder::new(w, width as u32, height as u32);
    encoder.set_color(png::ColorType::Grayscale);
    encoder.set_depth(png::BitDepth::One);
    let mut writer = encoder.write_header()?;
    writer.write_image_data(&image)?;

    Ok(())
}

fn save_raw(filename: &str, font_input: &[u8]) -> std::io::Result<()> {
    let path = Path::new(filename);
    let mut file = File::create(path)?;
    file.write_all(font_input)?;
    Ok(())
}

fn glyph_copy(
    output: &mut [u8],
    input: &[u8],
    input_index: usize,
    input_width_len: usize,
    input_height_len: usize,
) {
    let (x, y) = (input_index % CHARS_PER_ROW, input_index / CHARS_PER_ROW);
    for j in 0..input_height_len {
        let line_len = CHARS_PER_ROW * input_width_len;
        let out_index = y * line_len * input_height_len + j * line_len + x * input_width_len;
        let in_index = j * input_width_len;
        for i in 0..input_width_len {
            output[out_index + i] = input[in_index + i];
        }
    }
}

// #######################################################################
// IBM437 9x14 regular font (MDA)
fn extract_9x14(
    characters: &Vec<char>,
    ibm437_src: &[u8; 8192],
    base_dir: &str,
) -> std::io::Result<()> {
    const INPUT_WIDTH_LEN: usize = 2;
    const INPUT_HEIGHT_LEN: usize = 14;

    let mut font_output: [u8; INPUT_WIDTH_LEN
        * CHARS_PER_ROW
        * INPUT_HEIGHT_LEN
        * (256 / CHARS_PER_ROW)] =
        [0; INPUT_WIDTH_LEN * CHARS_PER_ROW * INPUT_HEIGHT_LEN * (256 / CHARS_PER_ROW)];

    for (i, chr) in characters.iter().enumerate() {
        let top = &ibm437_src[8 * i..8 * (i + 1)];
        let bottom = &ibm437_src[0x0800 + 8 * i..0x0800 + 8 * (i + 1) - 2];
        let mut glyph: [u8; INPUT_WIDTH_LEN * INPUT_HEIGHT_LEN] =
            [0; INPUT_WIDTH_LEN * INPUT_HEIGHT_LEN];

        for (j, e) in top.iter().chain(bottom.iter()).enumerate() {
            let c = match i & 0b1110_0000 {
                0b1100_0000 => (*e & 0b0000_0001) << 7,
                _ => 0b0000_0000,
            };
            glyph[2 * j] = *e;
            glyph[2 * j + 1] = c;
        }
        glyph_copy(
            &mut font_output,
            &glyph,
            char_offset_impl(*chr),
            INPUT_WIDTH_LEN,
            INPUT_HEIGHT_LEN,
        );
    }

    // Repack from 16-bit-wide to 9-bit-wide
    let mut packed = [0u8; 9 * 14 * 256 / 8];

    for y in 0..(256 / CHARS_PER_ROW) {
        for x in 0..CHARS_PER_ROW {
            let seek_src_bits_index = y * 16 * CHARS_PER_ROW * 14 + x * 16;
            let seek_dst_bits_index = y * 9 * CHARS_PER_ROW * 14 + x * 9;
            for gy in 0..14 {
                for gx in 0..9 {
                    let src = get_bit(
                        &font_output,
                        seek_src_bits_index + gx + gy * CHARS_PER_ROW * 16,
                    );
                    let iout = seek_dst_bits_index + gx + gy * CHARS_PER_ROW * 9;
                    set_bit(&mut packed, iout, src);
                }
            }
        }
    }

    save_raw(
        &format!("{}/src/fonts/ibm437_font_9_14_regular.raw", base_dir),
        &packed,
    )?;
    save_png(
        &format!("{}/doc/ibm437_font_9_14_regular.png", base_dir),
        &packed,
        9 * CHARS_PER_ROW,
        14 * (256 / CHARS_PER_ROW),
    )?;

    Ok(())
}

// #######################################################################
// IBM437 8x8 regular font (CGA)
fn extract_8x8_regular(
    characters: &Vec<char>,
    ibm437_src: &[u8; 8192],
    base_dir: &str,
) -> std::io::Result<()> {
    const INPUT_WIDTH_LEN: usize = 1;
    const INPUT_HEIGHT_LEN: usize = 8;

    let mut font_output: [u8; INPUT_WIDTH_LEN
        * CHARS_PER_ROW
        * INPUT_HEIGHT_LEN
        * (256 / CHARS_PER_ROW)] =
        [0; INPUT_WIDTH_LEN * CHARS_PER_ROW * INPUT_HEIGHT_LEN * (256 / CHARS_PER_ROW)];

    for (i, chr) in characters.iter().enumerate() {
        let glyph = &ibm437_src[0x1000 + 8 * i..0x1000 + 8 * (i + 1)];
        glyph_copy(
            &mut font_output,
            &glyph,
            char_offset_impl(*chr),
            INPUT_WIDTH_LEN,
            INPUT_HEIGHT_LEN,
        );
    }

    save_raw(
        &format!("{}/src/fonts/ibm437_font_8_8_regular.raw", base_dir),
        &font_output,
    )?;
    save_png(
        &format!("{}/doc/ibm437_font_8_8_regular.png", base_dir),
        &font_output,
        8 * CHARS_PER_ROW,
        8 * (256 / CHARS_PER_ROW),
    )?;

    Ok(())
}

// #######################################################################
// IBM437 8x8 bold font (CGA)
fn extract_8x8_bold(
    characters: &Vec<char>,
    ibm437_src: &[u8; 8192],
    base_dir: &str,
) -> std::io::Result<()> {
    const INPUT_WIDTH_LEN: usize = 1;
    const INPUT_HEIGHT_LEN: usize = 8;

    let mut font_output: [u8; INPUT_WIDTH_LEN
        * CHARS_PER_ROW
        * INPUT_HEIGHT_LEN
        * (256 / CHARS_PER_ROW)] =
        [0; INPUT_WIDTH_LEN * CHARS_PER_ROW * INPUT_HEIGHT_LEN * (256 / CHARS_PER_ROW)];

    for (i, chr) in characters.iter().enumerate() {
        let glyph = &ibm437_src[0x1800 + 8 * i..0x1800 + 8 * (i + 1)];
        glyph_copy(
            &mut font_output,
            &glyph,
            char_offset_impl(*chr),
            INPUT_WIDTH_LEN,
            INPUT_HEIGHT_LEN,
        );
    }

    save_raw(
        &format!("{}/src/fonts/ibm437_font_8_8_bold.raw", base_dir),
        &font_output,
    )?;
    save_png(
        &format!("{}/doc/ibm437_font_8_8_bold.png", base_dir),
        &font_output,
        8 * CHARS_PER_ROW,
        8 * (256 / CHARS_PER_ROW),
    )?;

    Ok(())
}

// #######################################################################
fn characters_mapping(characters: &Vec<char>, base_dir: &str) -> std::io::Result<()> {
    let mut mapping: [char; 256] = [' '; 256];

    for chr in characters.iter() {
        let chr_index = char_offset_impl(*chr);
        mapping[chr_index] = *chr;
    }

    let filename = format!("{}/doc/Characters.txt", base_dir);
    let path = Path::new(&filename);
    let mut file = File::create(path)?;

    for (i, c) in mapping.iter().enumerate() {
        write!(file, "{}", c)?;
        if (i + 1) % CHARS_PER_ROW == 0 {
            writeln!(file)?;
        }
    }
    Ok(())
}

// #######################################################################
fn main() -> std::io::Result<()> {
    let base_dir = env!("CARGO_MANIFEST_DIR").to_string();

    const IBM437_SRC: &[u8; 8192] = include_bytes!("../IBM_5788005_AM9264_1981_CGA_MDA_CARD.BIN");

    let characters: Vec<char> = include_str!("../Characters_src.txt")
        .lines()
        .map(|l| l.chars())
        .flatten()
        .collect::<Vec<char>>();

    println!("Generating fonts into {}/src/fonts/ ...", base_dir);

    extract_9x14(&characters, IBM437_SRC, &base_dir)?;
    println!("- 9x14 regular");

    extract_8x8_regular(&characters, IBM437_SRC, &base_dir)?;
    println!("- 8x8 regular");

    extract_8x8_bold(&characters, IBM437_SRC, &base_dir)?;
    println!("- 8x8 bold");

    characters_mapping(&characters, &base_dir)?;
    println!("- Characters.txt");

    println!("Done. PNG files updated in {}/doc/", base_dir);
    Ok(())
}