#include <eepp/core/retainsymbol.hpp>
#include <eepp/graphics/fontmanager.hpp>
#include <eepp/graphics/fonttruetype.hpp>
#include <eepp/graphics/text.hpp>
#include <eepp/graphics/texturefactory.hpp>
#include <eepp/system/filesystem.hpp>
#include <eepp/system/iostream.hpp>
#include <eepp/system/log.hpp>
#include <eepp/system/pack.hpp>
#include <eepp/system/packmanager.hpp>
#include <eepp/window/engine.hpp>
using namespace EE::Window;

#include <freetype/ftlcdfil.h>
#include <freetype/ftmodapi.h>
#include <freetype/otsvg.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_OUTLINE_H
#include FT_BITMAP_H
#include FT_STROKER_H
#include FT_TRUETYPE_TABLES_H
#include <atomic>
#include <cstdlib>
#include <cstring>

#ifdef EE_TEXT_SHAPER_ENABLED
#include <harfbuzz/hb-ft.h>
#include <harfbuzz/hb.h>
#endif

#include <thirdparty/nanosvg/nanosvg.h>
#include <thirdparty/nanosvg/nanosvgrast.h>

namespace {

RETAIN_SYMBOL( FT_Palette_Select );

using namespace EE;

#ifdef EE_TRUETYPE_SVG_FONT_ENABLED
struct SVG_Data {
	NSVGrasterizer* rasterizer;
};

FT_Error svg_init( FT_Pointer* data_pointer ) {
	SVG_Data* data = new SVG_Data();
	data->rasterizer = nsvgCreateRasterizer();
	if ( !data->rasterizer ) {
		delete data;
		return FT_Err_Out_Of_Memory;
	}
	*data_pointer = data;
	return FT_Err_Ok;
}

void svg_free( FT_Pointer* data_pointer ) {
	SVG_Data* data = static_cast<SVG_Data*>( *data_pointer );
	if ( data ) {
		nsvgDeleteRasterizer( data->rasterizer );
		delete data;
		*data_pointer = nullptr;
	}
}

FT_Error svg_preset( FT_GlyphSlot slot, FT_Bool cache, FT_Pointer* data_pointer ) {
	FT_UNUSED( data_pointer );
	FT_UNUSED( cache );
	FT_SVG_Document document = (FT_SVG_Document)slot->other;
	FT_Pos ascender = document->metrics.ascender;
	FT_Pos descender = document->metrics.descender;

	// The total height of the glyph's design space in pixels is ascender - descender.
	// We convert from 26.6 format to integer pixels using ceiling division.
	unsigned int pixel_height = ( ascender - descender + 63 ) >> 6;
	unsigned int pixel_width = pixel_height;

	if ( pixel_width == 0 || pixel_height == 0 ) {
		slot->bitmap.width = 0;
		slot->bitmap.rows = 0;
		return FT_Err_Ok;
	}

	slot->bitmap.width = pixel_width;
	slot->bitmap.rows = pixel_height;
	slot->bitmap.pitch = pixel_width * 4;
	slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
	slot->bitmap_top = ascender >> 6;
	slot->bitmap_left = 0;
	slot->metrics.width = (FT_Pos)( pixel_width * 64 );
	slot->metrics.height = (FT_Pos)( pixel_height * 64 );
	slot->metrics.horiBearingX = (FT_Pos)( slot->bitmap_left * 64 );
	slot->metrics.horiBearingY = (FT_Pos)( slot->bitmap_top * 64 );

	return FT_Err_Ok;
}

FT_Error svg_render( FT_GlyphSlot slot, FT_Pointer* data_pointer ) {
	SVG_Data* svg_data = static_cast<SVG_Data*>( *data_pointer );
	if ( !svg_data || !svg_data->rasterizer ) {
		return FT_Err_Invalid_Handle;
	}

	FT_SVG_Document document = (FT_SVG_Document)slot->other;

	memset( slot->bitmap.buffer, 0, slot->bitmap.pitch * slot->bitmap.rows );

	// Handle empty glyphs
	if ( document->svg_document_length == 0 ) {
		return FT_Err_Ok;
	}

	std::string svg_string( (const char*)document->svg_document, document->svg_document_length );
	NSVGimage* image = nsvgParse( &svg_string[0], "px", 96.0f, 0xFFFFFFFF );
	if ( !image ) {
		return FT_Err_Invalid_File_Format;
	}

	if ( !image->shapes ) {
		nsvgDelete( image );
		return FT_Err_Ok; // No shapes to render
	}

	float overallBounds[4];
	memcpy( overallBounds, image->shapes->bounds, sizeof( float ) * 4 );

	// Iterate through the rest of the shapes to find the union of all bounding boxes
	for ( NSVGshape* shape = image->shapes->next; shape != nullptr; shape = shape->next ) {
		overallBounds[0] = std::min( overallBounds[0], shape->bounds[0] ); // min-x
		overallBounds[1] = std::min( overallBounds[1], shape->bounds[1] ); // min-y
		overallBounds[2] = std::max( overallBounds[2], shape->bounds[2] ); // max-x
		overallBounds[3] = std::max( overallBounds[3], shape->bounds[3] ); // max-y
	}

	float svgMinX = overallBounds[0];
	float svgMinY = overallBounds[1];
	float svgWidth = overallBounds[2] - svgMinX;
	float svgHeight = overallBounds[3] - svgMinY;

	if ( svgWidth == 0 || svgHeight == 0 ) {
		nsvgDelete( image );
		return FT_Err_Ok; // Nothing to render
	}

	// Calculate the scale to fit the target bitmap size, preserving aspect ratio
	float scaleX = (float)slot->bitmap.width / svgWidth;
	float scaleY = (float)slot->bitmap.rows / svgHeight;
	float scale = std::min( scaleX, scaleY );

	// Calculate the translation needed to align the SVG's content with the bitmap's origin (0,0)
	float tx = -svgMinX * scale;
	float ty = -svgMinY * scale;

	// Center the glyph within the bitmap slot
	float centered_tx = tx + ( (float)slot->bitmap.width - svgWidth * scale ) * 0.5f;
	float centered_ty = ty + ( (float)slot->bitmap.rows - svgHeight * scale ) * 0.5f;

	// Rasterize directly into a temporary RGBA buffer.
	std::vector<unsigned char> rgba_buffer( slot->bitmap.width * slot->bitmap.rows * 4 );
	nsvgRasterize( svg_data->rasterizer, image, centered_tx, centered_ty, scale, rgba_buffer.data(),
				   slot->bitmap.width, slot->bitmap.rows, slot->bitmap.width * 4 );
	nsvgDelete( image );

	// Swizzle from RGBA (nanosvg) to BGRA (FreeType bitmap).
	// Your original code performed RGBA -> RGBA copy.
	// FT_PIXEL_MODE_BGRA requires Blue in the first byte.
	for ( unsigned int y = 0; y < slot->bitmap.rows; ++y ) {
		for ( unsigned int x = 0; x < slot->bitmap.width; ++x ) {
			unsigned char* src = &rgba_buffer[( y * slot->bitmap.width + x ) * 4];
			unsigned char* dst = &slot->bitmap.buffer[( y * slot->bitmap.pitch ) + x * 4];
			dst[0] = src[2]; // Blue
			dst[1] = src[1]; // Green
			dst[2] = src[0]; // Red
			dst[3] = src[3]; // Alpha
		}
	}

	slot->format = FT_GLYPH_FORMAT_BITMAP;

	return FT_Err_Ok;
}

static SVG_RendererHooks svg_hooks = { svg_init, svg_free, svg_render, svg_preset };
#endif

// FreeType callbacks that operate on a IOStream
unsigned long read( FT_Stream rec, unsigned long offset, unsigned char* buffer,
					unsigned long count ) {
	IOStream* stream = static_cast<IOStream*>( rec->descriptor.pointer );
	if ( static_cast<unsigned long>( stream->seek( offset ) ) == offset ) {
		if ( count > 0 )
			return static_cast<unsigned long>(
				stream->read( reinterpret_cast<char*>( buffer ), count ) );
		else
			return 0;
	} else
		return count > 0 ? 0 : 1; // error code is 0 if we're reading, or nonzero if we're seeking
}

void close( FT_Stream ) {}

// Helper to interpret memory as a specific type
template <typename T, typename U> inline T reinterpret( const U& input ) {
	T output;
	std::memcpy( &output, &input, sizeof( U ) );
	return output;
}

} // namespace

namespace EE { namespace Graphics {
using std::move;

static std::unordered_map<std::string, Uint32> fontsInternalIds;
static std::atomic<Uint32> fontInternalIdCounter{ 0 };

// Combine outline thickness, boldness, italics and font glyph index into a single 64-bit key
static inline Uint64 getIndexKey( Uint32 fontInternalId, Uint32 index, bool bold, bool italics,
								  Float outlineThickness ) {
	return ( static_cast<EE::Uint64>( reinterpret<EE::Uint32>( fontInternalId ) ) << 48 ) |
		   ( static_cast<EE::Uint64>(
				 reinterpret<EE::Uint32>( static_cast<Uint32>( outlineThickness ) * 100 ) )
			 << 34 ) |
		   ( static_cast<EE::Uint64>( bold ) << 33 ) |
		   ( static_cast<EE::Uint64>( italics ) << 32 ) | index;
}

static inline Uint64 getCodePointKey( Uint32 codePoint, bool bold, bool italics,
									  Float outlineThickness ) {
	return ( static_cast<EE::Uint64>(
				 reinterpret<EE::Uint32>( static_cast<Uint32>( outlineThickness ) * 100 ) )
			 << 34 ) |
		   ( static_cast<EE::Uint64>( bold ) << 33 ) |
		   ( static_cast<EE::Uint64>( italics ) << 32 ) | codePoint;
}

// Combine kerning parameters into a single 64-bit key for O(1) lookups.
// - first/index1 : 21 bits (covers full Unicode range up to 0x10FFFF)
// - second/index2: 21 bits
// - characterSize: 12 bits (max font size 4095)
// - bold         : 1 bit
// - italic       : 1 bit
// - outline      : 8 bits (max outline thickness 2.55 scaled by 100)
static inline Uint64 getKerningKey( Uint32 first, Uint32 second, unsigned int characterSize,
									bool bold, bool italic, Float outlineThickness ) {
	return ( static_cast<Uint64>( first & 0x1FFFFF ) << 43 ) |
		   ( static_cast<Uint64>( second & 0x1FFFFF ) << 22 ) |
		   ( static_cast<Uint64>( characterSize & 0xFFF ) << 10 ) |
		   ( static_cast<Uint64>( bold ) << 9 ) | ( static_cast<Uint64>( italic ) << 8 ) |
		   ( static_cast<Uint64>( static_cast<Uint32>( outlineThickness * 100.f ) & 0xFF ) );
}

FontTrueType* FontTrueType::New( const std::string& FontName ) {
	return eeNew( FontTrueType, ( FontName ) );
}

FontTrueType* FontTrueType::New( const std::string& FontName, const std::string& filename ) {
	FontTrueType* fontTrueType = New( FontName );
	fontTrueType->loadFromFile( filename );
	return fontTrueType;
}

FontTrueType::FontTrueType( const std::string& FontName ) :
	Font( FontType::TTF, FontName ),
	mLibrary( NULL ),
	mFace( NULL ),
	mStreamRec( NULL ),
	mStroker( NULL ),
	mHBFont( NULL ),
	mFontInternalId( 0 ),
	mBoldAdvanceSameAsRegular( false ),
	mIsColorEmojiFont( false ),
	mIsEmojiFont( false ),
	mHasSvgGlyphs( false ),
	mHasColrGlyphs( false ),
	mIsBitmapOnly( false ),
	mIsMonospace( false ),
	mIsMonospaceComplete( false ),
	mUsingFallback( false ),
	mEnableEmojiFallback( true ),
	mEnableFallbackFont( true ),
	mEnableDynamicMonospace( false ),
	mIsBold( false ),
	mIsItalic( false ),
	mIsMonospaceCompletePending( false ),
	mHinting( FontManager::instance()->getHinting() ),
	mAntialiasing( FontManager::instance()->getAntialiasing() ),
	mFontBold( nullptr ),
	mFontItalic( nullptr ),
	mFontBoldItalic( nullptr ),
	mFontBoldCb( 0 ),
	mFontItalicCb( 0 ),
	mFontBoldItalicCb( 0 ) {}

FontTrueType::~FontTrueType() {
	cleanup();
}

static bool checkHasSvgTable( const FT_Face& face ) {
	static const uint32_t tag = FT_MAKE_TAG( 'S', 'V', 'G', ' ' ); // The tag for the SVG table
	unsigned long length = 0;
	FT_Load_Sfnt_Table( face, tag, 0, nullptr, &length );
	return length > 0;
}

static bool checkIsColorEmojiFont( const FT_Face& face ) {
	static const uint32_t tag = FT_MAKE_TAG( 'C', 'B', 'D', 'T' );
	unsigned long length = 0;
	FT_Load_Sfnt_Table( face, tag, 0, nullptr, &length );
	return length > 0;
}

static bool checkHasColrTable( const FT_Face& face ) {
	static const uint32_t tag = FT_MAKE_TAG( 'C', 'O', 'L', 'R' );
	unsigned long length = 0;
	FT_Load_Sfnt_Table( face, tag, 0, nullptr, &length );
	return length > 0;
}

bool FontTrueType::loadFromFile( const std::string& filename ) {
	if ( !FileSystem::fileExists( filename ) &&
		 PackManager::instance()->isFallbackToPacksActive() ) {
		std::string path( filename );
		Pack* pack = PackManager::instance()->exists( path );

		if ( NULL != pack ) {
			Log::info( "Loading font from pack: %s", path.c_str() );

			return loadFromPack( pack, path );
		}

		return false;
	}

	// Cleanup the previous resources
	cleanup();

	// Initialize FreeType
	FT_Library library;
	if ( FT_Init_FreeType( &library ) != 0 ) {
		Log::error( "Failed to load font \"%s\" (%s) (failed to initialize FreeType)",
					filename.c_str(), mFontName.c_str() );
		return false;
	}
	mLibrary = library;

	// Load the new font face from the specified file
	FT_Face face;
	if ( FT_New_Face( static_cast<FT_Library>( mLibrary ), filename.c_str(), 0, &face ) != 0 ) {
		Log::error( "Failed to load font \"%s\" (%s) (failed to create the font face)",
					filename.c_str(), mFontName.c_str() );
		return false;
	}

	mInfo.fontpath = FileSystem::fileRemoveFileName( filename );
	mInfo.filename = FileSystem::fileNameFromPath( filename );

	return setFontFace( face );
}

bool FontTrueType::loadFromMemory( const void* data, std::size_t sizeInBytes, bool copyData ) {
	const void* ptr = data;

	if ( copyData ) {
		mMemCopy.reset( reinterpret_cast<const Uint8*>( data ), sizeInBytes );

		ptr = mMemCopy.get();
	}

	// Cleanup the previous resources
	cleanup();

	// Initialize FreeType
	FT_Library library;
	if ( FT_Init_FreeType( &library ) != 0 ) {
		Log::error( "Failed to load font from memory (failed to initialize FreeType)" );
		return false;
	}
	mLibrary = library;

	// Load the new font face from the specified file
	FT_Face face;
	if ( FT_New_Memory_Face( static_cast<FT_Library>( mLibrary ),
							 reinterpret_cast<const FT_Byte*>( ptr ),
							 static_cast<FT_Long>( sizeInBytes ), 0, &face ) != 0 ) {
		Log::error( "Failed to load font from memory (failed to create the font face)" );
		return false;
	}

	return setFontFace( face );
}

bool FontTrueType::loadFromStream( IOStream& stream ) {
	// Cleanup the previous resources
	cleanup();

	// Initialize FreeType
	FT_Library library;
	if ( FT_Init_FreeType( &library ) != 0 ) {
		Log::error( "Failed to load font from stream (failed to initialize FreeType)" );
		return false;
	}
	mLibrary = library;

	// Make sure that the stream's reading position is at the beginning
	stream.seek( 0 );

	// Prepare a wrapper for our stream, that we'll pass to FreeType callbacks
	FT_StreamRec* rec = new FT_StreamRec;
	std::memset( rec, 0, sizeof( *rec ) );
	rec->base = NULL;
	rec->size = static_cast<unsigned long>( stream.getSize() );
	rec->pos = 0;
	rec->descriptor.pointer = &stream;
	rec->read = &read;
	rec->close = &close;

	// Setup the FreeType callbacks that will read our stream
	FT_Open_Args args;
	args.flags = FT_OPEN_STREAM;
	args.stream = rec;
	args.driver = 0;

	// Load the new font face from the specified stream
	FT_Face face;
	if ( FT_Open_Face( static_cast<FT_Library>( mLibrary ), &args, 0, &face ) != 0 ) {
		Log::error( "Failed to load font from stream (failed to create the font face)" );
		delete rec;
		return false;
	}

	bool res = setFontFace( face );
	if ( res )
		mStreamRec = rec;

	return res;
}

bool FontTrueType::loadFromPack( Pack* pack, std::string filePackPath ) {
	if ( NULL == pack )
		return false;

	bool ret = false;

	mMemCopy.clear();

	if ( pack->isOpen() && pack->extractFileToMemory( filePackPath, mMemCopy ) )
		ret = loadFromMemory( mMemCopy.get(), mMemCopy.length(), false );

	mInfo.fontpath = FileSystem::fileRemoveFileName( filePackPath );
	mInfo.filename = FileSystem::fileNameFromPath( filePackPath );

	return ret;
}

bool FontTrueType::setFontFace( void* _face ) {
	FT_Face face = (FT_Face)_face;
	mFace = face;
#ifdef EE_TEXT_SHAPER_ENABLED
	mHBFont = hb_ft_font_create( static_cast<FT_Face>( face ), NULL );
#endif
	mIsMonospaceComplete = mIsMonospace = FT_IS_FIXED_WIDTH( face );
	mIsColorEmojiFont = checkIsColorEmojiFont( face ); // For CBDT (COLRv0)
	mHasSvgGlyphs = checkHasSvgTable( face );		   // For SVG table
	mHasColrGlyphs = checkHasColrTable( face );		   // For COLR table (COLRv1)
	mIsBitmapOnly = !FT_IS_SCALABLE( face ) && ( face->num_fixed_sizes > 0 );
	mIsEmojiFont = FT_Get_Char_Index( face, 0x1F600 ) != 0;
	mIsBold = face->style_flags & FT_STYLE_FLAG_BOLD;
	mIsItalic = face->style_flags & FT_STYLE_FLAG_ITALIC;

	if ( mHasSvgGlyphs ) {
#ifdef EE_TRUETYPE_SVG_FONT_ENABLED
		FT_Property_Set( static_cast<FT_Library>( mLibrary ), "ot-svg", "svg-hooks", &svg_hooks );
#else
		return false;
#endif
	}

	if ( ( mIsColorEmojiFont || mHasSvgGlyphs || mHasColrGlyphs ) &&
		 FontManager::instance()->getColorEmojiFont() == nullptr )
		FontManager::instance()->setColorEmojiFont( this );

	if ( mIsEmojiFont && FontManager::instance()->getEmojiFont() == nullptr )
		FontManager::instance()->setEmojiFont( this );

	// Load the stroker that will be used to outline the font
	FT_Stroker stroker = nullptr;
	if ( !mIsColorEmojiFont ) {
		if ( FT_Stroker_New( static_cast<FT_Library>( mLibrary ), &stroker ) != 0 ) {
			Log::error( "Failed to load font from memory (failed to create the stroker)" );
			FT_Done_Face( face );
			return false;
		}
	}

	// Select the Unicode character map
	if ( FT_Select_Charmap( face, FT_ENCODING_UNICODE ) != 0 ) {
		Log::error( "Failed to load font from memory (failed to set the Unicode character set)" );
		if ( stroker )
			FT_Stroker_Done( stroker );
		FT_Done_Face( face );
		return false;
	}

	// Store the loaded font in our ugly void* :)
	mStroker = stroker;

	// Store the font information
	mInfo.family = face->family_name ? face->family_name : std::string();

	updateFontInternalId();

	sendEvent( Event::Load );

	return true;
}

const FontTrueType::Info& FontTrueType::getInfo() const {
	return mInfo;
}

void FontTrueType::updateFontInternalId() {
	auto fontInternalId = fontsInternalIds.find( mInfo.family );
	if ( fontsInternalIds.end() == fontInternalId ) {
		mFontInternalId = ++fontInternalIdCounter;
		fontsInternalIds[mInfo.family] = mFontInternalId;
	} else {
		mFontInternalId = fontInternalId->second;
	}
}

bool FontTrueType::hasGlyph( Uint32 codePoint ) const {
	return getGlyphIndex( codePoint ) != 0;
}

Uint32 FontTrueType::getGlyphIndex( const Uint32& codePoint ) const {
	Uint32 index;
	auto indexIter = mCodePointIndexCache.find( codePoint );
	if ( mCodePointIndexCache.end() != indexIter ) {
		index = indexIter->second;
	} else {
		index = FT_Get_Char_Index( static_cast<FT_Face>( mFace ), codePoint );
		mCodePointIndexCache[codePoint] = index;
	}
	return index;
}

Glyph FontTrueType::getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic,
							  Float outlineThickness ) const {
	Uint32 idx = 0;
	if ( mEnableEmojiFallback && !mIsColorEmojiFont && !mHasSvgGlyphs && !mHasColrGlyphs &&
		 !mIsEmojiFont && Font::isEmojiCodePoint( codePoint ) ) {
		if ( !mIsColorEmojiFont && FontManager::instance()->getColorEmojiFont() != nullptr &&
			 FontManager::instance()->getColorEmojiFont()->getType() == FontType::TTF ) {

			FontTrueType* fontEmoji =
				static_cast<FontTrueType*>( FontManager::instance()->getColorEmojiFont() );
			if ( ( idx = fontEmoji->getGlyphIndex( codePoint ) ) ) {
				if ( mIsMonospace && mEnableDynamicMonospace ) {
					mIsMonospaceComplete = false;
					mUsingFallback = true;
				}
				return fontEmoji->getGlyphByIndex( idx, characterSize, bold, italic,
												   outlineThickness, getPage( characterSize ) );
			}
		} else if ( !mIsEmojiFont && FontManager::instance()->getEmojiFont() != nullptr &&
					FontManager::instance()->getEmojiFont()->getType() == FontType::TTF ) {

			FontTrueType* fontEmoji =
				static_cast<FontTrueType*>( FontManager::instance()->getEmojiFont() );
			if ( ( idx = fontEmoji->getGlyphIndex( codePoint ) ) ) {
				if ( mIsMonospace && mEnableDynamicMonospace ) {
					mIsMonospaceComplete = false;
					mUsingFallback = true;
				}
				return fontEmoji->getGlyphByIndex( idx, characterSize, bold, italic,
												   outlineThickness, getPage( characterSize ) );
			}
		}
	}

	if ( bold && italic && mFontBoldItalic != nullptr &&
		 ( idx = mFontBoldItalic->getGlyphIndex( codePoint ) ) ) {
		return mFontBoldItalic->getGlyphByIndex( idx, characterSize, true, true, outlineThickness,
												 getPage( characterSize ) );
	}

	if ( bold && !italic && mFontBold != nullptr &&
		 ( idx = mFontBold->getGlyphIndex( codePoint ) ) ) {
		return mFontBold->getGlyphByIndex( idx, characterSize, true, false, outlineThickness,
										   getPage( characterSize ) );
	}

	if ( italic && !bold && mFontItalic != nullptr &&
		 ( idx = mFontItalic->getGlyphIndex( codePoint ) ) ) {
		return mFontItalic->getGlyphByIndex( idx, characterSize, false, true, outlineThickness,
											 getPage( characterSize ) );
	}

	idx = getGlyphIndex( codePoint );
	if ( 0 == idx && mEnableFallbackFont && FontManager::instance()->hasFallbackFonts() ) {
		for ( Font* fallbackFontPtr : FontManager::instance()->getFallbackFonts() ) {
			if ( fallbackFontPtr->getType() != FontType::TTF )
				continue;
			FontTrueType* fallbackFont = static_cast<FontTrueType*>( fallbackFontPtr );
			if ( ( idx = fallbackFont->getGlyphIndex( codePoint ) ) ) {
				if ( mIsMonospace && mEnableDynamicMonospace ) {
					mIsMonospaceComplete = false;
					mUsingFallback = true;
				}
				return fallbackFont->getGlyphByIndex( idx, characterSize, bold, italic,
													  outlineThickness, getPage( characterSize ) );
			}
		}
	}

	return getGlyphByIndex( idx, characterSize, bold, italic, outlineThickness );
}

Glyph FontTrueType::getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic,
							  Float outlineThickness, Page& page ) const {
	Uint32 index = getGlyphIndex( codePoint );
	return getGlyphByIndex( index, characterSize, bold, italic, outlineThickness, page );
}

Glyph FontTrueType::getGlyphByIndex( Uint32 index, unsigned int characterSize, bool bold,
									 bool italic, Float outlineThickness, Page& page ) const {
	eeASSERT( Engine::isMainThread() );

	// Get the page corresponding to the character size
	GlyphTable& glyphs = page.glyphs;

	// Build the key by combining the code point, bold flag, and outline thickness
	Uint64 key = getIndexKey( mFontInternalId, index, bold, italic, outlineThickness );

	// Search the glyph into the cache
	GlyphTable::const_iterator it = glyphs.find( key );
	if ( it != glyphs.end() ) {
		// Found: just return it
		return it->second;
	} else {
		// Not found: we have to load it
		Glyph glyph =
			loadGlyphByIndex( index, characterSize, bold, italic, outlineThickness, page );

		return glyphs.emplace( key, glyph ).first->second;
	}
}

Glyph FontTrueType::getGlyphByIndex( Uint32 index, unsigned int characterSize, bool bold,
									 bool italic, Float outlineThickness ) const {
	return getGlyphByIndex( index, characterSize, bold, italic, outlineThickness,
							getPage( characterSize ) );
}

GlyphDrawable* FontTrueType::getGlyphDrawable( Uint32 codePoint, unsigned int characterSize,
											   bool bold, bool italic,
											   Float outlineThickness ) const {
	Page& page = getPage( characterSize );
	GlyphDrawableTable& drawables = page.drawables;

	Uint32 glyphIndex = 0;
	Uint32 fontInternalId = mFontInternalId;

	Uint64 codePointKey = getCodePointKey( codePoint, bold, italic, outlineThickness );
	auto cache = mKeyCache.find( codePointKey );
	bool isItalic = false;

	if ( cache != mKeyCache.end() ) {
		fontInternalId = std::get<0>( cache->second );
		glyphIndex = std::get<1>( cache->second );
		isItalic = std::get<2>( cache->second );
	} else {
		Uint32 tGlyphIndex = 0;

		if ( mEnableEmojiFallback && Font::isEmojiCodePoint( codePoint ) && !mIsColorEmojiFont &&
			 !mIsEmojiFont ) {
			if ( !mIsColorEmojiFont && FontManager::instance()->getColorEmojiFont() != nullptr &&
				 FontManager::instance()->getColorEmojiFont()->getType() == FontType::TTF ) {
				FontTrueType* fontEmoji =
					static_cast<FontTrueType*>( FontManager::instance()->getColorEmojiFont() );
				tGlyphIndex = fontEmoji->getGlyphIndex( codePoint );
				if ( 0 != tGlyphIndex ) {
					glyphIndex = tGlyphIndex;
					fontInternalId = fontEmoji->getFontInternalId();
				} else {
					glyphIndex = getGlyphIndex( codePoint );
				}
			} else if ( !mIsEmojiFont && FontManager::instance()->getEmojiFont() != nullptr &&
						FontManager::instance()->getEmojiFont()->getType() == FontType::TTF ) {
				FontTrueType* fontEmoji =
					static_cast<FontTrueType*>( FontManager::instance()->getEmojiFont() );
				tGlyphIndex = fontEmoji->getGlyphIndex( codePoint );
				if ( 0 != tGlyphIndex ) {
					glyphIndex = tGlyphIndex;
					fontInternalId = fontEmoji->getFontInternalId();
				} else {
					glyphIndex = getGlyphIndex( codePoint );
				}
			} else {
				glyphIndex = getGlyphIndex( codePoint );
			}
		} else {
			glyphIndex = getGlyphIndex( codePoint );
		}

		if ( bold && italic && mFontBoldItalic != nullptr &&
			 ( tGlyphIndex = mFontBoldItalic->getGlyphIndex( codePoint ) ) ) {
			glyphIndex = tGlyphIndex;
			fontInternalId = mFontBoldItalic->getFontInternalId();
			isItalic = true;
		}

		if ( bold && !italic && mFontBold != nullptr &&
			 ( tGlyphIndex = mFontBold->getGlyphIndex( codePoint ) ) ) {
			glyphIndex = tGlyphIndex;
			fontInternalId = mFontBold->getFontInternalId();
		}

		if ( italic && !bold && mFontItalic != nullptr &&
			 ( tGlyphIndex = mFontItalic->getGlyphIndex( codePoint ) ) ) {
			glyphIndex = tGlyphIndex;
			fontInternalId = mFontItalic->getFontInternalId();
			isItalic = true;
		}

		if ( 0 == glyphIndex && mEnableFallbackFont &&
			 FontManager::instance()->hasFallbackFonts() ) {
			for ( Font* fontFallbackPtr : FontManager::instance()->getFallbackFonts() ) {
				if ( fontFallbackPtr->getType() != FontType::TTF )
					continue;
				FontTrueType* fontFallback = static_cast<FontTrueType*>( fontFallbackPtr );
				tGlyphIndex = fontFallback->getGlyphIndex( codePoint );
				if ( 0 != tGlyphIndex ) {
					glyphIndex = tGlyphIndex;
					fontInternalId = fontFallback->getFontInternalId();
					if ( mIsMonospace && mEnableDynamicMonospace ) {
						mIsMonospaceComplete = false;
						mUsingFallback = true;
					}
					break;
				}
			}
			if ( 0 == glyphIndex )
				glyphIndex = getGlyphIndex( codePoint );
		}

		mKeyCache[codePointKey] = { fontInternalId, glyphIndex, isItalic };
	}

	Uint64 key = getIndexKey( fontInternalId, glyphIndex, bold, italic, outlineThickness );

	auto it = drawables.find( key );
	if ( it != drawables.end() ) {
		return it->second;
	} else {
		auto glyph = getGlyph( codePoint, characterSize, bold, italic, outlineThickness );
		GlyphDrawable* region = GlyphDrawable::New(
			page.texture, glyph.textureRect, glyph.size,
			String::format( "%s_%d_%u", mFontName.c_str(), characterSize, glyphIndex ) );

		region->setGlyphOffset(
			{ glyph.bounds.Left - outlineThickness,
			  getGlyphTopOffset( characterSize ) + glyph.bounds.Top - outlineThickness } );
		region->setAdvance( glyph.advance );
		region->setIsItalic( isItalic );

		drawables[key] = region;
		return region;
	}
	return nullptr;
}

GlyphDrawable* FontTrueType::getGlyphDrawableFromGlyphIndex( Uint32 glyphIndex,
															 unsigned int characterSize, bool bold,
															 bool italic, Float outlineThickness,
															 Page& page ) const {
	GlyphDrawableTable& drawables = page.drawables;
	Uint64 key = getIndexKey( mFontInternalId, glyphIndex, bold, italic, outlineThickness );

	auto it = drawables.find( key );
	if ( it != drawables.end() ) {
		return it->second;
	} else {
		auto glyph =
			getGlyphByIndex( glyphIndex, characterSize, bold, italic, outlineThickness, page );
		GlyphDrawable* region = GlyphDrawable::New(
			page.texture, glyph.textureRect, glyph.size,
			String::format( "%s_%d_%u", mFontName.c_str(), characterSize, glyphIndex ) );

		region->setGlyphOffset(
			{ glyph.bounds.Left - outlineThickness,
			  getGlyphTopOffset( characterSize ) + glyph.bounds.Top - outlineThickness } );
		region->setAdvance( glyph.advance );
		region->setIsItalic( italic );

		drawables[key] = region;
		return region;
	}
	return nullptr;
}

GlyphDrawable* FontTrueType::getGlyphDrawableFromGlyphIndex( Uint32 glyphIndex,
															 unsigned int characterSize, bool bold,
															 bool italic,
															 Float outlineThickness ) const {
	return getGlyphDrawableFromGlyphIndex( glyphIndex, characterSize, bold, italic,
										   outlineThickness, getPage( characterSize ) );
}

Float FontTrueType::getKerning( Uint32 first, Uint32 second, unsigned int characterSize, bool bold,
								bool italic, Float outlineThickness ) const {
	// Special case where first or second is 0 (null character)
	if ( first == 0 || second == 0 || isMonospace() )
		return 0.f;

	Uint64 key = getKerningKey( first, second, characterSize, bold, italic, outlineThickness );
	auto it = mKerningCache.find( key );
	if ( it != mKerningCache.end() ) {
		return it->second;
	}

	Float kerningVal = 0.f;
	FT_Face face = static_cast<FT_Face>( mFace );

	if ( face && setCurrentSize( characterSize ) ) {
		auto glyph1 = getGlyph( first, characterSize, bold, italic, outlineThickness );
		auto glyph2 = getGlyph( second, characterSize, bold, italic, outlineThickness );

		if ( glyph1.font == glyph2.font ) {
			// Convert the characters to indices
			FT_UInt index1 = getGlyphIndex( first );
			FT_UInt index2 = getGlyphIndex( second );

			// Get the kerning vector
			FT_Vector kerning;
			kerning.x = kerning.y = 0;
			if ( FT_HAS_KERNING( face ) )
				FT_Get_Kerning( face, index1, index2, FT_KERNING_UNFITTED, &kerning );

			// X advance is already in pixels for bitmap fonts
			if ( !FT_IS_SCALABLE( face ) ) {
				kerningVal = static_cast<Float>( kerning.x );
			} else {
				auto firstRsbDelta = static_cast<Float>( glyph1.rsbDelta );
				auto secondLsbDelta = static_cast<Float>( glyph2.lsbDelta );
				kerningVal = std::floor(
					( secondLsbDelta - firstRsbDelta + static_cast<float>( kerning.x ) + 32 ) /
					static_cast<float>( 1 << 6 ) );
			}
		}
	}

	mKerningCache[key] = kerningVal;
	return kerningVal;
}

Float FontTrueType::getKerningFromGlyphIndex( Uint32 index1, Uint32 index2,
											  unsigned int characterSize, bool bold, bool italic,
											  Float outlineThickness ) const {
	// Special case where first or second is 0 (null character)
	if ( index1 == 0 || index2 == 0 || isMonospace() )
		return 0.f;

	Uint64 key = getKerningKey( index1, index2, characterSize, bold, italic, outlineThickness );
	auto it = mKerningGlyphCache.find( key );
	if ( it != mKerningGlyphCache.end() ) {
		return it->second;
	}

	Float kerningVal = 0.f;
	FT_Face face = static_cast<FT_Face>( mFace );

	if ( face && setCurrentSize( characterSize ) ) {
		// Retrieve position compensation deltas generated by FT_LOAD_FORCE_AUTOHINT flag
		auto firstRsbDelta = static_cast<float>(
			getGlyphByIndex( index1, characterSize, bold, italic, outlineThickness ).rsbDelta );
		auto secondLsbDelta = static_cast<float>(
			getGlyphByIndex( index2, characterSize, bold, italic, outlineThickness ).lsbDelta );

		// Get the kerning vector
		FT_Vector kerning;
		kerning.x = kerning.y = 0;
		if ( FT_HAS_KERNING( face ) )
			FT_Get_Kerning( face, index1, index2, FT_KERNING_UNFITTED, &kerning );

		// X advance is already in pixels for bitmap fonts
		if ( !FT_IS_SCALABLE( face ) ) {
			kerningVal = static_cast<Float>( kerning.x );
		} else {
			// Get the X advance
			kerningVal = std::floor(
				( secondLsbDelta - firstRsbDelta + static_cast<float>( kerning.x ) + 32 ) /
				static_cast<float>( 1 << 6 ) );
		}
	}

	mKerningGlyphCache[key] = kerningVal;
	return kerningVal;
}

Float FontTrueType::getLineSpacing( unsigned int characterSize ) const {
	FT_Face face = static_cast<FT_Face>( mFace );

	if ( face && setCurrentSize( characterSize ) ) {
		return static_cast<Float>( face->size->metrics.height ) / static_cast<Float>( 1 << 6 );
	} else {
		return 0.f;
	}
}

Float FontTrueType::getGlyphTopOffset( unsigned int characterSize ) const {
	FT_Face face = static_cast<FT_Face>( mFace );
	return FT_IS_SCALABLE( face ) || mIsColorEmojiFont ? characterSize : getAscent( characterSize );
}

Float FontTrueType::getAscent( unsigned int characterSize ) const {
	FT_Face face = static_cast<FT_Face>( mFace );

	if ( face && setCurrentSize( characterSize ) ) {
		if ( !FT_IS_SCALABLE( face ) )
			return static_cast<Float>( face->size->metrics.ascender ) /
				   static_cast<Float>( 1 << 6 );

		return static_cast<Float>( FT_MulFix( face->ascender, face->size->metrics.y_scale ) ) /
			   static_cast<Float>( 1 << 6 );
	} else {
		return 0.f;
	}
}

Float FontTrueType::getDescent( unsigned int characterSize ) const {
	FT_Face face = static_cast<FT_Face>( mFace );

	if ( face && setCurrentSize( characterSize ) ) {
		if ( !FT_IS_SCALABLE( face ) )
			return static_cast<Float>( face->size->metrics.descender ) /
				   static_cast<Float>( 1 << 6 );

		return static_cast<Float>( FT_MulFix( -face->descender, face->size->metrics.y_scale ) ) /
			   static_cast<Float>( 1 << 6 );
	} else {
		return 0.f;
	}
}

#define FT_FLOOR( X ) ( ( X & -64 ) / 64 )
#define FT_CEIL( X ) ( ( ( X + 63 ) & -64 ) / 64 )

Uint32 FontTrueType::getFontHeight( const Uint32& characterSize ) const {
	FT_Face face = static_cast<FT_Face>( mFace );

	if ( face && setCurrentSize( characterSize ) ) {
		if ( FT_IS_SCALABLE( face ) ) {
			FT_Fixed scale = face->size->metrics.y_scale;

			int ascent = ( FT_CEIL( FT_MulFix( face->ascender, scale ) ) );
			int descent = ( FT_CEIL( FT_MulFix( face->descender, scale ) ) );
			int height = ( ascent - descent + 1 );
			return height;
		} else {
			return static_cast<Float>( (Float)face->size->metrics.height ) /
				   static_cast<Float>( 1 << 6 );
		}
	} else {
		return 0.f;
	}
}

Float FontTrueType::getUnderlinePosition( unsigned int characterSize ) const {
	FT_Face face = static_cast<FT_Face>( mFace );

	if ( face && setCurrentSize( characterSize ) ) {
		// Return a fixed position if font is a bitmap font
		if ( !FT_IS_SCALABLE( face ) )
			return -1; // getGlyphTopOffset( characterSize ) / 10.f;

		return -static_cast<Float>(
				   FT_MulFix( face->underline_position, face->size->metrics.y_scale ) ) /
			   static_cast<Float>( 1 << 6 );
	} else {
		return 0.f;
	}
}

Float FontTrueType::getUnderlineThickness( unsigned int characterSize ) const {
	FT_Face face = static_cast<FT_Face>( mFace );

	if ( face && setCurrentSize( characterSize ) ) {
		// Return a fixed thickness if font is a bitmap font
		if ( !FT_IS_SCALABLE( face ) )
			return getGlyphTopOffset( characterSize ) / 14.f;

		return static_cast<Float>(
				   FT_MulFix( face->underline_thickness, face->size->metrics.y_scale ) ) /
			   static_cast<Float>( 1 << 6 );
	} else {
		return 0.f;
	}
}

Texture* FontTrueType::getTexture( unsigned int characterSize ) const {
	return getPage( characterSize ).texture;
}

bool FontTrueType::loaded() const {
	return NULL != mFace;
}

void FontTrueType::cleanup() {
	sendEvent( Event::Unload );

	if ( FontManager::existsSingleton() && FontManager::instance()->getColorEmojiFont() == this )
		FontManager::instance()->setColorEmojiFont( nullptr );

	if ( mFontBoldItalicCb != 0 && mFontBoldItalic != nullptr ) {
		mFontBoldItalic->popFontEventCallback( mFontBoldItalicCb );
		mFontBoldItalicCb = 0;
	}

	if ( mFontBoldCb != 0 && mFontBold != nullptr ) {
		mFontBold->popFontEventCallback( mFontBoldCb );
		mFontBoldCb = 0;
	}

	if ( mFontItalicCb != 0 && mFontItalic != nullptr ) {
		mFontItalic->popFontEventCallback( mFontItalicCb );
		mFontItalicCb = 0;
	}

	mCallbacks.clear();
	mNumCallBacks = 0;

#ifdef EE_TEXT_SHAPER_ENABLED
	if ( mHBFont )
		hb_font_destroy( (hb_font_t*)mHBFont );
#endif

	// Destroy the stroker
	if ( mStroker )
		FT_Stroker_Done( static_cast<FT_Stroker>( mStroker ) );

	// Destroy the font face
	if ( mFace )
		FT_Done_Face( static_cast<FT_Face>( mFace ) );

	// Destroy the stream rec instance, if any (must be done after FT_Done_Face!)
	if ( mStreamRec )
		delete static_cast<FT_StreamRec*>( mStreamRec );

	// Close the library
	if ( mLibrary )
		FT_Done_FreeType( static_cast<FT_Library>( mLibrary ) );

	// Reset members
	mLibrary = NULL;
	mFace = NULL;
	mStroker = NULL;
	mHBFont = NULL;
	mStreamRec = NULL;
	mInfo = Info();
	mFontInternalId = 0;
	mBoldAdvanceSameAsRegular = false;
	mIsColorEmojiFont = false;
	mIsEmojiFont = false;
	mHasSvgGlyphs = false;
	mHasColrGlyphs = false;
	mIsBitmapOnly = false;
	mIsMonospace = false;
	mIsMonospaceComplete = false;
	mUsingFallback = false;
	mEnableEmojiFallback = true;
	mEnableFallbackFont = true;
	mEnableDynamicMonospace = false;
	mIsBold = false;
	mIsItalic = false;
	mIsMonospaceCompletePending = false;
	mFontBold = nullptr;
	mFontItalic = nullptr;
	mFontBoldItalic = nullptr;
	mFontBoldCb = 0;
	mFontItalicCb = 0;
	mFontBoldItalicCb = 0;
	mPages.clear();
	std::vector<Uint8>().swap( mPixelBuffer );
	mKerningCache.clear();
	mKerningGlyphCache.clear();
	mCodePointIndexCache.clear();
	mKeyCache.clear();
	mClosestCharacterSize.clear();
}

static int fontSetLoadOptions( FontAntialiasing antialiasing, FontHinting hinting ) {
	int load_target =
		antialiasing == FontAntialiasing::None
			? FT_LOAD_TARGET_MONO
			: ( hinting == FontHinting::Slight ? FT_LOAD_TARGET_LIGHT : FT_LOAD_TARGET_NORMAL );
	int hint = hinting == FontHinting::None ? FT_LOAD_NO_HINTING : FT_LOAD_FORCE_AUTOHINT;
	return load_target | hint;
}

static constexpr FT_Render_Mode fontSetRenderOptions( FT_Library library,
													  FontAntialiasing antialiasing,
													  FontHinting hinting,
													  FT_Glyph_Format glyphFormat ) {
	if ( antialiasing == FontAntialiasing::None )
		return FT_RENDER_MODE_MONO;
	if ( glyphFormat == FT_GLYPH_FORMAT_SVG )
		return FT_RENDER_MODE_NORMAL;
	if ( antialiasing == FontAntialiasing::Subpixel ) {
		unsigned char weights[] = { 0x10, 0x40, 0x70, 0x40, 0x10 };
		switch ( hinting ) {
			case FontHinting::None:
				FT_Library_SetLcdFilter( library, FT_LCD_FILTER_NONE );
				break;
			case FontHinting::Slight:
			case FontHinting::Full:
				FT_Library_SetLcdFilterWeights( library, weights );
				break;
		}
		return FT_RENDER_MODE_LCD;
	} else {
		switch ( hinting ) {
			case FontHinting::None:
				return FT_RENDER_MODE_NORMAL;
				break;
			case FontHinting::Slight:
				return FT_RENDER_MODE_LIGHT;
				break;
			case FontHinting::Full:
				return FT_RENDER_MODE_LIGHT;
				break;
		}
	}
	return FT_RENDER_MODE_NORMAL;
}

Glyph FontTrueType::loadGlyphByIndex( Uint32 index, unsigned int characterSize, bool bold,
									  bool /*italic*/, Float outlineThickness, Page& page ) const {
	// The glyph to return
	Glyph glyph;

	// First, transform our ugly void* to a FT_Face
	FT_Face face = static_cast<FT_Face>( mFace );
	if ( !face ) {
		Log::error( "FT_Face failed for: codePoint %d characterSize: %d font %s", index,
					characterSize, mFontName.c_str() );
		return glyph;
	}

	// Set the character size
	if ( !setCurrentSize( characterSize ) ) {
		Log::error(
			"FontTrueType::setCurrentSize failed for: codePoint %d characterSize: %d font %s",
			index, characterSize, mFontName.c_str() );
		return glyph;
	}

	FT_Error err = 0;

	auto loadOptions = fontSetLoadOptions( mAntialiasing, mHinting );
	if ( mIsColorEmojiFont || mHasSvgGlyphs )
		loadOptions = FT_LOAD_TARGET_NORMAL;

	// Load the glyph corresponding to the code point
	FT_Int32 flags = loadOptions | FT_LOAD_COLOR;
	if ( outlineThickness != 0 && !mIsColorEmojiFont )
		flags |= FT_LOAD_NO_BITMAP;
	if ( ( err = FT_Load_Glyph( face, index, flags ) ) != 0 ) {
		Log::error( "FT_Load_Char failed for: codePoint %d characterSize: %d font: %s error: %d",
					index, characterSize, mFontName.c_str(), err );
		return glyph;
	}

	// Retrieve the glyph
	FT_Glyph glyphDesc;
	FT_GlyphSlot slot = face->glyph;
	if ( FT_Get_Glyph( slot, &glyphDesc ) != 0 ) {
		Log::error( "FT_Get_Glyph failed for: codePoint %d characterSize: %d font: %s", index,
					characterSize, mFontName.c_str() );
		return glyph;
	}

	// Apply bold and outline (there is no fallback for outline) if necessary -- first technique
	// using outline (highest quality)
	FT_Pos weight = 1 << 6;
	bool outline = ( glyphDesc->format == FT_GLYPH_FORMAT_OUTLINE );
	if ( outline ) {
		if ( bold && !mIsBold ) {
			FT_OutlineGlyph outlineGlyph = (FT_OutlineGlyph)glyphDesc;
			FT_Outline_EmboldenXY( &outlineGlyph->outline, 1 << 5, weight );
		}

		if ( outlineThickness != 0 && !mIsColorEmojiFont ) {
			FT_Stroker stroker = static_cast<FT_Stroker>( mStroker );

			FT_Stroker_Set(
				stroker, static_cast<FT_Fixed>( outlineThickness * static_cast<Float>( 1 << 6 ) ),
				FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0 );
			FT_Glyph_Stroke( &glyphDesc, stroker, true );
		}
	}

	FT_Render_Mode finalRenderMode = fontSetRenderOptions(
		static_cast<FT_Library>( mLibrary ), mAntialiasing, mHinting, glyphDesc->format );

	// Convert the glyph to a bitmap (i.e. rasterize it)
	FT_Glyph_To_Bitmap( &glyphDesc, finalRenderMode, 0, 1 );
	FT_Bitmap& bitmap = reinterpret_cast<FT_BitmapGlyph>( glyphDesc )->bitmap;

	// Apply bold if necessary -- fallback technique using bitmap (lower quality)
	if ( !outline ) {
		if ( bold && !mIsBold )
			FT_Bitmap_Embolden( static_cast<FT_Library>( mLibrary ), &bitmap, weight, weight );

		if ( outlineThickness != 0 && !mIsColorEmojiFont )
			Log::error( "Failed to outline glyph (no fallback available)" );
	}

	// Compute the glyph's advance offset
	glyph.advance = static_cast<Float>( slot->metrics.horiAdvance ) / static_cast<Float>( 1 << 6 );

	if ( bold && !mBoldAdvanceSameAsRegular )
		glyph.advance += static_cast<Float>( weight ) / static_cast<Float>( 1 << 6 );

	glyph.lsbDelta = static_cast<int>( slot->lsb_delta );
	glyph.rsbDelta = static_cast<int>( slot->rsb_delta );
	glyph.font = (Font*)this;

	int width = bitmap.width;
	int height = bitmap.rows;

	if ( mAntialiasing == FontAntialiasing::Subpixel && bitmap.pixel_mode == FT_PIXEL_MODE_LCD )
		width /= 3;

	if ( ( width > 0 ) && ( height > 0 ) ) {
		// Leave a small padding around characters, so that filtering doesn't
		// pollute them with pixels from neighbors
		const int padding = 2;

		Float scale = 1.f;
		int destWidth = width;
		int destHeight = height;

		if ( mIsColorEmojiFont ) {
			// Browsers scale emojis slightly larger than the EM size to match the visual weight
			// of the text's Ascender.
			// Noto Sans Ascent is ~1.07em. Applying 1.1x scaling results in ~1.18em,
			// which matches Chrome/Firefox rendering behavior for Noto Color Emoji.
			Float targetSize = ( page.font != this )
								   ? (Float)page.font->getAscent( characterSize ) * 1.1f
								   : (Float)characterSize;

			scale = eemin( 1.f, targetSize / height );
		} else if ( mIsEmojiFont ) {
			scale = eemin( 1.f, (Float)characterSize / height );
		}

		glyph.advance = eeceil( glyph.advance * scale );

		if ( scale >= 1.f ) {
			destWidth *= scale;
			destHeight *= scale;
			width += 2 * padding;
			height += 2 * padding;
		}

		destWidth += 2 * padding;
		destHeight += 2 * padding;

		// Compute the glyph's bounding box
		glyph.bounds.Left =
			static_cast<Float>( slot->metrics.horiBearingX ) / static_cast<Float>( 1 << 6 );
		glyph.bounds.Top =
			-static_cast<Float>( slot->metrics.horiBearingY ) / static_cast<Float>( 1 << 6 );
		glyph.bounds.Right =
			static_cast<Float>( slot->metrics.width ) / static_cast<Float>( 1 << 6 ) +
			outlineThickness * 2;
		glyph.bounds.Bottom =
			static_cast<Float>( slot->metrics.height ) / static_cast<Float>( 1 << 6 ) +
			outlineThickness * 2;

		// Resize the pixel buffer to the new size and fill it with transparent white pixels
		const Uint32 bufferSize = width * height * 4;
		mPixelBuffer.resize( bufferSize );

		Uint8* pixelPtr = &mPixelBuffer[0];
		Uint8* current = pixelPtr;
		Uint8* end = current + bufferSize;

		std::fill( (Uint32*)pixelPtr, (Uint32*)end,
				   bitmap.pixel_mode == FT_PIXEL_MODE_LCD ? 0x00000000 : 0x00FFFFFF );

		// Extract the glyph's pixels from the bitmap
		const Uint8* pixels = bitmap.buffer;
		if ( bitmap.pixel_mode == FT_PIXEL_MODE_MONO ) {
			// Pixels are 1 bit monochrome values
			for ( int y = padding; y < height - padding; ++y ) {
				for ( int x = padding; x < width - padding;
					  ++x ) // Extract the glyph's pixels from the bitmap
				{
					// The color channels remain white, just fill the alpha channel
					std::size_t index = x + y * width;
					mPixelBuffer[index * 4 + 3] = ( ( pixels[( x - padding ) / 8] ) &
													( 1 << ( 7 - ( ( x - padding ) % 8 ) ) ) )
													  ? 255
													  : 0;
				}
				pixels += bitmap.pitch;
			}
		} else if ( bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ) {
			Image source( const_cast<Uint8*>( pixels ), bitmap.width, bitmap.rows, 4 );
			Image dest( &mPixelBuffer[0], width, height, 4 );
			source.avoidFreeImage( true );
			dest.avoidFreeImage( true );
			for ( size_t y = 0; y < bitmap.rows; ++y ) {
				for ( size_t x = 0; x < bitmap.width; ++x ) {
					Color col = source.getPixel( x, y );
					dest.setPixel( x, y, Color( col.b, col.g, col.r, col.a ) );
				}
			}

			if ( scale < 1.f ) {
				dest.scale( scale );
				dest.avoidFreeImage( true );
				pixelPtr = dest.getPixels();
				glyph.bounds.Left = glyph.bounds.Left * scale + outlineThickness;
				glyph.bounds.Right *= scale;
				glyph.bounds.Top = glyph.bounds.Top * scale + outlineThickness;
				glyph.bounds.Bottom *= scale;
				destWidth = dest.getWidth() + 2 * padding;
				destHeight = dest.getHeight() + 2 * padding;
			}
		} else if ( bitmap.pixel_mode == FT_PIXEL_MODE_LCD ) {
			for ( int y = padding; y < height - padding; ++y ) {
				for ( int x = padding; x < width - padding; ++x ) {
					const std::size_t index = ( x + y * width ) * 4;
					const Uint8* px = &pixels[( x - padding ) * 3];
					mPixelBuffer[index + 0] = px[0];
					mPixelBuffer[index + 1] = px[1];
					mPixelBuffer[index + 2] = px[2];
					mPixelBuffer[index + 3] =
						(Uint8)( ( (int)px[0] + (int)px[1] + (int)px[2] ) / 3.f );
				}
				pixels += bitmap.pitch;
			}
		} else {
			if ( scale < 1.f ) {
				// Pixels are 8 bits gray levels
				for ( int y = 0; y < height; ++y ) {
					for ( int x = 0; x < width; ++x ) {
						// The color channels remain white, just fill the alpha channel
						std::size_t index = x + y * width;
						mPixelBuffer[index * 4 + 3] = pixels[x];
					}
					pixels += bitmap.pitch;
				}

				Image dest( &mPixelBuffer[0], bitmap.width, bitmap.rows, 4 );
				dest.avoidFreeImage( true );
				dest.scale( scale );
				dest.avoidFreeImage( true );
				pixelPtr = dest.getPixels();
				glyph.bounds.Left = glyph.bounds.Left * scale;
				glyph.bounds.Right *= scale;
				glyph.bounds.Top = glyph.bounds.Top * scale;
				glyph.bounds.Bottom *= scale;
				destWidth = dest.getWidth() + 2 * padding;
				destHeight = dest.getHeight() + 2 * padding;
			} else {
				// Pixels are 8 bits gray levels
				for ( int y = padding; y < height - padding; ++y ) {
					for ( int x = padding; x < width - padding; ++x ) {
						// The color channels remain white, just fill the alpha channel
						std::size_t index = x + y * width;
						mPixelBuffer[index * 4 + 3] = pixels[x - padding];
					}
					pixels += bitmap.pitch;
				}
			}
		}

		// Find a good position for the new glyph into the texture
		glyph.textureRect = findGlyphRect( page, destWidth, destHeight );

		// Write the pixels to the texture
		unsigned int x = glyph.textureRect.Left;
		unsigned int y = glyph.textureRect.Top;
		unsigned int w = glyph.textureRect.Right;
		unsigned int h = glyph.textureRect.Bottom;

		if ( scale < 1.f ) {
			w = destWidth - 2 * padding;
			h = destHeight - 2 * padding;
			x += padding;
			y += padding;
		}

		// Make sure the texture data is positioned in the center
		// of the allocated texture rectangle
		glyph.textureRect.Left += padding;
		glyph.textureRect.Top += padding;
		glyph.textureRect.Right -= 2 * padding;
		glyph.textureRect.Bottom -= 2 * padding;

		glyph.size = { (Float)glyph.textureRect.Right, (Float)glyph.textureRect.Bottom };

		page.texture->update( pixelPtr, w, h, x, y );

		if ( scale < 1.f )
			eeSAFE_DELETE_ARRAY( pixelPtr );
	}

	// Delete the FT glyph
	FT_Done_Glyph( glyphDesc );

	// Done :)
	return glyph;
}

Rect FontTrueType::findGlyphRect( Page& page, unsigned int width, unsigned int height ) const {
	// Find the line that fits well the glyph
	Row* row = NULL;
	Float bestRatio = 0;
	for ( std::vector<Row>::iterator it = page.rows.begin(); it != page.rows.end() && !row; ++it ) {
		Float ratio = static_cast<Float>( height ) / it->height;

		// Ignore rows that are either too small or too high
		if ( ( ratio < 0.7f ) || ( ratio > 1.f ) )
			continue;

		// Check if there's enough horizontal space left in the row
		if ( width > page.texture->getPixelsSize().x - it->width )
			continue;

		// Make sure that this new row is the best found so far
		if ( ratio < bestRatio )
			continue;

		// The current row passed all the tests: we can select it
		row = &*it;
		bestRatio = ratio;
	}

	// If we didn't find a matching row, create a new one (10% taller than the glyph)
	if ( !row ) {
		int rowHeight = height + height / 10;
		while ( ( page.nextRow + rowHeight >= (Uint32)page.texture->getPixelsSize().y ) ||
				( width >= (Uint32)page.texture->getPixelsSize().x ) ) {
			// Not enough space: resize the texture if possible
			unsigned int textureWidth = page.texture->getPixelsSize().x;
			unsigned int textureHeight = page.texture->getPixelsSize().y;
			if ( ( textureWidth * 2 <= Texture::getMaximumSize() ) &&
				 ( textureHeight * 2 <= Texture::getMaximumSize() ) ) {
				// Make the texture 2 times bigger
				Image newImage;
				newImage.create( textureWidth * 2, textureHeight * 2, 4 );
				newImage.copyImage( page.texture );

				page.texture->replace( &newImage );
			} else {
				// Oops, we've reached the maximum texture size...
				Log::error(
					"Failed to add a new character to the font: the maximum texture size has "
					"been reached" );
				return Rect( 0, 0, 2, 2 );
			}
		}

		// We can now create the new row
		page.rows.push_back( Row( page.nextRow, rowHeight ) );
		page.nextRow += rowHeight;
		row = &page.rows.back();
	}

	// Find the glyph's rectangle on the selected row
	Rect rect( row->width, row->top, width, height );

	// Update the row information
	row->width += width;

	return rect;
}

bool FontTrueType::setCurrentSize( unsigned int characterSize ) const {
	// FT_Set_Pixel_Sizes is an expensive function, so we must call it
	// only when necessary to avoid killing performances

	FT_Face face = static_cast<FT_Face>( mFace );

	if ( !face || !face->size )
		return false;

	FT_UShort currentSize = face->size->metrics.x_ppem;

	if ( currentSize != characterSize ) {
		if ( mIsColorEmojiFont ) {
			int bestMatch = 0;
			int diff = eeabs( characterSize - face->available_sizes[0].width );
			for ( int i = 1; i < face->num_fixed_sizes; ++i ) {
				int ndiff = eeabs( characterSize - face->available_sizes[i].width );
				if ( ndiff < diff ) {
					bestMatch = i;
					diff = ndiff;
				}
			}
			characterSize = bestMatch;
		} else if ( mIsBitmapOnly ) {
			int bestMatch = face->available_sizes[0].height;
			int diff = eeabs( characterSize - face->available_sizes[0].height );
			for ( int i = 1; i < face->num_fixed_sizes; ++i ) {
				int ndiff = eeabs( characterSize - face->available_sizes[i].height );
				if ( ndiff < diff ) {
					bestMatch = face->available_sizes[i].height;
					diff = ndiff;
				}
			}
			characterSize = bestMatch;
		}

		FT_Error result = mIsColorEmojiFont ? FT_Select_Size( face, characterSize )
											: FT_Set_Pixel_Sizes( face, 0, characterSize );

		if ( result == FT_Err_Invalid_Pixel_Size ) {
			// In the case of bitmap fonts, resizing can
			// fail if the requested size is not available
			if ( !FT_IS_SCALABLE( face ) ) {
				auto it = mClosestCharacterSize.find( characterSize );

				if ( it == mClosestCharacterSize.end() ) {
					Log::warning( "Failed to set bitmap font size to %d", characterSize );
					Log::warning( "Available sizes are: " );
					if ( face->num_fixed_sizes > 0 ) {
						std::string str;
						unsigned int selectedHeight = face->available_sizes[0].height;
						int curDistance = eeabs( characterSize - selectedHeight );
						for ( int i = 0; i < face->num_fixed_sizes; ++i ) {
							str += String::format( "%d ", face->available_sizes[i].height );
							int tDistance =
								eeabs( characterSize - face->available_sizes[i].height );
							if ( tDistance < curDistance ) {
								curDistance = tDistance;
								selectedHeight = face->available_sizes[i].height;
							}
						}
						Log::warning( str );
						Log::warning( "Setting closest bitmap font size available: ",
									  selectedHeight );
						mClosestCharacterSize[characterSize] = selectedHeight;
						return setCurrentSize( selectedHeight );
					} else {
						return false;
					}
				} else if ( characterSize != currentSize &&
							( result = FT_Set_Pixel_Sizes( face, 0, it->second ) ) == FT_Err_Ok ) {
#ifdef EE_TEXT_SHAPER_ENABLED
					hb_ft_font_changed( (hb_font_t*)mHBFont );
#endif
					return true;
				}
			}
		}

#ifdef EE_TEXT_SHAPER_ENABLED
		hb_ft_font_changed( (hb_font_t*)mHBFont );
#endif
		return result == FT_Err_Ok;
	} else {
		return true;
	}
}

FontTrueType::Page& FontTrueType::getPage( unsigned int characterSize ) const {
	auto pageIt = mPages.find( characterSize );
	if ( pageIt == mPages.end() ) {
		std::string name =
			String::format( "@font:TrueType:%s:%d", mInfo.family.c_str(), characterSize );
		if ( mIsBold )
			name += ":bold";
		if ( mIsItalic )
			name += ":italic";
		mPages[characterSize] = std::make_unique<Page>( mFontInternalId, name, this );
		pageIt = mPages.find( characterSize );
	}
	return *pageIt->second;
}

FontAntialiasing FontTrueType::getAntialiasing() const {
	return mAntialiasing;
}

void FontTrueType::setAntialiasing( FontAntialiasing antialiasing ) {
	if ( antialiasing != mAntialiasing ) {
		mAntialiasing = antialiasing;
		clearCache();
	}
}

FontHinting FontTrueType::getHinting() const {
	return mHinting;
}

void FontTrueType::setHinting( FontHinting hinting ) {
	if ( hinting != mHinting ) {
		mHinting = hinting;
		clearCache();
	}
}

bool FontTrueType::getEnableDynamicMonospace() const {
	return mEnableDynamicMonospace;
}

void FontTrueType::setEnableDynamicMonospace( bool enableDynamicMonospace ) {
	mEnableDynamicMonospace = enableDynamicMonospace;
}

bool FontTrueType::isFallbackFontEnabled() const {
	return mEnableFallbackFont;
}

void FontTrueType::setEnableFallbackFont( bool enableFallbackFont ) {
	mEnableFallbackFont = enableFallbackFont;
}

bool FontTrueType::isEmojiFallbackEnabled() const {
	return mEnableEmojiFallback;
}

void FontTrueType::setEnableEmojiFallback( bool enableEmojiFallback ) {
	mEnableEmojiFallback = enableEmojiFallback;
}

const Uint32& FontTrueType::getFontInternalId() const {
	return mFontInternalId;
}

void FontTrueType::setIsEmojiFont( bool isEmojiFont ) {
	mIsEmojiFont = isEmojiFont;
}

void FontTrueType::setForceIsMonospace( bool isMonospace ) {
	mIsMonospaceComplete = isMonospace;
}

void FontTrueType::setIsColorEmojiFont( bool isColorEmojiFont ) {
	mIsColorEmojiFont = isColorEmojiFont;
}

bool FontTrueType::isColorEmojiFont() const {
	return mIsColorEmojiFont;
}

bool FontTrueType::isMonospace() const {
	if ( mIsMonospaceCompletePending )
		updateMonospaceState();
	return mIsMonospaceComplete;
}

bool FontTrueType::isIdentifiedAsMonospace() const {
	return mIsMonospace;
}

bool FontTrueType::isScalable() const {
	return mFace ? FT_IS_SCALABLE( static_cast<FT_Face>( mFace ) ) : false;
}

bool FontTrueType::isEmojiFont() const {
	return mIsEmojiFont;
}

bool FontTrueType::getBoldAdvanceSameAsRegular() const {
	return mBoldAdvanceSameAsRegular;
}

void FontTrueType::setBoldAdvanceSameAsRegular( bool boldAdvanceSameAsRegular ) {
	mBoldAdvanceSameAsRegular = boldAdvanceSameAsRegular;
}

void FontTrueType::updateMonospaceState() const {
	mIsMonospaceComplete = mIsMonospace && !mUsingFallback;
	if ( !Engine::isEngineRunning() || !Engine::instance()->isMainThread() ) {
		mIsMonospaceCompletePending = true;
		return;
	}
	mIsMonospaceCompletePending = false;
	if ( mIsMonospaceComplete && mFontBold != nullptr ) {
		mIsMonospaceComplete = mIsMonospaceComplete && mFontBold->isMonospace() &&
							   getGlyph( ' ', 10, false, false ).advance ==
								   mFontBold->getGlyph( ' ', 10, false, false ).advance;
	}
	if ( mIsMonospaceComplete && mFontItalic != nullptr ) {
		mIsMonospaceComplete = mIsMonospaceComplete && mFontItalic->isMonospace() &&
							   getGlyph( ' ', 10, false, false ).advance ==
								   mFontItalic->getGlyph( ' ', 10, false, false ).advance;
	}
	if ( mIsMonospaceComplete && mFontBoldItalic != nullptr ) {
		mIsMonospaceComplete = mIsMonospaceComplete && mFontBoldItalic->isMonospace() &&
							   getGlyph( ' ', 10, false, false ).advance ==
								   mFontBoldItalic->getGlyph( ' ', 10, false, false ).advance;
	}
}

void FontTrueType::setBoldFont( FontTrueType* fontBold ) {
	if ( fontBold == mFontBold )
		return;
	mFontBold = fontBold;
	if ( mFontBold != nullptr ) {
		mFontBoldCb = mFontBold->pushFontEventCallback( [this]( Uint32, Event event, Font* ) {
			if ( event == Font::Event::Unload ) {
				// Maybe we should recreate the page table
				mFontBold = nullptr;
				mFontBoldCb = 0;
			}
		} );
	}
	updateMonospaceState();
}

void FontTrueType::setItalicFont( FontTrueType* fontItalic ) {
	if ( fontItalic == mFontItalic )
		return;
	mFontItalic = fontItalic;
	if ( mFontItalic != nullptr ) {
		mFontItalicCb = mFontItalic->pushFontEventCallback( [this]( Uint32, Event event, Font* ) {
			if ( event == Font::Event::Unload ) {
				// Maybe we should recreate the page table
				mFontItalic = nullptr;
				mFontItalicCb = 0;
			}
		} );
	}
	updateMonospaceState();
}

void FontTrueType::setBoldItalicFont( FontTrueType* fontBoldItalic ) {
	if ( fontBoldItalic == mFontBoldItalic )
		return;
	mFontBoldItalic = fontBoldItalic;
	if ( mFontBoldItalic != nullptr ) {
		mFontBoldItalicCb =
			mFontBoldItalic->pushFontEventCallback( [this]( Uint32, Event event, Font* ) {
				if ( event == Font::Event::Unload ) {
					// Maybe we should recreate the page table
					mFontBoldItalic = nullptr;
					mFontBoldItalicCb = 0;
				}
			} );
	}
	updateMonospaceState();
}

bool FontTrueType::hasSvgGlyphs() const {
	return mHasSvgGlyphs;
}

bool FontTrueType::hasColrGlyphs() const {
	return mHasColrGlyphs;
}

FontTrueType::Page::Page( const Uint32 fontInternalId, const std::string& pageName,
						  const FontTrueType* font ) :
	texture( NULL ), fontInternalId( fontInternalId ), nextRow( 3 ), font( font ) {
	// Make sure that the texture is initialized by default
	Image image;
	image.create( 128, 128, 4 );

	// Reserve a 2x2 white square for texturing underlines
	for ( int x = 0; x < 2; ++x )
		for ( int y = 0; y < 2; ++y )
			image.setPixel( x, y, Color( 255, 255, 255, 255 ) );

	// Create the texture
	texture = TextureFactory::instance()->loadFromPixels(
		image.getPixelsPtr(), image.getWidth(), image.getHeight(), image.getChannels(), false,
		Texture::ClampMode::ClampToEdge, false, true );
	texture->setCoordinateType( Texture::CoordinateType::Pixels );
	texture->setName( pageName );
}

FontTrueType::Page::~Page() {
	for ( auto drawable : drawables )
		eeDelete( drawable.second );

	if ( NULL != texture && TextureFactory::existsSingleton() )
		TextureFactory::instance()->remove( texture->getTextureId() );
}

void FontTrueType::clearCache() {
	mPages.clear();
	mClosestCharacterSize.clear();
	mCodePointIndexCache.clear();
	mKeyCache.clear();
	mKerningCache.clear();
	mKerningGlyphCache.clear();
	Text::GlobalInvalidationId++;
}

}} // namespace EE::Graphics
