mirror of
https://github.com/CURTLab/LVGLBuilder.git
synced 2025-05-08 19:40:36 +08:00

* Renamed LVGLFont to LVGLFontData (due to LVGLImageData class) * Fixed uneven font pixels conversion * Added "Save as c code ..." and removal of fonts within the LVGLBuild font editor
343 lines
12 KiB
C++
343 lines
12 KiB
C++
#include "LVGLFontData.h"
|
|
|
|
#include <QDebug>
|
|
#include <QFile>
|
|
#include <QtMath>
|
|
#include "LVGLCore.h"
|
|
|
|
#include <ft2build.h>
|
|
#include FT_FREETYPE_H
|
|
|
|
LVGLFontData::LVGLFontData(const QString &name, const QString &codeName, uint8_t size, const lv_font_t *font)
|
|
: m_font(const_cast<lv_font_t*>(font))
|
|
, m_name(name)
|
|
, m_codeName(codeName)
|
|
, m_size(size)
|
|
, m_customFont(false)
|
|
{
|
|
}
|
|
|
|
LVGLFontData::~LVGLFontData()
|
|
{
|
|
if (m_customFont) {
|
|
lv_font_fmt_txt_dsc_t *fdsc = reinterpret_cast<lv_font_fmt_txt_dsc_t *>(m_font->dsc);
|
|
delete [] fdsc->glyph_bitmap;
|
|
delete [] fdsc->glyph_dsc;
|
|
delete [] fdsc->cmaps;
|
|
delete fdsc;
|
|
delete m_font;
|
|
m_font = nullptr;
|
|
}
|
|
}
|
|
|
|
LVGLFontData *LVGLFontData::parse(const QString &fileName, uint8_t size, uint8_t bpp, uint32_t unicodeFirst, uint32_t unicodeLast)
|
|
{
|
|
FT_Face face;
|
|
int error = FT_New_Face(lvgl.m_ft, qPrintable(fileName), 0, &face);
|
|
if (error == FT_Err_Unknown_File_Format) {
|
|
qCritical() << "LVGLFontData: Unkown file format";
|
|
return nullptr;
|
|
} else if (error) {
|
|
qCritical() << "LVGLFontData: Error loading font:" << FT_Error_String(error);
|
|
return nullptr;
|
|
}
|
|
|
|
//QString unicode_start_str = "U+" + QString::number(unicodeFirst, 16).rightJustified(4, '0');
|
|
//QString unicode_last_str = "U+" + QString::number(unicodeLast, 16).rightJustified(4, '0');
|
|
|
|
FT_Set_Char_Size(face, 0, size * 64, 300, 300);
|
|
FT_Set_Pixel_Sizes(face, 0, size);
|
|
|
|
const int mask = (~(((1 << bpp) - 1) & 0xff)) & 0xff;
|
|
|
|
std::vector<uint8_t> gylph_bitmap;
|
|
|
|
const uint32_t glyphs = unicodeLast - unicodeFirst + 1;
|
|
lv_font_fmt_txt_glyph_dsc_t *glyph_dsc = new lv_font_fmt_txt_glyph_dsc_t[glyphs + 1];
|
|
memset(glyph_dsc, 0, sizeof(lv_font_fmt_txt_glyph_dsc_t));
|
|
|
|
int ascent = 0;
|
|
int descent = 0;
|
|
|
|
uint32_t counter = 0;
|
|
for(uint32_t unicodeAct = unicodeFirst; unicodeAct <= unicodeLast; ++unicodeAct) {
|
|
int error = FT_Load_Char(face, unicodeAct, FT_LOAD_RENDER);
|
|
if (error) {
|
|
qCritical() << "Error" << error << FT_Error_String(error);
|
|
continue;
|
|
}
|
|
|
|
const size_t curr_buf_size = gylph_bitmap.size();
|
|
|
|
uint8_t w = 0, h = 0;
|
|
if (face->glyph->bitmap.buffer != nullptr) {
|
|
FT_Bitmap bitmap = face->glyph->bitmap;
|
|
|
|
w = bitmap.width & 0xff;
|
|
h = bitmap.rows & 0xff;
|
|
|
|
const uint size_bitmap = bitmap.rows * bitmap.width;
|
|
const size_t buf_size = (size_bitmap + 1) * bpp / 8;
|
|
gylph_bitmap.resize(curr_buf_size + buf_size);
|
|
|
|
//assert(size_bitmap % (8 / bpp) == 0);
|
|
|
|
uint8_t *buf_in = bitmap.buffer;
|
|
uint8_t *buf_out = gylph_bitmap.data() + curr_buf_size;
|
|
|
|
// convert to bpp format
|
|
for (uint j = 0; j < buf_size; ++j) {
|
|
*buf_out = 0;
|
|
for (int i = 0; i < 8; i += bpp) {
|
|
const bool ok = ((buf_in - bitmap.buffer) < size_bitmap);
|
|
const uint8_t val = (ok?*buf_in:0);
|
|
*buf_out |= ((val & mask) >> i);
|
|
if (ok)
|
|
++buf_in; // avoid pointing to not allocated memory
|
|
}
|
|
++buf_out; // avoid pointing to not allocated memory
|
|
}
|
|
}
|
|
|
|
lv_font_fmt_txt_glyph_dsc_t &desc = glyph_dsc[unicodeAct - unicodeFirst + 1];
|
|
desc.bitmap_index = curr_buf_size & 0xffff;
|
|
const double advance = face->glyph->metrics.horiAdvance / 64.0;
|
|
desc.adv_w = uint32_t(std::ceil(advance * 16));
|
|
desc.box_w = w;
|
|
desc.box_h = h;
|
|
desc.ofs_x = static_cast<int8_t>(face->glyph->bitmap_left);
|
|
desc.ofs_y = static_cast<int8_t>(face->glyph->bitmap_top) - h;
|
|
|
|
ascent = std::max(ascent, face->glyph->bitmap_top);
|
|
descent = std::min(descent, face->glyph->bitmap_top - h);
|
|
|
|
++counter;
|
|
|
|
//qDebug() << QString::number(unicodeAct, 16).rightJustified(4, '0') << QChar(unicodeAct);
|
|
}
|
|
Q_ASSERT(counter == glyphs);
|
|
|
|
uint8_t *gylph_bitmap_buf = new uint8_t[gylph_bitmap.size()];
|
|
memcpy(gylph_bitmap_buf, gylph_bitmap.data(), gylph_bitmap.size());
|
|
|
|
// allocate cmap
|
|
lv_font_fmt_txt_cmap_t *cmap = new lv_font_fmt_txt_cmap_t[1];
|
|
|
|
cmap[0].range_start = unicodeFirst;
|
|
cmap[0].range_length = static_cast<uint16_t>(glyphs & 0xffff);
|
|
cmap[0].glyph_id_start = 1;
|
|
cmap[0].unicode_list = nullptr;
|
|
cmap[0].glyph_id_ofs_list = nullptr;
|
|
cmap[0].list_length = 0;
|
|
cmap[0].type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY;
|
|
|
|
// allocate txt description
|
|
lv_font_fmt_txt_dsc_t *font_dsc = new lv_font_fmt_txt_dsc_t;
|
|
font_dsc->glyph_bitmap = gylph_bitmap_buf;
|
|
font_dsc->glyph_dsc = glyph_dsc;
|
|
font_dsc->cmaps = cmap;
|
|
font_dsc->kern_dsc = nullptr;
|
|
font_dsc->kern_scale = 0;
|
|
font_dsc->cmap_num = 1;
|
|
font_dsc->bpp = bpp;
|
|
font_dsc->kern_classes = 0;
|
|
font_dsc->bitmap_format = LV_FONT_FMT_TXT_PLAIN;
|
|
|
|
// allocate font in with default callbacks
|
|
lv_font_t *font = new lv_font_t;
|
|
font->get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt;
|
|
font->get_glyph_bitmap = lv_font_get_bitmap_fmt_txt;
|
|
//font->line_height = uint8_t(std::round((face->ascender - face->descender) / 64.0));
|
|
//font->base_line = uint8_t(-std::round(face->descender / 64.0));
|
|
font->line_height = uint8_t(ascent - descent);
|
|
font->base_line = uint8_t(-descent);
|
|
font->subpx = LV_FONT_SUBPX_NONE;
|
|
font->dsc = font_dsc;
|
|
|
|
// generate font name
|
|
QString baseName(face->family_name);
|
|
if (strcmp(face->style_name, "Regular") != 0)
|
|
baseName += QString(" %1").arg(face->style_name);
|
|
|
|
QString name = QString("%1 %2").arg(baseName).arg(size);
|
|
LVGLFontData *ret = new LVGLFontData(name, name.toLower().replace(" ", "_").replace("-", "_"), size, font);
|
|
ret->m_customFont = true;
|
|
ret->m_fileName = fileName;
|
|
return ret;
|
|
}
|
|
|
|
LVGLFontData *LVGLFontData::parse(QJsonObject object)
|
|
{
|
|
if (!object.contains("fileName") || !object.contains("bpp") ||
|
|
!object.contains("start") || !object.contains("end"))
|
|
return nullptr;
|
|
const uint8_t size = static_cast<uint8_t>(object["size"].toInt());
|
|
const uint8_t bpp = static_cast<uint8_t>(object["bpp"].toInt());
|
|
const uint32_t start = static_cast<uint32_t>(object["start"].toInt());
|
|
const uint32_t end = static_cast<uint32_t>(object["end"].toInt());
|
|
return parse(object["fileName"].toString(), size, bpp, start, end);
|
|
}
|
|
|
|
const lv_font_t *LVGLFontData::font() const
|
|
{
|
|
return m_font;
|
|
}
|
|
|
|
QString LVGLFontData::name() const
|
|
{
|
|
return m_name;
|
|
}
|
|
|
|
QString LVGLFontData::codeName() const
|
|
{
|
|
return m_codeName;
|
|
}
|
|
|
|
bool LVGLFontData::saveAsCode(const QString &fileName) const
|
|
{
|
|
QFile file(fileName);
|
|
if (!file.open(QIODevice::WriteOnly))
|
|
return false;
|
|
|
|
lv_font_fmt_txt_dsc_t *fdsc = reinterpret_cast<lv_font_fmt_txt_dsc_t *>(m_font->dsc);
|
|
|
|
if (fdsc->cmap_num != 1) {
|
|
qCritical() << "LVGLFontData: Char map count <==> 1!";
|
|
return false;
|
|
}
|
|
|
|
const uint32_t unicodeFirst = fdsc->cmaps[0].range_start;
|
|
const uint32_t unicodeSec = unicodeFirst + fdsc->cmaps[0].range_length - 1;
|
|
|
|
QTextStream stream(&file);
|
|
stream << "#include \"lvgl.h\"\n\n";
|
|
|
|
const QString defName = m_codeName.toUpper();
|
|
const QString output_name = m_codeName;
|
|
|
|
stream << "/*******************************************************************************\n";
|
|
stream << " * Size: " << m_size << " px\n";
|
|
stream << " * Bpp: " << fdsc->bpp << "\n";
|
|
stream << " * Opts: \n";
|
|
stream << " ******************************************************************************/\n\n";
|
|
stream << "#ifndef " << defName << "\n";
|
|
stream << "#define " << defName << " 1\n";
|
|
stream << "#endif\n\n";
|
|
stream << "#if " << defName << "\n\n";
|
|
stream << "/*-----------------\n";
|
|
stream << " * BITMAPS\n";
|
|
stream << " *----------------*/\n\n";
|
|
stream << "/* Store the image of the letters (glyph) */\n";
|
|
|
|
stream << "static const uint8_t " << output_name << "_glyph_bitmap[] =\n{\n";
|
|
for (uint32_t unicodeAct = unicodeFirst; unicodeAct <= unicodeSec; ++unicodeAct) {
|
|
stream << "\t/* U+" << QString::number(unicodeAct, 16).rightJustified(4, '0') << " \"" << QChar(unicodeAct) << "\" */";
|
|
const lv_font_fmt_txt_glyph_dsc_t &gdsc = fdsc->glyph_dsc[unicodeAct - unicodeFirst + 1];
|
|
const uint32_t size = (gdsc.box_w * gdsc.box_h + 1) * fdsc->bpp / 8;
|
|
const uint8_t *buf = fdsc->glyph_bitmap + gdsc.bitmap_index;
|
|
for (uint32_t i = 0; i < size; ++i, ++buf) {
|
|
if (i % 8 == 0) stream << "\n\t";
|
|
stream << "0x" << QString::number(*buf, 16).rightJustified(2, '0') << ", ";
|
|
}
|
|
stream << "\n";
|
|
if (unicodeAct != unicodeSec) stream << "\n";
|
|
}
|
|
stream << "};\n\n";
|
|
|
|
stream << "/*---------------------\n";
|
|
stream << " * GLYPH DESCRIPTION\n";
|
|
stream << " *--------------------*/\n\n";
|
|
|
|
stream << "static const lv_font_fmt_txt_glyph_dsc_t " << output_name << "_glyph_dsc[] =\n{\n";
|
|
stream << "\t{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,\n";
|
|
const lv_font_fmt_txt_glyph_dsc_t *gdsc = fdsc->glyph_dsc + 1;
|
|
for (uint32_t unicodeAct = unicodeFirst; unicodeAct <= unicodeSec; ++unicodeAct) {
|
|
stream << "\t{.bitmap_index = " << gdsc->bitmap_index << ", .adv_w = "
|
|
<< gdsc->adv_w << ", .box_w = " << gdsc->box_w << ", .box_h = "
|
|
<< gdsc->box_h << ", .ofs_x = " << gdsc->ofs_x << ", .ofs_y = "
|
|
<< gdsc->ofs_y << "}";
|
|
stream << " /* U+" << QString::number(unicodeAct, 16).rightJustified(4, '0') << " \"" << QChar(unicodeAct) << "\" */";
|
|
if (unicodeAct != unicodeSec) stream << ",";
|
|
stream << "\n";
|
|
++gdsc;
|
|
}
|
|
stream << "};\n\n";
|
|
|
|
stream << "/*---------------------\n";
|
|
stream << " * CHARACTER MAPPING\n";
|
|
stream << " *--------------------*/\n\n";
|
|
stream << "/* Collect the unicode lists and glyph_id offsets */\n";
|
|
stream << "static const lv_font_fmt_txt_cmap_t " << output_name << "_cmaps[] =\n{\n";
|
|
stream << "\t{\n";
|
|
stream << "\t\t.range_start = " << fdsc->cmaps[0].range_start
|
|
<< ", .range_length = " << fdsc->cmaps[0].range_length
|
|
<< ", .glyph_id_start = " << fdsc->cmaps[0].glyph_id_start << ",\n";
|
|
stream << "\t\t.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY\n";
|
|
stream << "\t}\n";
|
|
stream << "};\n\n";
|
|
|
|
stream << "/*--------------------\n";
|
|
stream << " * ALL CUSTOM DATA\n";
|
|
stream << " *--------------------*/\n\n";
|
|
|
|
stream << "/* Store all the custom data of the font */\n";
|
|
stream << "static lv_font_fmt_txt_dsc_t " << output_name << "_font_dsc = \n{\n";
|
|
stream << "\t.glyph_bitmap = " << output_name << "_glyph_bitmap,\n";
|
|
stream << "\t.glyph_dsc = " << output_name << "_glyph_dsc,\n";
|
|
stream << "\t.cmaps = " << output_name << "_cmaps,\n";
|
|
stream << "\t.kern_dsc = NULL,\n\t.kern_scale = 0,\n";
|
|
stream << "\t.cmap_num = " << fdsc->cmap_num << ",\n";
|
|
stream << "\t.bpp = " << fdsc->bpp << ",\n";
|
|
stream << "\t.kern_classes = 0,\n";
|
|
stream << "\t.bitmap_format = LV_FONT_FMT_TXT_PLAIN\n};\n\n";
|
|
|
|
stream << "/*-----------------\n";
|
|
stream << " * PUBLIC FONT\n";
|
|
stream << " *----------------*/\n\n";
|
|
stream << "/* Initialize a public general font descriptor */\n";
|
|
stream << "lv_font_t " << output_name << " = \n{\n";
|
|
stream << "\t.get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /* Function pointer to get glyph's data */\n";
|
|
stream << "\t.get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /* Function pointer to get glyph's bitmap */\n";
|
|
stream << "\t.line_height = " << m_font->line_height << ", /* The maximum line height required by the font */\n";
|
|
stream << "\t.base_line = " << m_font->base_line << ", /* Baseline measured from the bottom of the line */\n";
|
|
stream << "\t.subpx = LV_FONT_SUBPX_NONE,\n";
|
|
stream << "\t.dsc = &" << output_name << "_font_dsc /* The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */\n";
|
|
stream << "};\n\n";
|
|
|
|
stream << "#endif /*#if " << defName << "*/\n";
|
|
|
|
file.close();
|
|
return true;
|
|
}
|
|
|
|
bool LVGLFontData::isCustomFont() const
|
|
{
|
|
return m_customFont;
|
|
}
|
|
|
|
QString LVGLFontData::fileName() const
|
|
{
|
|
return m_fileName;
|
|
}
|
|
|
|
QJsonObject LVGLFontData::toJson() const
|
|
{
|
|
QJsonObject object({{"name", m_name},
|
|
{"code", m_codeName},
|
|
{"size", m_size}
|
|
});
|
|
if (m_customFont) {
|
|
object.insert("fileName", m_fileName);
|
|
lv_font_fmt_txt_dsc_t *fdsc = reinterpret_cast<lv_font_fmt_txt_dsc_t *>(m_font->dsc);
|
|
object.insert("bpp", fdsc->bpp);
|
|
object.insert("start", int(fdsc->cmaps[0].range_start));
|
|
object.insert("end", int(fdsc->cmaps[0].range_start + fdsc->cmaps[0].range_length - 1));
|
|
}
|
|
return object;
|
|
}
|
|
|
|
uint8_t LVGLFontData::size() const
|
|
{
|
|
return m_size;
|
|
}
|