Skip to content

Instantly share code, notes, and snippets.

@lioncash
Created October 19, 2016 04:22
Show Gist options
  • Save lioncash/628bd6ab0e65bfc89e652fd2e296e784 to your computer and use it in GitHub Desktop.
Save lioncash/628bd6ab0e65bfc89e652fd2e296e784 to your computer and use it in GitHub Desktop.
Doom ByteMap font reader | License: Don't care; WTFPL or public domain, etc, etc.
#include "BMF.h"
#include <algorithm>
#include <array>
#include <fstream>
#include <iterator>
namespace
{
constexpr std::array<unsigned char, 4> magic_identifier{0xE1, 0xE6, 0xD5, 0x1A};
bool is_valid_magic(std::istream& in)
{
std::array<unsigned char, 4> magic;
in.read(reinterpret_cast<char*>(magic.data()), magic.size());
return std::equal(magic.begin(), magic.end(),
magic_identifier.begin(), magic_identifier.end());
}
std::vector<uint8_t> read_palette_data(uint32_t num_rgb_entries, std::istream& in)
{
const uint32_t palette_data_size = num_rgb_entries * 3;
std::vector<uint8_t> palette_data(palette_data_size);
in.read(reinterpret_cast<char*>(palette_data.data()), palette_data.size());
return palette_data;
}
std::string read_info_string(std::istream& in)
{
uint8_t info_string_size;
in.read(reinterpret_cast<char*>(&info_string_size), sizeof(info_string_size));
if (!in)
return {};
std::string info_string(info_string_size, '\0');
in.read(&info_string[0], info_string.size());
return info_string;
}
BMF::bitmap_vector read_character_data(std::istream& in)
{
uint16_t num_characters_in_font;
in.read(reinterpret_cast<char*>(&num_characters_in_font), sizeof(num_characters_in_font));
if (!in)
return {};
BMF::bitmap_vector character_data(num_characters_in_font);
std::copy_n(std::istream_iterator<BMF::BitmapCharacter>(in),
num_characters_in_font,
character_data.begin());
return character_data;
}
} // Anonymous namespace
BMF::BMF(const std::string& file_path)
{
read_file(file_path);
}
bool BMF::is_valid() const noexcept
{
return m_valid;
}
void BMF::read_file(const std::string& file_path)
{
std::ifstream file(file_path, std::ifstream::binary);
if (!file)
return;
file >> *this;
if (file.good())
m_valid = true;
}
uint8_t BMF::version() const noexcept
{
return m_version;
}
uint8_t BMF::line_height() const noexcept
{
return m_line_height;
}
int8_t BMF::size_over_base_line() const noexcept
{
return m_size_over_base_line;
}
int8_t BMF::size_under_base_line() const noexcept
{
return m_size_under_base_line;
}
int8_t BMF::add_space_after_each_char() const noexcept
{
return m_add_space_after_each_char;
}
int8_t BMF::size_inner() const noexcept
{
return m_size_inner;
}
uint8_t BMF::num_colors_used() const noexcept
{
return m_num_colors_used;
}
uint8_t BMF::highest_used_color_attribute() const noexcept
{
return m_highest_used_color_attribute;
}
uint8_t BMF::num_rgb_entries() const noexcept
{
return m_num_rgb_entries;
}
const std::vector<uint8_t>& BMF::font_palette() const noexcept
{
return m_font_palette_rgb;
}
const std::string& BMF::info_string() const noexcept
{
return m_info_string;
}
const BMF::bitmap_vector& BMF::character_data() const noexcept
{
return m_character_data;
}
std::ostream& operator<<(std::ostream& out, const BMF& bmf)
{
// Damn
out.write(reinterpret_cast<const char*>(magic_identifier.data()), magic_identifier.size());
out.write(reinterpret_cast<const char*>(&bmf.m_version), sizeof(bmf.m_version));
out.write(reinterpret_cast<const char*>(&bmf.m_line_height), sizeof(bmf.m_line_height));
out.write(reinterpret_cast<const char*>(&bmf.m_size_over_base_line), sizeof(bmf.m_size_over_base_line));
out.write(reinterpret_cast<const char*>(&bmf.m_size_under_base_line), sizeof(bmf.m_size_under_base_line));
out.write(reinterpret_cast<const char*>(&bmf.m_add_space_after_each_char), sizeof(bmf.m_add_space_after_each_char));
out.write(reinterpret_cast<const char*>(&bmf.m_size_inner), sizeof(bmf.m_size_inner));
out.write(reinterpret_cast<const char*>(&bmf.m_num_colors_used), sizeof(bmf.m_num_colors_used));
out.write(reinterpret_cast<const char*>(&bmf.m_highest_used_color_attribute), sizeof(bmf.m_highest_used_color_attribute));
out.write(reinterpret_cast<const char*>(&bmf.m_reserved), sizeof(bmf.m_reserved));
out.write(reinterpret_cast<const char*>(&bmf.m_num_rgb_entries), sizeof(bmf.m_num_rgb_entries));
out.write(reinterpret_cast<const char*>(bmf.m_font_palette_rgb.data()), bmf.m_font_palette_rgb.size());
const auto info_size = static_cast<uint8_t>(bmf.m_info_string.size());
out.write(reinterpret_cast<const char*>(&info_size), sizeof(info_size));
out.write(bmf.m_info_string.c_str(), bmf.m_info_string.size());
const auto data_size = static_cast<uint16_t>(bmf.m_character_data.size());
out.write(reinterpret_cast<const char*>(&data_size), sizeof(data_size));
std::copy(bmf.m_character_data.begin(),
bmf.m_character_data.end(),
std::ostream_iterator<BMF::BitmapCharacter>(out));
return out;
}
std::istream& operator>>(std::istream& in, BMF& bmf)
{
bmf.m_valid = is_valid_magic(in);
if (!bmf.m_valid)
{
in.setstate(std::ios::failbit);
return in;
}
// >Try to wake up
// >Can't wake up
in.read(reinterpret_cast<char*>(&bmf.m_version), sizeof(bmf.m_version));
in.read(reinterpret_cast<char*>(&bmf.m_line_height), sizeof(bmf.m_line_height));
in.read(reinterpret_cast<char*>(&bmf.m_size_over_base_line), sizeof(bmf.m_size_over_base_line));
in.read(reinterpret_cast<char*>(&bmf.m_size_under_base_line), sizeof(bmf.m_size_under_base_line));
in.read(reinterpret_cast<char*>(&bmf.m_add_space_after_each_char), sizeof(bmf.m_add_space_after_each_char));
in.read(reinterpret_cast<char*>(&bmf.m_size_inner), sizeof(bmf.m_size_inner));
in.read(reinterpret_cast<char*>(&bmf.m_num_colors_used), sizeof(bmf.m_num_colors_used));
in.read(reinterpret_cast<char*>(&bmf.m_highest_used_color_attribute), sizeof(bmf.m_highest_used_color_attribute));
in.read(reinterpret_cast<char*>(&bmf.m_reserved), sizeof(bmf.m_reserved));
in.read(reinterpret_cast<char*>(&bmf.m_num_rgb_entries), sizeof(bmf.m_num_rgb_entries));
if (!in)
return in;
bmf.m_font_palette_rgb = read_palette_data(bmf.m_num_rgb_entries, in);
if (!in)
return in;
bmf.m_info_string = read_info_string(in);
if (!in)
return in;
bmf.m_character_data = read_character_data(in);
return in;
}
std::ostream& operator<<(std::ostream& out, const BMF::BitmapCharacter& character)
{
// Death, take me now; thanks in advance.
out.write(&character.character, sizeof(character.character));
out.write(reinterpret_cast<const char*>(&character.width), sizeof(character.width));
out.write(reinterpret_cast<const char*>(&character.height), sizeof(character.height));
out.write(reinterpret_cast<const char*>(&character.horizontal_offset), sizeof(character.horizontal_offset));
out.write(reinterpret_cast<const char*>(&character.vertical_offset), sizeof(character.vertical_offset));
out.write(reinterpret_cast<const char*>(&character.horizontal_cursor_shift), sizeof(character.horizontal_cursor_shift));
out.write(reinterpret_cast<const char*>(character.data.data()), character.data.size());
return out;
}
std::istream& operator>>(std::istream& in, BMF::BitmapCharacter& character)
{
// Wew lad
in.read(&character.character, sizeof(character.character));
in.read(reinterpret_cast<char*>(&character.width), sizeof(character.width));
in.read(reinterpret_cast<char*>(&character.height), sizeof(character.height));
in.read(reinterpret_cast<char*>(&character.horizontal_offset), sizeof(character.horizontal_offset));
in.read(reinterpret_cast<char*>(&character.vertical_offset), sizeof(character.vertical_offset));
in.read(reinterpret_cast<char*>(&character.horizontal_cursor_shift), sizeof(character.horizontal_cursor_shift));
if (!in)
return in;
character.data.resize(character.width * character.height);
in.read(reinterpret_cast<char*>(character.data.data()), character.data.size());
return in;
}
#pragma once
#include <cstdint>
#include <iosfwd>
#include <string>
#include <vector>
/// Represents a ByteMap v1.1 font
class BMF final
{
public:
struct BitmapCharacter final
{
char character = '\0';
uint8_t width = 0;
uint8_t height = 0;
int8_t horizontal_offset = 0;
int8_t vertical_offset = 0;
uint8_t horizontal_cursor_shift = 0;
std::vector<uint8_t> data;
};
using bitmap_vector = std::vector<BitmapCharacter>;
using iterator = bitmap_vector::iterator;
using const_iterator = bitmap_vector::const_iterator;
using reverse_iterator = bitmap_vector::reverse_iterator;
using const_reverse_iterator = bitmap_vector::const_reverse_iterator;
/**
* Default constructor
*
* @note read_file must be used to populate this instance with data.
*/
BMF() = default;
/**
* Constructor
*
* @param file_path Path to the BMF file to open.
*/
explicit BMF(const std::string& file_path);
/**
* Whether or not this BMF instance is valid.
*
* A BMF instance is invalid if any errors occurred
* while reading the BMF file.
*/
bool is_valid() const noexcept;
/**
* Reads the contents of a BMF file into this instance.
* This allows for two-step construction.
*
* @param file_path Path the the BMF file to read.
*/
void read_file(const std::string& file_path);
iterator begin() noexcept { return m_character_data.begin(); }
const_iterator begin() const noexcept { return m_character_data.begin(); }
const_iterator cbegin() const noexcept { return m_character_data.cbegin(); }
iterator end() noexcept { return m_character_data.end(); }
const_iterator end() const noexcept { return m_character_data.end(); }
const_iterator cend() const noexcept { return m_character_data.cend(); }
reverse_iterator rbegin() noexcept { return m_character_data.rbegin(); }
const_reverse_iterator rbegin() const noexcept { return m_character_data.rbegin(); }
const_reverse_iterator crbegin() const noexcept { return m_character_data.crbegin(); }
reverse_iterator rend() noexcept { return m_character_data.rend(); }
const_reverse_iterator rend() const noexcept { return m_character_data.rend(); }
const_reverse_iterator crend() const noexcept { return m_character_data.crend(); }
uint8_t version() const noexcept;
uint8_t line_height() const noexcept;
int8_t size_over_base_line() const noexcept;
int8_t size_under_base_line() const noexcept;
int8_t add_space_after_each_char() const noexcept;
int8_t size_inner() const noexcept;
uint8_t num_colors_used() const noexcept;
uint8_t highest_used_color_attribute() const noexcept;
uint8_t num_rgb_entries() const noexcept;
const std::vector<uint8_t>& font_palette() const noexcept;
const std::string& info_string() const noexcept;
const bitmap_vector& character_data() const noexcept;
private:
friend std::ostream& operator<<(std::ostream&, const BMF&);
friend std::istream& operator>>(std::istream&, BMF&);
bool m_valid = false;
uint8_t m_version = 0;
uint8_t m_line_height = 0;
int8_t m_size_over_base_line = 0;
int8_t m_size_under_base_line = 0;
int8_t m_add_space_after_each_char = 0;
int8_t m_size_inner = 0;
uint8_t m_num_colors_used = 0;
uint8_t m_highest_used_color_attribute = 0;
uint32_t m_reserved = 0;
uint8_t m_num_rgb_entries = 0;
std::vector<uint8_t> m_font_palette_rgb;
std::string m_info_string;
bitmap_vector m_character_data;
};
std::ostream& operator<<(std::ostream&, const BMF&);
std::istream& operator>>(std::istream&, BMF&);
std::ostream& operator<<(std::ostream&, const BMF::BitmapCharacter&);
std::istream& operator>>(std::istream&, BMF::BitmapCharacter&);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment