First commit
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
#include "Display/UI/Text/BitmapFont.h"
|
||||
|
||||
namespace Display::UI::Text
|
||||
{
|
||||
// 5x7 bitmap digits (0–9)
|
||||
static const uint8_t digits[10][7] = {
|
||||
{0x1E, 0x29, 0x25, 0x23, 0x21, 0x29, 0x1E}, // 0
|
||||
{0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E}, // 1
|
||||
{0x1E, 0x21, 0x01, 0x0E, 0x10, 0x20, 0x3F}, // 2
|
||||
{0x1E, 0x21, 0x02, 0x06, 0x01, 0x21, 0x1E}, // 3
|
||||
{0x02, 0x06, 0x0A, 0x12, 0x3F, 0x02, 0x02}, // 4
|
||||
{0x3F, 0x20, 0x3E, 0x01, 0x01, 0x21, 0x1E}, // 5
|
||||
{0x0E, 0x10, 0x20, 0x3E, 0x21, 0x21, 0x1E}, // 6
|
||||
{0x3F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08}, // 7
|
||||
{0x1E, 0x21, 0x21, 0x1E, 0x21, 0x21, 0x1E}, // 8
|
||||
{0x1E, 0x21, 0x21, 0x1F, 0x01, 0x02, 0x1C} // 9
|
||||
};
|
||||
|
||||
void BitmapFont::drawDigit(Display::Graphics::Framebuffer &fb, int x, int y, int d, uint16_t color)
|
||||
{
|
||||
if (d < 0 || d > 9)
|
||||
return;
|
||||
|
||||
for (int row = 0; row < 7; ++row)
|
||||
{
|
||||
for (int col = 0; col < 5; ++col)
|
||||
{
|
||||
if (digits[d][row] & (1 << (4 - col)))
|
||||
{
|
||||
// Рисуем пиксель как 2x2 квадрат для читаемости
|
||||
fb.fillRect(x + col * 2, y + row * 2, 2, 2, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace Display::UI::Text
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include "Display/Graphics/Framebuffer.h"
|
||||
#include "Display/Graphics/Color.h"
|
||||
|
||||
namespace Display::UI::Text
|
||||
{
|
||||
class BitmapFont
|
||||
{
|
||||
public:
|
||||
void drawDigit(Display::Graphics::Framebuffer &fb, int x, int y, int d, uint16_t color);
|
||||
};
|
||||
} // namespace Display::UI::Text
|
||||
@@ -0,0 +1,21 @@
|
||||
message(STATUS " Configuring Text")
|
||||
|
||||
add_library(Text
|
||||
BitmapFont.cpp
|
||||
FontFace.cpp
|
||||
GlyphCache.cpp
|
||||
Renderer.cpp
|
||||
)
|
||||
|
||||
find_package(Freetype REQUIRED)
|
||||
|
||||
target_link_libraries(Text
|
||||
PRIVATE Graphics ${FREETYPE_LIBRARIES}
|
||||
)
|
||||
|
||||
# include-root общий
|
||||
target_include_directories(Text
|
||||
PUBLIC
|
||||
${INCLUDE_BASE_DIR}
|
||||
${FREETYPE_INCLUDE_DIRS}
|
||||
)
|
||||
@@ -0,0 +1,38 @@
|
||||
#include "Display/UI/Text/FontFace.h"
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Display::UI::Text
|
||||
{
|
||||
struct FontFace::FreeType
|
||||
{
|
||||
FT_Library library = nullptr;
|
||||
FT_Face face = nullptr;
|
||||
};
|
||||
|
||||
FontFace::FontFace(const std::string &path, int pixelSize)
|
||||
: freetype(std::make_unique<FreeType>())
|
||||
{
|
||||
if (FT_Init_FreeType(&freetype->library))
|
||||
throw std::runtime_error("FT_Init_FreeType failed");
|
||||
|
||||
if (FT_New_Face(freetype->library, path.c_str(), 0, &freetype->face))
|
||||
throw std::runtime_error("FT_New_Face failed");
|
||||
|
||||
FT_Set_Pixel_Sizes(freetype->face, 0, pixelSize);
|
||||
}
|
||||
|
||||
FontFace::~FontFace()
|
||||
{
|
||||
if (freetype->face)
|
||||
FT_Done_Face(freetype->face);
|
||||
if (freetype->library)
|
||||
FT_Done_FreeType(freetype->library);
|
||||
}
|
||||
|
||||
void *FontFace::getFace() const
|
||||
{
|
||||
return freetype->face;
|
||||
}
|
||||
} // namespace Display::UI::Text
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace Display::UI::Text
|
||||
{
|
||||
class FontFace
|
||||
{
|
||||
public:
|
||||
FontFace(const std::string &path, int pixelSize);
|
||||
~FontFace();
|
||||
|
||||
FontFace(const FontFace &) = delete;
|
||||
FontFace &operator=(const FontFace &) = delete;
|
||||
|
||||
FontFace(FontFace &&) = delete;
|
||||
FontFace &operator=(FontFace &&) = delete;
|
||||
|
||||
void *getFace() const;
|
||||
|
||||
private:
|
||||
struct FreeType;
|
||||
std::unique_ptr<FreeType> freetype;
|
||||
};
|
||||
} // namespace Display::UI::Text
|
||||
@@ -0,0 +1,15 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Display::UI::Text
|
||||
{
|
||||
struct Glyph
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
int bearingX;
|
||||
int bearingY;
|
||||
int advance;
|
||||
std::vector<uint8_t> buffer; // grayscale bitmap
|
||||
};
|
||||
} // namespace Display::UI::Text
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "Display/UI/Text/GlyphCache.h"
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
#include "Display/UI/Text/FontFace.h"
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
namespace Display::UI::Text
|
||||
{
|
||||
GlyphCache::GlyphCache(const FontFace &fontFace) : fontFace(fontFace) {}
|
||||
|
||||
const Glyph &GlyphCache::getGlyph(char c)
|
||||
{
|
||||
FT_Face face = static_cast<FT_Face>(fontFace.getFace());
|
||||
auto it = cache.find(c);
|
||||
if (it != cache.end())
|
||||
return it->second;
|
||||
|
||||
if (!face)
|
||||
throw std::runtime_error("FontFace not initialized!");
|
||||
if (FT_Load_Char(face, c, FT_LOAD_RENDER))
|
||||
throw std::runtime_error("FT_Load_Char failed");
|
||||
FT_GlyphSlot g = face->glyph;
|
||||
|
||||
Glyph glyph;
|
||||
glyph.width = g->bitmap.width;
|
||||
glyph.height = g->bitmap.rows;
|
||||
glyph.bearingX = g->bitmap_left;
|
||||
glyph.bearingY = g->bitmap_top;
|
||||
glyph.advance = g->advance.x >> 6;
|
||||
glyph.buffer.assign(g->bitmap.buffer, g->bitmap.buffer + g->bitmap.width * g->bitmap.rows);
|
||||
|
||||
return cache.emplace(c, std::move(glyph)).first->second;
|
||||
}
|
||||
} // namespace Display::UI::Text
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "Display/UI/Text/Glyph.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Display::UI::Text
|
||||
{
|
||||
class FontFace;
|
||||
class GlyphCache
|
||||
{
|
||||
public:
|
||||
GlyphCache(const FontFace &fontFace);
|
||||
|
||||
GlyphCache(const GlyphCache &) = delete;
|
||||
GlyphCache &operator=(const GlyphCache &) = delete;
|
||||
|
||||
GlyphCache(GlyphCache &&) = delete;
|
||||
GlyphCache &operator=(GlyphCache &&) = delete;
|
||||
|
||||
const Glyph &getGlyph(char c);
|
||||
|
||||
private:
|
||||
const FontFace &fontFace;
|
||||
std::unordered_map<char, Glyph> cache;
|
||||
};
|
||||
} // namespace Display::UI::Text
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "Display/UI/Text/Renderer.h"
|
||||
#include "Display/UI/Text/UTFDecoder.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace Display::UI::Text
|
||||
{
|
||||
|
||||
Renderer::Renderer() : fontFace("/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf", 12), glyphCache(fontFace) {}
|
||||
|
||||
void Renderer::drawText(Display::Graphics::Framebuffer &fb, int x, int y,
|
||||
const std::string &text, const Display::Graphics::Color &color)
|
||||
{
|
||||
int penX = x;
|
||||
int baseline = y;
|
||||
|
||||
for (char ch : text)
|
||||
{
|
||||
const Glyph &g = glyphCache.getGlyph(ch);
|
||||
|
||||
for (int row = 0; row < g.height; ++row)
|
||||
{
|
||||
int py = baseline - g.bearingY + row;
|
||||
|
||||
for (int col = 0; col < g.width; ++col)
|
||||
{
|
||||
int px = penX + col + g.bearingX;
|
||||
|
||||
uint8_t alpha = g.buffer[row * g.width + col];
|
||||
if (!alpha)
|
||||
continue;
|
||||
|
||||
Display::Graphics::Color bg = fb.getPixel(px, py);
|
||||
Display::Graphics::Color out;
|
||||
out.r = (alpha * color.r + (255 - alpha) * bg.r) / 255;
|
||||
out.g = (alpha * color.g + (255 - alpha) * bg.g) / 255;
|
||||
out.b = (alpha * color.b + (255 - alpha) * bg.b) / 255;
|
||||
|
||||
fb.setPixel(px, py, out);
|
||||
}
|
||||
}
|
||||
|
||||
penX += g.advance;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Display::UI::Text
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "Display/Graphics/Framebuffer.h"
|
||||
#include "Display/UI/Text/FontFace.h"
|
||||
#include "Display/UI/Text/GlyphCache.h"
|
||||
#include "Display/Graphics/Color.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Display::UI::Text
|
||||
{
|
||||
|
||||
class Renderer
|
||||
{
|
||||
public:
|
||||
Renderer();
|
||||
|
||||
Renderer(const Renderer &) = delete;
|
||||
Renderer &operator=(const Renderer &) = delete;
|
||||
|
||||
void drawText(Display::Graphics::Framebuffer &fb, int x, int y, const std::string &text, const Display::Graphics::Color &color);
|
||||
|
||||
private:
|
||||
FontFace fontFace;
|
||||
GlyphCache glyphCache;
|
||||
};
|
||||
|
||||
} // namespace Display::UI::Text
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
#include <string_view>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Display::UI::Text
|
||||
{
|
||||
|
||||
inline bool utf8Next(std::string_view &s, uint32_t &codepoint)
|
||||
{
|
||||
if (s.empty())
|
||||
return false;
|
||||
|
||||
uint8_t c = s[0];
|
||||
|
||||
if ((c & 0x80) == 0)
|
||||
{
|
||||
codepoint = c;
|
||||
s.remove_prefix(1);
|
||||
}
|
||||
else if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
codepoint = ((c & 0x1F) << 6) | (s[1] & 0x3F);
|
||||
s.remove_prefix(2);
|
||||
}
|
||||
else if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
codepoint = ((c & 0x0F) << 12) |
|
||||
((s[1] & 0x3F) << 6) |
|
||||
(s[2] & 0x3F);
|
||||
s.remove_prefix(3);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Display::UI::Text
|
||||
Reference in New Issue
Block a user