LumixEngine/src/renderer/font.cpp
Mikulas Florek 998e2a4c6a
Anim compression (#1466)
Animation compression
2023-08-17 19:52:18 +02:00

326 lines
8.5 KiB
C++

#include "engine/log.h"
#include "engine/os.h"
#include "engine/stream.h"
#include "font.h"
#include "renderer/texture.h"
#include "renderer/renderer.h"
#define STB_RECT_PACK_IMPLEMENTATION
#include <stb/stb_rect_pack.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_MODULE_H // <freetype/ftmodapi.h>
#include FT_GLYPH_H // <freetype/ftglyph.h>
#include FT_SYNTHESIS_H // <freetype/ftsynth.h>
namespace Lumix
{
struct Font {
Font(IAllocator& allocator) : glyphs(allocator) {}
FontResource* resource;
HashMap<u32, Glyph> glyphs;
u32 font_size = 0;
float descender = 0;
float ascender = 0;
u32 ref = 0;
};
float getAdvanceY(const Font& font) { return float(font.font_size); }
float getDescender(const Font& font) { return font.descender; }
float getAscender(const Font& font) { return font.ascender; }
const Glyph* findGlyph(const Font& font, u32 codepoint) {
auto iter = font.glyphs.find(codepoint);
if (!iter.isValid()) return nullptr;
return &iter.value();
}
Vec2 measureTextA(const Font& font, const char* str, const char* str_end) {
Vec2 res;
res.x = 0;
res.y = (float)font.font_size;
const char* c = str;
while (*c && c != str_end) {
auto iter = font.glyphs.find(*c);
if (iter.isValid()) {
const Glyph& glyph = iter.value();
res.x += glyph.advance_x;
}
++c;
}
return res;
}
struct ToChar {
Font* font;
u32 codepoint;
u32 bmp_offset;
u32 advance_x;
};
static void blit(FT_Bitmap* bitmap, Array<u8>* out) {
ASSERT(bitmap->pixel_mode == FT_PIXEL_MODE_GRAY);
const u32 offset = out->size();
const u32 src_pitch = bitmap->pitch;
const u8* src = bitmap->buffer;
u8* dst = out->begin() + offset;
out->resize(out->size() + bitmap->width * bitmap->rows);
for (u32 y = 0; y < bitmap->rows; ++y, src += src_pitch, dst += bitmap->width) {
memcpy(dst, src, bitmap->width);
}
}
static void blit(const ToChar& tc, const Array<u8>& src_bmp, const IVec2& size, Array<u32>* out) {
const Glyph& c = tc.font->glyphs[tc.codepoint];
const u8* src = &src_bmp[tc.bmp_offset];
const u32 u0 = u32(c.u0 * size.x + 0.5f);
const u32 v0 = u32(c.v0 * size.y + 0.5f);
const u32 u1 = u32(c.u1 * size.x + 0.5f);
const u32 v1 = u32(c.v1 * size.y + 0.5f);
const u32 w = u1 - u0;
const u32 h = v1 - v0;
u32* dst = &(*out)[u0 + v0 * size.x];
for (u32 y = 0; y < h; ++y, dst += size.x, src += w) {
for (u32 x = 0; x < w; ++x) {
dst[x] = 0x00ffFFff | ((u32)src[x] << 24);
}
}
}
Texture* FontManager::getAtlasTexture() {
if (m_dirty) build();
return m_atlas_texture;
}
bool FontManager::build()
{
ASSERT(m_dirty);
for(Font* font : m_fonts) {
if (!font->resource->isReady()) return false;
}
m_dirty = false;
FT_MemoryRec_ memory_rec = {};
memory_rec.user = &m_allocator;
memory_rec.alloc = [](FT_Memory memory, long size) -> void* {
IAllocator* alloc = (IAllocator*)memory->user;
return alloc->allocate(size, 8);
};
memory_rec.free = [](FT_Memory memory, void* block) -> void {
IAllocator* alloc = (IAllocator*)memory->user;
alloc->deallocate(block);
};
memory_rec.realloc = [](FT_Memory memory, long cur_size, long new_size, void* block) -> void* {
IAllocator* alloc = (IAllocator*)memory->user;
return alloc->reallocate(block, new_size, cur_size, 8);
};
FT_Library ft_library;
FT_Error error = FT_New_Library(&memory_rec, &ft_library);
if (error != 0) return false;
FT_Add_Default_Modules(ft_library);
Array<u8> tmp_bmp(m_allocator);
tmp_bmp.reserve(1024 * 1024);
Array<stbrp_rect> rects(m_allocator);
Array<ToChar> to_char(m_allocator);
constexpr u32 PADDING = 1;
for(Font* font : m_fonts) {
FT_Face face;
error = FT_New_Memory_Face(ft_library, font->resource->m_file_data.data(), (u32)font->resource->m_file_data.size(), 0, &face);
if (error != 0) {
logError("Failed to create font ", font->resource->getPath());
continue;
}
FT_Size_RequestRec size_req;
size_req.type = FT_SIZE_REQUEST_TYPE_REAL_DIM;
size_req.width = 0;
size_req.height = (u32)font->font_size * 64;
size_req.horiResolution = 0;
size_req.vertResolution = 0;
error = FT_Request_Size(face, &size_req);
if (error != 0) {
logError("Failed to request font size ", font->font_size, " for ", font->resource->getPath());
continue;
}
error = FT_Select_Charmap(face, FT_ENCODING_UNICODE);
if (error != 0) {
logError("Failed to select unicode charmap of font ", font->resource->getPath());
continue;
}
font->descender = face->size->metrics.descender / 64.f;
font->ascender = face->size->metrics.ascender / 64.f;
for (Glyph& c : font->glyphs) {
c.u0 = c.v0 = 0;
c.u1 = c.v1 = 1;
const u32 glyph_index = FT_Get_Char_Index(face, c.codepoint);
if (glyph_index == 0) continue;
error = FT_Load_Glyph(face, glyph_index, FT_LOAD_NO_BITMAP);
if (error) continue;
FT_GlyphSlot slot = face->glyph;
error = FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL);
if (error != 0) continue;
FT_Bitmap* ft_bitmap = &face->glyph->bitmap;
stbrp_rect& r = rects.emplace();
r.w = ft_bitmap->width + 2 * PADDING;
r.h = ft_bitmap->rows + 2 * PADDING;
to_char.push({font, c.codepoint, (u32)tmp_bmp.size(), static_cast<u32>(slot->advance.x)});
blit(ft_bitmap, &tmp_bmp);
c.x0 = float(slot->bitmap_left);
c.y0 = float(-slot->bitmap_top);
c.x1 = float(c.x0 + r.w - 2 * PADDING);
c.y1 = float(c.y0 + r.h - 2 * PADDING);
}
}
stbrp_context ctx;
Array<stbrp_node> nodes(m_allocator);
nodes.resize(2048);
stbrp_init_target(&ctx, 2048, 32 * 1024, nodes.begin(), nodes.size());
stbrp_pack_rects(&ctx, rects.begin(), rects.size());
u32 w = 2048;
u32 h = 1;
for (const stbrp_rect& r : rects) {
ASSERT(u32(r.x + r.w) <= w);
h = maximum(h, r.y + r.h);
}
for (const stbrp_rect& r : rects) {
const ToChar& tc = to_char[int(&r - rects.begin())];
Glyph& c = tc.font->glyphs[tc.codepoint];
c.advance_x = float(((tc.advance_x + 63) & -64) / 64);
c.u0 = (r.x + PADDING) / (float)w;
c.v0 = (r.y + PADDING) / (float)h;
c.u1 = float(r.x + r.w - PADDING) / w;
c.v1 = float(r.y + r.h - PADDING) / h;
}
Array<u32> pixels(m_allocator);
pixels.resize(w * h);
for (u32& p : pixels) p = 0;
for (const ToChar& tc : to_char) {
blit(tc, tmp_bmp, IVec2(w, h), &pixels);
}
pixels[0] = 0xffFFffFF;
if (m_atlas_texture) {
m_atlas_texture->destroy();
}
else {
auto& texture_manager = m_renderer.getTextureManager();
m_atlas_texture = LUMIX_NEW(m_allocator, Texture)(Path("draw2d_atlas"), texture_manager, m_renderer, m_allocator);
}
m_atlas_texture->create(w, h, gpu::TextureFormat::RGBA8, pixels.begin(), pixels.byte_size());
FT_Done_Library(ft_library);
return true;
}
const ResourceType FontResource::TYPE("font");
FontResource::FontResource(const Path& path, ResourceManager& manager, IAllocator& allocator)
: Resource(path, manager, allocator)
, m_allocator(allocator, m_path.c_str())
, m_file_data(m_allocator)
{
}
bool FontResource::load(Span<const u8> mem) {
if (mem.length() == 0) return false;
m_file_data.resize(mem.length());
memcpy(m_file_data.getMutableData(), mem.begin(), mem.length());
return true;
}
Font* FontResource::addRef(int font_size)
{
auto& manager = (FontManager&)m_resource_manager;
for (Font* f : manager.m_fonts) {
if (f->resource == this && f->font_size == font_size) {
++f->ref;
return f;
}
}
Font* font = LUMIX_NEW(manager.m_allocator, Font)(manager.m_allocator);
font->ref = 1;
font->resource = this;
font->font_size = font_size;
for(u32 cp = 0x20; cp < 0xff; ++cp) {
Glyph c;
c.codepoint = cp;
font->glyphs.insert(cp, c);
}
manager.m_fonts.push(font);
manager.m_dirty = true;
if (isReady()) manager.build();
return font;
}
void FontResource::removeRef(Font& font)
{
ASSERT(font.ref > 0);
--font.ref;
if(font.ref == 0) {
auto& manager = (FontManager&)m_resource_manager;
LUMIX_DELETE(manager.m_allocator, &font);
manager.m_fonts.eraseItem(&font);
manager.m_dirty = true;
if (isReady()) manager.build();
}
}
FontManager::FontManager(Renderer& renderer, IAllocator& allocator)
: ResourceManager(allocator)
, m_allocator(allocator, "fonts")
, m_renderer(renderer)
, m_atlas_texture(nullptr)
, m_fonts(allocator)
{
build();
}
FontManager::~FontManager()
{
for (Font* font : m_fonts) {
LUMIX_DELETE(m_allocator, font);
}
if (m_atlas_texture) {
m_atlas_texture->destroy();
LUMIX_DELETE(m_allocator, m_atlas_texture);
}
}
Resource* FontManager::createResource(const Path& path)
{
return LUMIX_NEW(m_allocator, FontResource)(path, *this, m_allocator);
}
void FontManager::destroyResource(Resource& resource)
{
LUMIX_DELETE(m_allocator, static_cast<FontResource*>(&resource));
}
} // namespace Lumix