First init.

This commit is contained in:
2025-10-12 09:13:56 +02:00
commit 1548aeaf9b
458 changed files with 118808 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
/*
* MCU renderer fontconv
* Bit writer
*
* (C) 2023 Gissio
*
* License: MIT
*/
#include "bitwriter.h"
BitWriter::BitWriter()
{
bitIndex = 0;
}
void BitWriter::clear()
{
data.clear();
bitIndex = 0;
}
void BitWriter::writeBit(bool value)
{
if (bitIndex == 0)
data.push_back(0);
data.back() |= (value << bitIndex);
bitIndex = (bitIndex + 1) & 0x7;
}
void BitWriter::writeFixedEncodedValue(uint32_t value, uint32_t bitNum)
{
while (bitNum--)
{
writeBit(value & 0x1);
value >>= 1;
}
}
void BitWriter::writeUnaryEncodedValue(uint32_t value)
{
while (value--)
writeBit(1);
writeBit(0);
}
void BitWriter::writeRiceEncodedValue(uint32_t value, uint32_t fixedBitNum)
{
uint32_t remainder = value & ((1 << fixedBitNum) - 1);
uint32_t quotient = value >> fixedBitNum;
writeFixedEncodedValue(remainder, fixedBitNum);
writeUnaryEncodedValue(quotient);
}
void BitWriter::writeByte(uint8_t value)
{
data.push_back(value);
bitIndex = 0;
}
void BitWriter::writeByte(uint32_t address, uint8_t value)
{
data[address] = value;
}
void BitWriter::writeShort(int16_t value)
{
data.push_back((value >> 8) & 0xff);
data.push_back((value >> 0) & 0xff);
bitIndex = 0;
}
void BitWriter::writeShort(uint32_t address, int16_t value)
{
data[address] = (value >> 8) & 0xff;
data[address + 1] = (value >> 0) & 0xff;
}
void BitWriter::writeVariableLengthWord(uint32_t value)
{
if (!value)
data.push_back(0);
else
{
for (int32_t shift = 28; shift >= 0; shift -= 7)
{
uint32_t shiftedValue = value >> shift;
if (!shiftedValue)
continue;
shiftedValue &= 0x7f;
if (shift)
shiftedValue |= 0x80;
data.push_back(shiftedValue);
}
}
bitIndex = 0;
}
void BitWriter::write(BitWriter &value)
{
if (bitIndex == 0)
{
data.insert(data.end(),
value.data.begin(),
value.data.end());
bitIndex = value.bitIndex;
}
else
{
for (size_t i = 0; i < value.getBitNum(); i++)
writeBit(value.readBit(i));
}
}
bool BitWriter::readBit(uint32_t bitIndex)
{
return data[bitIndex / 8] &
(1 << (bitIndex & 0x7));
}
uint32_t BitWriter::getBitNum()
{
uint32_t n = (uint32_t)data.size();
return (bitIndex == 0)
? (8 * n)
: (8 * (n - 1) + bitIndex);
}
uint32_t BitWriter::getCurrentAddress()
{
return (uint32_t)data.size();
}
bool BitWriter::operator!=(BitWriter &value)
{
return (bitIndex != value.bitIndex) ||
(data != value.data);
}

View File

@@ -0,0 +1,49 @@
/*
* MCU renderer fontconv
* Bit writer
*
* (C) 2023 Gissio
*
* License: MIT
*/
#if !defined(BITWRITER_H)
#define BITWRITER_H
#include <cstdint>
#include <vector>
class BitWriter
{
public:
BitWriter();
void clear();
void writeBit(bool value);
void writeFixedEncodedValue(uint32_t value, uint32_t bitNum);
void writeUnaryEncodedValue(uint32_t value);
void writeRiceEncodedValue(uint32_t value, uint32_t fixedBitNum);
void writeByte(uint8_t value);
void writeByte(uint32_t address, uint8_t value);
void writeShort(int16_t value);
void writeShort(uint32_t address, int16_t value);
void writeVariableLengthWord(uint32_t value);
void write(BitWriter &value);
bool readBit(uint32_t bitIndex);
uint32_t getBitNum();
uint32_t getCurrentAddress();
bool operator!=(BitWriter &value);
std::vector<uint8_t> data;
uint8_t bitIndex;
};
#endif

View File

@@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.16)
project(fontconv)
set(CMAKE_CXX_STANDARD 20)
set(VCPKG_TARGET_TRIPLET x64-windows-static)
FILE(GLOB sources *.cpp)
find_package(Freetype REQUIRED)
include_directories(${FREETYPE_INCLUDE_DIRS})
add_executable(fontconv ${sources})
target_link_libraries(fontconv PRIVATE Freetype::Freetype)

View File

@@ -0,0 +1,74 @@
/*
* MCU renderer fontconv
* Font data structure
*
* (C) 2023 Gissio
*
* License: MIT
*/
#include "Font.h"
MinMax::MinMax()
{
min = INT_MAX;
max = INT_MIN;
}
void MinMax::update(int32_t value)
{
if (value < min)
min = value;
if (value > max)
max = value;
}
Font::Font()
{
capHeight = 0;
ascent = INT_MIN;
descent = INT_MIN;
boundingBoxLeft = INT_MAX;
boundingBoxBottom = INT_MAX;
boundingBoxWidth = 0;
boundingBoxHeight = 0;
}
void Font::add(Charcode charcode, Glyph &glyph)
{
int32_t fontRight;
int32_t fontTop;
int32_t glyphRight;
int32_t glyphTop;
fontRight = boundingBoxWidth
? (boundingBoxLeft + boundingBoxWidth)
: INT_MIN;
fontTop = boundingBoxHeight
? (boundingBoxBottom + boundingBoxHeight)
: INT_MIN;
glyphRight = glyph.left + glyph.width;
glyphTop = glyph.bottom + glyph.height;
if (glyph.left < boundingBoxLeft)
boundingBoxLeft = glyph.left;
if (glyph.bottom < boundingBoxBottom)
boundingBoxBottom = glyph.bottom;
if (glyphRight > fontRight)
fontRight = glyphRight;
if (glyphTop > fontTop)
fontTop = glyphTop;
boundingBoxWidth = fontRight - boundingBoxLeft;
boundingBoxHeight = fontTop - boundingBoxBottom;
boundingBoxLeftMinMax.update(glyph.left);
boundingBoxBottomMinMax.update(glyph.bottom);
boundingBoxWidthMinMax.update(glyph.width);
boundingBoxHeightMinMax.update(glyph.height);
advanceMinMax.update(glyph.advance);
glyphs[charcode] = glyph;
}

View File

@@ -0,0 +1,73 @@
/*
* MCU renderer fontconv
* Font data structure
*
* (C) 2023 Gissio
*
* License: MIT
*/
#if !defined(FONT_H)
#define FONT_H
#include <cstdint>
#include <map>
#include <string>
#include <vector>
typedef int32_t Charcode;
class Glyph
{
public:
int32_t left;
int32_t bottom;
int32_t width;
int32_t height;
int32_t advance;
std::vector<uint8_t> bitmap;
};
class MinMax
{
public:
MinMax();
void update(int32_t value);
int32_t min;
int32_t max;
};
class Font
{
public:
Font();
void add(Charcode charcode, Glyph &glyph);
std::string name;
std::string copyright;
int32_t boundingBoxLeft;
int32_t boundingBoxBottom;
int32_t boundingBoxWidth;
int32_t boundingBoxHeight;
MinMax boundingBoxLeftMinMax;
MinMax boundingBoxBottomMinMax;
MinMax boundingBoxWidthMinMax;
MinMax boundingBoxHeightMinMax;
MinMax advanceMinMax;
int32_t capHeight;
int32_t ascent;
int32_t descent;
std::map<Charcode, Glyph> glyphs;
};
#endif

View File

@@ -0,0 +1,315 @@
/*
* MCU renderer fontconv
* Font encoder
*
* (C) 2023 Gissio
*
* License: MIT
*/
#include <cstring>
#include <iostream>
#include "encode.h"
using namespace std;
struct EncoderSettings
{
uint32_t boundingBoxLeftBitNum;
uint32_t boundingBoxBottomBitNum;
uint32_t boundingBoxWidthBitNum;
uint32_t boundingBoxHeightBitNum;
uint32_t advanceBitNum;
uint32_t pixelBitNum;
uint32_t runLengthBlackBitNum;
uint32_t runLengthWhiteBitNum;
};
static uint32_t getBitNum(int32_t value)
{
if (value < 0)
value = -value - 1;
uint32_t bitNum = 0;
while (value > 0)
{
bitNum++;
value >>= 1;
}
return bitNum;
}
static uint32_t getBitNum(MinMax &minMax)
{
uint32_t bitNumA = getBitNum(minMax.min);
uint32_t bitNumB = getBitNum(minMax.max);
return (bitNumA > bitNumB) ? bitNumA : bitNumB;
}
static void encodeGlyph(Glyph &glyph,
EncoderSettings &encoderSettings,
BitWriter &bitstream)
{
uint8_t bitshift = (8 - encoderSettings.pixelBitNum);
uint32_t whiteValue = (1 << encoderSettings.pixelBitNum) - 1;
vector<BitWriter> symbols;
// Run-length encoding
uint32_t symbolIndex = 0;
while (symbolIndex < glyph.bitmap.size())
{
uint8_t value = glyph.bitmap[symbolIndex++] >> bitshift;
BitWriter symbol;
if (!value ||
(value == whiteValue))
{
uint32_t runLength = 1;
while (symbolIndex < glyph.bitmap.size())
{
uint8_t newValue = glyph.bitmap[symbolIndex] >> bitshift;
if (value != newValue)
break;
symbolIndex++;
runLength++;
}
symbol.writeFixedEncodedValue(value,
encoderSettings.pixelBitNum);
if (!value)
symbol.writeRiceEncodedValue(runLength,
encoderSettings.runLengthBlackBitNum);
else
symbol.writeRiceEncodedValue(runLength - 1,
encoderSettings.runLengthWhiteBitNum);
}
else
symbol.writeFixedEncodedValue(value,
encoderSettings.pixelBitNum);
symbols.push_back(symbol);
}
// Remove repetitions
for (int32_t symbolIndex = 0;
symbolIndex < symbols.size();
symbolIndex++)
{
// Search repetitions
uint32_t bestRepeatLength;
uint32_t bestRepeatNum = 1;
uint32_t bestRepeatBitNum = 0;
for (uint32_t repeatLength = 2;
repeatLength < glyph.width;
repeatLength++)
{
uint32_t source_index = symbolIndex;
uint32_t dest_index = symbolIndex + repeatLength;
uint32_t repeatNum = 1;
uint32_t repeatBitNum = 0;
uint32_t bitNum = 0;
while (dest_index < symbols.size())
{
if (symbols[source_index] != symbols[dest_index])
break;
bitNum += symbols[source_index].getBitNum();
source_index++;
dest_index++;
if (source_index == (symbolIndex + repeatLength))
{
source_index = symbolIndex;
repeatNum++;
repeatBitNum = bitNum;
}
}
if (repeatBitNum > bestRepeatBitNum)
{
bestRepeatLength = repeatLength;
bestRepeatNum = repeatNum;
bestRepeatBitNum = repeatBitNum;
}
}
// Replace repetitions
if (bestRepeatNum > 1)
{
uint32_t encodedRepeatLength = bestRepeatLength - 2;
uint32_t encodedRepeatNum = bestRepeatNum - 2;
BitWriter symbol;
symbol.writeFixedEncodedValue(0, encoderSettings.pixelBitNum);
symbol.writeRiceEncodedValue(0, encoderSettings.runLengthBlackBitNum);
symbol.writeUnaryEncodedValue(encodedRepeatLength);
symbol.writeUnaryEncodedValue(encodedRepeatNum);
uint32_t compressedBitNum = symbol.getBitNum();
if (compressedBitNum < bestRepeatBitNum)
{
symbols.erase(symbols.begin() + symbolIndex +
bestRepeatLength,
symbols.begin() + symbolIndex +
bestRepeatLength * bestRepeatNum);
symbols.insert(symbols.begin() + symbolIndex,
symbol);
symbolIndex += bestRepeatLength;
}
}
}
// Write glyph data
BitWriter glyphBitstream;
glyphBitstream.writeFixedEncodedValue(glyph.left,
encoderSettings.boundingBoxLeftBitNum);
glyphBitstream.writeFixedEncodedValue(glyph.bottom,
encoderSettings.boundingBoxBottomBitNum);
glyphBitstream.writeFixedEncodedValue(glyph.width,
encoderSettings.boundingBoxWidthBitNum);
glyphBitstream.writeFixedEncodedValue(glyph.height,
encoderSettings.boundingBoxHeightBitNum);
glyphBitstream.writeFixedEncodedValue(glyph.advance,
encoderSettings.advanceBitNum);
for (BitWriter &symbol : symbols)
glyphBitstream.write(symbol);
bitstream.writeVariableLengthWord(glyphBitstream.data.size());
bitstream.write(glyphBitstream);
}
void encodeFont(Font &font,
uint32_t pixelBitNum,
BitWriter &encodedFont)
{
// Analysis
uint32_t bestRunLengthBlackBitNum = UINT32_MAX;
uint32_t bestRunLengthWhiteBitNum = UINT32_MAX;
uint32_t bestEncodedSize = UINT32_MAX;
for (uint32_t runLengthBlackBitNum = 0;
runLengthBlackBitNum < 8;
runLengthBlackBitNum++)
{
for (uint32_t runLengthWhiteBitNum = 0;
runLengthWhiteBitNum < 8;
runLengthWhiteBitNum++)
{
// Get encoder settings
EncoderSettings encoderSettings;
encoderSettings.boundingBoxLeftBitNum =
getBitNum(font.boundingBoxLeftMinMax) + 1;
encoderSettings.boundingBoxBottomBitNum =
getBitNum(font.boundingBoxBottomMinMax) + 1;
encoderSettings.boundingBoxWidthBitNum =
getBitNum(font.boundingBoxWidthMinMax);
encoderSettings.boundingBoxHeightBitNum =
getBitNum(font.boundingBoxHeightMinMax);
encoderSettings.advanceBitNum =
getBitNum(font.advanceMinMax);
encoderSettings.pixelBitNum = pixelBitNum;
encoderSettings.runLengthBlackBitNum = runLengthBlackBitNum;
encoderSettings.runLengthWhiteBitNum = runLengthWhiteBitNum;
// Write header
BitWriter bitstream;
bitstream.writeShort(font.capHeight); // Font cap height (of uppercase letter A)
bitstream.writeShort(font.ascent); // Font ascent (from baseline to top of line)
bitstream.writeShort(font.descent); // Font descent (from baseline to bottom of line)
bitstream.writeShort(font.boundingBoxLeft); // Font bounding box left
bitstream.writeShort(font.boundingBoxBottom); // Font bounding box bottom
bitstream.writeShort(font.boundingBoxWidth); // Font bounding box width
bitstream.writeShort(font.boundingBoxHeight); // Font bounding box height
bitstream.writeByte(encoderSettings.boundingBoxLeftBitNum); // Bounding box left number of bits
bitstream.writeByte(encoderSettings.boundingBoxBottomBitNum); // Bounding box bottom number of bits
bitstream.writeByte(encoderSettings.boundingBoxWidthBitNum); // Bounding box width number of bits
bitstream.writeByte(encoderSettings.boundingBoxHeightBitNum); // Bounding box height number of bits
bitstream.writeByte(encoderSettings.advanceBitNum); // Advance number of bits
bitstream.writeByte(pixelBitNum); // Pixel number of bits
bitstream.writeByte(encoderSettings.runLengthBlackBitNum); // Repeat black number of bits
bitstream.writeByte(encoderSettings.runLengthWhiteBitNum); // Repeat white number of bits
// Write glyphs
BitWriter blockBitstream;
Charcode blockCharcode;
Charcode nextCharcode = -1;
for (auto &entry : font.glyphs)
{
Charcode charcode = entry.first;
Glyph &glyph = entry.second;
// Encode block
if ((charcode != nextCharcode) ||
(charcode == 'A') ||
(charcode == 'a'))
{
if (nextCharcode != -1)
{
bitstream.writeVariableLengthWord(blockBitstream.data.size());
bitstream.writeVariableLengthWord(blockCharcode);
bitstream.write(blockBitstream);
blockBitstream.clear();
}
blockCharcode = charcode;
}
nextCharcode = charcode + 1;
// Encode glyph
encodeGlyph(glyph, encoderSettings, blockBitstream);
}
// Last block
bitstream.writeVariableLengthWord(blockBitstream.data.size());
bitstream.writeVariableLengthWord(blockCharcode);
bitstream.write(blockBitstream);
bitstream.writeVariableLengthWord(0);
// Update best
if (bitstream.getBitNum() < bestEncodedSize)
{
bestRunLengthBlackBitNum = runLengthBlackBitNum;
bestRunLengthWhiteBitNum = runLengthWhiteBitNum;
bestEncodedSize = bitstream.getBitNum();
encodedFont = bitstream;
}
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* MCU renderer fontconv
* Font encoder
*
* (C) 2023 Gissio
*
* License: MIT
*/
#if !defined(ENCODER_H)
#define ENCODER_H
#include "font.h"
#include "bitwriter.h"
void encodeFont(Font &font,
uint32_t bitsPerPixel,
BitWriter &bitstream);
#endif

View File

@@ -0,0 +1,120 @@
/*
* MCU renderer fontconv
* Font export
*
* (C) 2023 Gissio
*
* License: MIT
*/
#include <iomanip>
#include <fstream>
#include "export.h"
#define BYTES_PER_LINE 16
using namespace std;
static string to_upper(string str)
{
for (auto &c : str)
c = toupper(c);
return str;
}
static string getFontVariableName(string filename)
{
size_t position;
position = filename.rfind('/');
if (position != string::npos)
filename = filename.substr(position + 1);
position = filename.rfind('\\');
if (position != string::npos)
filename = filename.substr(position + 1);
position = filename.rfind('.');
if (position != string::npos)
filename = filename.substr(0, position);
string filteredFilename;
for (char c : filename)
{
if (isalpha(c) || isdigit(c))
filteredFilename += c;
else
filteredFilename += '_';
}
return filteredFilename;
}
void exportFont(Font &font,
string variableName,
string charcodes,
vector<uint8_t> &fontData,
string filename)
{
if (variableName == "")
variableName = getFontVariableName(filename);
string constantName = to_upper(variableName);
if (charcodes == "")
charcodes = "all";
ofstream f(filename);
f << "/**" << endl;
f << " * Font: " << font.name << endl;
f << " * Copyright: " << font.copyright << endl;
f << " * Charcodes: " << charcodes << endl;
f << " */" << endl;
f << endl;
f << "#include <stdint.h>" << endl;
f << endl;
f << "#define " << constantName << "_ASCENT "
<< font.ascent << endl;
f << "#define " << constantName << "_DESCENT "
<< font.descent << endl;
f << "#define " << constantName << "_CAP_HEIGHT "
<< font.capHeight << endl;
f << "#define " << constantName << "_LINE_HEIGHT "
<< font.ascent + font.descent << endl;
f << "#define " << constantName << "_BOUNDINGBOX_LEFT "
<< font.boundingBoxLeft << endl;
f << "#define " << constantName << "_BOUNDINGBOX_BOTTOM "
<< font.boundingBoxBottom << endl;
f << "#define " << constantName << "_BOUNDINGBOX_WIDTH "
<< font.boundingBoxWidth << endl;
f << "#define " << constantName << "_BOUNDINGBOX_HEIGHT "
<< font.boundingBoxHeight << endl;
f << endl;
f << "const uint8_t " << variableName << "["
<< fontData.size() << "] =" << endl;
f << "{\n";
for (uint32_t i = 0; i < fontData.size(); i++)
{
if (i > 0)
{
if ((i % BYTES_PER_LINE) == 0)
f << ",\n";
else
f << ", ";
}
f << "0x" << hex << setw(2) << setfill('0') << (uint32_t)fontData[i];
}
f << "\n";
f << "};\n";
}

View File

@@ -0,0 +1,21 @@
/*
* MCU renderer fontconv
* Font export
*
* (C) 2023 Gissio
*
* License: MIT
*/
#if !defined(EXPORT_C_H)
#define EXPORT_C_H
#include "font.h"
void exportFont(Font &font,
std::string variableName,
std::string charcodes,
std::vector<uint8_t> &fontData,
std::string filename);
#endif

View File

@@ -0,0 +1,212 @@
/*
* MCU renderer fontconv
* BDF import
*
* (C) 2023 Gissio
*
* License: MIT
*/
#include <cstdlib>
#include <fstream>
#include "import_bdf.h"
#include "utils.h"
using namespace std;
enum BDFState
{
BDF_STATE_ROOT,
BDF_STATE_PROPERTIES,
BDF_STATE_CHAR,
BDF_STATE_BITMAP
};
static string filter(string &line)
{
string output;
for (char c : line)
{
if (c >= ' ')
output += c;
}
return output;
}
static string getBDFString(string &value)
{
return value.substr(1, value.size() - 2);
}
static bool getBDFValue(string line, string key, string &value)
{
if (line.size() > key.size())
key += " ";
bool match = to_upper(line).starts_with(to_upper(key));
if (match)
value = line.substr(key.size());
return match;
}
static uint8_t getHexByte(string str)
{
return strtoul(str.substr(0, 2).c_str(), NULL, 16);
}
Font loadBDFFont(string filename,
set<Charcode> &charcodeSet)
{
Font font;
ifstream file(filename);
if (!file.good())
throw runtime_error("could not open '" +
filename +
"'");
string line;
uint32_t lineNumber = 1;
BDFState bdfState = BDF_STATE_ROOT;
string value;
Charcode charcode;
Glyph glyph;
uint32_t glyphY;
string familyName;
string weightName;
while (getline(file, line))
{
line = filter(line);
switch (bdfState)
{
case BDF_STATE_ROOT:
if (getBDFValue(line, "FONT", value))
font.name = value;
else if (getBDFValue(line, "SIZE", value))
{
vector<string> parameters = split(value, ' ');
font.capHeight = stoi(parameters[0]);
}
else if (getBDFValue(line, "STARTPROPERTIES", value))
bdfState = BDF_STATE_PROPERTIES;
else if (getBDFValue(line, "STARTCHAR", value))
bdfState = BDF_STATE_CHAR;
break;
case BDF_STATE_PROPERTIES:
if (getBDFValue(line, "FAMILY_NAME", value))
familyName = getBDFString(value);
else if (getBDFValue(line, "WEIGHT_NAME", value))
weightName = getBDFString(value);
else if (getBDFValue(line, "COPYRIGHT", value))
font.copyright = getBDFString(value);
else if (getBDFValue(line, "FONT_ASCENT", value))
font.ascent = stoi(value);
else if (getBDFValue(line, "FONT_DESCENT", value))
font.descent = stoi(value);
else if (getBDFValue(line, "CAP_HEIGHT", value))
font.capHeight = stoi(value);
else if (getBDFValue(line, "ENDPROPERTIES", value))
bdfState = BDF_STATE_ROOT;
break;
case BDF_STATE_CHAR:
if (getBDFValue(line, "ENCODING", value))
charcode = stoi(value);
if (getBDFValue(line, "DWIDTH", value))
{
vector<string> parameters = split(value, ' ');
glyph.advance = stoi(parameters[0]);
}
else if (getBDFValue(line, "BBX", value))
{
vector<string> parameters = split(value, ' ');
if (parameters.size() != 4)
throw runtime_error("bdf file invalid (line " +
to_string(lineNumber) +
")");
glyph.width = stoi(parameters[0]);
glyph.height = stoi(parameters[1]);
glyph.left = stoi(parameters[2]);
glyph.bottom = stoi(parameters[3]);
glyph.bitmap.resize(glyph.width * glyph.height);
}
else if (getBDFValue(line, "BITMAP", value))
{
bdfState = BDF_STATE_BITMAP;
glyphY = 0;
}
break;
case BDF_STATE_BITMAP:
if (getBDFValue(line, "ENDCHAR", value))
{
bdfState = BDF_STATE_ROOT;
if (charcodeSet.empty() ||
charcodeSet.contains(charcode))
font.add(charcode, glyph);
}
else
{
uint32_t glyphX = 0;
while (line.size() >= 2)
{
uint8_t n = getHexByte(line);
uint32_t pixelNum = 0;
while ((glyphX < glyph.width) && (pixelNum < 8))
{
uint8_t alpha = (n & 0x80) ? 0xff : 0;
glyph.bitmap[glyphY * glyph.width + glyphX] = alpha;
n <<= 1;
pixelNum++;
glyphX++;
}
line = line.substr(2);
}
glyphY++;
}
break;
}
lineNumber++;
}
if (font.ascent == INT_MIN)
font.ascent = font.boundingBoxBottom + font.boundingBoxHeight;
if (font.descent == INT_MIN)
font.descent = font.boundingBoxBottom;
if (familyName != "")
font.name = familyName;
if (weightName != "")
{
font.name += " ";
font.name += weightName;
}
return font;
}

View File

@@ -0,0 +1,22 @@
/*
* MCU renderer fontconv
* BDF import
*
* (C) 2023 Gissio
*
* License: MIT
*/
#if !defined(IMPORT_BDF_H)
#define IMPORT_BDF_H
#include <cstdint>
#include <set>
#include <string>
#include "font.h"
Font loadBDFFont(std::string filename,
std::set<Charcode> &charcodeSet);
#endif

View File

@@ -0,0 +1,188 @@
/*
* MCU renderer fontconv
* FreeType import
*
* (C) 2023 Gissio
*
* License: MIT
*/
#include <iostream>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_SFNT_NAMES_H
#include FT_TRUETYPE_IDS_H
#include "import_freetype.h"
using namespace std;
static string convertFromUTF16(const FT_Byte *str, int size)
{
string s;
while (size > 0)
{
s.push_back((str[0] << 8) +
(str[1] << 0));
str += 2;
size -= 2;
}
return s;
}
static void checkFTError(FT_Error value, string message)
{
if (value == 0)
return;
throw runtime_error(message +
" (freefont error code " +
to_string(value) +
")");
}
void getFontInfo(FT_Face &face,
string filename,
uint32_t pointSize,
Font &font)
{
string fontFamily;
string fontSubFamily;
uint32_t count = FT_Get_Sfnt_Name_Count(face);
for (uint32_t i = 0; i < count; i++)
{
FT_SfntName fontName;
FT_Get_Sfnt_Name(face, i, &fontName);
string str = convertFromUTF16(fontName.string, fontName.string_len);
if (fontName.name_id == TT_NAME_ID_COPYRIGHT)
font.copyright = str;
else if (fontName.name_id == TT_NAME_ID_FONT_FAMILY)
fontFamily = str;
else if (fontName.name_id == TT_NAME_ID_FONT_SUBFAMILY)
fontSubFamily = str;
}
if ((fontFamily != "") || (fontSubFamily != ""))
font.name = fontFamily + " " + fontSubFamily;
else
{
string basename = filename.substr(filename.find_last_of("/\\") + 1);
size_t p = basename.find_last_of('.');
if (p > 0)
font.name = basename.substr(0, p);
}
if (pointSize)
font.name += string(" ") + to_string(pointSize);
}
Font loadFreeTypeFont(string filename,
set<Charcode> &charcodeSet,
uint32_t pointSize)
{
Font font;
// Load font
FT_Library freetype;
checkFTError(FT_Init_FreeType(&freetype),
"cannot initialize");
FT_Face face;
checkFTError(FT_New_Face(freetype,
filename.c_str(),
0,
&face),
"cannot open font");
// Process font
if (FT_IS_SCALABLE(face))
checkFTError(FT_Set_Pixel_Sizes(face,
pointSize,
0),
"cannot set char size");
else
pointSize = 0;
getFontInfo(face,
filename,
pointSize,
font);
font.capHeight = (face->size->metrics.ascender +
face->size->metrics.descender) >>
6;
font.ascent = face->size->metrics.ascender >> 6;
font.descent = -face->size->metrics.descender >> 6;
// Process glyphs
FT_ULong charcode;
FT_UInt glyphIndex;
charcode = FT_Get_First_Char(face,
&glyphIndex);
while (glyphIndex)
{
if (charcodeSet.empty() ||
charcodeSet.contains(charcode))
{
try
{
checkFTError(FT_Load_Glyph(face,
glyphIndex,
FT_LOAD_RENDER),
"cannot load glyph");
uint32_t pitch = face->glyph->bitmap.width;
Glyph glyph;
glyph.left = face->glyph->bitmap_left;
glyph.bottom = face->glyph->bitmap_top - face->glyph->bitmap.rows;
glyph.width = face->glyph->bitmap.width;
glyph.height = face->glyph->bitmap.rows;
glyph.advance = face->glyph->advance.x >> 6;
glyph.bitmap.resize(glyph.width * glyph.height);
for (uint32_t y = 0; y < face->glyph->bitmap.rows; y++)
copy(&face->glyph->bitmap.buffer[y * pitch],
&face->glyph->bitmap.buffer[y * pitch + glyph.width],
&glyph.bitmap[y * glyph.width]);
font.add(charcode, glyph);
}
catch (runtime_error &e)
{
cerr << "warning: skipping glyph " << glyphIndex << ": "
<< e.what() << endl;
continue;
}
}
charcode = FT_Get_Next_Char(face,
charcode,
&glyphIndex);
}
// Free library
checkFTError(FT_Done_Face(face),
"FT_Done_Face");
checkFTError(FT_Done_FreeType(freetype),
"FT_Done_FreeType");
return font;
}

View File

@@ -0,0 +1,23 @@
/*
* MCU renderer fontconv
* FreeType import
*
* (C) 2023 Gissio
*
* License: MIT
*/
#if !defined(IMPORT_FREETYPE_H)
#define IMPORT_FREETYPE_H
#include <cstdint>
#include <set>
#include <string>
#include "font.h"
Font loadFreeTypeFont(std::string filename,
std::set<Charcode> &charcodeSet,
uint32_t pointSize);
#endif

View File

@@ -0,0 +1,179 @@
/*
* MCU renderer fontconv
* Main module
*
* (C) 2023 Gissio
*
* License: MIT
*/
#include <fstream>
#include <iostream>
#include <set>
#include "encode.h"
#include "export.h"
#include "import_bdf.h"
#include "import_freetype.h"
#include "utils.h"
#define DEFAULT_POINT_SIZE 12
#define DEFAULT_BITS_PER_PIXEL 4
using namespace std;
void printHelp(void)
{
cout << "fontconv [options] input-file output-file\n"
<< "-h Display this help.\n"
<< "-p <size> Set point size for rasterizing fonts (default: " << DEFAULT_POINT_SIZE << ").\n"
<< "-b <size> Set bits per pixel (default: " << DEFAULT_BITS_PER_PIXEL << ").\n"
<< "-s <subset> Specify a subset of Unicode characters to convert.\n"
<< "-c <size> Override the font cap height.\n"
<< "-a <size> Override the font ascent (baseline to top of line).\n"
<< "-d <size> Override the font descent (bottom of line to baseline).\n"
<< "-n <name> Override the C-language font variable name.\n"
<< "\n"
<< "example:\n"
<< " -s 32-255 select Unicode characters 32 to 255.\n"
<< " -s 0x2e,0x30-0x39 select space and digit characters.\n";
exit(1);
}
int main(int argc, char **argv)
{
// Parse command line
vector<string> args(argv + 1, argv + argc);
string charcodes;
uint32_t pointSize = DEFAULT_POINT_SIZE;
uint32_t pixelBitNum = DEFAULT_BITS_PER_PIXEL;
int32_t overrideCapHeight = INT_MIN;
int32_t overrideAscent = INT_MIN;
int32_t overrideDescent = INT_MIN;
string variableName;
string inputFilename;
string outputFilename;
for (uint32_t i = 0; i < args.size(); i++)
{
if (args[i].compare("-h") == 0)
printHelp();
else if (args[i].compare("-p") == 0)
{
i++;
if (i < args.size())
{
int32_t value = stoi(args[i]);
if ((value >= 1) && (value <= 512))
pointSize = value;
}
}
else if (args[i].compare("-b") == 0)
{
i++;
if (i < args.size())
{
int32_t value = stoi(args[i]);
if ((value >= 1) &&
(value <= 8))
pixelBitNum = value;
}
}
else if (args[i].compare("-s") == 0)
{
i++;
if (i < args.size())
charcodes = args[i];
}
else if (args[i].compare("-c") == 0)
{
i++;
if (i < args.size())
overrideCapHeight = stoi(args[i]);
}
else if (args[i].compare("-a") == 0)
{
i++;
if (i < args.size())
overrideAscent = stoi(args[i]);
}
else if (args[i].compare("-d") == 0)
{
i++;
if (i < args.size())
overrideDescent = stoi(args[i]);
}
else if (args[i].compare("-n") == 0)
{
i++;
if (i < args.size())
variableName = args[i];
}
else if (inputFilename == "")
inputFilename = args[i];
else if (outputFilename == "")
outputFilename = args[i];
else
printHelp();
}
if ((inputFilename == "") || (outputFilename == ""))
printHelp();
// Process font
try
{
Font font;
set<Charcode> charcodeSet = parseCharcodes(charcodes);
if (to_lower(inputFilename).ends_with(".bdf"))
{
font = loadBDFFont(inputFilename,
charcodeSet);
pixelBitNum = 1;
}
else
font = loadFreeTypeFont(inputFilename,
charcodeSet,
pointSize);
if (overrideCapHeight != INT_MIN)
font.capHeight = overrideCapHeight;
if (overrideAscent != INT_MIN)
font.ascent = overrideAscent;
if (overrideDescent != INT_MIN)
font.descent = overrideDescent;
BitWriter encodedFont;
encodeFont(font,
pixelBitNum,
encodedFont);
exportFont(font,
variableName,
charcodes,
encodedFont.data,
outputFilename);
}
catch (runtime_error &e)
{
cerr << "error: " << e.what() << endl;
}
return 0;
}

View File

@@ -0,0 +1,99 @@
/*
* MCU renderer fontconv
* Utilities
*
* (C) 2023 Gissio
*
* License: MIT
*/
#include <stdexcept>
#include <string>
using namespace std;
#include "utils.h"
string to_lower(string str)
{
for (auto &c : str)
c = tolower(c);
return str;
}
string to_upper(string str)
{
for (auto &c : str)
c = toupper(c);
return str;
}
vector<string> split(string &str, char c)
{
size_t start = 0;
size_t end;
vector<string> tokens;
while ((end = str.find(c, start)) != string::npos)
{
tokens.push_back(str.substr(start, end - start));
start = end + 1;
}
tokens.push_back(str.substr(start));
return tokens;
}
static int convertValue(string &str)
{
if (str.starts_with("0x"))
return stoi(str, NULL, 16);
else
return stoi(str);
}
set<Charcode> parseCharcodes(string &charcodes)
{
set<Charcode> charcodeSet;
vector<string> items = split(charcodes, ',');
if (charcodes != "")
{
for (auto &item : items)
{
vector<string> rangeElements = split(item, '-');
if (rangeElements.size() == 1)
{
Charcode value = convertValue(rangeElements[0]);
if (value >= 0 && value <= UINT16_MAX)
charcodeSet.insert(value);
else
throw runtime_error("invalid value");
}
else if (rangeElements.size() == 2)
{
Charcode from = convertValue(rangeElements[0]);
Charcode to = convertValue(rangeElements[1]);
if ((from >= 0) && (to >= 0) && (from <= to))
{
for (Charcode value = from; value <= to; value++)
charcodeSet.insert(value);
}
else
throw runtime_error("invalid range values");
}
else
throw runtime_error("invalid range");
}
}
return charcodeSet;
}

View File

@@ -0,0 +1,23 @@
/*
* MCU renderer fontconv
* Utilities
*
* (C) 2023 Gissio
*
* License: MIT
*/
#if !defined(UTILS_H)
#define UTILS_H
#include <set>
#include <string>
#include "Font.h"
std::string to_lower(std::string str);
std::string to_upper(std::string str);
std::vector<std::string> split(std::string &str, char c);
std::set<Charcode> parseCharcodes(std::string &charcodes);
#endif