From 682aef84223d6e11b314d337fc84c6631e9df6f5 Mon Sep 17 00:00:00 2001 From: Hleb Valoshka <375gnu@gmail.com> Date: Mon, 3 Jun 2019 20:57:15 +0300 Subject: [PATCH] Initial implementation for TrueType fonts support --- .appveyor.yml | 3 +- .travis.yml | 4 +- CMakeLists.txt | 8 + src/CMakeLists.txt | 7 +- src/celengine/CMakeLists.txt | 12 +- src/celengine/console.h | 4 + src/celengine/overlay.h | 4 + src/celengine/render.h | 4 + src/celestia/CMakeLists.txt | 4 +- src/celestia/celestiacore.cpp | 4 + src/celscript/lua/celx_celestia.cpp | 7 +- src/celttf/CMakeLists.txt | 6 + src/celttf/truetypefont.cpp | 628 ++++++++++++++++++++++++++++ src/celttf/truetypefont.h | 54 +++ 14 files changed, 741 insertions(+), 8 deletions(-) create mode 100644 src/celttf/CMakeLists.txt create mode 100644 src/celttf/truetypefont.cpp create mode 100644 src/celttf/truetypefont.h diff --git a/.appveyor.yml b/.appveyor.yml index c6769c82..1c494cfd 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -68,6 +68,7 @@ install: - vcpkg install fmt:x86-windows - vcpkg install glew:x86-windows - vcpkg install eigen3:x86-windows +- vcpkg install freetype:x86-windows - cd c:\tools\vcpkg - vcpkg integrate install - cd %APPVEYOR_BUILD_FOLDER% @@ -80,7 +81,7 @@ build_script: cd build - cmake -DCMAKE_PREFIX_PATH=%Qt5_DIR% -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DENABLE_SPICE=ON -DENABLE_TOOLS=ON .. + cmake -DCMAKE_PREFIX_PATH=%Qt5_DIR% -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DENABLE_SPICE=ON -DENABLE_TOOLS=ON ENABLE_TTF=ON .. cmake --build . --config Release -- /maxcpucount:4 /nologo diff --git a/.travis.yml b/.travis.yml index f3f9d20a..1c4754dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ before_script: - cd build script: -- cmake -DENABLE_SPICE=ON -DENABLE_TOOLS=ON .. +- cmake -DENABLE_SPICE=ON -DENABLE_TOOLS=ON -DENABLE_TTF=ON .. - make -j $(nproc || echo 4) addons: @@ -52,6 +52,7 @@ addons: - qtbase5-dev - qtbase5-dev-tools - libqt5opengl5-dev + - libfreetype6-dev homebrew: packages: - eigen @@ -63,3 +64,4 @@ addons: - libpng - lua - qt5 + - freetype diff --git a/CMakeLists.txt b/CMakeLists.txt index a833415c..f3092888 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ option(ENABLE_THEORA "Support video capture to OGG Theora? (Default: on)" ON) option(ENABLE_TOOLS "Build different tools? (Default: off)" OFF) option(NATIVE_OSX_APP "Support native OSX paths read data from (Default: off)" OFF) option(FAST_MATH "Build with unsafe fast-math compiller option (Default: off)" OFF) +option(ENABLE_TTF "Use TrueType fonts instead of TXF (Default: off)" OFF) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type." FORCE) @@ -188,6 +189,13 @@ if(ENABLE_CELX) link_libraries(${LUA_LIBRARIES}) endif() +if(ENABLE_TTF) + find_package(Freetype REQUIRED) + link_libraries(Freetype::Freetype) +else() + add_definitions(-DNO_TTF=1) +endif() + #[[ get_cmake_property(_variableNames VARIABLES) list (SORT _variableNames) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2be64284..ac9a1907 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,6 @@ set(CELESTIA_LIBS celestia celmath celmodel - celtxf celutil ) @@ -18,6 +17,12 @@ add_subdirectory(celscript) add_subdirectory(celephem) add_subdirectory(celcompat) +if (ENABLE_TTF) + add_subdirectory(celttf) +else() + add_subdirectory(celtxf) +endif() + if (ENABLE_TOOLS) add_subdirectory(tools) endif() diff --git a/src/celengine/CMakeLists.txt b/src/celengine/CMakeLists.txt index 498b3b30..8d269560 100644 --- a/src/celengine/CMakeLists.txt +++ b/src/celengine/CMakeLists.txt @@ -160,8 +160,16 @@ set_target_properties(celengine PROPERTIES ) ]]# -add_dependencies(celengine cel3ds celmath celmodel celtxf celutil) -target_link_libraries(celengine cel3ds celmath celmodel celtxf celutil) +add_dependencies(celengine cel3ds celmath celmodel celutil) +target_link_libraries(celengine cel3ds celmath celmodel celutil) + +if (ENABLE_TTF) + add_dependencies(celengine celttf) + target_link_libraries(celengine celttf) +else() + add_dependencies(celengine celtxf) + target_link_libraries(celengine celtxf) +endif() # Some celephem objects depend on Spice if(ENABLE_SPICE) diff --git a/src/celengine/console.h b/src/celengine/console.h index d65b0f75..d6c7219c 100644 --- a/src/celengine/console.h +++ b/src/celengine/console.h @@ -12,7 +12,11 @@ #include #include +#if NO_TTF #include +#else +#include +#endif class Console; diff --git a/src/celengine/overlay.h b/src/celengine/overlay.h index f9a4b8b1..280747b6 100644 --- a/src/celengine/overlay.h +++ b/src/celengine/overlay.h @@ -15,7 +15,11 @@ #include #include #include +#if NO_TTF #include +#else +#include +#endif class Overlay; diff --git a/src/celengine/render.h b/src/celengine/render.h index 2ff9cdbb..a637cda7 100644 --- a/src/celengine/render.h +++ b/src/celengine/render.h @@ -22,7 +22,11 @@ #include #include #include "celengine/vertexobject.h" +#if NO_TTF #include +#else +#include +#endif #include #include #include diff --git a/src/celestia/CMakeLists.txt b/src/celestia/CMakeLists.txt index 86351988..6366d83a 100644 --- a/src/celestia/CMakeLists.txt +++ b/src/celestia/CMakeLists.txt @@ -49,8 +49,8 @@ set_target_properties(celestia PROPERTIES ) ]]# -add_dependencies(celestia celengine celmath celtxf celutil) -target_link_libraries(celestia celengine celmath celtxf celutil) +add_dependencies(celestia celengine celmath celutil) +target_link_libraries(celestia celengine celmath celutil) cotire(celestia) diff --git a/src/celestia/celestiacore.cpp b/src/celestia/celestiacore.cpp index b0849848..4d82261b 100644 --- a/src/celestia/celestiacore.cpp +++ b/src/celestia/celestiacore.cpp @@ -3818,7 +3818,11 @@ bool CelestiaCore::initRenderer() } if (config->mainFont == "") +#if NO_TTF font = LoadTextureFont("fonts/default.txf"); +#else + font = LoadTextureFont("fonts/FreeSans.ttf,12"); +#endif else font = LoadTextureFont(string("fonts/") + config->mainFont); diff --git a/src/celscript/lua/celx_celestia.cpp b/src/celscript/lua/celx_celestia.cpp index dde951ac..ceb73eac 100644 --- a/src/celscript/lua/celx_celestia.cpp +++ b/src/celscript/lua/celx_celestia.cpp @@ -10,7 +10,12 @@ // of the License, or (at your option) any later version. #include -#include +#if NO_TTF +#include "celtxf/texturefont.h" +#else +#include "celttf/truetypefont.h" +#endif +#include #include #include #include diff --git a/src/celttf/CMakeLists.txt b/src/celttf/CMakeLists.txt new file mode 100644 index 00000000..bfc475f9 --- /dev/null +++ b/src/celttf/CMakeLists.txt @@ -0,0 +1,6 @@ +set(CELTTF_SOURCES + truetypefont.cpp + truetypefont.h +) + +add_library(celttf STATIC ${CELTTF_SOURCES}) diff --git a/src/celttf/truetypefont.cpp b/src/celttf/truetypefont.cpp new file mode 100644 index 00000000..ae843f75 --- /dev/null +++ b/src/celttf/truetypefont.cpp @@ -0,0 +1,628 @@ +// truetypefont.cpp +// +// Copyright (C) 2019, Celestia Development Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include FT_FREETYPE_H +#include "truetypefont.h" + +#define DUMP_TEXTURE 0 + +#if DUMP_TEXTURE +#include +#endif + +using namespace std; + +static FT_Library ft = nullptr; + +struct Glyph +{ + wchar_t ch; + + float ax; // advance.x + float ay; // advance.y + + float bw; // bitmap.width; + float bh; // bitmap.height; + + float bl; // bitmap_left; + float bt; // bitmap_top; + + float tx; // x offset of glyph in texture coordinates + float ty; // y offset of glyph in texture coordinates +}; + +struct UnicodeBlock +{ + wchar_t first, last; +}; + +struct TextureFontPrivate +{ + TextureFontPrivate(); + ~TextureFontPrivate(); + TextureFontPrivate(const TextureFontPrivate&) = default; + TextureFontPrivate(TextureFontPrivate&&) = default; + TextureFontPrivate& operator=(const TextureFontPrivate&) = default; + TextureFontPrivate& operator=(TextureFontPrivate&&) = default; + + float render(const string &s, float x, float y); + float render(wchar_t ch, float xoffset, float yoffset); + + bool buildAtlas(); + void computeTextureSize(); + bool loadGlyphInfo(wchar_t, Glyph&); + void initCommonGlyphs(); + int getCommonGlyphsCount(); + Glyph& getGlyph(wchar_t); + Glyph& getGlyph(wchar_t, wchar_t); + int toPos(wchar_t) const; + void optimize(); + + FT_Face m_face; // font face + + int m_maxAscent; + int m_maxDescent; + int m_maxWidth; + + int m_texWidth; + int m_texHeight; + + GLuint m_texName; // texture object + vector m_glyphs; // character information + GLint m_maxTextureSize; // max supported texture size + + array m_unicodeBlocks; + int m_commonGlyphsCount { 0 }; + + int m_inserted { 0 }; +}; + +inline float pt_to_px(float pt, int dpi = 96) +{ + return dpi == 0 ? pt : pt / 72.0 * dpi; +} + +/* + first = ((c / 32) + 1) * 32 == c & ~0xdf + last = first + 32 +*/ + +TextureFontPrivate::TextureFontPrivate() +{ + m_unicodeBlocks[0] = { 0x0020, 0x007E }; // Basic Latin + m_unicodeBlocks[1] = { 0x03B1, 0x03CF }; // Lower case Greek + + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_maxTextureSize); +} + +TextureFontPrivate::~TextureFontPrivate() +{ + if (m_face) + FT_Done_Face(m_face); + if (m_texName != 0) + glDeleteTextures(1, &m_texName); +} + +bool TextureFontPrivate::loadGlyphInfo(wchar_t ch, Glyph &c) +{ + FT_GlyphSlot g = m_face->glyph; + if (FT_Load_Char(m_face, ch, FT_LOAD_RENDER) != 0) + { + c.ch = 0; + return false; + } + + c.ch = ch; + c.ax = g->advance.x >> 6; + c.ay = g->advance.y >> 6; + c.bw = g->bitmap.width; + c.bh = g->bitmap.rows; + c.bl = g->bitmap_left; + c.bt = g->bitmap_top; + return true; +} + +void TextureFontPrivate::initCommonGlyphs() +{ + if (m_glyphs.size() > 0) + return; + + m_glyphs.reserve(256); + + for (auto const &block : m_unicodeBlocks) + { + for (wchar_t ch = block.first, e = block.last; ch <= e; ch++) + { + Glyph c; + if (!loadGlyphInfo(ch, c)) + fmt::fprintf(cerr, "Loading character %x failed!\n", (unsigned)ch); + m_glyphs.push_back(c); // still pushing empty + } + } +} + +void TextureFontPrivate::computeTextureSize() +{ + FT_GlyphSlot g = m_face->glyph; + + int roww = 0; + int rowh = 0; + int w = 0; + int h = 0; + + // Find minimum size for a texture holding all visible ASCII characters + for (const auto &c : m_glyphs) + { + if (c.ch == 0) continue; // skip bad glyphs + + if (roww + c.bw + 1 >= m_maxTextureSize) + { + w = max(w, roww); + h += rowh; + roww = 0; + rowh = 0; + } + roww += c.bw + 1; + rowh = max(rowh, (int)c.bh); + } + + w = max(w, roww); + h += rowh; + + m_texWidth = w; + m_texHeight = h; +} + +bool TextureFontPrivate::buildAtlas() +{ + FT_GlyphSlot g = m_face->glyph; + Glyph c; + + initCommonGlyphs(); + computeTextureSize(); + + // Create a texture that will be used to hold all glyphs + glActiveTexture(GL_TEXTURE0); + if (m_texName != 0) + glDeleteTextures(1, &m_texName); + glGenTextures(1, &m_texName); + if (m_texName == 0) + return false; + + glBindTexture(GL_TEXTURE_2D, m_texName); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, m_texWidth, m_texHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, 0); + + // We require 1 byte alignment when uploading texture data + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + // Clamping to edges is important to prevent artifacts when scaling + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Linear filtering usually looks best for text + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Paste all glyph bitmaps into the texture, remembering the offset + int ox = 0; + int oy = 0; + + int rowh = 0; + + for (auto &c : m_glyphs) + { + if (c.ch == 0) continue; // skip bad glyphs + + if (FT_Load_Char(m_face, c.ch, FT_LOAD_RENDER)) + { + fmt::fprintf(cerr, "Loading character %x failed!\n", (unsigned)c.ch); + c.ch = 0; + continue; + } + + if (ox + g->bitmap.width > m_texWidth) + { + oy += rowh; + rowh = 0; + ox = 0; + } + + glTexSubImage2D(GL_TEXTURE_2D, 0, ox, oy, g->bitmap.width, g->bitmap.rows, GL_ALPHA, GL_UNSIGNED_BYTE, g->bitmap.buffer); + c.tx = ox / (float)m_texWidth; + c.ty = oy / (float)m_texHeight; + + rowh = max(rowh, (int)g->bitmap.rows); + ox += g->bitmap.width + 1; + } + +#if DUMP_TEXTURE + fmt::fprintf(cout/*cerr*/, "Generated a %d x %d (%d kb) texture atlas\n", m_texWidth, m_texHeight, m_texWidth * m_texHeight / 1024); + size_t img_size = sizeof(uint8_t) * m_texWidth * m_texHeight * 4; + uint8_t *raw_img = new uint8_t[img_size]; + glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, raw_img); + ofstream f(fmt::sprintf("/tmp/texture_%ix%i.data", m_texWidth, m_texHeight), ios::binary); + f.write(reinterpret_cast(raw_img), img_size); + f.close(); + delete[] raw_img; +#endif + return true; +} + +int TextureFontPrivate::getCommonGlyphsCount() +{ + if (m_commonGlyphsCount == 0) + { + for (auto const &block : m_unicodeBlocks) + m_commonGlyphsCount += (block.last - block.first + 1); + } + return m_commonGlyphsCount; +} + +int TextureFontPrivate::toPos(wchar_t ch) const +{ + int pos = 0; + + if (ch > m_unicodeBlocks.back().last) + return -1; + + for (const auto &r : m_unicodeBlocks) + { + if (ch < r.first) + return -1; + + if (ch <= r.last) + { + return pos + ch - r.first; + } + + pos += r.last - r.first + 1; + } + return -1; +} + +Glyph& TextureFontPrivate::getGlyph(wchar_t ch, wchar_t fallback) +{ + auto &g = getGlyph(ch); + return g.ch == ch ? g : getGlyph(fallback); +} + +Glyph g_badGlyph = {0}; +Glyph& TextureFontPrivate::getGlyph(wchar_t ch) +{ + auto pos = toPos(ch); + if (pos != -1) + return m_glyphs[pos]; + + auto it = find_if(m_glyphs.begin() + getCommonGlyphsCount(), + m_glyphs.end(), + [ch](Glyph &g) { return g.ch == ch; } + ); + + if (it != m_glyphs.end()) + return *it; + + Glyph c; + if (!loadGlyphInfo(ch, c)) + return g_badGlyph; + + m_glyphs.push_back(c); + if (++m_inserted == 10) + optimize(); + buildAtlas(); + + return m_glyphs.back(); +} + +void TextureFontPrivate::optimize() +{ + m_inserted = 0; +} + +/* + * Render text using the currently loaded font and currently set font size. + * Rendering starts at coordinates (x, y), z is always 0. + * The pixel coordinates that the FreeType2 library uses are scaled by (sx, sy). + */ +float TextureFontPrivate::render(const string &s, float x, float y) +{ + if (m_texName == 0) + return 0; + + // Use the texture containing the atlas + glBindTexture(GL_TEXTURE_2D, m_texName); + + // Loop through all characters + int len = s.length(); + bool validChar = true; + int i = 0; + + while (i < len && validChar) + { + wchar_t ch = 0; + validChar = UTF8Decode(s, i, ch); + if (!validChar) + break; + i += UTF8EncodedSize(ch); + + auto& g = getGlyph(ch, L'?'); + + // Calculate the vertex and texture coordinates + float x2 = x + g.bl; + float y2 = y + g.bt - g.bh; + float w = g.bw; + float h = g.bh; + + // Advance the cursor to the start of the next character + x += g.ax; + y += g.ay; + + // Skip glyphs that have no pixels + if (!w || !h) + continue; + + glBegin(GL_TRIANGLE_FAN); + glTexCoord2f(g.tx, g.ty + g.bh / m_texHeight); glVertex2f(x2, y2); + glTexCoord2f(g.tx + g.bw / m_texWidth, g.ty + g.bh / m_texHeight); glVertex2f(x2 + w, y2); + glTexCoord2f(g.tx + g.bw / m_texWidth, g.ty); glVertex2f(x2 + w, y2 + h); + glTexCoord2f(g.tx, g.ty); glVertex2f(x2, y2 + h); + glEnd(); + } + + return x; +} + +float TextureFontPrivate::render(wchar_t ch, float xoffset, float yoffset) +{ + + auto& g = getGlyph(ch, L'?'); + + // Calculate the vertex and texture coordinates + float x2 = xoffset + g.bl; + float y2 = yoffset + g.bt - g.bh; + float w = g.bw; + float h = g.bh; + + glBegin(GL_TRIANGLE_FAN); + glTexCoord2f(g.tx, g.ty + g.bh / m_texHeight); glVertex2f(x2, y2); + glTexCoord2f(g.tx + g.bw / m_texWidth, g.ty + g.bh / m_texHeight); glVertex2f(x2 + w, y2); + glTexCoord2f(g.tx + g.bw / m_texWidth, g.ty); glVertex2f(x2 + w, y2 + h); + glTexCoord2f(g.tx, g.ty); glVertex2f(x2, y2 + h); + glEnd(); + + return g.ax; +} + + +TextureFont::TextureFont() : + impl(new TextureFontPrivate) +{ +} + +TextureFont::~TextureFont() +{ + delete impl; +} + + +/** + * Render a single character and update the modelview transform + * + * Render a single character of the font. The modelview transform is + * automatically updated to advance to the next character. + * + * @param ch -- wide character + */ +void TextureFont::render(wchar_t ch) const +{ + float xoffset = impl->render(ch, 0, 0); + glTranslatef(xoffset, 0.0f, 0.0f); +} + +/** + * Render a single character of the font with offset + * + * Render a single character of the font, adding the specified offset + * to the location. Do *not* automatically update the modelview transform. + * + * @param ch -- wide character + * @param xoffset -- horizontal offset + * @param yoffset -- vertical offset + */ +void TextureFont::render(wchar_t ch, float xoffset, float yoffset) const +{ + impl->render(ch, xoffset, yoffset); +} + +/** + * Render a string with the specified offset + * + * Render a string with the specified offset. Do *not* automatically update + * the modelview transform. + * + * @param s -- string to render + * @param xoffset -- horizontal offset + * @param yoffset -- vertical offset + */ +void TextureFont::render(const string &s, float xoffset, float yoffset) const +{ + impl->render(s, xoffset, yoffset); +} + +/** + * Render a string and update the modelview transform + * + * Render a string and automatically update the modelview transform for the + * string width. + * + * @param s -- string to render + */ +void TextureFont::render(const string& s) const +{ + float xoffset = impl->render(s, 0, 0); + glTranslatef(xoffset, 0.0f, 0.0f); +} + +/** + * Calculate string width in pixels + * + * Calculate string width using the current font. + * + * @param s -- string to calculate width + * @return string width in pixels + */ +int TextureFont::getWidth(const string& s) const +{ + int width = 0; + int len = s.length(); + bool validChar = true; + int i = 0; + + while (i < len && validChar) + { + wchar_t ch = 0; + validChar = UTF8Decode(s, i, ch); + if (!validChar) + break; + + i += UTF8EncodedSize(ch); + + auto& g = impl->getGlyph(ch, L'?'); + width += g.ax; + } + + return width; +} + +int TextureFont::getHeight() const +{ + return impl->m_maxAscent + impl->m_maxDescent; +} + +int TextureFont::getMaxWidth() const +{ + return impl->m_maxWidth; +} + +int TextureFont::getMaxAscent() const +{ + return impl->m_maxAscent; +} + +void TextureFont::setMaxAscent(int _maxAscent) +{ + impl->m_maxAscent = _maxAscent; +} + +int TextureFont::getMaxDescent() const +{ + return impl->m_maxDescent; +} + +void TextureFont::setMaxDescent(int _maxDescent) +{ + impl->m_maxDescent = _maxDescent; +} + +int TextureFont::getTextureName() const +{ + return impl->m_texName; +} + +void TextureFont::bind() +{ + if (impl->m_texName != 0) + glBindTexture(GL_TEXTURE_2D, impl->m_texName); +} + +short TextureFont::getAdvance(wchar_t ch) const +{ + auto& g = impl->getGlyph(ch, L'?'); + return g.ax; +} + +bool TextureFont::buildTexture() +{ + return true; +} + +TextureFont* TextureFont::load(const fs::path &path, int size, int dpi) +{ + FT_Face face; + + if (FT_New_Face(ft, path.string().c_str(), 0, &face) != 0) + { + fmt::fprintf(cerr, "Could not open font %s\n", path); + return nullptr; + } + + if (!FT_IS_SCALABLE(face)) + { + fmt::fprintf(cerr, "Font is not scalable: %s\n", path); + return nullptr; + } + + if (FT_Set_Char_Size(face, 0, size << 6, dpi, dpi) != 0) + { + fmt::fprintf(cerr, "Could not font size %i\n", size); + return nullptr; + } + + auto* font = new TextureFont(); + font->impl->m_face = face; + + if (!font->impl->buildAtlas()) + return nullptr; + + font->setMaxAscent(face->size->metrics.ascender >> 6); + font->setMaxDescent(-face->size->metrics.descender >> 6); + + return font; +} + +// temporary while no fontconfig support +static fs::path ParseFontName(const fs::path &filename, int &size) +{ + auto fn = filename.string(); + auto pos = fn.rfind(','); + if (pos != string::npos) + { + size = (int) stof(fn.substr(pos + 1)); + return fn.substr(0, pos); + } + else + { + size = 12; + return filename; + } +} + +TextureFont* LoadTextureFont(const fs::path &filename, int size, int dpi) +{ + if (ft == nullptr) + { + if (FT_Init_FreeType(&ft)) + { + cerr << "Could not init freetype library\n"; + return nullptr; + } + } + + int psize = 0; + auto nameonly = ParseFontName(filename, psize); + return TextureFont::load(nameonly, psize, dpi); +} diff --git a/src/celttf/truetypefont.h b/src/celttf/truetypefont.h new file mode 100644 index 00000000..7ed3fd28 --- /dev/null +++ b/src/celttf/truetypefont.h @@ -0,0 +1,54 @@ +// truetypefont.h +// +// Copyright (C) 2019, Celestia Development Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +#pragma once + +#include +#include + +struct TextureFontPrivate; +class TextureFont +{ + public: + TextureFont(); + ~TextureFont(); + TextureFont(const TextureFont&) = delete; + TextureFont(TextureFont&&) = delete; + TextureFont& operator=(const TextureFont&) = delete; + TextureFont& operator=(TextureFont&&) = delete; + + void render(wchar_t c) const; + void render(const std::string& str) const; + + void render(wchar_t c, float xoffset, float yoffset) const; + void render(const std::string& str, float xoffset, float yoffset) const; + + int getWidth(const std::string&) const; + int getWidth(int c) const; + int getMaxWidth() const; + int getHeight() const; + + int getMaxAscent() const; + void setMaxAscent(int); + int getMaxDescent() const; + void setMaxDescent(int); + + short getAdvance(wchar_t c) const; + + int getTextureName() const; + void bind(); + bool buildTexture(); + + static TextureFont* load(const fs::path&, int size, int dpi); + + private: + TextureFontPrivate *impl; +}; + +TextureFont* LoadTextureFont(const fs::path&, int size = 12, int dpi = 96);