1
0
mirror of https://github.com/luanti-org/luanti.git synced 2025-12-17 04:25:26 +01:00

Refactor parts of CGUITTFont

This commit is contained in:
sfan5
2025-11-22 22:20:55 +01:00
parent 7570e9520d
commit 77ce40cf44
7 changed files with 215 additions and 323 deletions

View File

@@ -35,7 +35,6 @@
#include "irr_ptr.h"
#include "log.h"
#include "filesys.h"
#include "debug.h"
#include "IGUIEnvironment.h"
@@ -46,9 +45,8 @@
namespace gui
{
std::map<io::path, SGUITTFace*> SGUITTFace::faces;
FT_Library SGUITTFace::freetype_library = nullptr;
std::size_t SGUITTFace::n_faces = 0;
size_t SGUITTFace::n_faces = 0;
FT_Library SGUITTFace::getFreeTypeLibrary()
{
@@ -85,54 +83,28 @@ SGUITTFace* SGUITTFace::createFace(std::string &&buffer)
auto ft = getFreeTypeLibrary();
if (!ft)
return nullptr;
return (FT_New_Memory_Face(ft,
bool ok = FT_New_Memory_Face(ft,
reinterpret_cast<const FT_Byte*>(face->face_buffer.data()),
face->face_buffer.size(), 0, &face->face))
? nullptr : face.release();
face->face_buffer.size(), 0, &face->face) == 0;
return ok ? face.release() : nullptr;
}
SGUITTFace* SGUITTFace::loadFace(const io::path &filename)
{
auto it = faces.find(filename);
if (it != faces.end()) {
it->second->grab();
return it->second;
}
std::string buffer;
if (!fs::ReadFile(filename.c_str(), buffer, true)) {
errorstream << "CGUITTFont: Reading file " << filename.c_str() << " failed." << std::endl;
irr_ptr<SGUITTFace> face(new SGUITTFace(""));
auto ft = getFreeTypeLibrary();
if (!ft)
return nullptr;
}
auto *face = SGUITTFace::createFace(std::move(buffer));
if (!face) {
errorstream << "CGUITTFont: FT_New_Memory_Face failed." << std::endl;
return nullptr;
}
faces.emplace(filename, face);
return face;
}
void SGUITTFace::dropFilename()
{
if (!filename.has_value())
return;
auto it = faces.find(*filename);
if (it == faces.end())
return;
SGUITTFace* f = it->second;
// Drop our face. If this was the last face, the destructor will clean up.
if (f->drop())
faces.erase(*filename);
// Prefer FT_New_Face because it doesn't require loading everything
// to memory.
bool ok = FT_New_Face(ft, filename.c_str(), 0, &face->face) == 0;
return ok ? face.release() : nullptr;
}
video::IImage* SGUITTGlyph::createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const
{
// Make sure our casts to s32 in the loops below will not cause problems
if ((s32)bits.rows < 0 || (s32)bits.width < 0)
if (bits.rows > INT32_MAX || bits.width > INT32_MAX)
FATAL_ERROR("Insane font glyph size");
// Determine what our texture size should be.
@@ -140,8 +112,8 @@ video::IImage* SGUITTGlyph::createGlyphImage(const FT_Bitmap& bits, video::IVide
core::dimension2du d(bits.width + 1, bits.rows + 1);
core::dimension2du texture_size;
// Create and load our image now.
video::IImage* image = 0;
// Turn bitmap into an image
video::IImage *image = nullptr;
switch (bits.pixel_mode)
{
case FT_PIXEL_MODE_MONO:
@@ -233,44 +205,89 @@ void SGUITTGlyph::preload(u32 char_index, FT_Face face, CGUITTFont *parent, u32
return;
}
// Allocate slot from page
glyph_page = parent->getLastGlyphPageIndex();
u32 texture_side_length = page->texture->getOriginalSize().Width;
core::vector2di page_position(
(page->used_slots % (texture_side_length / font_size)) * font_size,
(page->used_slots / (texture_side_length / font_size)) * font_size
);
);
source_rect.UpperLeftCorner = page_position;
source_rect.LowerRightCorner = core::vector2di(page_position.X + bits.width, page_position.Y + bits.rows);
page->dirty = true;
++page->used_slots;
--page->available_slots;
// We grab the glyph bitmap here so the data won't be removed when the next glyph is loaded.
surface = createGlyphImage(bits, parent->getDriver());
// createGlyphImage can now be called, the next preload() call will however
// invalidate the data in `bits`.
}
void SGUITTGlyph::unload()
{
if (surface)
{
surface->drop();
surface = 0;
}
// reset isLoaded to false
source_rect = core::recti();
}
bool CGUITTGlyphPage::createPageTexture(const u8 pixel_mode,
const core::dimension2du texture_size)
{
if (texture)
return false;
bool flgmip = driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS);
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
bool flgcpy = driver->getTextureCreationFlag(video::ETCF_ALLOW_MEMORY_COPY);
driver->setTextureCreationFlag(video::ETCF_ALLOW_MEMORY_COPY, true);
// Create texture
switch (pixel_mode) {
case FT_PIXEL_MODE_MONO:
texture = driver->addTexture(texture_size, name, video::ECF_A1R5G5B5);
break;
case FT_PIXEL_MODE_GRAY:
default:
texture = driver->addTexture(texture_size, name, video::ECF_A8R8G8B8);
break;
}
// Restore texture creation flags
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, flgmip);
driver->setTextureCreationFlag(video::ETCF_ALLOW_MEMORY_COPY, flgcpy);
return texture ? true : false;
}
//! Updates the texture atlas with new glyphs.
void CGUITTGlyphPage::updateTexture()
{
if (!isDirty())
return;
void *ptr = texture->lock();
if (!ptr)
return;
video::ECOLOR_FORMAT format = texture->getColorFormat();
core::dimension2du size = texture->getOriginalSize();
video::IImage* pageholder = driver->createImageFromData(format, size, ptr, true, false);
for (auto &it : glyph_to_be_paged)
it.surface->copyTo(pageholder, it.glyph->source_rect.UpperLeftCorner);
pageholder->drop();
texture->unlock();
glyph_to_be_paged.clear();
}
//////////////////////
CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env,
CGUITTFont *CGUITTFont::createTTFont(IGUIEnvironment *env,
SGUITTFace *face, u32 size, bool antialias,
bool transparency, u32 shadow, u32 shadow_alpha)
bool preload, u32 shadow, u32 shadow_alpha)
{
CGUITTFont* font = new CGUITTFont(env);
bool ret = font->load(face, size, antialias, transparency);
if (!ret)
{
CGUITTFont *font = new CGUITTFont(env);
bool ret = font->load(face, size, antialias, true, preload);
if (!ret) {
font->drop();
return 0;
}
@@ -284,9 +301,9 @@ CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env,
//////////////////////
//! Constructor.
CGUITTFont::CGUITTFont(IGUIEnvironment *env)
: use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true),
batch_load_size(1)
CGUITTFont::CGUITTFont(IGUIEnvironment *env) :
use_monochrome(false), use_hinting(true), use_auto_hinting(true),
batch_load_size(1)
{
if (env) {
@@ -300,16 +317,16 @@ batch_load_size(1)
setInvisibleCharacters(L" ");
}
bool CGUITTFont::load(SGUITTFace *face, const u32 size, const bool antialias, const bool transparency)
bool CGUITTFont::load(SGUITTFace *face, const u32 size, const bool antialias,
const bool transparency, const bool preload)
{
if (!Driver || size == 0 || !face)
return false;
this->size = size;
// Update the font loading flags when the font is first loaded.
// Update the font loading flags when the font is first loaded
this->use_monochrome = !antialias;
this->use_transparency = transparency;
update_load_flags();
// Store our face.
@@ -327,11 +344,13 @@ bool CGUITTFont::load(SGUITTFace *face, const u32 size, const bool antialias, co
Glyphs.clear();
Glyphs.set_used(tt_face->num_glyphs);
// Cache the first 127 ascii characters.
u32 old_size = batch_load_size;
batch_load_size = 127;
getGlyphIndexByChar((char32_t)0);
batch_load_size = old_size;
// Cache the first 127 ASCII characters
if (preload) {
u32 old_size = batch_load_size;
batch_load_size = 127;
getGlyphIndexByChar(U' '); // char needs to exist, so pick space
batch_load_size = old_size;
}
return true;
}
@@ -366,31 +385,27 @@ void CGUITTFont::update_glyph_pages() const
{
for (u32 i = 0; i != Glyph_Pages.size(); ++i)
{
if (Glyph_Pages[i]->dirty)
if (Glyph_Pages[i]->isDirty())
Glyph_Pages[i]->updateTexture();
}
}
CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const
{
CGUITTGlyphPage* page = 0;
if (Glyph_Pages.empty())
return 0;
else
{
page = Glyph_Pages[getLastGlyphPageIndex()];
if (page->available_slots == 0)
page = 0;
}
return nullptr;
CGUITTGlyphPage *page = Glyph_Pages[getLastGlyphPageIndex()];
if (page->available_slots == 0)
return nullptr;
return page;
}
CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8 pixel_mode)
{
CGUITTGlyphPage* page = 0;
CGUITTGlyphPage *page = nullptr;
// Name of our page.
io::path name("TTFontGlyphPage_");
io::path name("glyph_");
name += tt_face->family_name;
name += ".";
name += tt_face->style_name;
@@ -403,19 +418,15 @@ CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8 pixel_mode)
page = new CGUITTGlyphPage(Driver, name);
// Determine our maximum texture size.
// If we keep getting 0, set it to 1024x1024, as that number is pretty safe.
core::dimension2du max_texture_size = max_page_texture_size;
if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
max_texture_size = Driver->getMaxTextureSize();
if (max_texture_size.Width == 0 || max_texture_size.Height == 0)
max_texture_size = core::dimension2du(1024, 1024);
core::dimension2du max_texture_size = Driver->getMaxTextureSize();
// We want to try to put at least 144 glyphs on a single texture.
// We want to try to put at least 180 glyphs on a single texture.
// magic number = floor(texture_size / sqrt(180))
core::dimension2du page_texture_size;
if (size <= 21) page_texture_size = core::dimension2du(256, 256);
else if (size <= 42) page_texture_size = core::dimension2du(512, 512);
else if (size <= 84) page_texture_size = core::dimension2du(1024, 1024);
else if (size <= 168) page_texture_size = core::dimension2du(2048, 2048);
if (size <= 19) page_texture_size = core::dimension2du(256, 256);
else if (size <= 38) page_texture_size = core::dimension2du(512, 512);
else if (size <= 76) page_texture_size = core::dimension2du(1024, 1024);
else if (size <= 152) page_texture_size = core::dimension2du(2048, 2048);
else page_texture_size = core::dimension2du(4096, 4096);
if (page_texture_size.Width > max_texture_size.Width || page_texture_size.Height > max_texture_size.Height)
@@ -428,21 +439,12 @@ CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8 pixel_mode)
return 0;
}
if (page)
{
// Determine the number of glyph slots on the page and add it to the list of pages.
page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size);
Glyph_Pages.push_back(page);
}
// Determine the number of glyph slots on the page and add it to the list of pages
page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size);
Glyph_Pages.push_back(page);
return page;
}
void CGUITTFont::setTransparency(const bool flag)
{
use_transparency = flag;
reset_images();
}
void CGUITTFont::setMonochrome(const bool flag)
{
use_monochrome = flag;
@@ -467,6 +469,7 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position
void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& position, bool hcenter, bool vcenter, const core::rect<s32>* clip)
{
const auto &colors = text.getColors();
constexpr video::SColor fallback_color(255, 255, 255, 255); // if colors is too short
if (!Driver)
return;
@@ -499,9 +502,6 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio
const std::u32string utext = convertWCharToU32String(text.c_str());
const u32 lineHeight = getLineHeight();
// Key: Glyph page index. Value: Arrays relevant for rendering
std::map<u32, CGUITTGlyphPage*> Render_Map;
// Start parsing characters.
// The same logic is applied to `CGUITTFont::getDimension`
char32_t previousChar = 0;
@@ -538,7 +538,7 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio
const u32 width = getWidthFromCharacter(currentChar);
// Skip whitespace characters
if (InvisibleChars.find_first_of(currentChar) != std::u32string::npos)
if (InvisibleChars.find(currentChar) != std::u32string::npos)
goto skip_invisible;
if (clip) {
@@ -568,14 +568,10 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio
offset += getKerning(currentChar, previousChar);
// Determine rendering information.
CGUITTGlyphPage* const page = Glyph_Pages[glyph->glyph_page];
page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy));
CGUITTGlyphPage *const page = Glyph_Pages[glyph->glyph_page];
page->render_positions.emplace_back(offset.X + offx, offset.Y + offy);
page->render_source_rects.push_back(glyph->source_rect);
if (i < colors.size())
page->render_colors.push_back(colors[i]);
else
page->render_colors.push_back(video::SColor(255,255,255,255));
Render_Map[glyph->glyph_page] = page;
page->render_colors.push_back(i < colors.size() ? colors[i] : fallback_color);
}
else if (fallback)
{
@@ -587,7 +583,7 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio
fallback->draw(core::stringw(l1),
core::rect<s32>({offset.X-1, offset.Y-1}, position.LowerRightCorner), // ???
i < colors.size() ? colors[i] : video::SColor(255, 255, 255, 255),
i < colors.size() ? colors[i] : fallback_color,
false, false, clip);
}
@@ -600,9 +596,14 @@ skip_invisible:
update_glyph_pages();
core::array<core::vector2di> tmp_positions;
core::array<core::recti> tmp_source_rects;
for (const auto &it : Render_Map)
{
CGUITTGlyphPage *page = it.second;
for (u32 page_i = 0; page_i < Glyph_Pages.size(); ++page_i) {
CGUITTGlyphPage *page = Glyph_Pages[page_i];
if (page->render_positions.empty())
continue;
assert(page->render_positions.size() == page->render_colors.size());
assert(page->render_positions.size() == page->render_source_rects.size());
// render runs of matching color in batch
video::SColor colprev;
@@ -616,9 +617,6 @@ skip_invisible:
tmp_source_rects.set_data(&page->render_source_rects[ibegin], i - ibegin);
--i;
if (!use_transparency)
colprev.color |= 0xff000000;
if (shadow_offset) {
for (size_t i = 0; i < tmp_positions.size(); ++i)
tmp_positions[i] += core::vector2di(shadow_offset, shadow_offset);
@@ -746,13 +744,14 @@ u32 CGUITTFont::getGlyphIndexByChar(char32_t c) const
return 0;
// If our glyph is already loaded, don't bother doing any batch loading code.
if (glyph != 0 && Glyphs[glyph - 1].isLoaded())
if (Glyphs[glyph - 1].isLoaded())
return glyph;
// Determine our batch loading positions.
u32 half_size = (batch_load_size / 2);
u32 start_pos = 0;
if (c > half_size) start_pos = c - half_size;
if (c > half_size)
start_pos = c - half_size;
u32 end_pos = start_pos + batch_load_size;
// Load all our characters.
@@ -769,7 +768,8 @@ u32 CGUITTFont::getGlyphIndexByChar(char32_t c) const
{
auto *this2 = const_cast<CGUITTFont*>(this); // oh well
glyph.preload(char_index, tt_face, this2, size, load_flags);
Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph);
auto *surface = glyph.createGlyphImage(tt_face->glyph->bitmap, Driver);
Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph, surface);
}
}
}
@@ -876,51 +876,6 @@ void CGUITTFont::setInvisibleCharacters(const wchar_t *s)
InvisibleChars = convertWCharToU32String(s);
}
video::IImage* CGUITTFont::createTextureFromChar(const char32_t& ch)
{
// This character allows us to print something to the screen for unknown, unrecognizable, or
// unrepresentable characters. See Unicode spec.
const char32_t UTF_REPLACEMENT_CHARACTER = 0xFFFD;
u32 n = getGlyphIndexByChar(ch);
if (n == 0)
n = getGlyphIndexByChar(UTF_REPLACEMENT_CHARACTER);
const SGUITTGlyph& glyph = Glyphs[n-1];
CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page];
if (page->dirty)
page->updateTexture();
video::ITexture* tex = page->texture;
// Acquire a read-only lock of the corresponding page texture.
void* ptr = tex->lock(video::ETLM_READ_ONLY);
if (!ptr)
return nullptr;
video::ECOLOR_FORMAT format = tex->getColorFormat();
core::dimension2du tex_size = tex->getOriginalSize();
video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false);
// Copy the image data out of the page texture.
core::dimension2du glyph_size(glyph.source_rect.getSize());
video::IImage* image = Driver->createImage(format, glyph_size);
pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect);
tex->unlock();
pageholder->drop();
return image;
}
video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const
{
if (page_index < Glyph_Pages.size())
return Glyph_Pages[page_index]->texture;
else
return 0;
}
std::u32string CGUITTFont::convertWCharToU32String(const wchar_t* const charArray) const
{
static_assert(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4, "unexpected wchar size");