346 lines
9.3 KiB
C++
346 lines
9.3 KiB
C++
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
|
|
|
#include "FontManager.h"
|
|
#include "FontTextureAtlas.h"
|
|
#include "FontAsset.h"
|
|
#include "Font.h"
|
|
#include "Engine/Platform/Platform.h"
|
|
#include "Engine/Core/Log.h"
|
|
#include "Engine/Content/Content.h"
|
|
#include "Engine/Engine/EngineService.h"
|
|
#include "Engine/Threading/Threading.h"
|
|
#include "IncludeFreeType.h"
|
|
#include <ThirdParty/freetype/ftsynth.h>
|
|
#include <ThirdParty/freetype/ftbitmap.h>
|
|
#include <ThirdParty/freetype/internal/ftdrv.h>
|
|
|
|
namespace FontManagerImpl
|
|
{
|
|
FT_Library Library;
|
|
CriticalSection Locker;
|
|
Array<AssetReference<FontTextureAtlas>> Atlases;
|
|
Array<byte> GlyphImageData;
|
|
}
|
|
|
|
using namespace FontManagerImpl;
|
|
|
|
class FontManagerService : public EngineService
|
|
{
|
|
public:
|
|
FontManagerService()
|
|
: EngineService(TEXT("Font Manager"), -700)
|
|
{
|
|
}
|
|
|
|
bool Init() override;
|
|
void Dispose() override;
|
|
};
|
|
|
|
FontManagerService FontManagerServiceInstance;
|
|
|
|
float FontManager::FontScale = 1.0f;
|
|
|
|
FT_Library FontManager::GetLibrary()
|
|
{
|
|
return Library;
|
|
}
|
|
|
|
FT_MemoryRec_ FreeTypeMemory;
|
|
|
|
static void* FreeTypeAlloc(FT_Memory memory, long size)
|
|
{
|
|
return Allocator::Allocate(size);
|
|
}
|
|
|
|
void* FreeTypeRealloc(FT_Memory memory, long oldSize, long newSize, void* ptr)
|
|
{
|
|
return AllocatorExt::Realloc(ptr, oldSize, newSize);
|
|
}
|
|
|
|
void FreeTypeFree(FT_Memory memory, void* ptr)
|
|
{
|
|
return Allocator::Free(ptr);
|
|
}
|
|
|
|
bool FontManagerService::Init()
|
|
{
|
|
ASSERT(Library == nullptr);
|
|
|
|
// Scale UI fonts to match the monitor DPI
|
|
FontManager::FontScale = (float)Platform::GetDpi() / (float)DefaultDPI; // TODO: Adjust this at runtime
|
|
|
|
// Init Free Type
|
|
FreeTypeMemory.user = nullptr;
|
|
FreeTypeMemory.alloc = &FreeTypeAlloc;
|
|
FreeTypeMemory.realloc = &FreeTypeRealloc;
|
|
FreeTypeMemory.free = &FreeTypeFree;
|
|
const FT_Error error = FT_New_Library(&FreeTypeMemory, &Library);
|
|
if (error)
|
|
{
|
|
LOG_FT_ERROR(error);
|
|
return true;
|
|
}
|
|
FT_Add_Default_Modules(Library);
|
|
|
|
// Log version info
|
|
FT_Int major, minor, patch;
|
|
FT_Library_Version(Library, &major, &minor, &patch);
|
|
LOG(Info, "FreeType initialized, version: {0}.{1}.{2}", major, minor, patch);
|
|
|
|
return false;
|
|
}
|
|
|
|
void FontManagerService::Dispose()
|
|
{
|
|
// Release font atlases
|
|
Atlases.Resize(0);
|
|
|
|
// Clean library
|
|
if (Library)
|
|
{
|
|
const FT_Error error = FT_Done_Library(Library);
|
|
Library = nullptr;
|
|
if (error)
|
|
{
|
|
LOG_FT_ERROR(error);
|
|
}
|
|
}
|
|
}
|
|
|
|
FontTextureAtlas* FontManager::GetAtlas(int32 index)
|
|
{
|
|
return index >= 0 && index < Atlases.Count() ? Atlases.Get()[index].Get() : nullptr;
|
|
}
|
|
|
|
bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry)
|
|
{
|
|
ScopeLock lock(Locker);
|
|
|
|
const FontAsset* asset = font->GetAsset();
|
|
const FontOptions& options = asset->GetOptions();
|
|
const FT_Face face = asset->GetFTFace();
|
|
ASSERT(face != nullptr);
|
|
font->FlushFaceSize();
|
|
|
|
// Set load flags
|
|
uint32 glyphFlags = FT_LOAD_NO_BITMAP;
|
|
const bool useAA = EnumHasAnyFlags(options.Flags, FontFlags::AntiAliasing);
|
|
if (useAA)
|
|
{
|
|
switch (options.Hinting)
|
|
{
|
|
case FontHinting::Auto:
|
|
glyphFlags |= FT_LOAD_FORCE_AUTOHINT;
|
|
break;
|
|
case FontHinting::AutoLight:
|
|
glyphFlags |= FT_LOAD_TARGET_LIGHT;
|
|
break;
|
|
case FontHinting::Monochrome:
|
|
glyphFlags |= FT_LOAD_TARGET_MONO;
|
|
break;
|
|
case FontHinting::None:
|
|
glyphFlags |= FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING;
|
|
break;
|
|
case FontHinting::Default:
|
|
default:
|
|
glyphFlags |= FT_LOAD_TARGET_NORMAL;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
glyphFlags |= FT_LOAD_TARGET_MONO | FT_LOAD_FORCE_AUTOHINT;
|
|
}
|
|
|
|
// Get the index to the glyph in the font face
|
|
const FT_UInt glyphIndex = FT_Get_Char_Index(face, c);
|
|
#if !BUILD_RELEASE
|
|
if (glyphIndex == 0)
|
|
{
|
|
LOG(Warning, "Font `{}` doesn't contain character `\\u{:x}`, consider choosing another font. ", String(face->family_name), c);
|
|
}
|
|
#endif
|
|
|
|
// Load the glyph
|
|
const FT_Error error = FT_Load_Glyph(face, glyphIndex, glyphFlags);
|
|
if (error)
|
|
{
|
|
LOG_FT_ERROR(error);
|
|
return true;
|
|
}
|
|
|
|
// Handle special effects
|
|
if (EnumHasAnyFlags(options.Flags, FontFlags::Bold))
|
|
{
|
|
FT_GlyphSlot_Embolden(face->glyph);
|
|
}
|
|
if (EnumHasAnyFlags(options.Flags, FontFlags::Italic))
|
|
{
|
|
FT_GlyphSlot_Oblique(face->glyph);
|
|
}
|
|
|
|
// Render glyph to the bitmap
|
|
FT_GlyphSlot glyph = face->glyph;
|
|
FT_Render_Glyph(glyph, useAA ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO);
|
|
|
|
FT_Bitmap* bitmap = &glyph->bitmap;
|
|
FT_Bitmap tmpBitmap;
|
|
if (bitmap->pixel_mode != FT_PIXEL_MODE_GRAY)
|
|
{
|
|
// Convert the bitmap to 8bpp grayscale
|
|
FT_Bitmap_New(&tmpBitmap);
|
|
FT_Bitmap_Convert(Library, bitmap, &tmpBitmap, 4);
|
|
bitmap = &tmpBitmap;
|
|
}
|
|
ASSERT(bitmap && bitmap->pixel_mode == FT_PIXEL_MODE_GRAY);
|
|
|
|
// Fill the character data
|
|
Platform::MemoryClear(&entry, sizeof(entry));
|
|
entry.Character = c;
|
|
entry.AdvanceX = Convert26Dot6ToRoundedPixel<int16>(glyph->advance.x);
|
|
entry.OffsetY = glyph->bitmap_top;
|
|
entry.OffsetX = glyph->bitmap_left;
|
|
entry.IsValid = true;
|
|
entry.BearingY = Convert26Dot6ToRoundedPixel<int16>(glyph->metrics.horiBearingY);
|
|
entry.Height = Convert26Dot6ToRoundedPixel<int16>(glyph->metrics.height);
|
|
|
|
// Allocate memory
|
|
const int32 glyphWidth = bitmap->width;
|
|
const int32 glyphHeight = bitmap->rows;
|
|
GlyphImageData.Clear();
|
|
GlyphImageData.Resize(glyphWidth * glyphHeight);
|
|
|
|
// End for empty glyphs
|
|
if (GlyphImageData.IsEmpty())
|
|
{
|
|
entry.TextureIndex = MAX_uint8;
|
|
if (bitmap == &tmpBitmap)
|
|
{
|
|
FT_Bitmap_Done(Library, bitmap);
|
|
bitmap = nullptr;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Copy glyph data after rasterization (row by row)
|
|
for (int32 row = 0; row < glyphHeight; row++)
|
|
{
|
|
Platform::MemoryCopy(&GlyphImageData[row * glyphWidth], &bitmap->buffer[row * bitmap->pitch], glyphWidth);
|
|
}
|
|
|
|
// Normalize gray scale images not using 256 colors
|
|
if (bitmap->num_grays != 256)
|
|
{
|
|
const int32 scale = 255 / (bitmap->num_grays - 1);
|
|
for (byte& pixel : GlyphImageData)
|
|
{
|
|
pixel *= scale;
|
|
}
|
|
}
|
|
|
|
// Free temporary bitmap if used
|
|
if (bitmap == &tmpBitmap)
|
|
{
|
|
FT_Bitmap_Done(Library, bitmap);
|
|
bitmap = nullptr;
|
|
}
|
|
|
|
// Find atlas for the character texture
|
|
int32 atlasIndex = 0;
|
|
const FontTextureAtlasSlot* slot = nullptr;
|
|
for (; atlasIndex < Atlases.Count(); atlasIndex++)
|
|
{
|
|
// Add the character to the texture
|
|
slot = Atlases[atlasIndex]->AddEntry(glyphWidth, glyphHeight, GlyphImageData);
|
|
|
|
// Check result, if not null char has been added
|
|
if (slot)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check if there is no atlas for this character
|
|
if (!slot)
|
|
{
|
|
// Create new atlas
|
|
auto atlas = Content::CreateVirtualAsset<FontTextureAtlas>();
|
|
atlas->Setup(PixelFormat::R8_UNorm, FontTextureAtlas::PaddingStyle::PadWithZero);
|
|
Atlases.Add(atlas);
|
|
|
|
// Init atlas
|
|
const int32 fontAtlasSize = 512; // TODO: make it a configuration variable
|
|
atlas->Init(fontAtlasSize, fontAtlasSize);
|
|
|
|
// Add the character to the texture
|
|
slot = atlas->AddEntry(glyphWidth, glyphHeight, GlyphImageData);
|
|
}
|
|
if (slot == nullptr)
|
|
{
|
|
LOG(Error, "Cannot find free space in texture atlases for character '{0}' from font {1} {2}. Size: {3}x{4}", c, String(face->family_name), String(face->style_name), glyphWidth, glyphHeight);
|
|
return true;
|
|
}
|
|
|
|
// Fill with atlas dependant data
|
|
const uint32 padding = Atlases[atlasIndex]->GetPaddingAmount();
|
|
entry.TextureIndex = atlasIndex;
|
|
entry.UV.X = static_cast<float>(slot->X + padding);
|
|
entry.UV.Y = static_cast<float>(slot->Y + padding);
|
|
entry.UVSize.X = static_cast<float>(slot->Width - 2 * padding);
|
|
entry.UVSize.Y = static_cast<float>(slot->Height - 2 * padding);
|
|
entry.Slot = slot;
|
|
entry.Font = font;
|
|
|
|
return false;
|
|
}
|
|
|
|
void FontManager::Invalidate(FontCharacterEntry& entry)
|
|
{
|
|
if (entry.TextureIndex == MAX_uint8)
|
|
return;
|
|
auto atlas = Atlases[entry.TextureIndex];
|
|
const uint32 padding = atlas->GetPaddingAmount();
|
|
const uint32 slotX = static_cast<uint32>(entry.UV.X - padding);
|
|
const uint32 slotY = static_cast<uint32>(entry.UV.Y - padding);
|
|
const uint32 slotSizeX = static_cast<uint32>(entry.UVSize.X + 2 * padding);
|
|
const uint32 slotSizeY = static_cast<uint32>(entry.UVSize.Y + 2 * padding);
|
|
atlas->Invalidate(slotX, slotY, slotSizeX, slotSizeY);
|
|
}
|
|
|
|
void FontManager::Flush()
|
|
{
|
|
for (const auto& atlas : Atlases)
|
|
{
|
|
atlas->Flush();
|
|
}
|
|
}
|
|
|
|
void FontManager::EnsureAtlasCreated(int32 index)
|
|
{
|
|
Atlases[index]->EnsureTextureCreated();
|
|
}
|
|
|
|
bool FontManager::IsDirty()
|
|
{
|
|
for (const auto atlas : Atlases)
|
|
{
|
|
if (atlas->IsDirty())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FontManager::HasDataSyncWithGPU()
|
|
{
|
|
for (const auto atlas : Atlases)
|
|
{
|
|
if (atlas->HasDataSyncWithGPU() == false)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|