summaryrefslogtreecommitdiff
path: root/Client/Source/GUI/Font.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Client/Source/GUI/Font.cpp')
-rw-r--r--Client/Source/GUI/Font.cpp302
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");
+ }
+}