First init.
This commit is contained in:
147
libraries/mcu-renderer/tools/fontconv/BitWriter.cpp
Normal file
147
libraries/mcu-renderer/tools/fontconv/BitWriter.cpp
Normal 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);
|
||||
}
|
||||
49
libraries/mcu-renderer/tools/fontconv/BitWriter.h
Normal file
49
libraries/mcu-renderer/tools/fontconv/BitWriter.h
Normal 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
|
||||
16
libraries/mcu-renderer/tools/fontconv/CMakeLists.txt
Normal file
16
libraries/mcu-renderer/tools/fontconv/CMakeLists.txt
Normal 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)
|
||||
74
libraries/mcu-renderer/tools/fontconv/Font.cpp
Normal file
74
libraries/mcu-renderer/tools/fontconv/Font.cpp
Normal 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;
|
||||
}
|
||||
73
libraries/mcu-renderer/tools/fontconv/Font.h
Normal file
73
libraries/mcu-renderer/tools/fontconv/Font.h
Normal 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
|
||||
315
libraries/mcu-renderer/tools/fontconv/encode.cpp
Normal file
315
libraries/mcu-renderer/tools/fontconv/encode.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
libraries/mcu-renderer/tools/fontconv/encode.h
Normal file
20
libraries/mcu-renderer/tools/fontconv/encode.h
Normal 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
|
||||
120
libraries/mcu-renderer/tools/fontconv/export.cpp
Normal file
120
libraries/mcu-renderer/tools/fontconv/export.cpp
Normal 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";
|
||||
}
|
||||
21
libraries/mcu-renderer/tools/fontconv/export.h
Normal file
21
libraries/mcu-renderer/tools/fontconv/export.h
Normal 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
|
||||
212
libraries/mcu-renderer/tools/fontconv/import_bdf.cpp
Normal file
212
libraries/mcu-renderer/tools/fontconv/import_bdf.cpp
Normal 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;
|
||||
}
|
||||
22
libraries/mcu-renderer/tools/fontconv/import_bdf.h
Normal file
22
libraries/mcu-renderer/tools/fontconv/import_bdf.h
Normal 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
|
||||
188
libraries/mcu-renderer/tools/fontconv/import_freetype.cpp
Normal file
188
libraries/mcu-renderer/tools/fontconv/import_freetype.cpp
Normal 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;
|
||||
}
|
||||
23
libraries/mcu-renderer/tools/fontconv/import_freetype.h
Normal file
23
libraries/mcu-renderer/tools/fontconv/import_freetype.h
Normal 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
|
||||
179
libraries/mcu-renderer/tools/fontconv/main.cpp
Normal file
179
libraries/mcu-renderer/tools/fontconv/main.cpp
Normal 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;
|
||||
}
|
||||
99
libraries/mcu-renderer/tools/fontconv/utils.cpp
Normal file
99
libraries/mcu-renderer/tools/fontconv/utils.cpp
Normal 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;
|
||||
}
|
||||
23
libraries/mcu-renderer/tools/fontconv/utils.h
Normal file
23
libraries/mcu-renderer/tools/fontconv/utils.h
Normal 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
|
||||
Reference in New Issue
Block a user