mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-05-28 17:16:29 +03:00
Added support to OpenType SVG font files in FontTrueType.
This commit is contained in:
@@ -32,7 +32,7 @@ class EE_API FontTrueType : public Font {
|
||||
const Font::Info& getInfo() const;
|
||||
|
||||
Glyph getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic,
|
||||
Float outlineThickness = 0 ) const;
|
||||
Float outlineThickness = 0 ) const;
|
||||
|
||||
Glyph getGlyphByIndex( Uint32 index, unsigned int characterSize, bool bold, bool italic,
|
||||
Float outlineThickness = 0 ) const;
|
||||
@@ -77,6 +77,10 @@ class EE_API FontTrueType : public Font {
|
||||
|
||||
bool isColorEmojiFont() const;
|
||||
|
||||
bool hasSvgGlyphs() const;
|
||||
|
||||
bool hasColrGlyphs() const;
|
||||
|
||||
/** @return True if the font identifies itself as a monospace font and currently does not hold
|
||||
* any non-monospaced glyph (from a fallback font) */
|
||||
bool isMonospace() const;
|
||||
@@ -187,10 +191,10 @@ class EE_API FontTrueType : public Font {
|
||||
void cleanup();
|
||||
|
||||
Glyph getGlyphByIndex( Uint32 index, unsigned int characterSize, bool bold, bool italic,
|
||||
Float outlineThickness, Page& page ) const;
|
||||
Float outlineThickness, Page& page ) const;
|
||||
|
||||
Glyph getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic,
|
||||
Float outlineThickness, Page& page ) const;
|
||||
Float outlineThickness, Page& page ) const;
|
||||
|
||||
GlyphDrawable* getGlyphDrawableFromGlyphIndex( Uint32 glyphIndex, unsigned int characterSize,
|
||||
bool bold, bool italic, Float outlineThickness,
|
||||
@@ -221,18 +225,20 @@ class EE_API FontTrueType : public Font {
|
||||
mutable PageTable mPages; ///< Table containing the glyphs pages by character size
|
||||
mutable std::vector<Uint8>
|
||||
mPixelBuffer; ///< Pixel buffer holding a glyph's pixels before being written to the texture
|
||||
bool mBoldAdvanceSameAsRegular;
|
||||
bool mIsColorEmojiFont{ false };
|
||||
bool mIsEmojiFont{ false };
|
||||
mutable bool mIsMonospace{ false };
|
||||
mutable bool mIsMonospaceComplete{ false };
|
||||
mutable bool mUsingFallback{ false };
|
||||
bool mEnableEmojiFallback{ true };
|
||||
bool mEnableFallbackFont{ true };
|
||||
bool mEnableDynamicMonospace{ false };
|
||||
bool mIsBold{ false };
|
||||
bool mIsItalic{ false };
|
||||
mutable bool mIsMonospaceCompletePending{ false };
|
||||
bool mBoldAdvanceSameAsRegular : 1 { false };
|
||||
bool mIsColorEmojiFont : 1 { false };
|
||||
bool mIsEmojiFont : 1 { false };
|
||||
bool mHasSvgGlyphs : 1 { false };
|
||||
bool mHasColrGlyphs : 1 { false };
|
||||
mutable bool mIsMonospace : 1 { false };
|
||||
mutable bool mIsMonospaceComplete : 1 { false };
|
||||
mutable bool mUsingFallback : 1 { false };
|
||||
bool mEnableEmojiFallback : 1 { true };
|
||||
bool mEnableFallbackFont : 1 { true };
|
||||
bool mEnableDynamicMonospace : 1 { false };
|
||||
bool mIsBold : 1 { false };
|
||||
bool mIsItalic : 1 { false };
|
||||
mutable bool mIsMonospaceCompletePending : 1 { false };
|
||||
mutable UnorderedMap<unsigned int, unsigned int> mClosestCharacterSize;
|
||||
mutable UnorderedMap<Uint32, Uint32> mCodePointIndexCache;
|
||||
mutable UnorderedMap<Uint32, std::tuple<Uint32, Uint32, bool>> mKeyCache;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE QtCreatorProject>
|
||||
<!-- Written by QtCreator 17.0.2, 2025-11-09T21:29:35. -->
|
||||
<!-- Written by QtCreator 17.0.2, 2025-11-16T00:28:29. -->
|
||||
<qtcreator>
|
||||
<data>
|
||||
<variable>EnvironmentId</variable>
|
||||
@@ -126,7 +126,7 @@
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{388e5431-b31b-42b3-b9ad-9002d279d75d}</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">10</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">28</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">19</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">../../make/linux</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
@@ -1600,7 +1600,7 @@
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">debug-all</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">28</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">19</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
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
|
||||
@@ -28,10 +30,158 @@ using namespace EE::Window;
|
||||
#include <harfbuzz/hb.h>
|
||||
#endif
|
||||
|
||||
#include <thirdparty/nanosvg/nanosvg.h>
|
||||
#include <thirdparty/nanosvg/nanosvgrast.h>
|
||||
|
||||
namespace {
|
||||
|
||||
RETAIN_SYMBOL( FT_Palette_Select );
|
||||
|
||||
using namespace EE;
|
||||
|
||||
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 };
|
||||
|
||||
// FreeType callbacks that operate on a IOStream
|
||||
unsigned long read( FT_Stream rec, unsigned long offset, unsigned char* buffer,
|
||||
unsigned long count ) {
|
||||
@@ -108,13 +258,27 @@ FontTrueType::~FontTrueType() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
bool checkIsColorEmojiFont( const FT_Face& face ) {
|
||||
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() ) {
|
||||
@@ -142,6 +306,8 @@ bool FontTrueType::loadFromFile( const std::string& filename ) {
|
||||
}
|
||||
mLibrary = library;
|
||||
|
||||
FT_Property_Set( static_cast<FT_Library>( mLibrary ), "ot-svg", "svg-hooks", &svg_hooks );
|
||||
|
||||
// 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 ) {
|
||||
@@ -176,6 +342,8 @@ bool FontTrueType::loadFromMemory( const void* data, std::size_t sizeInBytes, bo
|
||||
}
|
||||
mLibrary = library;
|
||||
|
||||
FT_Property_Set( static_cast<FT_Library>( mLibrary ), "ot-svg", "svg-hooks", &svg_hooks );
|
||||
|
||||
// Load the new font face from the specified file
|
||||
FT_Face face;
|
||||
if ( FT_New_Memory_Face( static_cast<FT_Library>( mLibrary ),
|
||||
@@ -200,6 +368,8 @@ bool FontTrueType::loadFromStream( IOStream& stream ) {
|
||||
}
|
||||
mLibrary = library;
|
||||
|
||||
FT_Property_Set( static_cast<FT_Library>( mLibrary ), "ot-svg", "svg-hooks", &svg_hooks );
|
||||
|
||||
// Make sure that the stream's reading position is at the beginning
|
||||
stream.seek( 0 );
|
||||
|
||||
@@ -258,12 +428,15 @@ bool FontTrueType::setFontFace( void* _face ) {
|
||||
mHBFont = hb_ft_font_create( static_cast<FT_Face>( face ), NULL );
|
||||
#endif
|
||||
mIsMonospaceComplete = mIsMonospace = FT_IS_FIXED_WIDTH( face );
|
||||
mIsColorEmojiFont = checkIsColorEmojiFont( face );
|
||||
mIsColorEmojiFont = checkIsColorEmojiFont( face ); // For CBDT (COLRv0)
|
||||
mHasSvgGlyphs = checkHasSvgTable( face ); // For SVG table
|
||||
mHasColrGlyphs = checkHasColrTable( face ); // For COLR table (COLRv1)
|
||||
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 ( mIsColorEmojiFont && FontManager::instance()->getColorEmojiFont() == nullptr )
|
||||
if ( ( mIsColorEmojiFont || mHasSvgGlyphs || mHasColrGlyphs ) &&
|
||||
FontManager::instance()->getColorEmojiFont() == nullptr )
|
||||
FontManager::instance()->setColorEmojiFont( this );
|
||||
|
||||
if ( mIsEmojiFont && FontManager::instance()->getEmojiFont() == nullptr )
|
||||
@@ -334,8 +507,8 @@ Uint32 FontTrueType::getGlyphIndex( const Uint32& codePoint ) const {
|
||||
Glyph FontTrueType::getGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic,
|
||||
Float outlineThickness ) const {
|
||||
Uint32 idx = 0;
|
||||
if ( mEnableEmojiFallback && !mIsColorEmojiFont && !mIsEmojiFont &&
|
||||
Font::isEmojiCodePoint( codePoint ) ) {
|
||||
if ( mEnableEmojiFallback && !mIsColorEmojiFont && !mHasSvgGlyphs && !mHasColrGlyphs &&
|
||||
!mIsEmojiFont && Font::isEmojiCodePoint( codePoint ) ) {
|
||||
if ( !mIsColorEmojiFont && FontManager::instance()->getColorEmojiFont() != nullptr &&
|
||||
FontManager::instance()->getColorEmojiFont()->getType() == FontType::TTF ) {
|
||||
|
||||
@@ -913,8 +1086,6 @@ Glyph FontTrueType::loadGlyphByIndex( Uint32 index, unsigned int characterSize,
|
||||
FT_Error err = 0;
|
||||
|
||||
auto loadOptions = fontSetLoadOptions( mAntialiasing, mHinting );
|
||||
auto renderOptions =
|
||||
fontSetRenderOptions( static_cast<FT_Library>( mLibrary ), mAntialiasing, mHinting );
|
||||
|
||||
// Load the glyph corresponding to the code point
|
||||
FT_Int32 flags = loadOptions | FT_LOAD_COLOR;
|
||||
@@ -955,8 +1126,14 @@ Glyph FontTrueType::loadGlyphByIndex( Uint32 index, unsigned int characterSize,
|
||||
}
|
||||
}
|
||||
|
||||
FT_Render_Mode finalRenderMode = FT_RENDER_MODE_NORMAL;
|
||||
if ( glyphDesc->format != FT_GLYPH_FORMAT_SVG ) {
|
||||
finalRenderMode =
|
||||
fontSetRenderOptions( static_cast<FT_Library>( mLibrary ), mAntialiasing, mHinting );
|
||||
}
|
||||
|
||||
// Convert the glyph to a bitmap (i.e. rasterize it)
|
||||
FT_Glyph_To_Bitmap( &glyphDesc, renderOptions, 0, 1 );
|
||||
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)
|
||||
@@ -1490,6 +1667,14 @@ void FontTrueType::setBoldItalicFont( FontTrueType* fontBoldItalic ) {
|
||||
updateMonospaceState();
|
||||
}
|
||||
|
||||
bool FontTrueType::hasSvgGlyphs() const {
|
||||
return mHasSvgGlyphs;
|
||||
}
|
||||
|
||||
bool FontTrueType::hasColrGlyphs() const {
|
||||
return mHasColrGlyphs;
|
||||
}
|
||||
|
||||
FontTrueType::Page::Page( const Uint32 fontInternalId, const std::string& pageName ) :
|
||||
texture( NULL ), nextRow( 3 ), fontInternalId( fontInternalId ) {
|
||||
// Make sure that the texture is initialized by default
|
||||
|
||||
Reference in New Issue
Block a user