diff --git a/.gitignore b/.gitignore index 7b9ca11df..78b7d035f 100644 --- a/.gitignore +++ b/.gitignore @@ -119,7 +119,6 @@ tags *.exe *.fifo *.filters -*.json *.lnk *.log *.opensdf diff --git a/CMakeLists.txt b/CMakeLists.txt index 17c605be7..dd3fe4910 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1451,9 +1451,10 @@ set(EXPECTED_DATA emoticons.png extras.png fonts/DejaVuSans.ttf - fonts/GlowSansJCompressed-Book.otf - fonts/Icons.otf - fonts/SourceHanSansSC-Regular.otf + fonts/Font_Awesome_6_Free-Solid-900.otf + fonts/GlowSansJ-Compressed-Book.otf + fonts/SourceHanSans.ttc + fonts/index.json game.png gui_buttons.png gui_cursor.png @@ -1731,7 +1732,7 @@ set(EXPECTED_DATA wordlist.txt ) -set_glob(DATA GLOB_RECURSE "cfg;frag;json;map;otf;png;rules;ttf;txt;vert;wv" data ${EXPECTED_DATA}) +set_glob(DATA GLOB_RECURSE "cfg;frag;json;map;otf;png;rules;ttc;ttf;txt;vert;wv" data ${EXPECTED_DATA}) ######################################################################## # COPY DATA AND SHARED LIBS diff --git a/data/fonts/DejaVuSans.ttf b/data/fonts/DejaVuSans.ttf index 444c22d6b..e5f7eecce 100644 Binary files a/data/fonts/DejaVuSans.ttf and b/data/fonts/DejaVuSans.ttf differ diff --git a/data/fonts/Font_Awesome_6_Free-Solid-900.otf b/data/fonts/Font_Awesome_6_Free-Solid-900.otf new file mode 100644 index 000000000..a95c71e05 Binary files /dev/null and b/data/fonts/Font_Awesome_6_Free-Solid-900.otf differ diff --git a/data/fonts/GlowSansJ-Compressed-Book.otf b/data/fonts/GlowSansJ-Compressed-Book.otf new file mode 100644 index 000000000..510cd9723 Binary files /dev/null and b/data/fonts/GlowSansJ-Compressed-Book.otf differ diff --git a/data/fonts/GlowSansJCompressed-Book.otf b/data/fonts/GlowSansJCompressed-Book.otf deleted file mode 100644 index afc333744..000000000 Binary files a/data/fonts/GlowSansJCompressed-Book.otf and /dev/null differ diff --git a/data/fonts/Icons.otf b/data/fonts/Icons.otf deleted file mode 100644 index d9390449d..000000000 Binary files a/data/fonts/Icons.otf and /dev/null differ diff --git a/data/fonts/SourceHanSansSC-Regular.otf b/data/fonts/SourceHanSans.ttc similarity index 84% rename from data/fonts/SourceHanSansSC-Regular.otf rename to data/fonts/SourceHanSans.ttc index 88e3a3546..cf5efd6d8 100644 Binary files a/data/fonts/SourceHanSansSC-Regular.otf and b/data/fonts/SourceHanSans.ttc differ diff --git a/data/fonts/index.json b/data/fonts/index.json new file mode 100644 index 000000000..2f2e9c460 --- /dev/null +++ b/data/fonts/index.json @@ -0,0 +1,19 @@ +{ + "font files": [ + "DejaVuSans.ttf", + "Font_Awesome_6_Free-Solid-900.otf", + "GlowSansJ-Compressed-Book.otf", + "SourceHanSans.ttc" + ], + "default": "DejaVu Sans", + "language variants": { + "japanese": "Glow Sans J Compressed Book", + "korean": "Source Han Sans K", + "simplified_chinese": "Source Han Sans SC", + "traditional_chinese": "Source Han Sans TC" + }, + "fallbacks": [ + "Source Han Sans" + ], + "icon": "Font Awesome 6 Free" +} diff --git a/license.txt b/license.txt index 7b3eee05b..1a931aa2f 100644 --- a/license.txt +++ b/license.txt @@ -23,19 +23,44 @@ freely, subject to the following restrictions: All content under 'data' except the assets, font, language & skin files, (which have their own licenses) are released under CC-BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0/). -SIL OFL 1.1 License for the 'Icon.otf' ('Font Awesome 6 Free-Solid-900.otf') file: -Copyright (c) 2022 Fonticons, Inc. (https://fontawesome.com) +DejaVuSans.ttf: +--------------- + +Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. +Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below) + +Font_Awesome_6_Free-Solid-900.otf: +---------------------------------- + +Copyright (c) 2023 Fonticons, Inc. (https://fontawesome.com) with Reserved Font Name: "Font Awesome". This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL -SIL OPEN FONT LICENSE -Version 1.1 - 26 February 2007 +GlowSansJ-Compressed-Book.otf: +------------------------------ -The other fonts are released under CC-BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0/). +© 2020 Project Wêlai + +Developer: Celestial Phineas + +Glow Sans fonts are released under SIL Open Font License 1.1. + +SourceHanSans.ttc: +------------------ + +Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font +Name 'Source'. Source is a trademark of Adobe in the United States +and/or other countries. + +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL ------------------------------------------------------------------------ @@ -47,3 +72,189 @@ check the individual libraries. With that being said, contact us if there is anything you want to do that the license does not permit. + +------------------------------------------------------------------------ + + +SIL OPEN FONT LICENSE +--------------------- +Version 1.1 - 26 February 2007 + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting — in part or in whole — any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + + +Bitstream Vera Fonts Copyright +------------------------------ + +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is +a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the fonts accompanying this license ("Fonts") and associated +documentation files (the "Font Software"), to reproduce and distribute the +Font Software, including without limitation the rights to use, copy, merge, +publish, distribute, and/or sell copies of the Font Software, and to permit +persons to whom the Font Software is furnished to do so, subject to the +following conditions: + +The above copyright and trademark notices and this permission notice shall +be included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular +the designs of glyphs or characters in the Fonts may be modified and +additional glyphs or characters may be added to the Fonts, only if the fonts +are renamed to names not containing either the words "Bitstream" or the word +"Vera". + +This License becomes null and void to the extent applicable to Fonts or Font +Software that has been modified and is distributed under the "Bitstream +Vera" names. + +The Font Software may be sold as part of a larger software package but no +copy of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, +TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME +FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING +ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE +FONT SOFTWARE. + +Except as contained in this notice, the names of Gnome, the Gnome +Foundation, and Bitstream Inc., shall not be used in advertising or +otherwise to promote the sale, use or other dealings in this Font Software +without prior written authorization from the Gnome Foundation or Bitstream +Inc., respectively. For further information, contact: fonts at gnome dot +org. + +Arev Fonts Copyright +------------------------------ + +Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the fonts accompanying this license ("Fonts") and +associated documentation files (the "Font Software"), to reproduce +and distribute the modifications to the Bitstream Vera Font Software, +including without limitation the rights to use, copy, merge, publish, +distribute, and/or sell copies of the Font Software, and to permit +persons to whom the Font Software is furnished to do so, subject to +the following conditions: + +The above copyright and trademark notices and this permission notice +shall be included in all copies of one or more of the Font Software +typefaces. + +The Font Software may be modified, altered, or added to, and in +particular the designs of glyphs or characters in the Fonts may be +modified and additional glyphs or characters may be added to the +Fonts, only if the fonts are renamed to names not containing either +the words "Tavmjong Bah" or the word "Arev". + +This License becomes null and void to the extent applicable to Fonts +or Font Software that has been modified and is distributed under the +"Tavmjong Bah Arev" names. + +The Font Software may be sold as part of a larger software package but +no copy of one or more of the Font Software typefaces may be sold by +itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL +TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the name of Tavmjong Bah shall not +be used in advertising or otherwise to promote the sale, use or other +dealings in this Font Software without prior written authorization +from Tavmjong Bah. For further information, contact: tavmjong @ free +. fr. diff --git a/src/engine/client.h b/src/engine/client.h index 5ee3e8608..3f7db9faa 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -180,7 +180,6 @@ public: virtual void SwitchWindowScreen(int Index) = 0; virtual void SetWindowParams(int FullscreenMode, bool IsBorderless, bool AllowResizing) = 0; virtual void ToggleWindowVSync() = 0; - virtual void LoadFont() = 0; virtual void Notify(const char *pTitle, const char *pMessage) = 0; virtual void UpdateAndSwap() = 0; diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 8191305d6..e6eab3217 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -3050,8 +3050,9 @@ void CClient::Run() } #endif - // init font rendering - Kernel()->RequestInterface()->Init(); + // init text render + IEngineTextRender *pTextRender = Kernel()->RequestInterface(); + pTextRender->Init(); // init the input Input()->Init(); @@ -3408,6 +3409,9 @@ void CClient::Run() m_aNetClient[i].Close(); delete m_pEditor; + + // shutdown text render while graphics are still available + pTextRender->Shutdown(); } bool CClient::InitNetworkClient(char *pError, size_t ErrorSize) @@ -4287,61 +4291,6 @@ void CClient::ToggleWindowVSync() g_Config.m_GfxVsync ^= 1; } -void CClient::LoadFont() -{ - static CFont *pDefaultFont = 0; - char aFilename[IO_MAX_PATH_LENGTH]; - char aBuff[1024]; - const char *pFontFile = "fonts/DejaVuSans.ttf"; - const char *apFallbackFontFiles[] = - { - "fonts/GlowSansJCompressed-Book.otf", - "fonts/SourceHanSansSC-Regular.otf", - }; - IOHANDLE File = Storage()->OpenFile(pFontFile, IOFLAG_READ, IStorage::TYPE_ALL, aFilename, sizeof(aFilename)); - if(File) - { - IEngineTextRender *pTextRender = Kernel()->RequestInterface(); - pDefaultFont = pTextRender->GetFont(aFilename); - if(pDefaultFont == NULL) - { - void *pBuf; - unsigned Size; - io_read_all(File, &pBuf, &Size); - pDefaultFont = pTextRender->LoadFont(aFilename, (unsigned char *)pBuf, Size); - } - io_close(File); - - for(auto &pFallbackFontFile : apFallbackFontFiles) - { - bool FontLoaded = false; - File = Storage()->OpenFile(pFallbackFontFile, IOFLAG_READ, IStorage::TYPE_ALL, aFilename, sizeof(aFilename)); - if(File) - { - void *pBuf; - unsigned Size; - io_read_all(File, &pBuf, &Size); - io_close(File); - FontLoaded = pTextRender->LoadFallbackFont(pDefaultFont, aFilename, (unsigned char *)pBuf, Size); - } - - if(!FontLoaded) - { - str_format(aBuff, std::size(aBuff), "failed to load the fallback font. filename='%s'", pFallbackFontFile); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "gameclient", aBuff); - } - } - - pTextRender->SetDefaultFont(pDefaultFont); - } - - if(!pDefaultFont) - { - str_format(aBuff, std::size(aBuff), "failed to load font. filename='%s'", pFontFile); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "gameclient", aBuff); - } -} - void CClient::Notify(const char *pTitle, const char *pMessage) { if(m_pGraphics->WindowActive() || !g_Config.m_ClShowNotifications) diff --git a/src/engine/client/client.h b/src/engine/client/client.h index d23b9da0f..9d59efd48 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -510,7 +510,6 @@ public: void SwitchWindowScreen(int Index) override; void SetWindowParams(int FullscreenMode, bool IsBorderless, bool AllowResizing) override; void ToggleWindowVSync() override; - void LoadFont() override; void Notify(const char *pTitle, const char *pMessage) override; void BenchmarkQuit(int Seconds, const char *pFilename); diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp index 428aabf14..c264c7d33 100644 --- a/src/engine/client/text.cpp +++ b/src/engine/client/text.cpp @@ -1,9 +1,12 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include #include #include +#include #include +#include #include #include @@ -14,16 +17,33 @@ #include #include #include -#include +#include +#include #include using namespace std::chrono_literals; -struct SFontSizeChar +enum { - int m_ID; + FONT_NAME_SIZE = 128, +}; - // these values are scaled to the pFont size +struct SGlyph +{ + enum class EState + { + UNINITIALIZED, + RENDERED, + ERROR, + }; + EState m_State = EState::UNINITIALIZED; + + int m_FontSize; + FT_Face m_Face; + int m_Chr; + FT_UInt m_GlyphIndex; + + // these values are scaled to the font size // width * font_size == real_size float m_Width; float m_Height; @@ -34,276 +54,207 @@ struct SFontSizeChar float m_AdvanceX; float m_aUVs[4]; - FT_UInt m_GlyphIndex; }; -typedef vector4_base STextCharQuadVertexColor; - -struct STextCharQuadVertex +struct SGlyphKeyHash { - STextCharQuadVertex() + size_t operator()(const std::tuple &Key) const { - m_Color.r = m_Color.g = m_Color.b = m_Color.a = 255; + size_t Hash = 17; + Hash = Hash * 31 + std::hash()(std::get<0>(Key)); + Hash = Hash * 31 + std::hash()(std::get<1>(Key)); + Hash = Hash * 31 + std::hash()(std::get<2>(Key)); + return Hash; } - float m_X, m_Y; - // do not use normalized floats as coordinates, since the texture might grow - float m_U, m_V; - STextCharQuadVertexColor m_Color; }; -struct STextCharQuad +struct SGlyphKeyEquals { - STextCharQuadVertex m_aVertices[4]; + bool operator()(const std::tuple &Lhs, const std::tuple &Rhs) const + { + return std::get<0>(Lhs) == std::get<0>(Rhs) && std::get<1>(Lhs) == std::get<1>(Rhs) && std::get<2>(Lhs) == std::get<2>(Rhs); + } }; struct STextureSkyline { // the height of each column - std::vector m_vCurHeightOfPixelColumn; + std::vector m_vCurHeightOfPixelColumn; }; -struct SFontSizeData -{ - int m_FontSize; - FT_Face *m_pFace; - - std::map m_Chars; -}; - -constexpr int MIN_FONT_SIZE = 6; -constexpr int MAX_FONT_SIZE = 128; -constexpr int NUM_FONT_SIZES = (MAX_FONT_SIZE - MIN_FONT_SIZE + 1); - -class CFont +class CGlyphMap { public: - ~CFont() + enum { - free(m_pBuf); - delete[] m_apTextureData[0]; - delete[] m_apTextureData[1]; - for(auto &FtFallbackFont : m_vFtFallbackFonts) - { - free(FtFallbackFont.m_pBuf); - } - } - - void InitFontSizes() - { - for(int i = 0; i < NUM_FONT_SIZES; ++i) - { - m_aFontSizes[i].m_FontSize = i + MIN_FONT_SIZE; - m_aFontSizes[i].m_pFace = &this->m_FtFace; - m_aFontSizes[i].m_Chars.clear(); - } - } - - SFontSizeData *GetFontSize(int Pixelsize) - { - int FontSize = (Pixelsize >= MIN_FONT_SIZE ? (Pixelsize > MAX_FONT_SIZE ? MAX_FONT_SIZE : Pixelsize) : MIN_FONT_SIZE); - return &m_aFontSizes[FontSize - MIN_FONT_SIZE]; - } - - void *m_pBuf; - char m_aFilename[IO_MAX_PATH_LENGTH]; - FT_Face m_FtFace; - - struct SFontFallBack - { - void *m_pBuf; - char m_aFilename[IO_MAX_PATH_LENGTH]; - FT_Face m_FtFace; + FONT_TEXTURE_FILL = 0, // the main text body + FONT_TEXTURE_OUTLINE, // the text outline + NUM_FONT_TEXTURES, }; - std::vector m_vFtFallbackFonts; +private: + /** + * The initial dimension of the atlas textures. + * Results in 1 MB of memory being used per texture. + */ + static constexpr int INITIAL_ATLAS_DIMENSION = 1024; - SFontSizeData m_aFontSizes[NUM_FONT_SIZES]; + /** + * The maximum dimension of the atlas textures. + * Results in 256 MB of memory being used per texture. + */ + static constexpr int MAXIMUM_ATLAS_DIMENSION = 16 * 1024; - IGraphics::CTextureHandle m_aTextures[2]; - // keep the full texture, because opengl doesn't provide texture copying - uint8_t *m_apTextureData[2]; + /** + * The minimum supported font size. + */ + static constexpr int MIN_FONT_SIZE = 6; - // width and height are the same - int m_aCurTextureDimensions[2]; + /** + * The maximum supported font size. + */ + static constexpr int MAX_FONT_SIZE = 128; - STextureSkyline m_aTextureSkyline[2]; -}; + /** + * White square to indicate missing glyph. + */ + static constexpr int REPLACEMENT_CHARACTER = 0x25a1; -struct STextString -{ - int m_QuadBufferObjectIndex; - int m_QuadBufferContainerIndex; - size_t m_QuadNum; - int m_SelectionQuadContainerIndex; - - std::vector m_vCharacterQuads; -}; - -struct STextContainer -{ - STextContainer() { Reset(); } - - CFont *m_pFont; - int m_FontSize; - STextString m_StringInfo; - - // keep these values to calculate offsets - float m_AlignedStartX; - float m_AlignedStartY; - float m_X; - float m_Y; - - int m_Flags; - int m_LineCount; - int m_GlyphCount; - int m_CharCount; - int m_MaxLines; - - float m_StartX; - float m_StartY; - float m_LineWidth; - float m_UnscaledFontSize; - - unsigned m_RenderFlags; - - bool m_HasCursor; - bool m_ForceCursorRendering; - bool m_HasSelection; - - bool m_SingleTimeUse; - - STextBoundingBox m_BoundingBox; - - // prefix of the containers text stored for debugging purposes - char m_aDebugText[32]; - - STextContainerIndex m_ContainerIndex; - - void Reset() - { - m_pFont = nullptr; - m_FontSize = 0; - - m_StringInfo.m_QuadBufferObjectIndex = m_StringInfo.m_QuadBufferContainerIndex = m_StringInfo.m_SelectionQuadContainerIndex = -1; - m_StringInfo.m_QuadNum = 0; - m_StringInfo.m_vCharacterQuads.clear(); - - m_AlignedStartX = m_AlignedStartY = m_X = m_Y = 0.f; - m_Flags = m_LineCount = m_CharCount = m_GlyphCount = 0; - m_MaxLines = -1; - m_StartX = m_StartY = 0.f; - m_LineWidth = -1.f; - m_UnscaledFontSize = 0.f; - - m_RenderFlags = 0; - - m_HasCursor = false; - m_HasSelection = false; - m_ForceCursorRendering = false; - - m_SingleTimeUse = false; - - m_BoundingBox = {0.0f, 0.0f, 0.0f, 0.0f}; - - m_aDebugText[0] = '\0'; - - m_ContainerIndex = STextContainerIndex{}; - } -}; - -class CTextRender : public IEngineTextRender -{ IGraphics *m_pGraphics; IGraphics *Graphics() { return m_pGraphics; } - unsigned m_RenderFlags; + // Atlas textures and data + IGraphics::CTextureHandle m_aTextures[NUM_FONT_TEXTURES]; + // Width and height are the same, all font textures have the same dimensions + size_t m_TextureDimension = INITIAL_ATLAS_DIMENSION; + // Keep the full texture data, because OpenGL doesn't provide texture copying + uint8_t *m_apTextureData[NUM_FONT_TEXTURES]; + STextureSkyline m_TextureSkyline; + std::unordered_map, SGlyph, SGlyphKeyHash, SGlyphKeyEquals> m_Glyphs; - std::vector m_vpTextContainers; - std::vector m_vTextContainerIndices; - int m_FirstFreeTextContainerIndex; + // Data used for rendering glyphs + uint8_t m_aaGlyphData[NUM_FONT_TEXTURES][64 * 1024]; - SBufferContainerInfo m_DefaultTextContainerInfo; + // Font faces + FT_Face m_DefaultFace; + FT_Face m_IconFace; + FT_Face m_VariantFace; + FT_Face m_SelectedFace; + std::vector m_vFallbackFaces; + std::vector m_vFtFaces; - std::vector m_vpFonts; - CFont *m_pCurFont; - - std::chrono::nanoseconds m_CursorRenderTime; - - int GetFreeTextContainerIndex() + FT_Face GetFaceByName(const char *pFamilyName) { - if(m_FirstFreeTextContainerIndex == -1) + if(pFamilyName == nullptr || pFamilyName[0] == '\0') + return nullptr; + + FT_Face FamilyNameMatch = nullptr; + char aFamilyStyleName[FONT_NAME_SIZE]; + + for(const auto &CurrentFace : m_vFtFaces) { - int Index = (int)m_vTextContainerIndices.size(); - m_vTextContainerIndices.push_back(Index); - return Index; - } - else - { - int Index = m_FirstFreeTextContainerIndex; - m_FirstFreeTextContainerIndex = m_vTextContainerIndices[Index]; - m_vTextContainerIndices[Index] = Index; - return Index; - } - } + // Best match: font face with matching family and style name + str_format(aFamilyStyleName, sizeof(aFamilyStyleName), "%s %s", CurrentFace->family_name, CurrentFace->style_name); + if(str_comp(pFamilyName, aFamilyStyleName) == 0) + { + return CurrentFace; + } - void FreeTextContainerIndex(STextContainerIndex &Index) - { - m_vTextContainerIndices[Index.m_Index] = m_FirstFreeTextContainerIndex; - m_FirstFreeTextContainerIndex = Index.m_Index; - Index.Reset(); - } - - void FreeTextContainer(STextContainerIndex &Index) - { - m_vpTextContainers[Index.m_Index]->Reset(); - FreeTextContainerIndex(Index); - } - - STextContainer &GetTextContainer(const STextContainerIndex &Index) - { - dbg_assert(Index.Valid(), "Text container index was invalid."); - if(Index.m_Index >= (int)m_vpTextContainers.size()) - { - int Size = (int)m_vpTextContainers.size(); - for(int i = 0; i < (Index.m_Index + 1) - Size; ++i) - m_vpTextContainers.push_back(new STextContainer()); + // Second best match: font face with matching family + if(!FamilyNameMatch && str_comp(pFamilyName, CurrentFace->family_name) == 0) + { + FamilyNameMatch = CurrentFace; + } } - if(m_vpTextContainers[Index.m_Index]->m_ContainerIndex.m_UseCount.get() != Index.m_UseCount.get()) + return FamilyNameMatch; + } + + bool IncreaseGlyphMapSize() + { + if(m_TextureDimension >= MAXIMUM_ATLAS_DIMENSION) + return false; + + const size_t NewTextureDimension = m_TextureDimension * 2; + log_debug("textrender", "Increasing atlas dimension to %" PRIzu " (%" PRIzu " MB used for textures)", NewTextureDimension, (NewTextureDimension / 1024) * (NewTextureDimension / 1024) * NUM_FONT_TEXTURES); + UnloadTextures(); + + for(auto &pTextureData : m_apTextureData) { - m_vpTextContainers[Index.m_Index]->m_ContainerIndex = Index; + uint8_t *pTmpTexBuffer = new uint8_t[NewTextureDimension * NewTextureDimension]; + mem_zero(pTmpTexBuffer, NewTextureDimension * NewTextureDimension * sizeof(uint8_t)); + for(size_t y = 0; y < m_TextureDimension; ++y) + { + mem_copy(&pTmpTexBuffer[y * NewTextureDimension], &pTextureData[y * m_TextureDimension], m_TextureDimension); + } + delete[] pTextureData; + pTextureData = pTmpTexBuffer; } - return *m_vpTextContainers[Index.m_Index]; + m_TextureSkyline.m_vCurHeightOfPixelColumn.resize(NewTextureDimension, 0); + m_TextureDimension = NewTextureDimension; + + UploadTextures(); + return true; } - int WordLength(const char *pText) const + void UploadTextures() { - const char *pCursor = pText; - while(true) + const size_t NewTextureSize = m_TextureDimension * m_TextureDimension; + void *pTmpTextFillData = malloc(NewTextureSize); // NOLINT(clang-analyzer-optin.portability.UnixAPI) + void *pTmpTextOutlineData = malloc(NewTextureSize); // NOLINT(clang-analyzer-optin.portability.UnixAPI) + mem_copy(pTmpTextFillData, m_apTextureData[FONT_TEXTURE_FILL], NewTextureSize); + mem_copy(pTmpTextOutlineData, m_apTextureData[FONT_TEXTURE_OUTLINE], NewTextureSize); + Graphics()->LoadTextTextures(m_TextureDimension, m_TextureDimension, m_aTextures[FONT_TEXTURE_FILL], m_aTextures[FONT_TEXTURE_OUTLINE], pTmpTextFillData, pTmpTextOutlineData); + } + + void UnloadTextures() + { + Graphics()->UnloadTextTextures(m_aTextures[FONT_TEXTURE_FILL], m_aTextures[FONT_TEXTURE_OUTLINE]); + } + + FT_UInt GetCharGlyph(int Chr, FT_Face *pFace, bool AllowReplacementCharacter) + { + for(FT_Face Face : {m_SelectedFace, m_DefaultFace, m_VariantFace}) { - if(*pCursor == '\0') - return pCursor - pText; - if(*pCursor == '\n' || *pCursor == '\t' || *pCursor == ' ') - return pCursor - pText + 1; - str_utf8_decode(&pCursor); + if(Face && Face->charmap) + { + FT_UInt GlyphIndex = FT_Get_Char_Index(Face, (FT_ULong)Chr); + if(GlyphIndex) + { + *pFace = Face; + return GlyphIndex; + } + } } - } - ColorRGBA m_Color; - ColorRGBA m_OutlineColor; - ColorRGBA m_SelectionColor; - CFont *m_pDefaultFont; + for(const auto &FallbackFace : m_vFallbackFaces) + { + if(FallbackFace->charmap) + { + FT_UInt GlyphIndex = FT_Get_Char_Index(FallbackFace, (FT_ULong)Chr); + if(GlyphIndex) + { + *pFace = FallbackFace; + return GlyphIndex; + } + } + } - FT_Library m_FTLibrary; + if(!m_DefaultFace || !m_DefaultFace->charmap || !AllowReplacementCharacter) + { + *pFace = nullptr; + return 0; + } - void SetRenderFlags(unsigned Flags) override - { - m_RenderFlags = Flags; - } + FT_UInt GlyphIndex = FT_Get_Char_Index(m_DefaultFace, (FT_ULong)REPLACEMENT_CHARACTER); + *pFace = m_DefaultFace; - unsigned GetRenderFlags() const override - { - return m_RenderFlags; + if(GlyphIndex == 0) + { + log_debug("textrender", "Default font has no glyph for either %d or replacement char %d.", Chr, REPLACEMENT_CHARACTER); + } + + return GlyphIndex; } void Grow(const unsigned char *pIn, unsigned char *pOut, int w, int h, int OutlineCount) const @@ -334,51 +285,6 @@ class CTextRender : public IEngineTextRender } } - void InitTextures(size_t Width, size_t Height, IGraphics::CTextureHandle (&aTextures)[2], uint8_t *(&aTextureData)[2]) - { - const size_t NewTextureSize = Width * Height; - void *pTmpTextData = malloc(NewTextureSize); - void *pTmpTextOutlineData = malloc(NewTextureSize); - mem_copy(pTmpTextData, aTextureData[0], NewTextureSize); - mem_copy(pTmpTextOutlineData, aTextureData[1], NewTextureSize); - Graphics()->LoadTextTextures(Width, Height, aTextures[0], aTextures[1], pTmpTextData, pTmpTextOutlineData); - } - - void UnloadTextures(IGraphics::CTextureHandle (&aTextures)[2]) - { - Graphics()->UnloadTextTextures(aTextures[0], aTextures[1]); - } - - void IncreaseFontTextureImpl(CFont *pFont, size_t TextureIndex, size_t NewDimensions) const - { - unsigned char *pTmpTexBuffer = new unsigned char[NewDimensions * NewDimensions]; - mem_zero(pTmpTexBuffer, NewDimensions * NewDimensions * sizeof(unsigned char)); - - for(int y = 0; y < pFont->m_aCurTextureDimensions[TextureIndex]; ++y) - { - for(int x = 0; x < pFont->m_aCurTextureDimensions[TextureIndex]; ++x) - { - pTmpTexBuffer[x + y * NewDimensions] = pFont->m_apTextureData[TextureIndex][x + y * pFont->m_aCurTextureDimensions[TextureIndex]]; - } - } - - delete[] pFont->m_apTextureData[TextureIndex]; - pFont->m_apTextureData[TextureIndex] = pTmpTexBuffer; - pFont->m_aCurTextureDimensions[TextureIndex] = NewDimensions; - pFont->m_aTextureSkyline[TextureIndex].m_vCurHeightOfPixelColumn.resize(NewDimensions, 0); - } - - void IncreaseFontTexture(CFont *pFont) - { - const int NewDimensions = pFont->m_aCurTextureDimensions[0] * 2; - UnloadTextures(pFont->m_aTextures); - - IncreaseFontTextureImpl(pFont, 0, NewDimensions); - IncreaseFontTextureImpl(pFont, 1, NewDimensions); - - InitTextures(NewDimensions, NewDimensions, pFont->m_aTextures, pFont->m_apTextureData); - } - int AdjustOutlineThicknessToFontSize(int OutlineThickness, int FontSize) const { if(FontSize > 48) @@ -388,40 +294,35 @@ class CTextRender : public IEngineTextRender return OutlineThickness; } - void UploadGlyph(CFont *pFont, int TextureIndex, int PosX, int PosY, int Width, int Height, const unsigned char *pData) + void UploadGlyph(int TextureIndex, int PosX, int PosY, size_t Width, size_t Height, const unsigned char *pData) { - for(int y = 0; y < Height; ++y) + for(size_t y = 0; y < Height; ++y) { - for(int x = 0; x < Width; ++x) - { - pFont->m_apTextureData[TextureIndex][x + PosX + ((y + PosY) * pFont->m_aCurTextureDimensions[TextureIndex])] = pData[x + y * Width]; - } + mem_copy(&m_apTextureData[TextureIndex][PosX + ((y + PosY) * m_TextureDimension)], &pData[y * Width], Width); } - Graphics()->UpdateTextTexture(pFont->m_aTextures[TextureIndex], PosX, PosY, Width, Height, pData); + Graphics()->UpdateTextTexture(m_aTextures[TextureIndex], PosX, PosY, Width, Height, pData); } - bool GetCharacterSpace(CFont *pFont, int TextureIndex, int Width, int Height, int &PosX, int &PosY) const + bool GetCharacterSpace(size_t Width, size_t Height, int &PosX, int &PosY) { - if(pFont->m_aCurTextureDimensions[TextureIndex] < Width) - return false; - if(pFont->m_aCurTextureDimensions[TextureIndex] < Height) + if(m_TextureDimension < Width || m_TextureDimension < Height) return false; // skyline bottom left algorithm - std::vector &vSkylineHeights = pFont->m_aTextureSkyline[TextureIndex].m_vCurHeightOfPixelColumn; + std::vector &vSkylineHeights = m_TextureSkyline.m_vCurHeightOfPixelColumn; - // search a fitting area with less pixel loss - int SmallestPixelLossAreaX = 0; - int SmallestPixelLossAreaY = pFont->m_aCurTextureDimensions[TextureIndex] + 1; - int SmallestPixelLossCurPixelLoss = pFont->m_aCurTextureDimensions[TextureIndex] * pFont->m_aCurTextureDimensions[TextureIndex]; + // search a fitting area with the least pixel loss + size_t SmallestPixelLossAreaX = 0; + size_t SmallestPixelLossAreaY = m_TextureDimension + 1; + size_t SmallestPixelLossCurPixelLoss = m_TextureDimension * m_TextureDimension; bool FoundAnyArea = false; for(size_t i = 0; i < vSkylineHeights.size(); i++) { - int CurHeight = vSkylineHeights[i]; - int CurPixelLoss = 0; + size_t CurHeight = vSkylineHeights[i]; + size_t CurPixelLoss = 0; // find width pixels, and we are happy - int AreaWidth = 1; + size_t AreaWidth = 1; for(size_t n = i + 1; n < i + Width && n < vSkylineHeights.size(); ++n) { ++AreaWidth; @@ -442,7 +343,7 @@ class CTextRender : public IEngineTextRender } // if the area is too high, continue - if(CurHeight + Height > pFont->m_aCurTextureDimensions[TextureIndex]) + if(CurHeight + Height > m_TextureDimension) continue; // if the found area fits our needs, check if we can use it if(AreaWidth == Width) @@ -466,7 +367,7 @@ class CTextRender : public IEngineTextRender { PosX = SmallestPixelLossAreaX; PosY = SmallestPixelLossAreaY; - for(int i = PosX; i < PosX + Width; ++i) + for(size_t i = PosX; i < PosX + Width; ++i) { vSkylineHeights[i] = PosY + Height; } @@ -475,55 +376,17 @@ class CTextRender : public IEngineTextRender return false; } - // 128k * 2 of data used for rendering glyphs - unsigned char m_aGlyphData[(1024 / 4) * (1024 / 4)]; - unsigned char m_aGlyphDataOutlined[(1024 / 4) * (1024 / 4)]; - - void RenderGlyph(CFont *pFont, SFontSizeData *pSizeData, int Chr) + bool RenderGlyph(SGlyph &Glyph) { - FT_Face FtFace = pFont->m_FtFace; + FT_Set_Pixel_Sizes(Glyph.m_Face, 0, Glyph.m_FontSize); - FT_Set_Pixel_Sizes(FtFace, 0, pSizeData->m_FontSize); - - FT_UInt GlyphIndex = 0; - if(FtFace->charmap) - GlyphIndex = FT_Get_Char_Index(FtFace, (FT_ULong)Chr); - - if(GlyphIndex == 0) + if(FT_Load_Glyph(Glyph.m_Face, Glyph.m_GlyphIndex, FT_LOAD_RENDER | FT_LOAD_NO_BITMAP)) { - for(const CFont::SFontFallBack &FallbackFont : pFont->m_vFtFallbackFonts) - { - FtFace = FallbackFont.m_FtFace; - FT_Set_Pixel_Sizes(FtFace, 0, pSizeData->m_FontSize); - - if(FtFace->charmap) - GlyphIndex = FT_Get_Char_Index(FtFace, (FT_ULong)Chr); - - if(GlyphIndex != 0) - break; - } - - if(GlyphIndex == 0) - { - const int ReplacementChr = 0x25a1; // White square to indicate missing glyph - FtFace = pFont->m_FtFace; - GlyphIndex = FT_Get_Char_Index(FtFace, (FT_ULong)ReplacementChr); - - if(GlyphIndex == 0) - { - dbg_msg("textrender", "font has no glyph for either %d or replacement char %d", Chr, ReplacementChr); - return; - } - } + log_debug("textrender", "Error loading glyph. Chr=%d GlyphIndex=%u", Glyph.m_Chr, Glyph.m_GlyphIndex); + return false; } - if(FT_Load_Glyph(FtFace, GlyphIndex, FT_LOAD_RENDER | FT_LOAD_NO_BITMAP)) - { - dbg_msg("textrender", "error loading glyph %d", Chr); - return; - } - - const FT_Bitmap *pBitmap = &FtFace->glyph->bitmap; + const FT_Bitmap *pBitmap = &Glyph.m_Face->glyph->bitmap; const unsigned RealWidth = pBitmap->width; const unsigned RealHeight = pBitmap->rows; @@ -534,7 +397,7 @@ class CTextRender : public IEngineTextRender int y = 0; if(RealWidth > 0) { - OutlineThickness = AdjustOutlineThicknessToFontSize(1, pSizeData->m_FontSize); + OutlineThickness = AdjustOutlineThicknessToFontSize(1, Glyph.m_FontSize); x += (OutlineThickness + 1); y += (OutlineThickness + 1); } @@ -547,127 +410,559 @@ class CTextRender : public IEngineTextRender if(Width > 0 && Height > 0) { - // prepare glyph data - mem_zero(m_aGlyphData, (size_t)Width * Height); + // find space in atlas, or increase size if necessary + while(!GetCharacterSpace(Width, Height, X, Y)) + { + if(!IncreaseGlyphMapSize()) + { + log_debug("textrender", "Cannot fit glyph into atlas, which is already at maximum size. Chr=%d GlyphIndex=%u", Glyph.m_Chr, Glyph.m_GlyphIndex); + return false; + } + } - for(unsigned py = 0; py < pBitmap->rows; py++) - for(unsigned px = 0; px < pBitmap->width; px++) - m_aGlyphData[(py + y) * Width + px + x] = pBitmap->buffer[py * pBitmap->width + px]; + // prepare glyph data + mem_zero(m_aaGlyphData[FONT_TEXTURE_FILL], (size_t)Width * Height * sizeof(uint8_t)); + for(unsigned py = 0; py < pBitmap->rows; ++py) + { + mem_copy(&m_aaGlyphData[FONT_TEXTURE_FILL][(py + y) * Width + x], &pBitmap->buffer[py * pBitmap->width], pBitmap->width); + } // upload the glyph - while(!GetCharacterSpace(pFont, 0, (int)Width, (int)Height, X, Y)) - { - IncreaseFontTexture(pFont); - } - UploadGlyph(pFont, 0, X, Y, (int)Width, (int)Height, m_aGlyphData); - - Grow(m_aGlyphData, m_aGlyphDataOutlined, Width, Height, OutlineThickness); - - while(!GetCharacterSpace(pFont, 1, (int)Width, (int)Height, X, Y)) - { - IncreaseFontTexture(pFont); - } - UploadGlyph(pFont, 1, X, Y, (int)Width, (int)Height, m_aGlyphDataOutlined); + UploadGlyph(FONT_TEXTURE_FILL, X, Y, Width, Height, m_aaGlyphData[FONT_TEXTURE_FILL]); + Grow(m_aaGlyphData[FONT_TEXTURE_FILL], m_aaGlyphData[FONT_TEXTURE_OUTLINE], Width, Height, OutlineThickness); + UploadGlyph(FONT_TEXTURE_OUTLINE, X, Y, Width, Height, m_aaGlyphData[FONT_TEXTURE_OUTLINE]); } - // set char info + // set glyph info { - SFontSizeChar *pFontchr = &pSizeData->m_Chars[Chr]; - const int BMPHeight = pBitmap->rows + y * 2; - const int BMPWidth = pBitmap->width + x * 2; + const int BmpWidth = pBitmap->width + x * 2; + const int BmpHeight = pBitmap->rows + y * 2; - pFontchr->m_ID = Chr; - pFontchr->m_Height = Height; - pFontchr->m_Width = Width; - pFontchr->m_CharHeight = RealHeight; - pFontchr->m_CharWidth = RealWidth; - pFontchr->m_OffsetX = (FtFace->glyph->metrics.horiBearingX >> 6); - pFontchr->m_OffsetY = -((FtFace->glyph->metrics.height >> 6) - (FtFace->glyph->metrics.horiBearingY >> 6)); - pFontchr->m_AdvanceX = (FtFace->glyph->advance.x >> 6); + Glyph.m_Height = Height; + Glyph.m_Width = Width; + Glyph.m_CharHeight = RealHeight; + Glyph.m_CharWidth = RealWidth; + Glyph.m_OffsetX = (Glyph.m_Face->glyph->metrics.horiBearingX >> 6); + Glyph.m_OffsetY = -((Glyph.m_Face->glyph->metrics.height >> 6) - (Glyph.m_Face->glyph->metrics.horiBearingY >> 6)); + Glyph.m_AdvanceX = (Glyph.m_Face->glyph->advance.x >> 6); - pFontchr->m_aUVs[0] = X; - pFontchr->m_aUVs[1] = Y; - pFontchr->m_aUVs[2] = pFontchr->m_aUVs[0] + BMPWidth; - pFontchr->m_aUVs[3] = pFontchr->m_aUVs[1] + BMPHeight; - pFontchr->m_GlyphIndex = GlyphIndex; + Glyph.m_aUVs[0] = X; + Glyph.m_aUVs[1] = Y; + Glyph.m_aUVs[2] = Glyph.m_aUVs[0] + BmpWidth; + Glyph.m_aUVs[3] = Glyph.m_aUVs[1] + BmpHeight; + + Glyph.m_State = SGlyph::EState::RENDERED; + } + return true; + } + +public: + CGlyphMap(IGraphics *pGraphics) + { + m_pGraphics = pGraphics; + for(auto &pTextureData : m_apTextureData) + { + pTextureData = new uint8_t[m_TextureDimension * m_TextureDimension]; + mem_zero(pTextureData, m_TextureDimension * m_TextureDimension * sizeof(uint8_t)); + } + + m_TextureSkyline.m_vCurHeightOfPixelColumn.resize(m_TextureDimension, 0); + UploadTextures(); + } + + ~CGlyphMap() + { + UnloadTextures(); + for(auto &pTextureData : m_apTextureData) + { + delete[] pTextureData; } } - const SFontSizeChar *GetChar(CFont *pFont, SFontSizeData *pSizeData, int Chr) + FT_Face DefaultFace() const { - const std::map::iterator it = pSizeData->m_Chars.find(Chr); - if(it == pSizeData->m_Chars.end()) + return m_DefaultFace; + } + + FT_Face IconFace() const + { + return m_IconFace; + } + + void AddFace(FT_Face Face) + { + m_vFtFaces.push_back(Face); + if(!m_DefaultFace) + m_DefaultFace = Face; + } + + void SetDefaultFaceByName(const char *pFamilyName) + { + m_DefaultFace = GetFaceByName(pFamilyName); + } + + void SetIconFaceByName(const char *pFamilyName) + { + m_IconFace = GetFaceByName(pFamilyName); + } + + void AddFallbackFaceByName(const char *pFamilyName) + { + FT_Face Face = GetFaceByName(pFamilyName); + if(Face != nullptr && std::find(m_vFallbackFaces.begin(), m_vFallbackFaces.end(), Face) == m_vFallbackFaces.end()) { - // render and add character - SFontSizeChar &FontSizeChr = pSizeData->m_Chars[Chr]; - RenderGlyph(pFont, pSizeData, Chr); - return &FontSizeChr; + m_vFallbackFaces.push_back(Face); + } + } + + void SetVariantFaceByName(const char *pFamilyName) + { + FT_Face Face = GetFaceByName(pFamilyName); + if(m_VariantFace != Face) + { + m_VariantFace = Face; + Clear(); // rebuild atlas after changing variant font + } + } + + void SetFontPreset(EFontPreset FontPreset) + { + switch(FontPreset) + { + case EFontPreset::DEFAULT_FONT: + m_SelectedFace = nullptr; + break; + case EFontPreset::ICON_FONT: + m_SelectedFace = m_IconFace; + break; + } + } + + void Clear() + { + for(size_t TextureIndex = 0; TextureIndex < NUM_FONT_TEXTURES; ++TextureIndex) + { + mem_zero(m_apTextureData[TextureIndex], m_TextureDimension * m_TextureDimension * sizeof(uint8_t)); + Graphics()->UpdateTextTexture(m_aTextures[TextureIndex], 0, 0, m_TextureDimension, m_TextureDimension, m_apTextureData[TextureIndex]); + } + + std::fill(m_TextureSkyline.m_vCurHeightOfPixelColumn.begin(), m_TextureSkyline.m_vCurHeightOfPixelColumn.end(), 0); + m_Glyphs.clear(); + } + + const SGlyph *GetGlyph(int Chr, int FontSize) + { + FontSize = clamp(FontSize, MIN_FONT_SIZE, MAX_FONT_SIZE); + + // Find glyph index and most appropriate font face. + FT_Face Face; + FT_UInt GlyphIndex = GetCharGlyph(Chr, &Face, false); + if(GlyphIndex == 0) + { + // Use replacement character if glyph could not be found, + // also retrieve replacement character from the atlas. + return Chr == REPLACEMENT_CHARACTER ? nullptr : GetGlyph(REPLACEMENT_CHARACTER, FontSize); + } + + // Check if glyph for this (font face, character, font size)-combination was already rendered. + SGlyph &Glyph = m_Glyphs[std::make_tuple(Face, Chr, FontSize)]; + if(Glyph.m_State == SGlyph::EState::RENDERED) + return &Glyph; + else if(Glyph.m_State == SGlyph::EState::ERROR) + return nullptr; + + // Else, render it. + Glyph.m_FontSize = FontSize; + Glyph.m_Face = Face; + Glyph.m_Chr = Chr; + Glyph.m_GlyphIndex = GlyphIndex; + if(RenderGlyph(Glyph)) + return &Glyph; + + // Use replacement character if the glyph could not be rendered, + // also retrieve replacement character from the atlas. + const SGlyph *pReplacementCharacter = Chr == REPLACEMENT_CHARACTER ? nullptr : GetGlyph(REPLACEMENT_CHARACTER, FontSize); + if(pReplacementCharacter) + { + Glyph = *pReplacementCharacter; + return &Glyph; + } + + // Keep failed glyph in the cache so we don't attempt to render it again, + // but set its state to ERROR so we don't return it to the text render. + Glyph.m_State = SGlyph::EState::ERROR; + return nullptr; + } + + vec2 Kerning(const SGlyph *pLeft, const SGlyph *pRight) const + { + if(pLeft != nullptr && pRight != nullptr && pLeft->m_Face == pRight->m_Face && pLeft->m_FontSize == pRight->m_FontSize) + { + FT_Vector Kerning = {0, 0}; + FT_Set_Pixel_Sizes(pLeft->m_Face, 0, pLeft->m_FontSize); + FT_Get_Kerning(pLeft->m_Face, pLeft->m_Chr, pRight->m_Chr, FT_KERNING_DEFAULT, &Kerning); + return vec2(Kerning.x >> 6, Kerning.y >> 6); + } + return vec2(0.0f, 0.0f); + } + + void UploadEntityLayerText(void *pTexBuff, size_t ImageColorChannelCount, int TexWidth, int TexHeight, int TexSubWidth, int TexSubHeight, const char *pText, int Length, float x, float y, int FontSize) + { + if(FontSize < 1) + return; + + const char *pCurrent = pText; + const char *pEnd = pCurrent + Length; + int WidthLastChars = 0; + + while(pCurrent < pEnd) + { + const char *pTmp = pCurrent; + const int NextCharacter = str_utf8_decode(&pTmp); + + if(NextCharacter) + { + FT_Face Face; + FT_UInt GlyphIndex = GetCharGlyph(NextCharacter, &Face, true); + if(GlyphIndex == 0) + { + pCurrent = pTmp; + continue; + } + + FT_Set_Pixel_Sizes(Face, 0, FontSize); + if(FT_Load_Char(Face, NextCharacter, FT_LOAD_RENDER | FT_LOAD_NO_BITMAP)) + { + log_debug("textrender", "Error loading glyph. Chr=%d GlyphIndex=%u", NextCharacter, GlyphIndex); + pCurrent = pTmp; + continue; + } + + const FT_Bitmap *pBitmap = &Face->glyph->bitmap; + + // prepare glyph data + const size_t GlyphDataSize = (size_t)pBitmap->width * pBitmap->rows * sizeof(uint8_t); + if(pBitmap->pixel_mode == FT_PIXEL_MODE_GRAY) + mem_copy(m_aaGlyphData[FONT_TEXTURE_FILL], pBitmap->buffer, GlyphDataSize); + else + mem_zero(m_aaGlyphData[FONT_TEXTURE_FILL], GlyphDataSize); + + uint8_t *pImageBuff = (uint8_t *)pTexBuff; + for(unsigned OffY = 0; OffY < pBitmap->rows; ++OffY) + { + for(unsigned OffX = 0; OffX < pBitmap->width; ++OffX) + { + const int ImgOffX = clamp(x + OffX + WidthLastChars, x, (x + TexSubWidth) - 1); + const int ImgOffY = clamp(y + OffY, y, (y + TexSubHeight) - 1); + const size_t ImageOffset = ImgOffY * (TexWidth * ImageColorChannelCount) + ImgOffX * ImageColorChannelCount; + const size_t GlyphOffset = OffY * pBitmap->width + OffX; + for(size_t i = 0; i < ImageColorChannelCount; ++i) + { + if(i != ImageColorChannelCount - 1) + { + *(pImageBuff + ImageOffset + i) = 255; + } + else + { + *(pImageBuff + ImageOffset + i) = *(m_aaGlyphData[FONT_TEXTURE_FILL] + GlyphOffset); + } + } + } + } + + WidthLastChars += (pBitmap->width + 1); + } + pCurrent = pTmp; + } + } + + size_t TextureDimension() const + { + return m_TextureDimension; + } + + IGraphics::CTextureHandle Texture(size_t TextureIndex) const + { + return m_aTextures[TextureIndex]; + } +}; + +typedef vector4_base STextCharQuadVertexColor; + +struct STextCharQuadVertex +{ + STextCharQuadVertex() + { + m_Color.r = m_Color.g = m_Color.b = m_Color.a = 255; + } + float m_X, m_Y; + // do not use normalized floats as coordinates, since the texture might grow + float m_U, m_V; + STextCharQuadVertexColor m_Color; +}; + +struct STextCharQuad +{ + STextCharQuadVertex m_aVertices[4]; +}; + +struct SStringInfo +{ + int m_QuadBufferObjectIndex; + int m_QuadBufferContainerIndex; + int m_SelectionQuadContainerIndex; + + std::vector m_vCharacterQuads; +}; + +struct STextContainer +{ + STextContainer() + { + Reset(); + } + + SStringInfo m_StringInfo; + + // keep these values to calculate offsets + float m_AlignedStartX; + float m_AlignedStartY; + float m_X; + float m_Y; + + int m_Flags; + int m_LineCount; + int m_GlyphCount; + int m_CharCount; + int m_MaxLines; + float m_LineWidth; + + unsigned m_RenderFlags; + + bool m_HasCursor; + bool m_ForceCursorRendering; + bool m_HasSelection; + + bool m_SingleTimeUse; + + STextBoundingBox m_BoundingBox; + + // prefix of the container's text stored for debugging purposes + char m_aDebugText[32]; + + STextContainerIndex m_ContainerIndex; + + void Reset() + { + m_StringInfo.m_QuadBufferObjectIndex = m_StringInfo.m_QuadBufferContainerIndex = m_StringInfo.m_SelectionQuadContainerIndex = -1; + m_StringInfo.m_vCharacterQuads.clear(); + + m_AlignedStartX = m_AlignedStartY = m_X = m_Y = 0.0f; + m_Flags = m_LineCount = m_CharCount = m_GlyphCount = 0; + m_MaxLines = -1; + m_LineWidth = -1.0f; + + m_RenderFlags = 0; + + m_HasCursor = false; + m_ForceCursorRendering = false; + m_HasSelection = false; + + m_SingleTimeUse = false; + + m_BoundingBox = {0.0f, 0.0f, 0.0f, 0.0f}; + + m_aDebugText[0] = '\0'; + + m_ContainerIndex = STextContainerIndex{}; + } +}; + +struct SFontLanguageVariant +{ + char m_aLanguageFile[IO_MAX_PATH_LENGTH]; + char m_aFamilyName[FONT_NAME_SIZE]; +}; + +class CTextRender : public IEngineTextRender +{ + IConsole *m_pConsole; + IGraphics *m_pGraphics; + IStorage *m_pStorage; + IConsole *Console() { return m_pConsole; } + IGraphics *Graphics() { return m_pGraphics; } + IStorage *Storage() { return m_pStorage; } + + CGlyphMap *m_pGlyphMap; + std::vector m_vpFontData; + + std::vector m_vVariants; + + unsigned m_RenderFlags; + + ColorRGBA m_Color; + ColorRGBA m_OutlineColor; + ColorRGBA m_SelectionColor; + + FT_Library m_FTLibrary; + + std::vector m_vpTextContainers; + std::vector m_vTextContainerIndices; + int m_FirstFreeTextContainerIndex; + + SBufferContainerInfo m_DefaultTextContainerInfo; + + std::chrono::nanoseconds m_CursorRenderTime; + + int GetFreeTextContainerIndex() + { + if(m_FirstFreeTextContainerIndex == -1) + { + const int Index = (int)m_vTextContainerIndices.size(); + m_vTextContainerIndices.push_back(Index); + return Index; } else { - return &it->second; + const int Index = m_FirstFreeTextContainerIndex; + m_FirstFreeTextContainerIndex = m_vTextContainerIndices[Index]; + m_vTextContainerIndices[Index] = Index; + return Index; } } - float Kerning(CFont *pFont, FT_UInt GlyphIndexLeft, FT_UInt GlyphIndexRight) const + void FreeTextContainerIndex(STextContainerIndex &Index) { - FT_Vector Kerning = {0, 0}; - FT_Get_Kerning(pFont->m_FtFace, GlyphIndexLeft, GlyphIndexRight, FT_KERNING_DEFAULT, &Kerning); - return (Kerning.x >> 6); + m_vTextContainerIndices[Index.m_Index] = m_FirstFreeTextContainerIndex; + m_FirstFreeTextContainerIndex = Index.m_Index; + Index.Reset(); + } + + void FreeTextContainer(STextContainerIndex &Index) + { + m_vpTextContainers[Index.m_Index]->Reset(); + FreeTextContainerIndex(Index); + } + + STextContainer &GetTextContainer(const STextContainerIndex &Index) + { + dbg_assert(Index.Valid(), "Text container index was invalid."); + if(Index.m_Index >= (int)m_vpTextContainers.size()) + { + for(int i = 0; i < Index.m_Index + 1 - (int)m_vpTextContainers.size(); ++i) + m_vpTextContainers.push_back(new STextContainer()); + } + + if(m_vpTextContainers[Index.m_Index]->m_ContainerIndex.m_UseCount.get() != Index.m_UseCount.get()) + { + m_vpTextContainers[Index.m_Index]->m_ContainerIndex = Index; + } + return *m_vpTextContainers[Index.m_Index]; + } + + int WordLength(const char *pText) const + { + const char *pCursor = pText; + while(true) + { + if(*pCursor == '\0') + return pCursor - pText; + if(*pCursor == '\n' || *pCursor == '\t' || *pCursor == ' ') + return pCursor - pText + 1; + str_utf8_decode(&pCursor); + } + } + + bool LoadFontCollection(const char *pFontName, const FT_Byte *pFontData, FT_Long FontDataSize) + { + FT_Face FtFace; + FT_Error CollectionLoadError = FT_New_Memory_Face(m_FTLibrary, pFontData, FontDataSize, -1, &FtFace); + if(CollectionLoadError) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "Failed to load font file '%s': %s", pFontName, FT_Error_String(CollectionLoadError)); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aBuf); + return false; + } + + const FT_Long NumFaces = FtFace->num_faces; + FT_Done_Face(FtFace); + + bool LoadedAny = false; + for(FT_Long FaceIndex = 0; FaceIndex < NumFaces; ++FaceIndex) + { + FT_Error FaceLoadError = FT_New_Memory_Face(m_FTLibrary, pFontData, FontDataSize, FaceIndex, &FtFace); + if(FaceLoadError) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "Failed to load font face %ld from font file '%s': %s", FaceIndex, pFontName, FT_Error_String(FaceLoadError)); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aBuf); + FT_Done_Face(FtFace); + continue; + } + + m_pGlyphMap->AddFace(FtFace); + + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "Loaded font face %ld '%s %s' from font file '%s'", FaceIndex, FtFace->family_name, FtFace->style_name, pFontName); + Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "textrender", aBuf); + LoadedAny = true; + } + + if(!LoadedAny) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "Failed to load font file '%s': no font faces could be loaded", pFontName); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aBuf); + return false; + } + + return true; + } + + void SetRenderFlags(unsigned Flags) override + { + m_RenderFlags = Flags; + } + + unsigned GetRenderFlags() const override + { + return m_RenderFlags; } public: CTextRender() { + m_pConsole = nullptr; m_pGraphics = nullptr; + m_pStorage = nullptr; + m_pGlyphMap = nullptr; m_Color = DefaultTextColor(); m_OutlineColor = DefaultTextOutlineColor(); m_SelectionColor = DefaultTextSelectionColor(); - m_pCurFont = nullptr; - m_pDefaultFont = nullptr; m_FTLibrary = nullptr; m_RenderFlags = 0; m_CursorRenderTime = time_get_nanoseconds(); } - virtual ~CTextRender() - { - for(auto *pTextCont : m_vpTextContainers) - { - pTextCont->Reset(); - delete pTextCont; - } - m_vpTextContainers.clear(); - - for(auto &pFont : m_vpFonts) - { - FT_Done_Face(pFont->m_FtFace); - - for(CFont::SFontFallBack &FallbackFont : pFont->m_vFtFallbackFonts) - { - FT_Done_Face(FallbackFont.m_FtFace); - } - - delete pFont; - } - - if(m_FTLibrary != nullptr) - FT_Done_FreeType(m_FTLibrary); - } - void Init() override { + m_pConsole = Kernel()->RequestInterface(); m_pGraphics = Kernel()->RequestInterface(); + m_pStorage = Kernel()->RequestInterface(); FT_Init_FreeType(&m_FTLibrary); + m_pGlyphMap = new CGlyphMap(m_pGraphics); + // print freetype version { int LMajor, LMinor, LPatch; FT_Library_Version(m_FTLibrary, &LMajor, &LMinor, &LPatch); - dbg_msg("freetype", "freetype version %d.%d.%d (compiled = %d.%d.%d)", LMajor, LMinor, LPatch, - FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH); + char aFreetypeVersion[128]; + str_format(aFreetypeVersion, sizeof(aFreetypeVersion), "Freetype version %d.%d.%d (compiled = %d.%d.%d)", LMajor, LMinor, LPatch, FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aFreetypeVersion); } m_FirstFreeTextContainerIndex = -1; @@ -698,121 +993,169 @@ public: pAttr->m_Normalized = true; pAttr->m_pOffset = (void *)(sizeof(float) * 2 + sizeof(float) * 2); pAttr->m_Type = GRAPHICS_TYPE_UNSIGNED_BYTE; - - IStorage *pStorage = Kernel()->RequestInterface(); - char aFilename[IO_MAX_PATH_LENGTH]; - const char *pFontFile = "fonts/Icons.otf"; - IOHANDLE File = pStorage->OpenFile(pFontFile, IOFLAG_READ, IStorage::TYPE_ALL, aFilename, sizeof(aFilename)); - if(File) - { - void *pBuf; - unsigned Size; - io_read_all(File, &pBuf, &Size); - io_close(File); - LoadFont(aFilename, (unsigned char *)pBuf, Size); - } } - CFont *LoadFont(const char *pFilename, unsigned char *pBuf, size_t Size) override + void Shutdown() override { - CFont *pFont = new CFont(); + for(auto *pTextCont : m_vpTextContainers) + delete pTextCont; + m_vpTextContainers.clear(); - str_copy(pFont->m_aFilename, pFilename); + delete m_pGlyphMap; + m_pGlyphMap = nullptr; - if(FT_New_Memory_Face(m_FTLibrary, pBuf, Size, 0, &pFont->m_FtFace)) + if(m_FTLibrary != nullptr) + FT_Done_FreeType(m_FTLibrary); + m_FTLibrary = nullptr; + + for(auto *pFontData : m_vpFontData) + free(pFontData); + m_vpFontData.clear(); + + m_DefaultTextContainerInfo.m_vAttributes.clear(); + + m_pConsole = nullptr; + m_pGraphics = nullptr; + m_pStorage = nullptr; + } + + void LoadFonts() override + { + // read file data into buffer + const char *pFilename = "fonts/index.json"; + void *pFileData; + unsigned JsonFileSize; + if(!Storage()->ReadFile(pFilename, IStorage::TYPE_ALL, &pFileData, &JsonFileSize)) { - delete pFont; - return nullptr; + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "Failed to open/read font index file '%s'", pFilename); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aBuf); + return; } - dbg_msg("textrender", "loaded font from '%s'", pFilename); - - pFont->m_pBuf = pBuf; - - for(size_t i = 0; i < 2; i++) + // parse json data + json_settings JsonSettings{}; + char aError[256]; + json_value *pJsonData = json_parse_ex(&JsonSettings, static_cast(pFileData), JsonFileSize, aError); + free(pFileData); + if(pJsonData == nullptr) { - pFont->m_aCurTextureDimensions[i] = 1024; - pFont->m_apTextureData[i] = new unsigned char[pFont->m_aCurTextureDimensions[i] * pFont->m_aCurTextureDimensions[i]]; - mem_zero(pFont->m_apTextureData[i], (size_t)pFont->m_aCurTextureDimensions[i] * pFont->m_aCurTextureDimensions[i] * sizeof(unsigned char)); + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "Failed to parse font index file '%s': %s", pFilename, aError); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "textrender", aBuf); + return; } - InitTextures(pFont->m_aCurTextureDimensions[0], pFont->m_aCurTextureDimensions[0], pFont->m_aTextures, pFont->m_apTextureData); - - for(size_t i = 0; i < 2; i++) - pFont->m_aTextureSkyline[i].m_vCurHeightOfPixelColumn.resize(pFont->m_aCurTextureDimensions[i], 0); - - pFont->InitFontSizes(); - - m_vpFonts.push_back(pFont); - - return pFont; - } - - bool LoadFallbackFont(CFont *pFont, const char *pFilename, unsigned char *pBuf, size_t Size) const override - { - CFont::SFontFallBack FallbackFont; - FallbackFont.m_pBuf = pBuf; - str_copy(FallbackFont.m_aFilename, pFilename); - - if(FT_New_Memory_Face(m_FTLibrary, pBuf, Size, 0, &FallbackFont.m_FtFace) == 0) + // extract font file definitions + const json_value &FontFiles = (*pJsonData)["font files"]; + if(FontFiles.type == json_array) { - dbg_msg("textrender", "loaded fallback font from '%s'", pFilename); - pFont->m_vFtFallbackFonts.emplace_back(FallbackFont); - return true; + for(unsigned FontFileIndex = 0; FontFileIndex < FontFiles.u.array.length; ++FontFileIndex) + { + if(FontFiles[FontFileIndex].type != json_string) + continue; + + char aFontName[IO_MAX_PATH_LENGTH]; + str_format(aFontName, sizeof(aFontName), "fonts/%s", FontFiles[FontFileIndex].u.string.ptr); + void *pFontData; + unsigned FontDataSize; + if(Storage()->ReadFile(aFontName, IStorage::TYPE_ALL, &pFontData, &FontDataSize)) + { + if(LoadFontCollection(aFontName, static_cast(pFontData), (FT_Long)FontDataSize)) + { + m_vpFontData.push_back(pFontData); + } + else + { + free(pFontData); + } + } + } } - return false; - } - - CFont *GetFont(size_t FontIndex) override - { - if(FontIndex < m_vpFonts.size()) - return m_vpFonts[FontIndex]; - - return nullptr; - } - - CFont *GetFont(const char *pFilename) override - { - for(auto &pFont : m_vpFonts) + // extract default family name + const json_value &DefaultFace = (*pJsonData)["default"]; + if(DefaultFace.type == json_string) { - if(str_comp(pFilename, pFont->m_aFilename) == 0) - return pFont; + m_pGlyphMap->SetDefaultFaceByName(DefaultFace.u.string.ptr); } - return nullptr; + // extract language variant family names + const json_value &Variants = (*pJsonData)["language variants"]; + if(Variants.type == json_object) + { + m_vVariants.resize(Variants.u.object.length); + for(size_t i = 0; i < Variants.u.object.length; ++i) + { + str_format(m_vVariants[i].m_aLanguageFile, sizeof(m_vVariants[i].m_aLanguageFile), "languages/%s.txt", Variants.u.object.values[i].name); + + const json_value *pFamilyName = Variants.u.object.values[i].value; + if(pFamilyName->type == json_string) + str_copy(m_vVariants[i].m_aFamilyName, pFamilyName->u.string.ptr); + else + m_vVariants[i].m_aFamilyName[0] = '\0'; + } + } + + // extract fallback family names + const json_value &FallbackFaces = (*pJsonData)["fallbacks"]; + if(FallbackFaces.type == json_array) + { + for(unsigned i = 0; i < FallbackFaces.u.array.length; ++i) + { + if(FallbackFaces[i].type == json_string) + { + m_pGlyphMap->AddFallbackFaceByName(FallbackFaces[i].u.string.ptr); + } + } + } + + // extract icon font family name + const json_value &IconFace = (*pJsonData)["icon"]; + if(IconFace.type == json_string) + { + m_pGlyphMap->SetIconFaceByName(IconFace.u.string.ptr); + } + + json_value_free(pJsonData); } - void SetDefaultFont(CFont *pFont) override + void SetFontPreset(EFontPreset FontPreset) override { - dbg_msg("textrender", "default font set to '%s'", pFont->m_aFilename); - m_pDefaultFont = pFont; - m_pCurFont = m_pDefaultFont; + m_pGlyphMap->SetFontPreset(FontPreset); } - void SetCurFont(CFont *pFont) override + void SetFontLanguageVariant(const char *pLanguageFile) override { - if(pFont == nullptr) - m_pCurFont = m_pDefaultFont; - else - m_pCurFont = pFont; + for(const auto &Variant : m_vVariants) + { + if(str_comp(pLanguageFile, Variant.m_aLanguageFile) == 0) + { + m_pGlyphMap->SetVariantFaceByName(Variant.m_aFamilyName); + return; + } + } + m_pGlyphMap->SetVariantFaceByName(nullptr); } void SetCursor(CTextCursor *pCursor, float x, float y, float FontSize, int Flags) const override { - mem_zero(pCursor, sizeof(*pCursor)); - pCursor->m_FontSize = FontSize; - pCursor->m_StartX = x; - pCursor->m_StartY = y; - pCursor->m_X = x; - pCursor->m_Y = y; - pCursor->m_LineCount = 1; - pCursor->m_LineWidth = -1; pCursor->m_Flags = Flags; + pCursor->m_LineCount = 1; pCursor->m_GlyphCount = 0; pCursor->m_CharCount = 0; - pCursor->m_MaxCharacterHeight = 0; - pCursor->m_LongestLineWidth = 0; + pCursor->m_MaxLines = 0; + + pCursor->m_StartX = x; + pCursor->m_StartY = y; + pCursor->m_LineWidth = -1.0f; + pCursor->m_X = x; + pCursor->m_Y = y; + pCursor->m_MaxCharacterHeight = 0.0f; + pCursor->m_LongestLineWidth = 0.0f; + + pCursor->m_FontSize = FontSize; + pCursor->m_AlignedFontSize = FontSize; pCursor->m_CalculateSelectionMode = TEXT_CURSOR_SELECTION_MODE_NONE; pCursor->m_SelectionHeightFactor = 1.0f; @@ -949,57 +1292,31 @@ public: bool CreateTextContainer(STextContainerIndex &TextContainerIndex, CTextCursor *pCursor, const char *pText, int Length = -1) override { dbg_assert(!TextContainerIndex.Valid(), "Text container index was not cleared."); + TextContainerIndex.Reset(); - - CFont *pFont = pCursor->m_pFont; - - // fetch pFont data - if(!pFont) - pFont = m_pCurFont; - - if(!pFont) - return false; - - const bool IsRendered = (pCursor->m_Flags & TEXTFLAG_RENDER) != 0; - TextContainerIndex.m_Index = GetFreeTextContainerIndex(); - STextContainer &TextContainer = GetTextContainer(TextContainerIndex); - TextContainer.m_pFont = pFont; - TextContainer.m_SingleTimeUse = (m_RenderFlags & TEXT_RENDER_FLAG_ONE_TIME_USE) != 0; - - // calculate the font size of the displayed glyphs float ScreenX0, ScreenY0, ScreenX1, ScreenY1; Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - const float FakeToScreenX = Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0); - const float FakeToScreenY = Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0); - - const int ActualX = round_to_int(pCursor->m_X * FakeToScreenX); - const int ActualY = round_to_int(pCursor->m_Y * FakeToScreenY); - - TextContainer.m_AlignedStartX = ActualX / FakeToScreenX; - TextContainer.m_AlignedStartY = ActualY / FakeToScreenY; + STextContainer &TextContainer = GetTextContainer(TextContainerIndex); + TextContainer.m_SingleTimeUse = (m_RenderFlags & TEXT_RENDER_FLAG_ONE_TIME_USE) != 0; + const vec2 FakeToScreen = vec2(Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0), Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0)); + TextContainer.m_AlignedStartX = round_to_int(pCursor->m_X * FakeToScreen.x) / FakeToScreen.x; + TextContainer.m_AlignedStartY = round_to_int(pCursor->m_Y * FakeToScreen.y) / FakeToScreen.y; TextContainer.m_X = pCursor->m_X; TextContainer.m_Y = pCursor->m_Y; TextContainer.m_Flags = pCursor->m_Flags; - const unsigned OldRenderFlags = m_RenderFlags; if(pCursor->m_LineWidth <= 0) - SetRenderFlags(OldRenderFlags | ETextRenderFlags::TEXT_RENDER_FLAG_NO_FIRST_CHARACTER_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_LAST_CHARACTER_ADVANCE); - - TextContainer.m_RenderFlags = m_RenderFlags; - SetRenderFlags(OldRenderFlags); - - // same with size - const float Size = pCursor->m_FontSize; - const int ActualSize = (int)(Size * FakeToScreenY); - - const SFontSizeData *pSizeData = pFont->GetFontSize(ActualSize); - TextContainer.m_FontSize = pSizeData->m_FontSize; + TextContainer.m_RenderFlags = m_RenderFlags | ETextRenderFlags::TEXT_RENDER_FLAG_NO_FIRST_CHARACTER_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_LAST_CHARACTER_ADVANCE; + else + TextContainer.m_RenderFlags = m_RenderFlags; AppendTextContainer(TextContainerIndex, pCursor, pText, Length); + const bool IsRendered = (pCursor->m_Flags & TEXTFLAG_RENDER) != 0; + if(TextContainer.m_StringInfo.m_vCharacterQuads.empty() && TextContainer.m_StringInfo.m_SelectionQuadContainerIndex == -1 && IsRendered) { FreeTextContainer(TextContainerIndex); @@ -1007,7 +1324,6 @@ public: } else { - TextContainer.m_StringInfo.m_QuadNum = TextContainer.m_StringInfo.m_vCharacterQuads.size(); if(Graphics()->IsTextBufferingEnabled() && IsRendered && !TextContainer.m_StringInfo.m_vCharacterQuads.empty()) { if((TextContainer.m_RenderFlags & TEXT_RENDER_FLAG_NO_AUTOMATIC_QUAD_UPLOAD) == 0) @@ -1020,10 +1336,7 @@ public: TextContainer.m_GlyphCount = pCursor->m_GlyphCount; TextContainer.m_CharCount = pCursor->m_CharCount; TextContainer.m_MaxLines = pCursor->m_MaxLines; - TextContainer.m_StartX = pCursor->m_StartX; - TextContainer.m_StartY = pCursor->m_StartY; TextContainer.m_LineWidth = pCursor->m_LineWidth; - TextContainer.m_UnscaledFontSize = pCursor->m_FontSize; return true; } } @@ -1033,25 +1346,14 @@ public: STextContainer &TextContainer = GetTextContainer(TextContainerIndex); str_append(TextContainer.m_aDebugText, pText); - // calculate the font size of the displayed glyphs float ScreenX0, ScreenY0, ScreenX1, ScreenY1; Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - const float FakeToScreenX = Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0); - const float FakeToScreenY = Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0); - - const int ActualX = round_to_int(pCursor->m_X * FakeToScreenX); - const int ActualY = round_to_int(pCursor->m_Y * FakeToScreenY); - const float CursorX = ActualX / FakeToScreenX; - const float CursorY = ActualY / FakeToScreenY; - - // same with size - float Size = pCursor->m_FontSize; - const int ActualSize = (int)(Size * FakeToScreenY); - Size = ActualSize / FakeToScreenY; - pCursor->m_AlignedFontSize = Size; - - SFontSizeData *pSizeData = TextContainer.m_pFont->GetFontSize(TextContainer.m_FontSize); + const vec2 FakeToScreen = vec2(Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0), Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0)); + const float CursorX = round_to_int(pCursor->m_X * FakeToScreen.x) / FakeToScreen.x; + const float CursorY = round_to_int(pCursor->m_Y * FakeToScreen.y) / FakeToScreen.y; + const int ActualSize = round_truncate(pCursor->m_FontSize * FakeToScreen.y); + pCursor->m_AlignedFontSize = ActualSize / FakeToScreen.y; // string length if(Length < 0) @@ -1059,18 +1361,16 @@ public: else Length = minimum(Length, str_length(pText)); - const float Scale = 1.0f / pSizeData->m_FontSize; - const char *pCurrent = pText; const char *pEnd = pCurrent + Length; const char *pEllipsis = "…"; - const SFontSizeChar *pEllipsisChr = nullptr; + const SGlyph *pEllipsisGlyph = nullptr; if(pCursor->m_Flags & TEXTFLAG_ELLIPSIS_AT_END) { if(pCursor->m_LineWidth != -1 && pCursor->m_LineWidth < TextWidth(pCursor->m_FontSize, pText, -1, -1.0f)) { - pEllipsisChr = GetChar(TextContainer.m_pFont, pSizeData, 0x2026); // … - if(pEllipsisChr == nullptr) + pEllipsisGlyph = m_pGlyphMap->GetGlyph(0x2026, ActualSize); // … + if(pEllipsisGlyph == nullptr) { // no ellipsis char in font, just stop at end instead pCursor->m_Flags &= ~TEXTFLAG_ELLIPSIS_AT_END; @@ -1108,15 +1408,15 @@ public: int SelectionStartChar = -1; int SelectionEndChar = -1; - auto &&CheckInsideChar = [&](bool CheckOuter, vec2 CursorPos, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) -> bool { + const auto &&CheckInsideChar = [&](bool CheckOuter, vec2 CursorPos, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) -> bool { return (LastCharX - LastCharWidth / 2 <= CursorPos.x && CharX + CharWidth / 2 > CursorPos.x && - CharY - Size <= CursorPos.y && + CharY - pCursor->m_AlignedFontSize <= CursorPos.y && CharY > CursorPos.y) || (CheckOuter && - CharY - Size > CursorPos.y); + CharY - pCursor->m_AlignedFontSize > CursorPos.y); }; - auto &&CheckSelectionStart = [&](bool CheckOuter, vec2 CursorPos, int &SelectionChar, bool &SelectionUsedCase, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) { + const auto &&CheckSelectionStart = [&](bool CheckOuter, vec2 CursorPos, int &SelectionChar, bool &SelectionUsedCase, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) { if(!SelectionStarted && !SelectionUsedCase) { if(CheckInsideChar(CheckOuter, CursorPos, LastCharX, LastCharWidth, CharX, CharWidth, CharY)) @@ -1127,14 +1427,14 @@ public: } } }; - auto &&CheckOutsideChar = [&](bool CheckOuter, vec2 CursorPos, float CharX, float CharWidth, float CharY) -> bool { + const auto &&CheckOutsideChar = [&](bool CheckOuter, vec2 CursorPos, float CharX, float CharWidth, float CharY) -> bool { return (CharX + CharWidth / 2 > CursorPos.x && - CharY - Size <= CursorPos.y && + CharY - pCursor->m_AlignedFontSize <= CursorPos.y && CharY > CursorPos.y) || (CheckOuter && CharY <= CursorPos.y); }; - auto &&CheckSelectionEnd = [&](bool CheckOuter, vec2 CursorPos, int &SelectionChar, bool &SelectionUsedCase, float CharX, float CharWidth, float CharY) { + const auto &&CheckSelectionEnd = [&](bool CheckOuter, vec2 CursorPos, int &SelectionChar, bool &SelectionUsedCase, float CharX, float CharWidth, float CharY) { if(SelectionStarted && !SelectionUsedCase) { if(CheckOutsideChar(CheckOuter, CursorPos, CharX, CharWidth, CharY)) @@ -1151,13 +1451,13 @@ public: float LastCharX = DrawX; float LastCharWidth = 0; - auto &&StartNewLine = [&]() { + const auto &&StartNewLine = [&]() { DrawX = pCursor->m_StartX; - DrawY += Size; + DrawY += pCursor->m_AlignedFontSize; if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) { - DrawX = round_to_int(DrawX * FakeToScreenX) / FakeToScreenX; // realign - DrawY = round_to_int(DrawY * FakeToScreenY) / FakeToScreenY; + DrawX = round_to_int(DrawX * FakeToScreen.x) / FakeToScreen.x; // realign + DrawY = round_to_int(DrawY * FakeToScreen.y) / FakeToScreen.y; } LastSelX = DrawX; LastSelWidth = 0; @@ -1179,7 +1479,7 @@ public: IGraphics::CQuadItem aCursorQuads[2]; bool HasCursor = false; - FT_UInt LastCharGlyphIndex = 0; + const SGlyph *pLastGlyph = nullptr; bool GotNewLine = false; bool GotNewLineLast = false; @@ -1243,7 +1543,7 @@ public: { if((pCursor->m_Flags & TEXTFLAG_DISALLOW_NEWLINE) == 0) { - LastCharGlyphIndex = 0; + pLastGlyph = nullptr; StartNewLine(); if(pCursor->m_MaxLines > 0 && LineCount > pCursor->m_MaxLines) break; @@ -1255,26 +1555,28 @@ public: } } - const SFontSizeChar *pChr = GetChar(TextContainer.m_pFont, pSizeData, Character); - if(pChr) + const SGlyph *pGlyph = m_pGlyphMap->GetGlyph(Character, ActualSize); + if(pGlyph) { - bool ApplyBearingX = !(((RenderFlags & TEXT_RENDER_FLAG_NO_X_BEARING) != 0) || (pCursor->m_GlyphCount == 0 && (RenderFlags & TEXT_RENDER_FLAG_NO_FIRST_CHARACTER_X_BEARING) != 0)); - float Advance = ((((RenderFlags & TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH) != 0) ? (pChr->m_Width) : (pChr->m_AdvanceX + ((!ApplyBearingX) ? (-pChr->m_OffsetX) : 0.f)))) * Scale * Size; + const float Scale = 1.0f / pGlyph->m_FontSize; - float OutLineRealDiff = (pChr->m_Width - pChr->m_CharWidth) * Scale * Size; + const bool ApplyBearingX = !(((RenderFlags & TEXT_RENDER_FLAG_NO_X_BEARING) != 0) || (pCursor->m_GlyphCount == 0 && (RenderFlags & TEXT_RENDER_FLAG_NO_FIRST_CHARACTER_X_BEARING) != 0)); + const float Advance = ((((RenderFlags & TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH) != 0) ? (pGlyph->m_Width) : (pGlyph->m_AdvanceX + ((!ApplyBearingX) ? (-pGlyph->m_OffsetX) : 0.f)))) * Scale * pCursor->m_AlignedFontSize; - float CharKerning = 0.f; + const float OutLineRealDiff = (pGlyph->m_Width - pGlyph->m_CharWidth) * Scale * pCursor->m_AlignedFontSize; + + float CharKerning = 0.0f; if((RenderFlags & TEXT_RENDER_FLAG_KERNING) != 0) - CharKerning = Kerning(TextContainer.m_pFont, LastCharGlyphIndex, pChr->m_GlyphIndex) * Scale * Size; - LastCharGlyphIndex = pChr->m_GlyphIndex; + CharKerning = m_pGlyphMap->Kerning(pLastGlyph, pGlyph).x * Scale * pCursor->m_AlignedFontSize; + pLastGlyph = pGlyph; - if(pEllipsisChr != nullptr && pCursor->m_Flags & TEXTFLAG_ELLIPSIS_AT_END && pCurrent < pBatchEnd && pCurrent != pEllipsis) + if(pEllipsisGlyph != nullptr && pCursor->m_Flags & TEXTFLAG_ELLIPSIS_AT_END && pCurrent < pBatchEnd && pCurrent != pEllipsis) { - float AdvanceEllipsis = ((((RenderFlags & TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH) != 0) ? (pEllipsisChr->m_Width) : (pEllipsisChr->m_AdvanceX + ((!ApplyBearingX) ? (-pEllipsisChr->m_OffsetX) : 0.f)))) * Scale * Size; - float CharKerningEllipsis = 0.f; + float AdvanceEllipsis = ((((RenderFlags & TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH) != 0) ? (pEllipsisGlyph->m_Width) : (pEllipsisGlyph->m_AdvanceX + ((!ApplyBearingX) ? (-pEllipsisGlyph->m_OffsetX) : 0.f)))) * Scale * pCursor->m_AlignedFontSize; + float CharKerningEllipsis = 0.0f; if((RenderFlags & TEXT_RENDER_FLAG_KERNING) != 0) { - CharKerningEllipsis = Kerning(TextContainer.m_pFont, pChr->m_GlyphIndex, pEllipsisChr->m_GlyphIndex) * Scale * Size; + CharKerningEllipsis = m_pGlyphMap->Kerning(pGlyph, pEllipsisGlyph).x * Scale * pCursor->m_AlignedFontSize; } if(DrawX + CharKerning + Advance + CharKerningEllipsis + AdvanceEllipsis - pCursor->m_StartX > pCursor->m_LineWidth) { @@ -1292,26 +1594,26 @@ public: break; } - float BearingX = (!ApplyBearingX ? 0.f : pChr->m_OffsetX) * Scale * Size; - float CharWidth = pChr->m_Width * Scale * Size; + float BearingX = (!ApplyBearingX ? 0.f : pGlyph->m_OffsetX) * Scale * pCursor->m_AlignedFontSize; + float CharWidth = pGlyph->m_Width * Scale * pCursor->m_AlignedFontSize; - float BearingY = (((RenderFlags & TEXT_RENDER_FLAG_NO_Y_BEARING) != 0) ? 0.f : (pChr->m_OffsetY * Scale * Size)); - float CharHeight = pChr->m_Height * Scale * Size; + float BearingY = (((RenderFlags & TEXT_RENDER_FLAG_NO_Y_BEARING) != 0) ? 0.f : (pGlyph->m_OffsetY * Scale * pCursor->m_AlignedFontSize)); + float CharHeight = pGlyph->m_Height * Scale * pCursor->m_AlignedFontSize; if((RenderFlags & TEXT_RENDER_FLAG_NO_OVERSIZE) != 0) { - if(CharHeight + BearingY > Size) + if(CharHeight + BearingY > pCursor->m_AlignedFontSize) { BearingY = 0; - float ScaleChar = (CharHeight + BearingY) / Size; - CharHeight = Size; + float ScaleChar = (CharHeight + BearingY) / pCursor->m_AlignedFontSize; + CharHeight = pCursor->m_AlignedFontSize; CharWidth /= ScaleChar; } } - float TmpY = (DrawY + Size); - float CharX = (DrawX + CharKerning) + BearingX; - float CharY = TmpY - BearingY; + const float TmpY = (DrawY + pCursor->m_AlignedFontSize); + const float CharX = (DrawX + CharKerning) + BearingX; + const float CharY = TmpY - BearingY; // don't add text that isn't drawn, the color overwrite is used for that if(m_Color.a != 0.f && IsRendered) @@ -1321,8 +1623,8 @@ public: TextCharQuad.m_aVertices[0].m_X = CharX; TextCharQuad.m_aVertices[0].m_Y = CharY; - TextCharQuad.m_aVertices[0].m_U = pChr->m_aUVs[0]; - TextCharQuad.m_aVertices[0].m_V = pChr->m_aUVs[3]; + TextCharQuad.m_aVertices[0].m_U = pGlyph->m_aUVs[0]; + TextCharQuad.m_aVertices[0].m_V = pGlyph->m_aUVs[3]; TextCharQuad.m_aVertices[0].m_Color.r = (unsigned char)(m_Color.r * 255.f); TextCharQuad.m_aVertices[0].m_Color.g = (unsigned char)(m_Color.g * 255.f); TextCharQuad.m_aVertices[0].m_Color.b = (unsigned char)(m_Color.b * 255.f); @@ -1330,8 +1632,8 @@ public: TextCharQuad.m_aVertices[1].m_X = CharX + CharWidth; TextCharQuad.m_aVertices[1].m_Y = CharY; - TextCharQuad.m_aVertices[1].m_U = pChr->m_aUVs[2]; - TextCharQuad.m_aVertices[1].m_V = pChr->m_aUVs[3]; + TextCharQuad.m_aVertices[1].m_U = pGlyph->m_aUVs[2]; + TextCharQuad.m_aVertices[1].m_V = pGlyph->m_aUVs[3]; TextCharQuad.m_aVertices[1].m_Color.r = (unsigned char)(m_Color.r * 255.f); TextCharQuad.m_aVertices[1].m_Color.g = (unsigned char)(m_Color.g * 255.f); TextCharQuad.m_aVertices[1].m_Color.b = (unsigned char)(m_Color.b * 255.f); @@ -1339,8 +1641,8 @@ public: TextCharQuad.m_aVertices[2].m_X = CharX + CharWidth; TextCharQuad.m_aVertices[2].m_Y = CharY - CharHeight; - TextCharQuad.m_aVertices[2].m_U = pChr->m_aUVs[2]; - TextCharQuad.m_aVertices[2].m_V = pChr->m_aUVs[1]; + TextCharQuad.m_aVertices[2].m_U = pGlyph->m_aUVs[2]; + TextCharQuad.m_aVertices[2].m_V = pGlyph->m_aUVs[1]; TextCharQuad.m_aVertices[2].m_Color.r = (unsigned char)(m_Color.r * 255.f); TextCharQuad.m_aVertices[2].m_Color.g = (unsigned char)(m_Color.g * 255.f); TextCharQuad.m_aVertices[2].m_Color.b = (unsigned char)(m_Color.b * 255.f); @@ -1348,8 +1650,8 @@ public: TextCharQuad.m_aVertices[3].m_X = CharX; TextCharQuad.m_aVertices[3].m_Y = CharY - CharHeight; - TextCharQuad.m_aVertices[3].m_U = pChr->m_aUVs[0]; - TextCharQuad.m_aVertices[3].m_V = pChr->m_aUVs[1]; + TextCharQuad.m_aVertices[3].m_U = pGlyph->m_aUVs[0]; + TextCharQuad.m_aVertices[3].m_V = pGlyph->m_aUVs[1]; TextCharQuad.m_aVertices[3].m_Color.r = (unsigned char)(m_Color.r * 255.f); TextCharQuad.m_aVertices[3].m_Color.g = (unsigned char)(m_Color.g * 255.f); TextCharQuad.m_aVertices[3].m_Color.b = (unsigned char)(m_Color.b * 255.f); @@ -1357,8 +1659,8 @@ public: } // calculate the full width from the last selection point to the end of this selection draw on screen - float SelWidth = (CharX + maximum(Advance, CharWidth - OutLineRealDiff / 2)) - (LastSelX + LastSelWidth); - float SelX = (LastSelX + LastSelWidth); + const float SelWidth = (CharX + maximum(Advance, CharWidth - OutLineRealDiff / 2)) - (LastSelX + LastSelWidth); + const float SelX = (LastSelX + LastSelWidth); if(pCursor->m_CursorMode == TEXT_CURSOR_CURSOR_MODE_CALCULATE) { @@ -1403,8 +1705,8 @@ public: if((int)pCursor->m_GlyphCount == pCursor->m_CursorCharacter) { HasCursor = true; - aCursorQuads[0] = IGraphics::CQuadItem(SelX - CursorOuterInnerDiff, DrawY, CursorOuterWidth, Size); - aCursorQuads[1] = IGraphics::CQuadItem(SelX, DrawY + CursorOuterInnerDiff, CursorInnerWidth, Size - CursorOuterInnerDiff * 2); + aCursorQuads[0] = IGraphics::CQuadItem(SelX - CursorOuterInnerDiff, DrawY, CursorOuterWidth, pCursor->m_AlignedFontSize); + aCursorQuads[1] = IGraphics::CQuadItem(SelX, DrawY + CursorOuterInnerDiff, CursorInnerWidth, pCursor->m_AlignedFontSize - CursorOuterInnerDiff * 2); pCursor->m_CursorRenderedPosition = vec2(SelX, DrawY); } } @@ -1420,7 +1722,7 @@ public: if(SelectionStarted && IsRendered) { - vSelectionQuads.emplace_back(SelX, DrawY + (1.0f - pCursor->m_SelectionHeightFactor) * Size, SelWidth, pCursor->m_SelectionHeightFactor * Size); + vSelectionQuads.emplace_back(SelX, DrawY + (1.0f - pCursor->m_SelectionHeightFactor) * pCursor->m_AlignedFontSize, SelWidth, pCursor->m_SelectionHeightFactor * pCursor->m_AlignedFontSize); } LastSelX = SelX; @@ -1444,17 +1746,16 @@ public: if(!TextContainer.m_StringInfo.m_vCharacterQuads.empty() && IsRendered) { - TextContainer.m_StringInfo.m_QuadNum = TextContainer.m_StringInfo.m_vCharacterQuads.size(); // setup the buffers if(Graphics()->IsTextBufferingEnabled()) { - size_t DataSize = TextContainer.m_StringInfo.m_vCharacterQuads.size() * sizeof(STextCharQuad); + const size_t DataSize = TextContainer.m_StringInfo.m_vCharacterQuads.size() * sizeof(STextCharQuad); void *pUploadData = TextContainer.m_StringInfo.m_vCharacterQuads.data(); if(TextContainer.m_StringInfo.m_QuadBufferObjectIndex != -1 && (TextContainer.m_RenderFlags & TEXT_RENDER_FLAG_NO_AUTOMATIC_QUAD_UPLOAD) == 0) { Graphics()->RecreateBufferObject(TextContainer.m_StringInfo.m_QuadBufferObjectIndex, DataSize, pUploadData, TextContainer.m_SingleTimeUse ? IGraphics::EBufferObjectCreateFlags::BUFFER_OBJECT_CREATE_FLAGS_ONE_TIME_USE_BIT : 0); - Graphics()->IndicesNumRequiredNotify(TextContainer.m_StringInfo.m_QuadNum * 6); + Graphics()->IndicesNumRequiredNotify(TextContainer.m_StringInfo.m_vCharacterQuads.size() * 6); } } } @@ -1466,8 +1767,8 @@ public: if(SelectionStarted) { - CheckSelectionEnd(true, pCursor->m_ReleaseMouse, SelectionEndChar, SelectionUsedRelease, std::numeric_limits::max(), 0, DrawY + Size); - CheckSelectionEnd(true, pCursor->m_PressMouse, SelectionStartChar, SelectionUsedPress, std::numeric_limits::max(), 0, DrawY + Size); + CheckSelectionEnd(true, pCursor->m_ReleaseMouse, SelectionEndChar, SelectionUsedRelease, std::numeric_limits::max(), 0, DrawY + pCursor->m_AlignedFontSize); + CheckSelectionEnd(true, pCursor->m_PressMouse, SelectionStartChar, SelectionUsedPress, std::numeric_limits::max(), 0, DrawY + pCursor->m_AlignedFontSize); } } else if(pCursor->m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_SET) @@ -1488,7 +1789,7 @@ public: if(pCursor->m_CursorMode != TEXT_CURSOR_CURSOR_MODE_NONE) { - if(pCursor->m_CursorMode == TEXT_CURSOR_CURSOR_MODE_CALCULATE && pCursor->m_CursorCharacter == -1 && CheckOutsideChar(true, pCursor->m_ReleaseMouse, std::numeric_limits::max(), 0, DrawY + Size)) + if(pCursor->m_CursorMode == TEXT_CURSOR_CURSOR_MODE_CALCULATE && pCursor->m_CursorCharacter == -1 && CheckOutsideChar(true, pCursor->m_ReleaseMouse, std::numeric_limits::max(), 0, DrawY + pCursor->m_AlignedFontSize)) { pCursor->m_CursorCharacter = pCursor->m_GlyphCount; } @@ -1496,8 +1797,8 @@ public: if((int)pCursor->m_GlyphCount == pCursor->m_CursorCharacter) { HasCursor = true; - aCursorQuads[0] = IGraphics::CQuadItem((LastSelX + LastSelWidth) - CursorOuterInnerDiff, DrawY, CursorOuterWidth, Size); - aCursorQuads[1] = IGraphics::CQuadItem((LastSelX + LastSelWidth), DrawY + CursorOuterInnerDiff, CursorInnerWidth, Size - CursorOuterInnerDiff * 2); + aCursorQuads[0] = IGraphics::CQuadItem((LastSelX + LastSelWidth) - CursorOuterInnerDiff, DrawY, CursorOuterWidth, pCursor->m_AlignedFontSize); + aCursorQuads[1] = IGraphics::CQuadItem((LastSelX + LastSelWidth), DrawY + CursorOuterInnerDiff, CursorInnerWidth, pCursor->m_AlignedFontSize - CursorOuterInnerDiff * 2); pCursor->m_CursorRenderedPosition = vec2(LastSelX + LastSelWidth, DrawY); } } @@ -1564,7 +1865,6 @@ public: { STextContainer &TextContainer = GetTextContainer(TextContainerIndex); TextContainer.m_StringInfo.m_vCharacterQuads.clear(); - TextContainer.m_StringInfo.m_QuadNum = 0; // the text buffer gets then recreated by the appended quads AppendTextContainer(TextContainerIndex, pCursor, pText, Length); } @@ -1593,39 +1893,35 @@ public: m_DefaultTextContainerInfo.m_VertBufferBindingIndex = TextContainer.m_StringInfo.m_QuadBufferObjectIndex; TextContainer.m_StringInfo.m_QuadBufferContainerIndex = Graphics()->CreateBufferContainer(&m_DefaultTextContainerInfo); - Graphics()->IndicesNumRequiredNotify(TextContainer.m_StringInfo.m_QuadNum * 6); + Graphics()->IndicesNumRequiredNotify(TextContainer.m_StringInfo.m_vCharacterQuads.size() * 6); } } void RenderTextContainer(STextContainerIndex TextContainerIndex, const ColorRGBA &TextColor, const ColorRGBA &TextOutlineColor) override { const STextContainer &TextContainer = GetTextContainer(TextContainerIndex); - const CFont *pFont = TextContainer.m_pFont; - if(TextContainer.m_StringInfo.m_QuadNum > 0) + if(!TextContainer.m_StringInfo.m_vCharacterQuads.empty()) { if(Graphics()->IsTextBufferingEnabled()) { Graphics()->TextureClear(); // render buffered text - Graphics()->RenderText(TextContainer.m_StringInfo.m_QuadBufferContainerIndex, TextContainer.m_StringInfo.m_QuadNum, pFont->m_aCurTextureDimensions[0], pFont->m_aTextures[0].Id(), pFont->m_aTextures[1].Id(), TextColor, TextOutlineColor); + Graphics()->RenderText(TextContainer.m_StringInfo.m_QuadBufferContainerIndex, TextContainer.m_StringInfo.m_vCharacterQuads.size(), m_pGlyphMap->TextureDimension(), m_pGlyphMap->Texture(CGlyphMap::FONT_TEXTURE_FILL).Id(), m_pGlyphMap->Texture(CGlyphMap::FONT_TEXTURE_OUTLINE).Id(), TextColor, TextOutlineColor); } else { // render tiles - const float UVScale = 1.0f / pFont->m_aCurTextureDimensions[0]; + const float UVScale = 1.0f / m_pGlyphMap->TextureDimension(); Graphics()->FlushVertices(); - Graphics()->TextureSet(pFont->m_aTextures[1]); + Graphics()->TextureSet(m_pGlyphMap->Texture(CGlyphMap::FONT_TEXTURE_OUTLINE)); Graphics()->QuadsBegin(); - for(size_t i = 0; i < TextContainer.m_StringInfo.m_QuadNum; ++i) + for(const STextCharQuad &TextCharQuad : TextContainer.m_StringInfo.m_vCharacterQuads) { - const STextCharQuad &TextCharQuad = TextContainer.m_StringInfo.m_vCharacterQuads[i]; - Graphics()->SetColor(TextCharQuad.m_aVertices[0].m_Color.r / 255.f * TextOutlineColor.r, TextCharQuad.m_aVertices[0].m_Color.g / 255.f * TextOutlineColor.g, TextCharQuad.m_aVertices[0].m_Color.b / 255.f * TextOutlineColor.b, TextCharQuad.m_aVertices[0].m_Color.a / 255.f * TextOutlineColor.a); - Graphics()->QuadsSetSubset(TextCharQuad.m_aVertices[0].m_U * UVScale, TextCharQuad.m_aVertices[0].m_V * UVScale, TextCharQuad.m_aVertices[2].m_U * UVScale, TextCharQuad.m_aVertices[2].m_V * UVScale); IGraphics::CQuadItem QuadItem(TextCharQuad.m_aVertices[0].m_X, TextCharQuad.m_aVertices[0].m_Y, TextCharQuad.m_aVertices[1].m_X - TextCharQuad.m_aVertices[0].m_X, TextCharQuad.m_aVertices[2].m_Y - TextCharQuad.m_aVertices[0].m_Y); Graphics()->QuadsDrawTL(&QuadItem, 1); @@ -1634,16 +1930,17 @@ public: if(TextColor.a != 0) { Graphics()->QuadsEndKeepVertices(); - Graphics()->TextureSet(pFont->m_aTextures[0]); + Graphics()->TextureSet(m_pGlyphMap->Texture(CGlyphMap::FONT_TEXTURE_FILL)); - for(size_t i = 0; i < TextContainer.m_StringInfo.m_QuadNum; ++i) + int TextCharQuadIndex = 0; + for(const STextCharQuad &TextCharQuad : TextContainer.m_StringInfo.m_vCharacterQuads) { - const STextCharQuad &TextCharQuad = TextContainer.m_StringInfo.m_vCharacterQuads[i]; unsigned char CR = (unsigned char)((float)(TextCharQuad.m_aVertices[0].m_Color.r) * TextColor.r); unsigned char CG = (unsigned char)((float)(TextCharQuad.m_aVertices[0].m_Color.g) * TextColor.g); unsigned char CB = (unsigned char)((float)(TextCharQuad.m_aVertices[0].m_Color.b) * TextColor.b); unsigned char CA = (unsigned char)((float)(TextCharQuad.m_aVertices[0].m_Color.a) * TextColor.a); - Graphics()->ChangeColorOfQuadVertices((int)i, CR, CG, CB, CA); + Graphics()->ChangeColorOfQuadVertices(TextCharQuadIndex, CR, CG, CB, CA); + ++TextCharQuadIndex; } // render non outlined @@ -1698,12 +1995,9 @@ public: if((TextContainer.m_RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) { - const float FakeToScreenX = Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0); - const float FakeToScreenY = Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0); - const int ActualX = round_to_int((TextContainer.m_X + X) * FakeToScreenX); - const int ActualY = round_to_int((TextContainer.m_Y + Y) * FakeToScreenY); - const float AlignedX = ActualX / FakeToScreenX; - const float AlignedY = ActualY / FakeToScreenY; + const vec2 FakeToScreen = vec2(Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0), Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0)); + const float AlignedX = round_to_int((TextContainer.m_X + X) * FakeToScreen.x) / FakeToScreen.x; + const float AlignedY = round_to_int((TextContainer.m_Y + Y) * FakeToScreen.y) / FakeToScreen.y; X = AlignedX - TextContainer.m_AlignedStartX; Y = AlignedY - TextContainer.m_AlignedStartY; } @@ -1724,82 +2018,14 @@ public: void UploadEntityLayerText(void *pTexBuff, size_t ImageColorChannelCount, int TexWidth, int TexHeight, int TexSubWidth, int TexSubHeight, const char *pText, int Length, float x, float y, int FontSize) override { - if(m_pDefaultFont == nullptr) - return; - if(FontSize < 1) - return; - - const char *pCurrent = pText; - const char *pEnd = pCurrent + Length; - const CFont *pFont = m_pDefaultFont; - int WidthLastChars = 0; - - while(pCurrent < pEnd) - { - const char *pTmp = pCurrent; - const int NextCharacter = str_utf8_decode(&pTmp); - - if(NextCharacter) - { - FT_Set_Pixel_Sizes(pFont->m_FtFace, 0, FontSize); - - if(FT_Load_Char(pFont->m_FtFace, NextCharacter, FT_LOAD_RENDER | FT_LOAD_NO_BITMAP)) - { - dbg_msg("textrender", "error loading glyph %d", NextCharacter); - pCurrent = pTmp; - continue; - } - - const FT_Bitmap *pBitmap = &pFont->m_FtFace->glyph->bitmap; - - // prepare glyph data - mem_zero(m_aGlyphData, (size_t)pBitmap->width * pBitmap->rows); - - if(pBitmap->pixel_mode == FT_PIXEL_MODE_GRAY) - { - for(unsigned py = 0; py < pBitmap->rows; py++) - { - for(unsigned px = 0; px < pBitmap->width; px++) - { - m_aGlyphData[py * pBitmap->width + px] = pBitmap->buffer[py * pBitmap->width + px]; - } - } - } - - uint8_t *pImageBuff = (uint8_t *)pTexBuff; - for(unsigned OffY = 0; OffY < pBitmap->rows; ++OffY) - { - for(unsigned OffX = 0; OffX < pBitmap->width; ++OffX) - { - const int ImgOffX = clamp(x + OffX + WidthLastChars, x, (x + TexSubWidth) - 1); - const int ImgOffY = clamp(y + OffY, y, (y + TexSubHeight) - 1); - const size_t ImageOffset = ImgOffY * (TexWidth * ImageColorChannelCount) + ImgOffX * ImageColorChannelCount; - const size_t GlyphOffset = OffY * pBitmap->width + OffX; - for(size_t i = 0; i < ImageColorChannelCount; ++i) - { - if(i != ImageColorChannelCount - 1) - { - *(pImageBuff + ImageOffset + i) = 255; - } - else - { - *(pImageBuff + ImageOffset + i) = *(m_aGlyphData + GlyphOffset); - } - } - } - } - - WidthLastChars += (pBitmap->width + 1); - } - pCurrent = pTmp; - } + m_pGlyphMap->UploadEntityLayerText(pTexBuff, ImageColorChannelCount, TexWidth, TexHeight, TexSubWidth, TexSubHeight, pText, Length, x, y, FontSize); } int AdjustFontSize(const char *pText, int TextLength, int MaxSize, int MaxWidth) const override { const int WidthOfText = CalculateTextWidth(pText, TextLength, 0, 100); - int FontSize = 100.f / ((float)WidthOfText / (float)MaxWidth); + int FontSize = 100.0f / ((float)WidthOfText / (float)MaxWidth); if(MaxSize > 0 && FontSize > MaxSize) FontSize = MaxSize; @@ -1808,11 +2034,10 @@ public: float GetGlyphOffsetX(int FontSize, char TextCharacter) const override { - if(m_pDefaultFont == nullptr) - return -1; + if(m_pGlyphMap->DefaultFace() == nullptr) + return -1.0f; - const CFont *pFont = m_pDefaultFont; - FT_Set_Pixel_Sizes(pFont->m_FtFace, 0, FontSize); + FT_Set_Pixel_Sizes(m_pGlyphMap->DefaultFace(), 0, FontSize); const char *pTmp = &TextCharacter; const int NextCharacter = str_utf8_decode(&pTmp); @@ -1823,28 +2048,27 @@ public: #else const FT_Int32 FTFlags = FT_LOAD_RENDER | FT_LOAD_NO_BITMAP; #endif - if(FT_Load_Char(pFont->m_FtFace, NextCharacter, FTFlags)) + if(FT_Load_Char(m_pGlyphMap->DefaultFace(), NextCharacter, FTFlags)) { - dbg_msg("textrender", "error loading glyph %d in GetGlyphOffsetX", NextCharacter); - return -1; + log_debug("textrender", "Error loading glyph. Chr=%d", NextCharacter); + return -1.0f; } - return (float)(pFont->m_FtFace->glyph->metrics.horiBearingX >> 6); + return (float)(m_pGlyphMap->DefaultFace()->glyph->metrics.horiBearingX >> 6); } - return 0; + return 0.0f; } int CalculateTextWidth(const char *pText, int TextLength, int FontWidth, int FontHeight) const override { - if(m_pDefaultFont == nullptr) + if(m_pGlyphMap->DefaultFace() == nullptr) return 0; - const CFont *pFont = m_pDefaultFont; const char *pCurrent = pText; const char *pEnd = pCurrent + TextLength; int WidthOfText = 0; - FT_Set_Pixel_Sizes(pFont->m_FtFace, FontWidth, FontHeight); + FT_Set_Pixel_Sizes(m_pGlyphMap->DefaultFace(), FontWidth, FontHeight); while(pCurrent < pEnd) { const char *pTmp = pCurrent; @@ -1856,14 +2080,14 @@ public: #else const FT_Int32 FTFlags = FT_LOAD_RENDER | FT_LOAD_NO_BITMAP; #endif - if(FT_Load_Char(pFont->m_FtFace, NextCharacter, FTFlags)) + if(FT_Load_Char(m_pGlyphMap->DefaultFace(), NextCharacter, FTFlags)) { - dbg_msg("textrender", "error loading glyph %d in CalculateTextWidth", NextCharacter); + log_debug("textrender", "Error loading glyph. Chr=%d", NextCharacter); pCurrent = pTmp; continue; } - WidthOfText += (pFont->m_FtFace->glyph->metrics.width >> 6) + 1; + WidthOfText += (m_pGlyphMap->DefaultFace()->glyph->metrics.width >> 6) + 1; } pCurrent = pTmp; } @@ -1974,7 +2198,7 @@ public: { if(pTextContainer->m_ContainerIndex.Valid() && pTextContainer->m_ContainerIndex.m_UseCount.use_count() <= 1) { - dbg_msg("textrender", "Found non empty text container with index %d with %d quads '%s'", pTextContainer->m_StringInfo.m_QuadBufferContainerIndex, (int)pTextContainer->m_StringInfo.m_QuadNum, pTextContainer->m_aDebugText); + log_error("textrender", "Found non empty text container with index %d with %" PRIzu " quads '%s'", pTextContainer->m_StringInfo.m_QuadBufferContainerIndex, pTextContainer->m_StringInfo.m_vCharacterQuads.size(), pTextContainer->m_aDebugText); dbg_assert(false, "Text container was forgotten by the implementation (the index was overwritten)."); } } @@ -1987,28 +2211,15 @@ public: { if(pTextContainer->m_StringInfo.m_QuadBufferContainerIndex != -1) { - dbg_msg("textrender", "Found non empty text container with index %d with %d quads '%s'", pTextContainer->m_StringInfo.m_QuadBufferContainerIndex, (int)pTextContainer->m_StringInfo.m_QuadNum, pTextContainer->m_aDebugText); - dbg_msg("textrender", "The text container index was in use by %d ", (int)pTextContainer->m_ContainerIndex.m_UseCount.use_count()); + log_error("textrender", "Found non empty text container with index %d with %" PRIzu " quads '%s'", pTextContainer->m_StringInfo.m_QuadBufferContainerIndex, pTextContainer->m_StringInfo.m_vCharacterQuads.size(), pTextContainer->m_aDebugText); + log_error("textrender", "The text container index was in use by %d ", (int)pTextContainer->m_ContainerIndex.m_UseCount.use_count()); HasNonEmptyTextContainer = true; // NOLINT(clang-analyzer-deadcode.DeadStores) } } dbg_assert(!HasNonEmptyTextContainer, "text container was not empty"); - for(auto &pFont : m_vpFonts) - { - // reset the skylines - for(size_t j = 0; j < 2; ++j) - { - for(int &k : pFont->m_aTextureSkyline[j].m_vCurHeightOfPixelColumn) - k = 0; - - mem_zero(pFont->m_apTextureData[j], (size_t)pFont->m_aCurTextureDimensions[j] * pFont->m_aCurTextureDimensions[j] * sizeof(unsigned char)); - Graphics()->UpdateTextTexture(pFont->m_aTextures[j], 0, 0, pFont->m_aCurTextureDimensions[j], pFont->m_aCurTextureDimensions[j], pFont->m_apTextureData[j]); - } - - pFont->InitFontSizes(); - } + m_pGlyphMap->Clear(); } }; diff --git a/src/engine/textrender.h b/src/engine/textrender.h index f22548502..ec405eb56 100644 --- a/src/engine/textrender.h +++ b/src/engine/textrender.h @@ -57,9 +57,10 @@ enum ETextRenderFlags TEXT_RENDER_FLAG_ONE_TIME_USE = 1 << 9, }; -enum +enum class EFontPreset { - TEXT_FONT_ICON_FONT = 0, + DEFAULT_FONT, + ICON_FONT, }; namespace FontIcons { @@ -133,8 +134,6 @@ MAYBE_UNUSED static const char *FONT_ICON_DICE_FIVE = "\xEF\x94\xA3"; MAYBE_UNUSED static const char *FONT_ICON_DICE_SIX = "\xEF\x94\xA6"; } // end namespace FontIcons -class CFont; - enum ETextCursorSelectionMode { // ignore any kind of selection @@ -186,10 +185,8 @@ public: float m_LineWidth; float m_X, m_Y; float m_MaxCharacterHeight; - float m_LongestLineWidth; - CFont *m_pFont; float m_FontSize; float m_AlignedFontSize; @@ -255,13 +252,9 @@ public: virtual void MoveCursor(CTextCursor *pCursor, float x, float y) const = 0; virtual void SetCursorPosition(CTextCursor *pCursor, float x, float y) const = 0; - virtual CFont *LoadFont(const char *pFilename, unsigned char *pBuf, size_t Size) = 0; - virtual bool LoadFallbackFont(CFont *pFont, const char *pFilename, unsigned char *pBuf, size_t Size) const = 0; - virtual CFont *GetFont(size_t FontIndex) = 0; - virtual CFont *GetFont(const char *pFilename) = 0; - - virtual void SetDefaultFont(CFont *pFont) = 0; - virtual void SetCurFont(CFont *pFont) = 0; + virtual void LoadFonts() = 0; + virtual void SetFontPreset(EFontPreset FontPreset) = 0; + virtual void SetFontLanguageVariant(const char *pLanguageFile) = 0; virtual void SetRenderFlags(unsigned Flags) = 0; virtual unsigned GetRenderFlags() const = 0; @@ -288,10 +281,10 @@ public: virtual STextBoundingBox GetBoundingBoxTextContainer(STextContainerIndex TextContainerIndex) = 0; - virtual void UploadEntityLayerText(void *pTexBuff, size_t ImageColorChannelCount, int TexWidth, int TexHeight, int TexSubWidth, int TexSubHeight, const char *pText, int Length, float x, float y, int FontHeight) = 0; + virtual void UploadEntityLayerText(void *pTexBuff, size_t ImageColorChannelCount, int TexWidth, int TexHeight, int TexSubWidth, int TexSubHeight, const char *pText, int Length, float x, float y, int FontSize) = 0; virtual int AdjustFontSize(const char *pText, int TextLength, int MaxSize, int MaxWidth) const = 0; virtual float GetGlyphOffsetX(int FontSize, char TextCharacter) const = 0; - virtual int CalculateTextWidth(const char *pText, int TextLength, int FontWidth, int FontHeight) const = 0; + virtual int CalculateTextWidth(const char *pText, int TextLength, int FontWidth, int FontSize) const = 0; virtual bool SelectionToUTF8OffSets(const char *pText, int SelStart, int SelEnd, int &OffUTF8Start, int &OffUTF8End) const = 0; virtual bool UTF8OffToDecodedOff(const char *pText, int UTF8Off, int &DecodedOff) const = 0; @@ -321,6 +314,7 @@ class IEngineTextRender : public ITextRender MACRO_INTERFACE("enginetextrender", 0) public: virtual void Init() = 0; + virtual void Shutdown() override = 0; }; extern IEngineTextRender *CreateEngineTextRender(); diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index e7566b339..23a48932a 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -272,9 +272,9 @@ int CMenus::DoButton_CheckBox_Common(const void *pID, const char *pText, const c if(Checkable) { TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT); - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); UI()->DoLabel(&Box, FONT_ICON_XMARK, Box.h * CUI::ms_FontmodHeight, TEXTALIGN_MC); - TextRender()->SetCurFont(nullptr); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); } else UI()->DoLabel(&Box, pBoxText, Box.h * CUI::ms_FontmodHeight, TEXTALIGN_MC); @@ -538,7 +538,7 @@ int CMenus::RenderMenubar(CUIRect r) Box.VSplitLeft(33.0f, &Button, &Box); static CButtonContainer s_StartButton; - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); bool GotNewsOrUpdate = false; @@ -573,7 +573,7 @@ int CMenus::RenderMenubar(CUIRect r) } TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(NULL); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); Box.VSplitLeft(10.0f, 0, &Box); @@ -694,7 +694,7 @@ int CMenus::RenderMenubar(CUIRect r) } } - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); Box.VSplitRight(33.0f, &Box, &Button); @@ -745,7 +745,7 @@ int CMenus::RenderMenubar(CUIRect r) } TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(nullptr); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); if(NewPage != -1) { diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp index a4a04dcd4..30fd88ea9 100644 --- a/src/game/client/components/menus_browser.cpp +++ b/src/game/client/components/menus_browser.cpp @@ -199,7 +199,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) float FontSize = 14.0f; if(SmallFont) FontSize = 6.0f; - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); TextRender()->TextColor(TextColor); TextRender()->TextOutlineColor(TextOutlineColor); @@ -207,7 +207,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) TextRender()->TextOutlineColor(TextRender()->DefaultTextOutlineColor()); TextRender()->TextColor(TextRender()->DefaultTextColor()); TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(nullptr); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); }; int NumPlayers = 0; @@ -474,7 +474,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) // render quick search { - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); UI()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 16.0f, TEXTALIGN_ML); @@ -482,7 +482,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) ExcludeIconWidth = TextRender()->TextWidth(16.0f, FONT_ICON_BAN, -1, -1.0f); ExcludeSearchIconMax = maximum(SearchIconWidth, ExcludeIconWidth); TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(NULL); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); QuickSearch.VSplitLeft(ExcludeSearchIconMax, 0, &QuickSearch); QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch); @@ -504,12 +504,12 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) // render quick exclude { - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); UI()->DoLabel(&QuickExclude, FONT_ICON_BAN, 16.0f, TEXTALIGN_ML); TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(NULL); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); QuickExclude.VSplitLeft(ExcludeSearchIconMax, 0, &QuickExclude); QuickExclude.VSplitLeft(5.0f, 0, &QuickExclude); @@ -1416,11 +1416,11 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) Header.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, UI()->MouseHovered(&Header) ? 0.4f : 0.25f), IGraphics::CORNER_ALL, 5.0f); Header.VSplitLeft(Header.h, &GroupIcon, &GroupLabel); GroupIcon.Margin(2.0f, &GroupIcon); - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->TextColor(UI()->MouseHovered(&Header) ? TextRender()->DefaultTextColor() : ColorRGBA(0.6f, 0.6f, 0.6f, 1.0f)); UI()->DoLabel(&GroupIcon, s_aListExtended[FriendType] ? FONT_ICON_SQUARE_MINUS : FONT_ICON_SQUARE_PLUS, GroupIcon.h * CUI::ms_FontmodHeight, TEXTALIGN_MC); TextRender()->TextColor(TextRender()->DefaultTextColor()); - TextRender()->SetCurFont(nullptr); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); switch(FriendType) { case FRIEND_PLAYER_ON: @@ -1535,7 +1535,7 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) SLabelProperties Props; Props.m_EnableWidthCheck = false; - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); TextRender()->TextColor(0.4f, 0.7f, 0.94f, 1.0f); TextRender()->TextOutlineColor(0.0f, 0.0f, 0.0f, 1.0f); @@ -1546,7 +1546,7 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) TextRender()->TextColor(TextRender()->DefaultTextColor()); TextRender()->TextOutlineColor(TextRender()->DefaultTextOutlineColor()); TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(nullptr); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); } // server info text diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp index ce9a5f9fd..a19e9eeb5 100644 --- a/src/game/client/components/menus_demo.cpp +++ b/src/game/client/components/menus_demo.cpp @@ -32,7 +32,7 @@ int CMenus::DoButton_FontIcon(CButtonContainer *pButtonContainer, const char *pT { pRect->Draw(ColorRGBA(1.0f, 1.0f, 1.0f, (Checked ? 0.10f : 0.5f) * UI()->ButtonColorMul(pButtonContainer)), Corners, 5.0f); - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->TextOutlineColor(TextRender()->DefaultTextOutlineColor()); TextRender()->TextColor(TextRender()->DefaultTextColor()); CUIRect Temp; @@ -48,7 +48,7 @@ int CMenus::DoButton_FontIcon(CButtonContainer *pButtonContainer, const char *pT TextRender()->TextColor(TextRender()->DefaultTextColor()); } - TextRender()->SetCurFont(nullptr); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); return UI()->DoButtonLogic(pButtonContainer, Checked, pRect); } @@ -1162,13 +1162,13 @@ void CMenus::RenderDemoList(CUIRect MainView) else IconColor = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->TextColor(IconColor); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING); UI()->DoLabel(&Button, pIconType, 12.0f, TEXTALIGN_ML); TextRender()->SetRenderFlags(0); TextRender()->TextColor(TextRender()->DefaultTextColor()); - TextRender()->SetCurFont(nullptr); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); } else if(ID == COL_DEMONAME) { diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index 2f468cc34..0d3c0b914 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -665,13 +665,13 @@ void CMenus::RenderServerControl(CUIRect MainView) { Bottom.VSplitLeft(240.0f, &QuickSearch, &Bottom); QuickSearch.HSplitTop(5.0f, 0, &QuickSearch); - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); UI()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML); float wSearch = TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f); TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(NULL); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); QuickSearch.VSplitLeft(wSearch, 0, &QuickSearch); QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch); diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 746eafd9a..0206b8175 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -660,7 +660,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) static CButtonContainer s_RandomSkinButtonID; static const char *s_apDice[] = {FONT_ICON_DICE_ONE, FONT_ICON_DICE_TWO, FONT_ICON_DICE_THREE, FONT_ICON_DICE_FOUR, FONT_ICON_DICE_FIVE, FONT_ICON_DICE_SIX}; static int s_CurrentDie = rand() % std::size(s_apDice); - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); if(DoButton_Menu(&s_RandomSkinButtonID, s_apDice[s_CurrentDie], 1, &Button, nullptr, IGraphics::CORNER_ALL, 5.0f, -0.2f)) { @@ -668,7 +668,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) s_CurrentDie = rand() % std::size(s_apDice); } TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(nullptr); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); GameClient()->m_Tooltips.DoToolTip(&s_RandomSkinButtonID, &Button, Localize("Create a random skin")); // custom color selector @@ -770,7 +770,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) } auto &&RenderFavIcon = [&](const CUIRect &FavIcon, bool AsFav) { - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); if(AsFav) TextRender()->TextColor({1, 1, 0, 1}); @@ -782,7 +782,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) UI()->DoLabel(&FavIcon, FONT_ICON_STAR, 12.0f, TEXTALIGN_MR, Props); TextRender()->TextColor(TextRender()->DefaultTextColor()); TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(nullptr); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); }; int OldSelected = -1; @@ -873,13 +873,13 @@ void CMenus::RenderSettingsTee(CUIRect MainView) MainView.HSplitBottom(ms_ButtonHeight, &MainView, &QuickSearch); QuickSearch.VSplitLeft(240.0f, &QuickSearch, &SkinDB); QuickSearch.HSplitTop(5.0f, 0, &QuickSearch); - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); UI()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML); float wSearch = TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f); TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(NULL); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); QuickSearch.VSplitLeft(wSearch, 0, &QuickSearch); QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch); QuickSearch.VSplitLeft(QuickSearch.w - 15.0f, &QuickSearch, &QuickSearchClearButton); @@ -922,19 +922,19 @@ void CMenus::RenderSettingsTee(CUIRect MainView) } GameClient()->m_Tooltips.DoToolTip(&s_DirectoryButtonID, &DirectoryButton, Localize("Open the directory to add custom skins")); - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); static CButtonContainer s_SkinRefreshButtonID; if(DoButton_Menu(&s_SkinRefreshButtonID, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &RefreshButton, nullptr, IGraphics::CORNER_ALL, 5, 0, vec4(1.0f, 1.0f, 1.0f, 0.75f), vec4(1, 1, 1, 0.5f))) { // reset render flags for possible loading screen TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(NULL); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); RefreshSkins(); s_InitSkinlist = true; } TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(NULL); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); } typedef struct @@ -1993,7 +1993,6 @@ bool CMenus::RenderLanguageSelection(CUIRect MainView) if(OldSelected != s_SelectedLanguage) { str_copy(g_Config.m_ClLanguagefile, g_Localization.Languages()[s_SelectedLanguage].m_FileName.c_str()); - g_Localization.Load(g_Localization.Languages()[s_SelectedLanguage].m_FileName.c_str(), Storage(), Console()); GameClient()->OnLanguageChange(); } diff --git a/src/game/client/components/menus_settings_assets.cpp b/src/game/client/components/menus_settings_assets.cpp index b1a0e355b..e5f328917 100644 --- a/src/game/client/components/menus_settings_assets.cpp +++ b/src/game/client/components/menus_settings_assets.cpp @@ -640,13 +640,13 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) MainView.HSplitBottom(ms_ButtonHeight, &MainView, &QuickSearch); QuickSearch.VSplitLeft(240.0f, &QuickSearch, &DirectoryButton); QuickSearch.HSplitTop(5.0f, 0, &QuickSearch); - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); UI()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 14.0f, TEXTALIGN_ML); float wSearch = TextRender()->TextWidth(14.0f, FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f); TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(NULL); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); QuickSearch.VSplitLeft(wSearch, 0, &QuickSearch); QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch); QuickSearch.VSplitLeft(QuickSearch.w - 15.0f, &QuickSearch, &QuickSearchClearButton); @@ -691,7 +691,7 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) } GameClient()->m_Tooltips.DoToolTip(&s_AssetsDirID, &DirectoryButton, Localize("Open the directory to add custom assets")); - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); static CButtonContainer s_AssetsReloadBtnID; if(DoButton_Menu(&s_AssetsReloadBtnID, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &ReloadButton, nullptr, IGraphics::CORNER_ALL, 5, 0, vec4(1.0f, 1.0f, 1.0f, 0.75f), vec4(1, 1, 1, 0.5f))) @@ -699,7 +699,7 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) ClearCustomItems(s_CurCustomTab); } TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(NULL); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); } void CMenus::ConchainAssetsEntities(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 48678be2a..63ee17ae1 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -192,7 +192,8 @@ void CGameClient::OnConsoleInit() for(auto &pComponent : m_vpAll) pComponent->OnConsoleInit(); - // + Console()->Chain("cl_languagefile", ConchainLanguageUpdate, this); + Console()->Chain("player_name", ConchainSpecialInfoupdate, this); Console()->Chain("player_clan", ConchainSpecialInfoupdate, this); Console()->Chain("player_country", ConchainSpecialInfoupdate, this); @@ -254,7 +255,8 @@ void CGameClient::OnInit() for(int i = 0; i < NUM_NETOBJTYPES; i++) Client()->SnapSetStaticsize(i, m_NetObjHandler.GetObjSize(i)); - Client()->LoadFont(); + TextRender()->LoadFonts(); + TextRender()->SetFontLanguageVariant(g_Config.m_ClLanguagefile); // update and swap after font loading, they are quite huge Client()->UpdateAndSwap(); @@ -380,6 +382,8 @@ void CGameClient::OnInit() void CGameClient::OnUpdate() { + HandleLanguageChanged(); + CUIElementBase::Init(UI()); // update static pointer because game and editor use separate UI // handle mouse movement @@ -971,6 +975,21 @@ void CGameClient::OnWindowResize() void CGameClient::OnLanguageChange() { + // The actual language change is delayed because it + // might require clearing the text render font atlas, + // which would invalidate text that is currently drawn. + m_LanguageChanged = true; +} + +void CGameClient::HandleLanguageChanged() +{ + if(!m_LanguageChanged) + return; + m_LanguageChanged = false; + + g_Localization.Load(g_Config.m_ClLanguagefile, Storage(), Console()); + TextRender()->SetFontLanguageVariant(g_Config.m_ClLanguagefile); + UI()->OnLanguageChange(); } @@ -2285,6 +2304,13 @@ void CGameClient::ConKill(IConsole::IResult *pResult, void *pUserData) ((CGameClient *)pUserData)->SendKill(-1); } +void CGameClient::ConchainLanguageUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments()) + ((CGameClient *)pUserData)->OnLanguageChange(); +} + void CGameClient::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { pfnCallback(pResult, pCallbackUserData); diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index 088f45942..3efea6557 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -201,6 +201,7 @@ private: static void ConTeam(IConsole::IResult *pResult, void *pUserData); static void ConKill(IConsole::IResult *pResult, void *pUserData); + static void ConchainLanguageUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainSpecialDummyInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainSpecialDummy(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); @@ -487,7 +488,9 @@ public: void OnWindowResize(); + bool m_LanguageChanged = false; void OnLanguageChange(); + void HandleLanguageChanged(); void RenderShutdownMessage(); diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index 11f1503bb..1fafd7de7 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -934,10 +934,10 @@ int CUI::DoButton_Menu(CUIElement &UIElement, const CButtonContainer *pID, const pText = GetTextLambda(); NewRect.m_Text = pText; if(Props.m_UseIconFont) - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); DoLabel(NewRect, &Text, pText, Text.h * CUI::ms_FontmodHeight, TEXTALIGN_MC); if(Props.m_UseIconFont) - TextRender()->SetCurFont(nullptr); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); } } Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); @@ -1615,11 +1615,11 @@ int CUI::DoDropDown(CUIRect *pRect, int CurSelection, const char **pStrs, int Nu CUIRect DropDownIcon; pRect->HMargin(2.0f, &DropDownIcon); DropDownIcon.VSplitRight(5.0f, &DropDownIcon, nullptr); - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); DoLabel(&DropDownIcon, FONT_ICON_CIRCLE_CHEVRON_DOWN, DropDownIcon.h * CUI::ms_FontmodHeight, TEXTALIGN_MR); TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(nullptr); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); if(State.m_SelectionPopupContext.m_SelectionIndex >= 0) { diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 90040f697..2ef03caf0 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -466,11 +466,11 @@ int CEditor::DoButton_FontIcon(const void *pID, const char *pText, int Checked, { pRect->Draw(GetButtonColor(pID, Checked), Corners, 3.0f); - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING); UI()->DoLabel(pRect, pText, FontSize, TEXTALIGN_MC); TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(nullptr); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip); } @@ -5203,11 +5203,11 @@ void CEditor::RenderFileDialog() pIconType = FONT_ICON_FOLDER; } - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING); UI()->DoLabel(&FileIcon, pIconType, 12.0f, TEXTALIGN_ML); TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(nullptr); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); SLabelProperties Props; Props.m_MaxWidth = Button.w; @@ -7347,11 +7347,11 @@ void CEditor::RenderMenubar(CUIRect MenuBar) if(m_Map.m_Modified) { - TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); UI()->DoLabel(&ChangedIndicator, FONT_ICON_CIRCLE, 8.0f, TEXTALIGN_MC); TextRender()->SetRenderFlags(0); - TextRender()->SetCurFont(nullptr); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); static int s_ChangedIndicator; DoButton_Editor_Common(&s_ChangedIndicator, "", 0, &ChangedIndicator, 0, "This map has unsaved changes"); // just for the tooltip, result unused }