First steps in supporting color fonts.

Some minor fixes to build with emscripten.
This commit is contained in:
Martín Lucas Golini
2022-02-08 03:06:35 -03:00
parent 4a23c59b46
commit 7a221d19c1
12 changed files with 167 additions and 58 deletions

Binary file not shown.

View File

@@ -113,6 +113,7 @@ class EE_API FontTrueType : public Font {
mutable std::vector<Uint8>
mPixelBuffer; ///< Pixel buffer holding a glyph's pixels before being written to the texture
bool mBoldAdvanceSameAsRegular;
bool mIsColorEmojiFont{ false };
mutable std::map<unsigned int, unsigned int> mClosestCharacterSize;
};

View File

@@ -9,6 +9,11 @@ namespace EE { namespace UI { namespace Models {
class EE_API ModelEditingDelegate {
public:
enum SelectionBehavior {
DoNotSelect,
SelectAll,
};
virtual ~ModelEditingDelegate() {}
void bind( std::shared_ptr<Model> model, const ModelIndex& index ) {
@@ -23,9 +28,12 @@ class EE_API ModelEditingDelegate {
UIWidget* getWidget() const { return mWidget; }
std::function<void()> onCommit;
std::function<void()> onRollback;
std::function<void()> onChange;
virtual Variant getValue() const = 0;
virtual void setValue( const Variant& ) = 0;
virtual void setValue( const Variant&,
SelectionBehavior selectionBehavior = SelectionBehavior::SelectAll ) = 0;
virtual void willBeginEditing() {}
protected:
@@ -38,6 +46,16 @@ class EE_API ModelEditingDelegate {
onCommit();
}
void rollback() {
if ( onRollback )
onRollback();
}
void change() {
if ( onChange )
onChange();
}
private:
std::shared_ptr<Model> mModel;
ModelIndex mIndex;
UIWidget* mWidget{ nullptr };

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 6.0.1, 2022-01-07T02:26:18. -->
<!-- Written by QtCreator 6.0.2, 2022-02-08T03:04:51. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
@@ -193,7 +193,7 @@
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Arguments">gmake</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Arguments">--with-dynamic-freetype gmake</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Command">premake4</value>
<value type="QString" key="ProjectExplorer.ProcessStep.WorkingDirectory">%{buildDir}../../../</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.ProcessStep</value>
@@ -234,7 +234,7 @@
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Arguments">gmake</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Arguments">--with-dynamic-freetype gmake</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Command">premake5</value>
<value type="QString" key="ProjectExplorer.ProcessStep.WorkingDirectory">%{buildDir}../../../</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.ProcessStep</value>
@@ -590,7 +590,7 @@
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Arguments">gmake</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Arguments">--with-dynamic-freetype gmake</value>
<value type="QString" key="ProjectExplorer.ProcessStep.Command">premake5</value>
<value type="QString" key="ProjectExplorer.ProcessStep.WorkingDirectory">%{buildDir}../../../</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.ProcessStep</value>

View File

@@ -33,7 +33,7 @@ void eeREPORT_ASSERT( const char* File, int Line, const char* Exp ) {
printf( "ASSERT: %s file:%s line:%d", Exp, File, Line );
}
#if defined( EE_COMPILER_GCC ) && !defined( EE_ARM )
#if defined( EE_COMPILER_GCC ) && !defined( EE_ARM ) && EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN
asm( "int3" );
#else
assert( false );

View File

@@ -12,6 +12,7 @@
#include FT_OUTLINE_H
#include FT_BITMAP_H
#include FT_STROKER_H
#include FT_TRUETYPE_TABLES_H
#include <cstdlib>
#include <cstring>
@@ -73,6 +74,13 @@ FontTrueType::~FontTrueType() {
cleanup();
}
bool isColorEmojiFont( 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;
}
bool FontTrueType::loadFromFile( const std::string& filename ) {
if ( !FileSystem::fileExists( filename ) &&
PackManager::instance()->isFallbackToPacksActive() ) {
@@ -109,27 +117,33 @@ bool FontTrueType::loadFromFile( const std::string& filename ) {
return false;
}
// Load the stroker that will be used to outline the font
FT_Stroker stroker;
if ( FT_Stroker_New( static_cast<FT_Library>( mLibrary ), &stroker ) != 0 ) {
Log::error( "Failed to load font \"%s\" (failed to create the stroker)", filename.c_str() );
FT_Done_Face( face );
return false;
mFace = face;
mIsColorEmojiFont = isColorEmojiFont( static_cast<FT_Face>( mFace ) );
FT_Stroker stroker = nullptr;
if ( !mIsColorEmojiFont ) {
// Load the stroker that will be used to outline the font
if ( FT_Stroker_New( static_cast<FT_Library>( mLibrary ), &stroker ) != 0 ) {
Log::error( "Failed to load font \"%s\" (failed to create the stroker)",
filename.c_str() );
FT_Done_Face( face );
return false;
}
// Store the loaded font in our ugly void* :)
mStroker = stroker;
}
// Select the unicode character map
if ( FT_Select_Charmap( face, FT_ENCODING_UNICODE ) != 0 ) {
Log::error( "Failed to load font \"%s\" (failed to set the Unicode character set)",
filename.c_str() );
FT_Stroker_Done( stroker );
if ( stroker )
FT_Stroker_Done( stroker );
FT_Done_Face( face );
return false;
}
// Store the loaded font in our ugly void* :)
mStroker = stroker;
mFace = face;
// Store the font information
mInfo.family = face->family_name ? face->family_name : std::string();
@@ -168,25 +182,30 @@ bool FontTrueType::loadFromMemory( const void* data, std::size_t sizeInBytes, bo
return false;
}
mFace = face;
mIsColorEmojiFont = isColorEmojiFont( static_cast<FT_Face>( mFace ) );
// Load the stroker that will be used to outline the font
FT_Stroker stroker;
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;
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)" );
FT_Stroker_Done( stroker );
if ( stroker )
FT_Stroker_Done( stroker );
FT_Done_Face( face );
return false;
}
// Store the loaded font in our ugly void* :)
mStroker = stroker;
mFace = face;
// Store the font information
mInfo.family = face->family_name ? face->family_name : std::string();
@@ -236,19 +255,25 @@ bool FontTrueType::loadFromStream( IOStream& stream ) {
return false;
}
// Load the stroker that will be used to outline the font
FT_Stroker stroker;
if ( FT_Stroker_New( static_cast<FT_Library>( mLibrary ), &stroker ) != 0 ) {
Log::error( "Failed to load font from stream (failed to create the stroker)" );
FT_Done_Face( face );
delete rec;
return false;
mFace = face;
mIsColorEmojiFont = isColorEmojiFont( static_cast<FT_Face>( mFace ) );
FT_Stroker stroker = nullptr;
if ( !mIsColorEmojiFont ) {
// Load the stroker that will be used to outline the font
if ( FT_Stroker_New( static_cast<FT_Library>( mLibrary ), &stroker ) != 0 ) {
Log::error( "Failed to load font from stream (failed to create the stroker)" );
FT_Done_Face( face );
delete rec;
return false;
}
}
// Select the Unicode character map
if ( FT_Select_Charmap( face, FT_ENCODING_UNICODE ) != 0 ) {
Log::error( "Failed to load font from stream (failed to set the Unicode character set)" );
FT_Stroker_Done( stroker );
if ( stroker )
FT_Stroker_Done( stroker );
FT_Done_Face( face );
delete rec;
return false;
@@ -256,7 +281,6 @@ bool FontTrueType::loadFromStream( IOStream& stream ) {
// Store the loaded font in our ugly void* :)
mStroker = stroker;
mFace = face;
mStreamRec = rec;
// Store the font information
@@ -514,7 +538,7 @@ Glyph FontTrueType::loadGlyph( Uint32 codePoint, unsigned int characterSize, boo
FT_Error err = 0;
// Load the glyph corresponding to the code point
FT_Int32 flags = FT_LOAD_TARGET_NORMAL | FT_LOAD_FORCE_AUTOHINT;
FT_Int32 flags = FT_LOAD_TARGET_NORMAL | FT_LOAD_FORCE_AUTOHINT | FT_LOAD_COLOR;
if ( outlineThickness != 0 )
flags |= FT_LOAD_NO_BITMAP;
if ( ( err = FT_Load_Char( face, codePoint, flags ) ) != 0 ) {
@@ -578,14 +602,28 @@ Glyph FontTrueType::loadGlyph( Uint32 codePoint, unsigned int characterSize, boo
// pollute them with pixels from neighbors
const int padding = 2;
Float scale =
mIsColorEmojiFont ? (Float)characterSize / (Float)eemax( width, height ) : 1.f;
int destWidth = width;
int destHeight = height;
if ( mIsColorEmojiFont ) {
destWidth *= scale;
destHeight *= scale;
glyph.advance *= scale;
}
width += 2 * padding;
height += 2 * padding;
destWidth += 2 * padding;
destHeight += 2 * padding;
// Get the glyphs page corresponding to the character size
Page& page = mPages[characterSize];
// Find a good position for the new glyph into the texture
glyph.textureRect = findGlyphRect( page, width, height );
glyph.textureRect = findGlyphRect( page, destWidth, destHeight );
// Make sure the texture data is positioned in the center
// of the allocated texture rectangle
@@ -594,6 +632,12 @@ Glyph FontTrueType::loadGlyph( Uint32 codePoint, unsigned int characterSize, boo
glyph.textureRect.Right -= 2 * padding;
glyph.textureRect.Bottom -= 2 * padding;
// Write the pixels to the texture
unsigned int x = glyph.textureRect.Left - padding;
unsigned int y = glyph.textureRect.Top - padding;
unsigned int w = glyph.textureRect.Right + 2 * padding;
unsigned int h = glyph.textureRect.Bottom + 2 * padding;
// Compute the glyph's bounding box
glyph.bounds.Left =
static_cast<Float>( face->glyph->metrics.horiBearingX ) / static_cast<Float>( 1 << 6 );
@@ -609,14 +653,17 @@ Glyph FontTrueType::loadGlyph( Uint32 codePoint, unsigned int characterSize, boo
// Resize the pixel buffer to the new size and fill it with transparent white pixels
mPixelBuffer.resize( width * height * 4 );
Uint8* current = &mPixelBuffer[0];
Uint8* pixelPtr = &mPixelBuffer[0];
Uint8* current = pixelPtr;
Uint8* end = current + width * height * 4;
while ( current != end ) {
( *current++ ) = 255;
( *current++ ) = 255;
( *current++ ) = 255;
( *current++ ) = 0;
if ( bitmap.pixel_mode != FT_PIXEL_MODE_BGRA ) {
while ( current != end ) {
( *current++ ) = 255;
( *current++ ) = 255;
( *current++ ) = 255;
( *current++ ) = 0;
}
}
// Extract the glyph's pixels from the bitmap
@@ -636,6 +683,30 @@ Glyph FontTrueType::loadGlyph( Uint32 codePoint, unsigned int characterSize, boo
}
pixels += bitmap.pitch;
}
} else if ( bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ) {
Image source( (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( padding + x, padding + y, Color( col.b, col.g, col.r, col.a ) );
}
}
if ( scale < 1.f ) {
dest.scale( scale );
pixelPtr = dest.getPixels();
dest.saveToFile( String::format( "/home/downloads/%uld.png", codePoint ),
Image::SAVE_TYPE_PNG );
glyph.bounds.Left *= scale;
glyph.bounds.Right *= scale;
glyph.bounds.Top *= scale;
glyph.bounds.Bottom *= scale;
w = dest.getWidth();
h = dest.getHeight();
}
} else {
// Pixels are 8 bits gray levels
for ( int y = padding; y < height - padding; ++y ) {
@@ -648,12 +719,7 @@ Glyph FontTrueType::loadGlyph( Uint32 codePoint, unsigned int characterSize, boo
}
}
// Write the pixels to the texture
unsigned int x = glyph.textureRect.Left - padding;
unsigned int y = glyph.textureRect.Top - padding;
unsigned int w = glyph.textureRect.Right + 2 * padding;
unsigned int h = glyph.textureRect.Bottom + 2 * padding;
page.texture->update( &mPixelBuffer[0], w, h, x, y );
page.texture->update( pixelPtr, w, h, x, y );
}
// Delete the FT glyph
@@ -735,7 +801,21 @@ bool FontTrueType::setCurrentSize( unsigned int characterSize ) const {
FT_UShort currentSize = face->size->metrics.x_ppem;
if ( currentSize != characterSize ) {
FT_Error result = FT_Set_Pixel_Sizes( face, 0, 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;
}
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
@@ -767,7 +847,7 @@ bool FontTrueType::setCurrentSize( unsigned int characterSize ) const {
} else {
return false;
}
} else {
} else if ( characterSize != currentSize && face->size->metrics.x_ppem > 0 ) {
return setCurrentSize( it->second );
}
}

View File

@@ -855,10 +855,10 @@ void Image::scale( const Float& scale, ResamplerFilter filter ) {
if ( 1.f == scale )
return;
Int32 new_width = (Int32)( (Float)mWidth * scale );
Int32 new_height = (Int32)( (Float)mHeight * scale );
Int32 newWidth = (Int32)( (Float)mWidth * scale );
Int32 newHeight = (Int32)( (Float)mHeight * scale );
resize( new_width, new_height, filter );
resize( newWidth, newHeight, filter );
}
Graphics::Image* Image::thumbnail( const Uint32& maxWidth, const Uint32& maxHeight,

View File

@@ -3,6 +3,7 @@
EE::Window::Window* win = NULL;
FontTrueType* fontTest;
FontTrueType* fontTest2;
FontTrueType* fontEmoji;
FontBMFont* fontBMFont;
FontSprite* fontSprite;
Text text;
@@ -10,6 +11,7 @@ Text text2;
Text text3;
Text text4;
Text text5;
Text text6;
void mainLoop() {
// Clear the screen buffer
@@ -38,6 +40,8 @@ void mainLoop() {
text5.draw( ( win->getWidth() - text5.getTextWidth() ) * 0.5f, 640 );
text6.draw( ( win->getWidth() - text6.getTextWidth() ) * 0.5f, 690 );
// Draw frame
win->display();
}
@@ -113,6 +117,13 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
text5.setString( "Lorem ipsum dolor sit amet, consectetur adipisicing elit." );
text5.setFontSize( 38 );
fontEmoji = FontTrueType::New( "NotoColorEmoji" );
fontEmoji->loadFromFile( "assets/fonts/NotoColorEmoji.ttf" );
text6.setFont( fontEmoji );
text6.setFontSize( 64 );
text6.setString( "👽 😀 💩 😃 👻" );
// Application loop
win->runMainLoop( &mainLoop );
}

View File

@@ -1056,7 +1056,9 @@ void App::syncProjectTreeWithEditor( UICodeEditor* editor ) {
std::string path = editor->getDocument().getFilePath();
if ( path.size() >= mCurrentProject.size() ) {
path = path.substr( mCurrentProject.size() );
mProjectTreeView->setFocusOnSelection( false );
mProjectTreeView->selectRowWithPath( path );
mProjectTreeView->setFocusOnSelection( true );
}
}
}

View File

@@ -261,7 +261,6 @@ void DocSearchController::hideSearchBar() {
}
void DocSearchController::onCodeEditorFocusChange( UICodeEditor* editor ) {
if ( mSearchState.editor && mSearchState.editor != editor ) {
String word = mSearchState.editor->getHighlightWord();
mSearchState.editor->setHighlightWord( "" );

View File

@@ -24,7 +24,7 @@ void FileLocator::updateLocateTable() {
} );
#else
mLocateTable->setModel(
mDirTree->fuzzyMatchTree( mLocateInput->getText(), LOCATEBAR_MAX_RESULTS ) );
mApp->getDirTree()->fuzzyMatchTree( mLocateInput->getText(), LOCATEBAR_MAX_RESULTS ) );
mLocateTable->getSelection().set( mLocateTable->getModel()->index( 0 ) );
#endif
} else {

View File

@@ -149,10 +149,8 @@ void FormatterModule::runFormatter( UICodeEditor* editor, const Formatter& forma
strings.push_back( cmdArr[i].c_str() );
strings.push_back( NULL );
struct subprocess_s subprocess;
int result = subprocess_create( strings.data(),
subprocess_option_inherit_environment |
subprocess_option_combined_stdout_stderr,
&subprocess );
int result =
subprocess_create( strings.data(), subprocess_option_inherit_environment, &subprocess );
if ( 0 == result ) {
std::string buffer( 1024, '\0' );
std::string data;