1
0
mirror of https://github.com/luanti-org/luanti.git synced 2025-11-28 11:45:29 +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

@@ -270,6 +270,8 @@ u32 CNullDriver::getTextureCount() const
ITexture *CNullDriver::addTexture(const core::dimension2d<u32> &size, const io::path &name, ECOLOR_FORMAT format) ITexture *CNullDriver::addTexture(const core::dimension2d<u32> &size, const io::path &name, ECOLOR_FORMAT format)
{ {
IImage *image = new CImage(format, size); IImage *image = new CImage(format, size);
// the image data will be uploaded, so zero it
memset(image->getData(), 0, image->getImageDataSizeInBytes());
ITexture *t = addTexture(name, image); ITexture *t = addTexture(name, image);
image->drop(); image->drop();
return t; return t;

View File

@@ -185,7 +185,8 @@ void FontEngine::updateCache()
getFont(FONT_SIZE_UNSPECIFIED, FM_Unspecified); getFont(FONT_SIZE_UNSPECIFIED, FM_Unspecified);
} }
void FontEngine::refresh() { void FontEngine::refresh()
{
clearCache(); clearCache();
updateCache(); updateCache();
updateSkin(); updateSkin();
@@ -193,7 +194,7 @@ void FontEngine::refresh() {
void FontEngine::setMediaFont(const std::string &name, const std::string &data) void FontEngine::setMediaFont(const std::string &name, const std::string &data)
{ {
static std::unordered_set<std::string> valid_names { const static std::unordered_set<std::string> valid_names{
"regular", "bold", "italic", "bold_italic", "regular", "bold", "italic", "bold_italic",
"mono", "mono_bold", "mono_italic", "mono_bold_italic", "mono", "mono_bold", "mono_italic", "mono_bold_italic",
}; };
@@ -222,6 +223,20 @@ void FontEngine::clearMediaFonts()
refresh(); refresh();
} }
gui::SGUITTFace *FontEngine::getOrLoadFace(const std::string &filename)
{
auto it = m_local_faces.find(filename);
if (it != m_local_faces.end())
return it->second.get();
irr_ptr<gui::SGUITTFace> face(gui::SGUITTFace::loadFace(filename));
if (!face)
return nullptr;
auto *ret = face.get();
m_local_faces.emplace(filename, std::move(face));
return ret;
}
gui::IGUIFont *FontEngine::initFont(FontSpec spec) gui::IGUIFont *FontEngine::initFont(FontSpec spec)
{ {
assert(spec.mode != FM_Unspecified); assert(spec.mode != FM_Unspecified);
@@ -261,7 +276,7 @@ gui::IGUIFont *FontEngine::initFont(FontSpec spec)
auto createFont = [&](gui::SGUITTFace *face) -> gui::CGUITTFont* { auto createFont = [&](gui::SGUITTFace *face) -> gui::CGUITTFont* {
auto *font = gui::CGUITTFont::createTTFont(m_env, auto *font = gui::CGUITTFont::createTTFont(m_env,
face, size, true, true, font_shadow, face, size, true, spec.mode != _FM_Fallback, font_shadow,
font_shadow_alpha); font_shadow_alpha);
if (!font) if (!font)
@@ -309,11 +324,8 @@ gui::IGUIFont *FontEngine::initFont(FontSpec spec)
infostream << "Creating new font: " << font_path.c_str() infostream << "Creating new font: " << font_path.c_str()
<< " " << size << "pt" << std::endl; << " " << size << "pt" << std::endl;
// Grab the face. if (auto *face = getOrLoadFace(font_path)) {
if (auto *face = gui::SGUITTFace::loadFace(font_path)) { return createFont(face);
auto *font = createFont(face);
face->drop();
return font;
} }
errorstream << "FontEngine: Cannot load '" << font_path << errorstream << "FontEngine: Cannot load '" << font_path <<

View File

@@ -162,6 +162,8 @@ private:
/** refresh after fonts have been changed */ /** refresh after fonts have been changed */
void refresh(); void refresh();
gui::SGUITTFace *getOrLoadFace(const std::string &filename);
/** callback to be used on change of font size setting */ /** callback to be used on change of font size setting */
static void fontSettingChanged(const std::string &name, void *userdata); static void fontSettingChanged(const std::string &name, void *userdata);
@@ -174,6 +176,9 @@ private:
/** internal storage for caching fonts of different size */ /** internal storage for caching fonts of different size */
std::map<unsigned int, gui::IGUIFont*> m_font_cache[FontSpec::MAX_VARIANTS]; std::map<unsigned int, gui::IGUIFont*> m_font_cache[FontSpec::MAX_VARIANTS];
/** local faces, indexed by file path */
std::unordered_map<std::string, irr_ptr<gui::SGUITTFace>> m_local_faces;
/** media-provided faces, indexed by filename (without extension) */ /** media-provided faces, indexed by filename (without extension) */
std::unordered_map<std::string, irr_ptr<gui::SGUITTFace>> m_media_faces; std::unordered_map<std::string, irr_ptr<gui::SGUITTFace>> m_media_faces;

View File

@@ -156,11 +156,7 @@ bool TextureBuffer::ensureTexture(video::ITexture **texture, const TextureDefini
// (could be solved by more refactoring in Irrlicht, but not needed for now) // (could be solved by more refactoring in Irrlicht, but not needed for now)
sanity_check(definition.msaa < 1); sanity_check(definition.msaa < 1);
video::IImage *image = m_driver->createImage(definition.format, size); *texture = m_driver->addTexture(size, definition.name.c_str(), definition.format);
// Cannot use image->fill because it's not implemented for all formats.
std::memset(image->getData(), 0, image->getDataSizeFromFormat(definition.format, size.Width, size.Height));
*texture = m_driver->addTexture(definition.name.c_str(), image);
image->drop();
} else if (definition.msaa > 0) { } else if (definition.msaa > 0) {
*texture = m_driver->addRenderTargetTextureMs(size, definition.msaa, definition.name.c_str(), definition.format); *texture = m_driver->addRenderTargetTextureMs(size, definition.msaa, definition.name.c_str(), definition.format);
} else { } else {

View File

@@ -15,6 +15,7 @@
#include "irrlicht_changes/CGUITTFont.h" #include "irrlicht_changes/CGUITTFont.h"
#include "util/string.h" #include "util/string.h"
#include "guiScrollBar.h" #include "guiScrollBar.h"
#include <IOSOperator.h>
#include <string> #include <string>
inline u32 clamp_u8(s32 value) inline u32 clamp_u8(s32 value)

View File

@@ -35,7 +35,6 @@
#include "irr_ptr.h" #include "irr_ptr.h"
#include "log.h" #include "log.h"
#include "filesys.h"
#include "debug.h" #include "debug.h"
#include "IGUIEnvironment.h" #include "IGUIEnvironment.h"
@@ -46,9 +45,8 @@
namespace gui namespace gui
{ {
std::map<io::path, SGUITTFace*> SGUITTFace::faces;
FT_Library SGUITTFace::freetype_library = nullptr; FT_Library SGUITTFace::freetype_library = nullptr;
std::size_t SGUITTFace::n_faces = 0; size_t SGUITTFace::n_faces = 0;
FT_Library SGUITTFace::getFreeTypeLibrary() FT_Library SGUITTFace::getFreeTypeLibrary()
{ {
@@ -85,54 +83,28 @@ SGUITTFace* SGUITTFace::createFace(std::string &&buffer)
auto ft = getFreeTypeLibrary(); auto ft = getFreeTypeLibrary();
if (!ft) if (!ft)
return nullptr; return nullptr;
return (FT_New_Memory_Face(ft, bool ok = FT_New_Memory_Face(ft,
reinterpret_cast<const FT_Byte*>(face->face_buffer.data()), reinterpret_cast<const FT_Byte*>(face->face_buffer.data()),
face->face_buffer.size(), 0, &face->face)) face->face_buffer.size(), 0, &face->face) == 0;
? nullptr : face.release(); return ok ? face.release() : nullptr;
} }
SGUITTFace* SGUITTFace::loadFace(const io::path &filename) SGUITTFace* SGUITTFace::loadFace(const io::path &filename)
{ {
auto it = faces.find(filename); irr_ptr<SGUITTFace> face(new SGUITTFace(""));
if (it != faces.end()) { auto ft = getFreeTypeLibrary();
it->second->grab(); if (!ft)
return it->second;
}
std::string buffer;
if (!fs::ReadFile(filename.c_str(), buffer, true)) {
errorstream << "CGUITTFont: Reading file " << filename.c_str() << " failed." << std::endl;
return nullptr; return nullptr;
} // Prefer FT_New_Face because it doesn't require loading everything
// to memory.
auto *face = SGUITTFace::createFace(std::move(buffer)); bool ok = FT_New_Face(ft, filename.c_str(), 0, &face->face) == 0;
if (!face) { return ok ? face.release() : nullptr;
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);
} }
video::IImage* SGUITTGlyph::createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const 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 // 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"); FATAL_ERROR("Insane font glyph size");
// Determine what our texture size should be. // 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 d(bits.width + 1, bits.rows + 1);
core::dimension2du texture_size; core::dimension2du texture_size;
// Create and load our image now. // Turn bitmap into an image
video::IImage* image = 0; video::IImage *image = nullptr;
switch (bits.pixel_mode) switch (bits.pixel_mode)
{ {
case FT_PIXEL_MODE_MONO: case FT_PIXEL_MODE_MONO:
@@ -233,44 +205,89 @@ void SGUITTGlyph::preload(u32 char_index, FT_Face face, CGUITTFont *parent, u32
return; return;
} }
// Allocate slot from page
glyph_page = parent->getLastGlyphPageIndex(); glyph_page = parent->getLastGlyphPageIndex();
u32 texture_side_length = page->texture->getOriginalSize().Width; u32 texture_side_length = page->texture->getOriginalSize().Width;
core::vector2di page_position( core::vector2di page_position(
(page->used_slots % (texture_side_length / font_size)) * font_size, (page->used_slots % (texture_side_length / font_size)) * font_size,
(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.UpperLeftCorner = page_position;
source_rect.LowerRightCorner = core::vector2di(page_position.X + bits.width, page_position.Y + bits.rows); source_rect.LowerRightCorner = core::vector2di(page_position.X + bits.width, page_position.Y + bits.rows);
page->dirty = true;
++page->used_slots; ++page->used_slots;
--page->available_slots; --page->available_slots;
// We grab the glyph bitmap here so the data won't be removed when the next glyph is loaded. // createGlyphImage can now be called, the next preload() call will however
surface = createGlyphImage(bits, parent->getDriver()); // invalidate the data in `bits`.
} }
void SGUITTGlyph::unload() void SGUITTGlyph::unload()
{ {
if (surface)
{
surface->drop();
surface = 0;
}
// reset isLoaded to false // reset isLoaded to false
source_rect = core::recti(); 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, 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); CGUITTFont *font = new CGUITTFont(env);
bool ret = font->load(face, size, antialias, transparency); bool ret = font->load(face, size, antialias, true, preload);
if (!ret) if (!ret) {
{
font->drop(); font->drop();
return 0; return 0;
} }
@@ -284,9 +301,9 @@ CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env,
////////////////////// //////////////////////
//! Constructor. //! Constructor.
CGUITTFont::CGUITTFont(IGUIEnvironment *env) CGUITTFont::CGUITTFont(IGUIEnvironment *env) :
: use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true), use_monochrome(false), use_hinting(true), use_auto_hinting(true),
batch_load_size(1) batch_load_size(1)
{ {
if (env) { if (env) {
@@ -300,16 +317,16 @@ batch_load_size(1)
setInvisibleCharacters(L" "); 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) if (!Driver || size == 0 || !face)
return false; return false;
this->size = size; 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_monochrome = !antialias;
this->use_transparency = transparency;
update_load_flags(); update_load_flags();
// Store our face. // Store our face.
@@ -327,11 +344,13 @@ bool CGUITTFont::load(SGUITTFace *face, const u32 size, const bool antialias, co
Glyphs.clear(); Glyphs.clear();
Glyphs.set_used(tt_face->num_glyphs); Glyphs.set_used(tt_face->num_glyphs);
// Cache the first 127 ascii characters. // Cache the first 127 ASCII characters
u32 old_size = batch_load_size; if (preload) {
batch_load_size = 127; u32 old_size = batch_load_size;
getGlyphIndexByChar((char32_t)0); batch_load_size = 127;
batch_load_size = old_size; getGlyphIndexByChar(U' '); // char needs to exist, so pick space
batch_load_size = old_size;
}
return true; return true;
} }
@@ -366,31 +385,27 @@ void CGUITTFont::update_glyph_pages() const
{ {
for (u32 i = 0; i != Glyph_Pages.size(); ++i) for (u32 i = 0; i != Glyph_Pages.size(); ++i)
{ {
if (Glyph_Pages[i]->dirty) if (Glyph_Pages[i]->isDirty())
Glyph_Pages[i]->updateTexture(); Glyph_Pages[i]->updateTexture();
} }
} }
CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const
{ {
CGUITTGlyphPage* page = 0;
if (Glyph_Pages.empty()) if (Glyph_Pages.empty())
return 0; return nullptr;
else CGUITTGlyphPage *page = Glyph_Pages[getLastGlyphPageIndex()];
{ if (page->available_slots == 0)
page = Glyph_Pages[getLastGlyphPageIndex()]; return nullptr;
if (page->available_slots == 0)
page = 0;
}
return page; return page;
} }
CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8 pixel_mode) CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8 pixel_mode)
{ {
CGUITTGlyphPage* page = 0; CGUITTGlyphPage *page = nullptr;
// Name of our page. // Name of our page.
io::path name("TTFontGlyphPage_"); io::path name("glyph_");
name += tt_face->family_name; name += tt_face->family_name;
name += "."; name += ".";
name += tt_face->style_name; name += tt_face->style_name;
@@ -403,19 +418,15 @@ CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8 pixel_mode)
page = new CGUITTGlyphPage(Driver, name); page = new CGUITTGlyphPage(Driver, name);
// Determine our maximum texture size. // 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 = Driver->getMaxTextureSize();
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);
// 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; core::dimension2du page_texture_size;
if (size <= 21) page_texture_size = core::dimension2du(256, 256); if (size <= 19) page_texture_size = core::dimension2du(256, 256);
else if (size <= 42) page_texture_size = core::dimension2du(512, 512); else if (size <= 38) page_texture_size = core::dimension2du(512, 512);
else if (size <= 84) page_texture_size = core::dimension2du(1024, 1024); else if (size <= 76) page_texture_size = core::dimension2du(1024, 1024);
else if (size <= 168) page_texture_size = core::dimension2du(2048, 2048); else if (size <= 152) page_texture_size = core::dimension2du(2048, 2048);
else page_texture_size = core::dimension2du(4096, 4096); 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) 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; 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);
// Determine the number of glyph slots on the page and add it to the list of pages. Glyph_Pages.push_back(page);
page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size);
Glyph_Pages.push_back(page);
}
return page; return page;
} }
void CGUITTFont::setTransparency(const bool flag)
{
use_transparency = flag;
reset_images();
}
void CGUITTFont::setMonochrome(const bool flag) void CGUITTFont::setMonochrome(const bool flag)
{ {
use_monochrome = 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) 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(); const auto &colors = text.getColors();
constexpr video::SColor fallback_color(255, 255, 255, 255); // if colors is too short
if (!Driver) if (!Driver)
return; 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 std::u32string utext = convertWCharToU32String(text.c_str());
const u32 lineHeight = getLineHeight(); const u32 lineHeight = getLineHeight();
// Key: Glyph page index. Value: Arrays relevant for rendering
std::map<u32, CGUITTGlyphPage*> Render_Map;
// Start parsing characters. // Start parsing characters.
// The same logic is applied to `CGUITTFont::getDimension` // The same logic is applied to `CGUITTFont::getDimension`
char32_t previousChar = 0; char32_t previousChar = 0;
@@ -538,7 +538,7 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio
const u32 width = getWidthFromCharacter(currentChar); const u32 width = getWidthFromCharacter(currentChar);
// Skip whitespace characters // Skip whitespace characters
if (InvisibleChars.find_first_of(currentChar) != std::u32string::npos) if (InvisibleChars.find(currentChar) != std::u32string::npos)
goto skip_invisible; goto skip_invisible;
if (clip) { if (clip) {
@@ -568,14 +568,10 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio
offset += getKerning(currentChar, previousChar); offset += getKerning(currentChar, previousChar);
// Determine rendering information. // Determine rendering information.
CGUITTGlyphPage* const page = Glyph_Pages[glyph->glyph_page]; CGUITTGlyphPage *const page = Glyph_Pages[glyph->glyph_page];
page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy)); page->render_positions.emplace_back(offset.X + offx, offset.Y + offy);
page->render_source_rects.push_back(glyph->source_rect); page->render_source_rects.push_back(glyph->source_rect);
if (i < colors.size()) page->render_colors.push_back(i < colors.size() ? colors[i] : fallback_color);
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;
} }
else if (fallback) else if (fallback)
{ {
@@ -587,7 +583,7 @@ void CGUITTFont::draw(const EnrichedString &text, const core::rect<s32>& positio
fallback->draw(core::stringw(l1), fallback->draw(core::stringw(l1),
core::rect<s32>({offset.X-1, offset.Y-1}, position.LowerRightCorner), // ??? 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); false, false, clip);
} }
@@ -600,9 +596,14 @@ skip_invisible:
update_glyph_pages(); update_glyph_pages();
core::array<core::vector2di> tmp_positions; core::array<core::vector2di> tmp_positions;
core::array<core::recti> tmp_source_rects; core::array<core::recti> tmp_source_rects;
for (const auto &it : Render_Map) for (u32 page_i = 0; page_i < Glyph_Pages.size(); ++page_i) {
{ CGUITTGlyphPage *page = Glyph_Pages[page_i];
CGUITTGlyphPage *page = it.second;
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 // render runs of matching color in batch
video::SColor colprev; video::SColor colprev;
@@ -616,9 +617,6 @@ skip_invisible:
tmp_source_rects.set_data(&page->render_source_rects[ibegin], i - ibegin); tmp_source_rects.set_data(&page->render_source_rects[ibegin], i - ibegin);
--i; --i;
if (!use_transparency)
colprev.color |= 0xff000000;
if (shadow_offset) { if (shadow_offset) {
for (size_t i = 0; i < tmp_positions.size(); ++i) for (size_t i = 0; i < tmp_positions.size(); ++i)
tmp_positions[i] += core::vector2di(shadow_offset, shadow_offset); tmp_positions[i] += core::vector2di(shadow_offset, shadow_offset);
@@ -746,13 +744,14 @@ u32 CGUITTFont::getGlyphIndexByChar(char32_t c) const
return 0; return 0;
// If our glyph is already loaded, don't bother doing any batch loading code. // 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; return glyph;
// Determine our batch loading positions. // Determine our batch loading positions.
u32 half_size = (batch_load_size / 2); u32 half_size = (batch_load_size / 2);
u32 start_pos = 0; 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; u32 end_pos = start_pos + batch_load_size;
// Load all our characters. // Load all our characters.
@@ -769,7 +768,8 @@ u32 CGUITTFont::getGlyphIndexByChar(char32_t c) const
{ {
auto *this2 = const_cast<CGUITTFont*>(this); // oh well auto *this2 = const_cast<CGUITTFont*>(this); // oh well
glyph.preload(char_index, tt_face, this2, size, load_flags); 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); 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 std::u32string CGUITTFont::convertWCharToU32String(const wchar_t* const charArray) const
{ {
static_assert(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4, "unexpected wchar size"); static_assert(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4, "unexpected wchar size");

View File

@@ -39,72 +39,49 @@
#include "IGUIEnvironment.h" #include "IGUIEnvironment.h"
#include "IGUIFont.h" #include "IGUIFont.h"
#include "IVideoDriver.h" #include "IVideoDriver.h"
#include "IrrlichtDevice.h"
#include "util/enriched_string.h" #include "util/enriched_string.h"
#include "util/basic_macros.h" #include "util/basic_macros.h"
#include <map> #include <map>
#include <optional>
namespace gui namespace gui
{ {
class CGUITTFont;
// Manages the FT_Face cache. // Manages the FT_Face cache.
struct SGUITTFace : public IReferenceCounted struct SGUITTFace : public IReferenceCounted
{ {
private: private:
static std::map<io::path, SGUITTFace*> faces;
static FT_Library freetype_library; static FT_Library freetype_library;
static std::size_t n_faces; static size_t n_faces;
static FT_Library getFreeTypeLibrary(); static FT_Library getFreeTypeLibrary();
// This holds the font file data for this face.
// Must not be deallocated until we are done with the face!
std::string face_buffer;
public: public:
SGUITTFace(std::string &&buffer); SGUITTFace(std::string &&buffer);
~SGUITTFace(); ~SGUITTFace();
std::optional<std::string> filename;
FT_Face face; FT_Face face;
/// Must not be deallocated until we are done with the face!
std::string face_buffer;
static SGUITTFace* createFace(std::string &&buffer); static SGUITTFace* createFace(std::string &&buffer);
static SGUITTFace* loadFace(const io::path &filename); static SGUITTFace* loadFace(const io::path &filename);
void dropFilename();
}; };
class CGUITTFont;
//! Structure representing a single TrueType glyph. //! Structure representing a single TrueType glyph.
struct SGUITTGlyph struct SGUITTGlyph
{ {
//! Constructor.
SGUITTGlyph() : SGUITTGlyph() :
glyph_page(0), glyph_page(0),
source_rect(), source_rect(),
offset(), offset(),
advance(), advance()
surface(0)
{} {}
DISABLE_CLASS_COPY(SGUITTGlyph);
//! This class would be trivially copyable except for the reference count on `surface`.
SGUITTGlyph(SGUITTGlyph &&other) noexcept :
glyph_page(other.glyph_page),
source_rect(other.source_rect),
offset(other.offset),
advance(other.advance),
surface(other.surface)
{
other.surface = 0;
}
//! Destructor.
~SGUITTGlyph() { unload(); } ~SGUITTGlyph() { unload(); }
//! If true, the glyph has been loaded. //! If true, the glyph has been loaded.
@@ -113,17 +90,13 @@ namespace gui
} }
//! Preload the glyph. //! Preload the glyph.
//! The preload process occurs when the program tries to cache the glyph from FT_Library.
//! However, it simply defines the SGUITTGlyph's properties and will only create the page
//! textures if necessary. The actual creation of the textures should only occur right
//! before the batch draw call.
void preload(u32 char_index, FT_Face face, CGUITTFont *parent, u32 font_size, const FT_Int32 loadFlags); void preload(u32 char_index, FT_Face face, CGUITTFont *parent, u32 font_size, const FT_Int32 loadFlags);
//! Unloads the glyph. //! Unloads the glyph.
void unload(); void unload();
//! Creates the IImage object from the FT_Bitmap. //! Creates the IImage object from the FT_Bitmap.
video::IImage* createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const; video::IImage* createGlyphImage(const FT_Bitmap &bits, video::IVideoDriver *driver) const;
//! The page the glyph is on. //! The page the glyph is on.
u32 glyph_page; u32 glyph_page;
@@ -136,107 +109,74 @@ namespace gui
//! Glyph advance information. //! Glyph advance information.
core::vector2di advance; core::vector2di advance;
};
//! This is just the temporary image holder. After this glyph is paged, //! Wrapper struct for a preloaded glyph
//! it will be dropped. struct SGUITTGlyphPending {
mutable video::IImage* surface; SGUITTGlyphPending(const SGUITTGlyph *glyph, video::IImage *surface) noexcept :
glyph(glyph), surface(surface)
{}
~SGUITTGlyphPending() {
if (surface)
surface->drop();
}
DISABLE_CLASS_COPY(SGUITTGlyphPending)
SGUITTGlyphPending(SGUITTGlyphPending &&other) noexcept :
glyph(other.glyph), surface(other.surface)
{
other.surface = nullptr;
}
const SGUITTGlyph *glyph;
video::IImage *surface;
}; };
//! Holds a sheet of glyphs. //! Holds a sheet of glyphs.
class CGUITTGlyphPage class CGUITTGlyphPage
{ {
public: public:
CGUITTGlyphPage(video::IVideoDriver* Driver, const io::path& texture_name) :texture(0), available_slots(0), used_slots(0), dirty(false), driver(Driver), name(texture_name) {} CGUITTGlyphPage(video::IVideoDriver *Driver, const io::path &texture_name) :
texture(0), available_slots(0), used_slots(0),
driver(Driver), name(texture_name)
{}
~CGUITTGlyphPage() ~CGUITTGlyphPage()
{ {
if (texture) if (texture)
{ driver->removeTexture(texture);
if (driver)
driver->removeTexture(texture);
else
texture->drop();
}
} }
//! Create the actual page texture, //! Create the actual page texture,
bool createPageTexture(const u8& pixel_mode, const core::dimension2du& texture_size) bool createPageTexture(u8 pixel_mode, 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);
// Set the texture color format.
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 our texture creation flags.
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, flgmip);
driver->setTextureCreationFlag(video::ETCF_ALLOW_MEMORY_COPY, flgcpy);
return texture ? true : false;
}
//! Add the glyph to a list of glyphs to be paged. //! Add the glyph to a list of glyphs to be paged.
//! This collection will be cleared after updateTexture is called. //! This collection will be cleared after updateTexture is called.
void pushGlyphToBePaged(const SGUITTGlyph* glyph) void pushGlyphToBePaged(const SGUITTGlyph *glyph, video::IImage *surface)
{ {
glyph_to_be_paged.push_back(glyph); if (!glyph || !surface)
return;
glyph_to_be_paged.emplace_back(glyph, surface);
}
inline bool isDirty() const
{
return !glyph_to_be_paged.empty();
} }
//! Updates the texture atlas with new glyphs. //! Updates the texture atlas with new glyphs.
void updateTexture() void updateTexture();
{
if (!dirty)
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 (u32 i = 0; i < glyph_to_be_paged.size(); ++i)
{
const SGUITTGlyph* glyph = glyph_to_be_paged[i];
if (glyph && glyph->surface)
{
glyph->surface->copyTo(pageholder, glyph->source_rect.UpperLeftCorner);
glyph->surface->drop();
glyph->surface = 0;
}
}
pageholder->drop();
texture->unlock();
glyph_to_be_paged.clear();
dirty = false;
}
video::ITexture* texture; video::ITexture* texture;
u32 available_slots; u32 available_slots;
u32 used_slots; u32 used_slots;
bool dirty;
core::array<core::vector2di> render_positions; std::vector<core::vector2di> render_positions;
core::array<core::recti> render_source_rects; std::vector<core::recti> render_source_rects;
core::array<video::SColor> render_colors; std::vector<video::SColor> render_colors;
private: private:
core::array<const SGUITTGlyph*> glyph_to_be_paged; std::vector<SGUITTGlyphPending> glyph_to_be_paged;
video::IVideoDriver* driver; video::IVideoDriver* driver;
io::path name; io::path name;
}; };
@@ -249,11 +189,11 @@ namespace gui
//! \param env The IGUIEnvironment the font loads out of. //! \param env The IGUIEnvironment the font loads out of.
//! \param size The size of the font glyphs in pixels. Since this is the size of the individual glyphs, the true height of the font may change depending on the characters used. //! \param size The size of the font glyphs in pixels. Since this is the size of the individual glyphs, the true height of the font may change depending on the characters used.
//! \param antialias set the use_monochrome (opposite to antialias) flag //! \param antialias set the use_monochrome (opposite to antialias) flag
//! \param transparency set the use_transparency flag //! \param preload create texture with important glyphs directly
//! \return Returns a pointer to a CGUITTFont. Will return 0 if the font failed to load. //! \return Returns a pointer to a CGUITTFont. Will return 0 if the font failed to load.
static CGUITTFont* createTTFont(IGUIEnvironment *env, static CGUITTFont* createTTFont(IGUIEnvironment *env,
SGUITTFace *face, u32 size, bool antialias = true, SGUITTFace *face, u32 size, bool antialias = true,
bool transparency = true, u32 shadow = 0, u32 shadow_alpha = 255); bool preload = true, u32 shadow = 0, u32 shadow_alpha = 255);
//! Destructor //! Destructor
virtual ~CGUITTFont(); virtual ~CGUITTFont();
@@ -261,15 +201,9 @@ namespace gui
//! Sets the amount of glyphs to batch load. //! Sets the amount of glyphs to batch load.
void setBatchLoadSize(u32 batch_size) { batch_load_size = batch_size; } void setBatchLoadSize(u32 batch_size) { batch_load_size = batch_size; }
//! Sets the maximum texture size for a page of glyphs.
void setMaxPageTextureSize(const core::dimension2du& texture_size) { max_page_texture_size = texture_size; }
//! Get the font size. //! Get the font size.
u32 getFontSize() const { return size; } u32 getFontSize() const { return size; }
//! Check the font's transparency.
bool isTransparent() const { return use_transparency; }
//! Check if the font auto-hinting is enabled. //! Check if the font auto-hinting is enabled.
//! Auto-hinting is FreeType's built-in font hinting engine. //! Auto-hinting is FreeType's built-in font hinting engine.
bool useAutoHinting() const { return use_auto_hinting; } bool useAutoHinting() const { return use_auto_hinting; }
@@ -281,11 +215,6 @@ namespace gui
//! The font can either be a 256 color grayscale font, or a 2 color monochrome font. //! The font can either be a 256 color grayscale font, or a 2 color monochrome font.
bool useMonochrome() const { return use_monochrome; } bool useMonochrome() const { return use_monochrome; }
//! Tells the font to allow transparency when rendering.
//! Default: true.
//! \param flag If true, the font draws using transparency.
void setTransparency(const bool flag);
//! Tells the font to use monochrome rendering. //! Tells the font to use monochrome rendering.
//! Default: false. //! Default: false.
//! \param flag If true, the font draws using a monochrome image. If false, the font uses a grayscale image. //! \param flag If true, the font draws using a monochrome image. If false, the font uses a grayscale image.
@@ -339,27 +268,14 @@ namespace gui
//! Set font that should be used for glyphs not present in ours //! Set font that should be used for glyphs not present in ours
void setFallback(gui::IGUIFont* font) { fallback = font; } void setFallback(gui::IGUIFont* font) { fallback = font; }
//! Create corresponding character's software image copy from the font,
//! so you can use this data just like any ordinary video::IImage.
//! \param ch The character you need
video::IImage* createTextureFromChar(const char32_t& ch);
//! This function is for debugging mostly. If the page doesn't exist it returns zero.
//! \param page_index Simply return the texture handle of a given page index.
video::ITexture* getPageTextureByIndex(const u32& page_index) const;
inline video::IVideoDriver *getDriver() const { return Driver; }
inline s32 getAscender() const { return font_metrics.ascender; } inline s32 getAscender() const { return font_metrics.ascender; }
protected: protected:
bool use_monochrome; bool use_monochrome;
bool use_transparency;
bool use_hinting; bool use_hinting;
bool use_auto_hinting; bool use_auto_hinting;
u32 size; u32 size;
u32 batch_load_size; u32 batch_load_size;
core::dimension2du max_page_texture_size;
private: private:
// Helper functions for the same-named public member functions above // Helper functions for the same-named public member functions above
@@ -371,17 +287,23 @@ namespace gui
std::u32string convertWCharToU32String(const wchar_t* const) const; std::u32string convertWCharToU32String(const wchar_t* const) const;
CGUITTFont(IGUIEnvironment *env); CGUITTFont(IGUIEnvironment *env);
bool load(SGUITTFace *face, const u32 size, const bool antialias, const bool transparency); bool load(SGUITTFace *face, const u32 size, const bool antialias,
const bool transparency, const bool preload);
void reset_images(); void reset_images();
void update_glyph_pages() const; void update_glyph_pages() const;
void update_load_flags() void update_load_flags()
{ {
// Set up our loading flags. // Set up our loading flags.
load_flags = FT_LOAD_DEFAULT | FT_LOAD_RENDER; load_flags = FT_LOAD_DEFAULT | FT_LOAD_RENDER;
if (!useHinting()) load_flags |= FT_LOAD_NO_HINTING; if (!useHinting())
if (!useAutoHinting()) load_flags |= FT_LOAD_NO_AUTOHINT; load_flags |= FT_LOAD_NO_HINTING;
if (useMonochrome()) load_flags |= FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO; if (!useAutoHinting())
else load_flags |= FT_LOAD_TARGET_NORMAL; load_flags |= FT_LOAD_NO_AUTOHINT;
if (useMonochrome())
load_flags |= FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO;
else
load_flags |= FT_LOAD_TARGET_NORMAL;
} }
/// Gets the overall font height, including a line gap of 1 px /// Gets the overall font height, including a line gap of 1 px
@@ -394,7 +316,6 @@ namespace gui
core::vector2di getKerning(const char32_t thisLetter, const char32_t previousLetter) const; core::vector2di getKerning(const char32_t thisLetter, const char32_t previousLetter) const;
video::IVideoDriver* Driver = nullptr; video::IVideoDriver* Driver = nullptr;
std::optional<io::path> filename;
FT_Face tt_face; FT_Face tt_face;
FT_Size_Metrics font_metrics; FT_Size_Metrics font_metrics;
FT_Int32 load_flags; FT_Int32 load_flags;