ZmFont: Store character padding in font file

The size/variant specific character padding should be stored with the font data.
Modify the FontBitmapHeader accordingly and introduce a version field in the FontFileHeader
so we are able to check we have a font file with the right structure associated with that version.

The version field is set to 1 in this changeset.
pull/3223/head
Peter Keresztes Schmidt 2021-04-24 00:54:41 +02:00
parent a918e8aeba
commit 3020acf994
10 changed files with 37 additions and 22 deletions

Binary file not shown.

View File

@ -20,14 +20,16 @@
#include <cstring> #include <cstring>
#include <fstream> #include <fstream>
constexpr uint8 kRequiredZmFntVersion = 1;
constexpr uint8 FontVariant::kMaxNumCodePoints; constexpr uint8 FontVariant::kMaxNumCodePoints;
constexpr uint8 FontVariant::kMaxCharHeight; constexpr uint8 FontVariant::kMaxCharHeight;
constexpr uint8 FontVariant::kMaxCharWidth; constexpr uint8 FontVariant::kMaxCharWidth;
FontVariant::FontVariant() : char_height_(0), char_width_(0), codepoint_count_(0) {} FontVariant::FontVariant() : char_height_(0), char_width_(0), char_padding_(0), codepoint_count_(0) {}
FontVariant::FontVariant(uint16 char_height, uint16 char_width, std::vector<uint64> bitmap) FontVariant::FontVariant(uint16 char_height, uint16 char_width, uint8 char_padding, std::vector<uint64> bitmap)
: char_height_(char_height), char_width_(char_width), bitmap_(std::move(bitmap)) { : char_height_(char_height), char_width_(char_width), char_padding_(char_padding), bitmap_(std::move(bitmap)) {
if (char_height_ > kMaxCharHeight) { if (char_height_ > kMaxCharHeight) {
throw std::invalid_argument("char_height > kMaxCharHeight"); throw std::invalid_argument("char_height > kMaxCharHeight");
} }
@ -61,6 +63,7 @@ std::ifstream &operator>>(std::ifstream &stream, FontBitmapHeader &bm_header) {
std::ifstream &operator>>(std::ifstream &stream, FontFileHeader &header) { std::ifstream &operator>>(std::ifstream &stream, FontFileHeader &header) {
stream.read(header.magic, sizeof(header.magic)); stream.read(header.magic, sizeof(header.magic));
stream.read(reinterpret_cast<char *>(&header.version), sizeof(header.version));
stream.seekg(sizeof(header.pad), std::ifstream::cur); stream.seekg(sizeof(header.pad), std::ifstream::cur);
for (FontBitmapHeader &bm_header : header.bitmap_header) for (FontBitmapHeader &bm_header : header.bitmap_header)
@ -84,7 +87,7 @@ FontLoadError ZmFont::LoadFontFile(const std::string &loc) {
return FontLoadError::kInvalidFile; return FontLoadError::kInvalidFile;
} }
if (memcmp(file_header.magic, "ZMFNT", 5) != 0) { if (memcmp(file_header.magic, "ZMFNT", 5) != 0 || file_header.version != kRequiredZmFntVersion) {
return FontLoadError::kInvalidFile; return FontLoadError::kInvalidFile;
} }
@ -104,7 +107,7 @@ FontLoadError ZmFont::LoadFontFile(const std::string &loc) {
font_file.read(reinterpret_cast<char *>(bitmap.data()), static_cast<std::streamsize>(bitmap_bytes)); font_file.read(reinterpret_cast<char *>(bitmap.data()), static_cast<std::streamsize>(bitmap_bytes));
variants_[i] = variants_[i] =
{bitmap_header.char_height, bitmap_header.char_width, std::move(bitmap)}; {bitmap_header.char_height, bitmap_header.char_width, bitmap_header.char_padding, std::move(bitmap)};
} }
if (font_file.fail()) { if (font_file.fail()) {

View File

@ -39,14 +39,16 @@ struct FontBitmapHeader {
uint16 char_width; // width of every character uint16 char_width; // width of every character
uint32 number_of_code_points; // number of codepoints; max. 255 for now uint32 number_of_code_points; // number of codepoints; max. 255 for now
uint32 idx; // offset in data where data for the bitmap starts; not used uint32 idx; // offset in data where data for the bitmap starts; not used
uint32 pad; // padding uint8 char_padding; // padding around characters
uint8 pad[3]; // struct padding
}; };
#pragma pack(pop) #pragma pack(pop)
#pragma pack(push, 1) #pragma pack(push, 1)
struct FontFileHeader { struct FontFileHeader {
char magic[6]; // "ZMFNT\0" char magic[6]; // "ZMFNT\0"
uint8 pad[2]; uint8 version;
uint8 pad;
std::array<FontBitmapHeader, kNumFontSizes> bitmap_header; std::array<FontBitmapHeader, kNumFontSizes> bitmap_header;
}; };
#pragma pack(pop) #pragma pack(pop)
@ -60,10 +62,11 @@ class FontVariant {
static constexpr uint8 kMaxCharWidth = 64; static constexpr uint8 kMaxCharWidth = 64;
FontVariant(); FontVariant();
FontVariant(uint16 char_height, uint16 char_width, std::vector<uint64> bitmap); FontVariant(uint16 char_height, uint16 char_width, uint8 char_padding, std::vector<uint64> bitmap);
uint16 GetCharHeight() const { return char_height_; } uint16 GetCharHeight() const { return char_height_; }
uint16 GetCharWidth() const { return char_width_; } uint16 GetCharWidth() const { return char_width_; }
uint8 GetCharPadding() const { return char_padding_; }
uint8 GetCodepointsCount() const { return codepoint_count_; } uint8 GetCodepointsCount() const { return codepoint_count_; }
// Returns the bitmap of the codepoint `idx`. If `idx` is greater than `GetCodepointsCount` // Returns the bitmap of the codepoint `idx`. If `idx` is greater than `GetCodepointsCount`
@ -73,6 +76,7 @@ class FontVariant {
private: private:
uint16 char_height_; uint16 char_height_;
uint16 char_width_; uint16 char_width_;
uint8 char_padding_;
uint8 codepoint_count_; uint8 codepoint_count_;
std::vector<uint64> bitmap_; std::vector<uint64> bitmap_;
}; };

View File

@ -2033,7 +2033,7 @@ void Image::Annotate(
} }
while (cp_row != 0) { while (cp_row != 0) {
int column_idx = char_width - __builtin_ctzll(cp_row) + size; int column_idx = char_width - __builtin_ctzll(cp_row) + font_variant.GetCharPadding();
*(ptr + column_idx) = fg_colour & 0xff; *(ptr + column_idx) = fg_colour & 0xff;
cp_row = cp_row & (cp_row - 1); cp_row = cp_row & (cp_row - 1);
} }
@ -2061,7 +2061,7 @@ void Image::Annotate(
} }
while (cp_row != 0) { while (cp_row != 0) {
int column_idx = char_width - __builtin_ctzll(cp_row) + size; int column_idx = char_width - __builtin_ctzll(cp_row) + font_variant.GetCharPadding();
uint8 *colour_ptr = ptr + (column_idx * bytesPerPixel); uint8 *colour_ptr = ptr + (column_idx * bytesPerPixel);
RED_PTR_RGBA(colour_ptr) = RED_VAL_RGBA(fg_colour); RED_PTR_RGBA(colour_ptr) = RED_VAL_RGBA(fg_colour);
GREEN_PTR_RGBA(colour_ptr) = GREEN_VAL_RGBA(fg_colour); GREEN_PTR_RGBA(colour_ptr) = GREEN_VAL_RGBA(fg_colour);
@ -2087,7 +2087,7 @@ void Image::Annotate(
} }
while (cp_row != 0) { while (cp_row != 0) {
uint32 column_idx = char_width - __builtin_ctzll(cp_row) + size; uint32 column_idx = char_width - __builtin_ctzll(cp_row) + font_variant.GetCharPadding();
*(ptr + column_idx) = fg_rgb_col; *(ptr + column_idx) = fg_rgb_col;
cp_row = cp_row & (cp_row - 1); cp_row = cp_row & (cp_row - 1);
} }

Binary file not shown.

Binary file not shown.

View File

@ -6,6 +6,8 @@ GOOD_MAGIC = b"ZMFNT\0"
BAD_MAGIC = b"ABCDE\0" BAD_MAGIC = b"ABCDE\0"
NUM_FONT_SIZES = 4 NUM_FONT_SIZES = 4
ZMFNT_VERSION = 1
class FontFile: class FontFile:
def __init__(self, path): def __init__(self, path):
@ -14,11 +16,12 @@ class FontFile:
def write_file_header(self, magic): def write_file_header(self, magic):
with open(self.path, "wb") as f: with open(self.path, "wb") as f:
f.write(magic) f.write(magic)
f.write(struct.pack("BB", 0, 0)) # pad f.write(struct.pack("B", ZMFNT_VERSION))
f.write(struct.pack("B", 0)) # pad
def write_bm_header(self, height, width, cp_count, idx): def write_bm_header(self, height, width, cp_count, idx, padding):
with open(self.path, "ab") as f: with open(self.path, "ab") as f:
f.write(struct.pack("HHIII", height, width, cp_count, idx, 0)) f.write(struct.pack("HHIIBBBB", height, width, cp_count, idx, padding, 0, 0, 0))
def write_codepoints(self, value, height, count): def write_codepoints(self, value, height, count):
with open(self.path, "ab") as f: with open(self.path, "ab") as f:
@ -32,14 +35,14 @@ font.write_file_header(BAD_MAGIC)
# height, width and number of codepoints out of bounds # height, width and number of codepoints out of bounds
font = FontFile("02_variant_invalid.zmfnt") font = FontFile("02_variant_invalid.zmfnt")
font.write_file_header(GOOD_MAGIC) font.write_file_header(GOOD_MAGIC)
font.write_bm_header(201, 65, 256, 0) font.write_bm_header(201, 65, 256, 0, 2)
# mismatch between number of codepoints specified in header and actually stored ones # mismatch between number of codepoints specified in header and actually stored ones
font = FontFile("03_missing_cps.zmfnt") font = FontFile("03_missing_cps.zmfnt")
font.write_file_header(GOOD_MAGIC) font.write_file_header(GOOD_MAGIC)
offs = 0 offs = 0
for _ in range(NUM_FONT_SIZES): for _ in range(NUM_FONT_SIZES):
font.write_bm_header(10, 10, 10, offs) font.write_bm_header(10, 10, 10, offs, 2)
offs += 10 * 10 offs += 10 * 10
for _ in range(NUM_FONT_SIZES): for _ in range(NUM_FONT_SIZES):
font.write_codepoints(1, 10, 9) font.write_codepoints(1, 10, 9)
@ -48,7 +51,7 @@ font = FontFile("04_valid.zmfnt")
font.write_file_header(GOOD_MAGIC) font.write_file_header(GOOD_MAGIC)
offs = 0 offs = 0
for i in range(NUM_FONT_SIZES): for i in range(NUM_FONT_SIZES):
font.write_bm_header(10 + i, 10 + i, 10, offs) font.write_bm_header(10 + i, 10 + i, 10, offs, 2)
offs += 10 * (10 + i) offs += 10 * (10 + i)
for i in range(NUM_FONT_SIZES): for i in range(NUM_FONT_SIZES):
font.write_codepoints(i, 10 + i, 10) font.write_codepoints(i, 10 + i, 10)

View File

@ -35,9 +35,10 @@ TEST_CASE("FontVariant: construction") {
SECTION("values in range") { SECTION("values in range") {
constexpr uint8 height = 10; constexpr uint8 height = 10;
constexpr uint8 width = 10; constexpr uint8 width = 10;
constexpr uint8 padding = 2;
std::vector<uint64> bitmap(FontVariant::kMaxNumCodePoints * height); std::vector<uint64> bitmap(FontVariant::kMaxNumCodePoints * height);
REQUIRE_NOTHROW(variant = FontVariant(height, width, bitmap)); REQUIRE_NOTHROW(variant = FontVariant(height, width, padding, bitmap));
REQUIRE(variant.GetCharHeight() == height); REQUIRE(variant.GetCharHeight() == height);
REQUIRE(variant.GetCharWidth() == width); REQUIRE(variant.GetCharWidth() == width);
@ -47,31 +48,35 @@ TEST_CASE("FontVariant: construction") {
SECTION("height out of range") { SECTION("height out of range") {
constexpr uint8 height = FontVariant::kMaxCharHeight + 1; constexpr uint8 height = FontVariant::kMaxCharHeight + 1;
constexpr uint8 width = 10; constexpr uint8 width = 10;
constexpr uint8 padding = 2;
std::vector<uint64> bitmap(FontVariant::kMaxNumCodePoints * height); std::vector<uint64> bitmap(FontVariant::kMaxNumCodePoints * height);
REQUIRE_THROWS(variant = FontVariant(height, width, bitmap)); REQUIRE_THROWS(variant = FontVariant(height, width, padding, bitmap));
} }
SECTION("width out of range") { SECTION("width out of range") {
constexpr uint8 height = 10; constexpr uint8 height = 10;
constexpr uint8 width = FontVariant::kMaxCharWidth + 1; constexpr uint8 width = FontVariant::kMaxCharWidth + 1;
constexpr uint8 padding = 2;
std::vector<uint64> bitmap(FontVariant::kMaxNumCodePoints * height); std::vector<uint64> bitmap(FontVariant::kMaxNumCodePoints * height);
REQUIRE_THROWS(variant = FontVariant(height, width, bitmap)); REQUIRE_THROWS(variant = FontVariant(height, width, padding, bitmap));
} }
SECTION("bitmap of wrong size") { SECTION("bitmap of wrong size") {
constexpr uint8 height = 10; constexpr uint8 height = 10;
constexpr uint8 width = 10; constexpr uint8 width = 10;
constexpr uint8 padding = 2;
std::vector<uint64> bitmap(FontVariant::kMaxNumCodePoints * height + 1); std::vector<uint64> bitmap(FontVariant::kMaxNumCodePoints * height + 1);
REQUIRE_THROWS(variant = FontVariant(height, width, bitmap)); REQUIRE_THROWS(variant = FontVariant(height, width, padding, bitmap));
} }
} }
TEST_CASE("FontVariant: GetCodepoint") { TEST_CASE("FontVariant: GetCodepoint") {
constexpr uint8 height = 10; constexpr uint8 height = 10;
constexpr uint8 width = 10; constexpr uint8 width = 10;
constexpr uint8 padding = 2;
std::vector<uint64> bitmap(FontVariant::kMaxNumCodePoints * height); std::vector<uint64> bitmap(FontVariant::kMaxNumCodePoints * height);
// fill bitmap for each codepoint alternating with 1 and std::numeric_limits<uint64>::max() // fill bitmap for each codepoint alternating with 1 and std::numeric_limits<uint64>::max()
@ -89,7 +94,7 @@ TEST_CASE("FontVariant: GetCodepoint") {
} }
}); });
FontVariant variant(height, width, bitmap); FontVariant variant(height, width, padding, bitmap);
nonstd::span<const uint64> cp; nonstd::span<const uint64> cp;
SECTION("in bounds") { SECTION("in bounds") {