diff options
Diffstat (limited to 'Client/Source/GUI/Font.cpp')
-rw-r--r-- | Client/Source/GUI/Font.cpp | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/Client/Source/GUI/Font.cpp b/Client/Source/GUI/Font.cpp new file mode 100644 index 0000000..1efe551 --- /dev/null +++ b/Client/Source/GUI/Font.cpp @@ -0,0 +1,302 @@ +#include "freetype.h" +#include "Font.h" + +#include "../Math/Functions.h" +#include "../Math/Math.h" +#include "../Debug/Log.h" +#include "../Utilities/Assert.h" +#include "../Graphics/ImageData.h" + +using namespace character; + +/* +* http://madig.github.io/freetype-web/documentation/tutorial/ +* https://unicode-table.com/en/#cjk-unified-ideographs-extension-a +* https://learnopengl.com/In-Practice/Text-Rendering +* https://www.zhihu.com/question/294660079 +* https://baike.baidu.com/item/%E5%9F%BA%E6%9C%AC%E5%A4%9A%E6%96%87%E7%A7%8D%E5%B9%B3%E9%9D%A2/10788078 +* https://stackoverflow.com/questions/27331819/whats-the-difference-between-a-character-a-code-point-a-glyph-and-a-grapheme +*/ + +static std::string s_FontError; + +static std::vector<unsigned char> s_PixelBuffer; +static const int s_SizePerPixel = sizeof(unsigned char); + +Font::Font(std::string path, TextGeneratingSettings settings) + : m_Characters() +{ + m_AtlasMargin = settings.margin; + m_GlyphPadding = settings.padding; + m_AtlasSize = settings.atlasSize; + + if (FT_Init_FreeType(&m_FTLibrary)) + { + s_FontError = "Init freetype error. Font path " + path; + throw FontException(s_FontError.c_str()); + } + + if (FT_New_Face(m_FTLibrary, path.c_str(), 0, &m_FTFace)) + { + s_FontError = "Create freetype face error. Font path " + path; + throw FontException(s_FontError.c_str()); + } +} + +Font::Font(DataBuffer* db, TextGeneratingSettings settings) + : m_Characters() +{ + m_AtlasMargin = settings.margin; + m_GlyphPadding = settings.padding; + m_AtlasSize = settings.atlasSize; + + if (FT_Init_FreeType(&m_FTLibrary)) + { + s_FontError = "Init freetype error."; + throw FontException(s_FontError.c_str()); + } + + if (FT_New_Memory_Face(m_FTLibrary, db->udata, db->length, 0, &m_FTFace)) + { + s_FontError = "Create freetype face error."; + throw FontException(s_FontError.c_str()); + } +} + +character::Hash Font::GetHash(Unicode codepoint, int pixelSize) +{ + character::Hash hash; + hash.codepoint = codepoint; + hash.size = pixelSize; + return hash; +} + +const Character* Font::GetCharacter(character::Unicode codepoint, int pixelSize) +{ + character::Hash hash = GetHash(codepoint, pixelSize); + auto iter = m_Characters.find(hash.hashCode); + if (iter == m_Characters.end()) + { + if (RenderCharacter(codepoint, pixelSize)) + { + iter = m_Characters.find(hash.hashCode); + } + else + { + return NULL; + } + } + Assert(iter != m_Characters.end()); + return &iter->second; +} + +void Font::RenderCharacters(character::Unicode* codepoint, int n, int pixelSize) +{ + for (int i = 0; i < n; ++i) + { + RenderCharacter(codepoint[i], pixelSize); + } +} + +void Font::RenderCharacters(std::vector<character::Unicode>& codepoint, int pixelSize) +{ + int n = codepoint.size(); + for (int i = 0; i < n; ++i) + { + RenderCharacter(codepoint[i], pixelSize); + } +} + +bool Font::RenderCharacter(character::Unicode codepoint, int pixelSize) +{ + character::Hash hash = GetHash(codepoint, pixelSize); + if (m_Characters.size() > 0 && m_Characters.count(hash.hashCode) != 0) + return true; + FT_Set_Pixel_Sizes(m_FTFace, 0, pixelSize); + + // bug: 发现有时候渲染的结果是FT_PIXEL_MODE_MONO(1-bits),试过不同的flags组合还是不对,最后手动转换为8-bits + + FT_Int32 flags = FT_LOAD_RENDER; + if (FT_Load_Char(m_FTFace, codepoint, flags)) + return false; + + Character character; + + int w = m_FTFace->glyph->bitmap.width; + int h = m_FTFace->glyph->bitmap.rows; + + const unsigned char* pixels = m_FTFace->glyph->bitmap.buffer; + if (pixels != NULL) // 有些字体中space、tab没有字形的 + { + s_PixelBuffer.resize(w * h); + if (m_FTFace->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) + { + // 1-bit monochrome + int pitch = m_FTFace->glyph->bitmap.pitch; + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + int index = x + y * w; + s_PixelBuffer[index] = ((pixels[pitch * y + x / 8]) & (1 << (7 - x % 8))) ? 255 : 0; + } + } + } + else if (m_FTFace->glyph->bitmap.pixel_mode) + { + // 8-bit grayscale + memcpy(&s_PixelBuffer[0], pixels, s_SizePerPixel * w * h); + } + + //TextHelper::print_glyph(&s_PixelBuffer[0], w, h); + + GlyphAtals* atlas = RequestAtlas(pixelSize, Vector2f(w, h)); + Assert(atlas); + + Rect rect = GetRenderChartAndMove(atlas, Vector2f(w, h)); + + try + { + atlas->altas->UpdateSubImage(rect, EPixelFormat::PixelFormat_R, EPixelElementType::PixelType_UNSIGNED_BYTE, &s_PixelBuffer[0]); + } + catch (TextureException& e) + { + s_PixelBuffer.clear(); + std::string error = e.what(); + error = "Render Character Error: " + error; + log_error(e.what()); + return false; + } + s_PixelBuffer.clear(); + + character.atlas = atlas->index; + character.position = rect; + character.bearing = Vector2f(m_FTFace->glyph->bitmap_left, m_FTFace->glyph->bitmap_top); + } + else // space、tab + { + character.atlas = FONT_NOT_IN_ATLAS_PLACEHOLDER; + character.position = Rect(0,0,0,0); + character.bearing = Vector2f(0, 0); + } + + character.advance = m_FTFace->glyph->advance.x * 1/64.f; + + m_Characters.insert(std::pair<unsigned int, Character>(hash.hashCode, character)); + + return true; +} + +const GlyphAtals* Font::GetGlyphAtlas(int index) +{ + if (index >= m_Atlases.size()) + return NULL; + return &m_Atlases[index]; +} + +Rect Font::GetRenderChartAndMove(GlyphAtals* atlas, Vector2f preferSize) +{ + Rect rect; + Vector2f space; + space.x = atlas->width - atlas->cursor.x - m_AtlasMargin; + space.y = atlas->height - atlas->cursor.y - m_AtlasMargin; + if (space.x > preferSize.x && space.y > preferSize.y) + { + rect.x = atlas->cursor.x; + rect.y = atlas->cursor.y; + rect.width = preferSize.x; + rect.height = preferSize.y; + atlas->cursor.x += rect.width + m_GlyphPadding; + atlas->rowHeight = MathUtils::Max((float)atlas->rowHeight, preferSize.y); + } + else if (space.y - atlas->rowHeight - m_GlyphPadding - m_AtlasMargin > preferSize.y) + { + rect.x = m_AtlasMargin; + rect.y = atlas->cursor.y + m_GlyphPadding + atlas->rowHeight; + rect.width = preferSize.x; + rect.height = preferSize.y; + atlas->cursor.x = m_AtlasMargin; + atlas->cursor.y += atlas->rowHeight + m_GlyphPadding; + atlas->rowHeight = rect.height; + } + else + { + Assert(false); + } + return rect; +} + +GlyphAtals* Font::RequestAtlas(int pixelSize, Vector2f preferSize) +{ + GlyphAtals* atlas = NULL; + auto iter = m_AtlasCache.find(pixelSize); + if (iter == m_AtlasCache.end() || !HasEnoughSpace(iter->second, preferSize)) + { + Assert(m_Atlases.size() < FONT_NOT_IN_ATLAS_PLACEHOLDER); + + Texture* tex = CreateAtlas(); + GlyphAtals newAtlas = GlyphAtals(); + newAtlas.altas = tex; + newAtlas.width = m_AtlasSize.x; + newAtlas.height = m_AtlasSize.y; + newAtlas.index = m_Atlases.size(); + newAtlas.cursor = Vector2f(m_AtlasMargin, m_AtlasMargin); + + m_Atlases.push_back(newAtlas); + atlas = &m_Atlases[m_Atlases.size() - 1]; + + if (iter != m_AtlasCache.end()) + m_AtlasCache.erase(pixelSize); + m_AtlasCache.insert(std::pair<int, GlyphAtals*>(pixelSize, atlas)); + } + else + { + atlas = iter->second; + } + Assert(atlas); + return atlas; +} + +Texture* Font::CreateAtlas() +{ + TextureSetting setting; + setting.filterMode = ETextureFilterMode::Nearest; + setting.wrapMode = ETextureWrapMode::Clamp; + setting.format = ETextureFormat::R8; + setting.type = ETextureType::TEX_2D; + setting.keepImageData = false; + Texture* tex = new Texture(setting, m_AtlasSize.x, m_AtlasSize.y); + return tex; +} + +bool Font::HasEnoughSpace(GlyphAtals* atlas, Vector2f preferSize) +{ + if (atlas == NULL) + return false; + Vector2f space; + space.x = atlas->width - atlas->cursor.x - m_AtlasMargin; + space.y = atlas->height - atlas->cursor.y - m_AtlasMargin; + if (space.x > preferSize.x && space.y > preferSize.y) + return true; + if (space.y - atlas->rowHeight - m_GlyphPadding - m_AtlasMargin > preferSize.y) + return true; + return false; +} + +Character GetCharacter(character::Unicode Unicode, int pixelSize) +{ + return Character(); +} + +void TextHelper::print_glyph(unsigned char* glyph, int width, int height) +{ + for (int r = 0; r < height; ++r) + { + for (int c = 0; c < width; ++c) + { + unsigned char ch = glyph[c + r * width]; + printf("%c", ch == 0 ? ' ' : '+'); + } + printf("\n"); + } +} |