mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 17:50:04 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			375 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			375 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // No suitable standard C++ replacement
 | |
| #define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
 | |
| 
 | |
| #include <scwx/qt/util/font.hpp>
 | |
| #include <scwx/util/logger.hpp>
 | |
| 
 | |
| #include <codecvt>
 | |
| #include <unordered_map>
 | |
| 
 | |
| #include <boost/timer/timer.hpp>
 | |
| #include <QFile>
 | |
| #include <QFileInfo>
 | |
| 
 | |
| #include <ft2build.h>
 | |
| #include FT_FREETYPE_H
 | |
| #include FT_SFNT_NAMES_H
 | |
| #include FT_TRUETYPE_IDS_H
 | |
| 
 | |
| #pragma warning(push, 0)
 | |
| // #include <freetype-gl.h> (exclude opengl.h)
 | |
| #include <platform.h>
 | |
| #include <vec234.h>
 | |
| #include <vector.h>
 | |
| #include <texture-atlas.h>
 | |
| #include <texture-font.h>
 | |
| #include <ftgl-utils.h>
 | |
| #pragma warning(pop)
 | |
| 
 | |
| #ifdef WIN32
 | |
| #   include <WinSock2.h>
 | |
| #else
 | |
| #   include <arpa/inet.h>
 | |
| #endif
 | |
| 
 | |
| namespace scwx
 | |
| {
 | |
| namespace qt
 | |
| {
 | |
| namespace util
 | |
| {
 | |
| 
 | |
| struct TextureGlyph
 | |
| {
 | |
|    int   offsetX_;
 | |
|    int   offsetY_;
 | |
|    int   width_;
 | |
|    int   height_;
 | |
|    float s0_;
 | |
|    float t0_;
 | |
|    float s1_;
 | |
|    float t1_;
 | |
|    float advanceX_;
 | |
| 
 | |
|    TextureGlyph(int   offsetX,
 | |
|                 int   offsetY,
 | |
|                 int   width,
 | |
|                 int   height,
 | |
|                 float s0,
 | |
|                 float t0,
 | |
|                 float s1,
 | |
|                 float t1,
 | |
|                 float advanceX) :
 | |
|        offsetX_ {offsetX},
 | |
|        offsetY_ {offsetY},
 | |
|        width_ {width},
 | |
|        height_ {height},
 | |
|        s0_ {s0},
 | |
|        t0_ {t0},
 | |
|        s1_ {s1},
 | |
|        t1_ {t1},
 | |
|        advanceX_ {advanceX}
 | |
|    {
 | |
|    }
 | |
| };
 | |
| 
 | |
| static const std::string CODEPOINTS =
 | |
|    " !\"#$%&'()*+,-./0123456789:;<=>?"
 | |
|    "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
 | |
|    "`abcdefghijklmnopqrstuvwxyz{|}~";
 | |
| 
 | |
| static const std::string logPrefix_ = "scwx::qt::util::font";
 | |
| static const auto        logger_    = scwx::util::Logger::Create(logPrefix_);
 | |
| 
 | |
| static constexpr float BASE_POINT_SIZE = 72.0f;
 | |
| static constexpr float POINT_SCALE     = 1.0f / BASE_POINT_SIZE;
 | |
| 
 | |
| static std::unordered_map<std::string, std::shared_ptr<Font>> fontMap_;
 | |
| 
 | |
| static void ParseSfntName(const FT_SfntName& sfntName, std::string& str);
 | |
| 
 | |
| class FontImpl
 | |
| {
 | |
| public:
 | |
|    explicit FontImpl(const std::string& resource) :
 | |
|        resource_(resource), atlas_ {nullptr}
 | |
|    {
 | |
|    }
 | |
| 
 | |
|    ~FontImpl()
 | |
|    {
 | |
|       if (atlas_ != nullptr)
 | |
|       {
 | |
|          ftgl::texture_atlas_delete(atlas_);
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    void ParseNames(FT_Face face);
 | |
| 
 | |
|    const std::string resource_;
 | |
| 
 | |
|    struct
 | |
|    {
 | |
|       std::string fontFamily_;
 | |
|       std::string fontSubfamily_;
 | |
|    } fontData_;
 | |
| 
 | |
|    ftgl::texture_atlas_t*                 atlas_;
 | |
|    std::unordered_map<char, TextureGlyph> glyphs_;
 | |
| };
 | |
| 
 | |
| Font::Font(const std::string& resource) :
 | |
|     p(std::make_unique<FontImpl>(resource))
 | |
| {
 | |
| }
 | |
| Font::~Font() = default;
 | |
| 
 | |
| float Font::BufferText(std::shared_ptr<FontBuffer> buffer,
 | |
|                        const std::string&          text,
 | |
|                        float                       x,
 | |
|                        float                       y,
 | |
|                        float                       pointSize,
 | |
|                        boost::gil::rgba8_pixel_t   color) const
 | |
| {
 | |
|    static constexpr float colorScale = 1.0f / 255.0f;
 | |
| 
 | |
|    const float scale = pointSize * POINT_SCALE;
 | |
| 
 | |
|    float r = color[0] * colorScale;
 | |
|    float g = color[1] * colorScale;
 | |
|    float b = color[2] * colorScale;
 | |
|    float a = color[3] * colorScale;
 | |
| 
 | |
|    for (size_t i = 0; i < text.length(); ++i)
 | |
|    {
 | |
|       const char& c = text[i];
 | |
| 
 | |
|       auto it = p->glyphs_.find(c);
 | |
|       if (it == p->glyphs_.end())
 | |
|       {
 | |
|          logger_->info("Could not draw character: {}",
 | |
|                        static_cast<uint32_t>(c));
 | |
|          continue;
 | |
|       }
 | |
| 
 | |
|       TextureGlyph& glyph = it->second;
 | |
| 
 | |
|       if (i > 0)
 | |
|       {
 | |
|          x += Kerning(text[i - 1], c) * scale;
 | |
|       }
 | |
| 
 | |
|       float x0 = x + glyph.offsetX_ * scale;
 | |
|       float y0 = y + glyph.offsetY_ * scale;
 | |
|       float x1 = x0 + glyph.width_ * scale;
 | |
|       float y1 = y0 - glyph.height_ * scale;
 | |
| 
 | |
|       float s0 = glyph.s0_;
 | |
|       float t0 = glyph.t0_;
 | |
|       float s1 = glyph.s1_;
 | |
|       float t1 = glyph.t1_;
 | |
| 
 | |
|       buffer->Push(/* Indices  */ {0, 1, 2, 0, 2, 3},             //
 | |
|                    /* Vertices */ {x0, y0, 0, s0, t0, r, g, b, a, //
 | |
|                                    x0, y1, 0, s0, t1, r, g, b, a, //
 | |
|                                    x1, y1, 0, s1, t1, r, g, b, a, //
 | |
|                                    x1, y0, 0, s1, t0, r, g, b, a});
 | |
| 
 | |
|       x += glyph.advanceX_ * scale;
 | |
|    }
 | |
| 
 | |
|    return x;
 | |
| }
 | |
| 
 | |
| float Font::Kerning(char /*c1*/, char /*c2*/) const
 | |
| {
 | |
|    // TODO
 | |
|    return 0.0f;
 | |
| }
 | |
| 
 | |
| float Font::TextLength(const std::string& text, float pointSize) const
 | |
| {
 | |
|    const float scale = pointSize * POINT_SCALE;
 | |
| 
 | |
|    float x = 0.0f;
 | |
| 
 | |
|    for (size_t i = 0; i < text.length(); ++i)
 | |
|    {
 | |
|       const char& c = text[i];
 | |
| 
 | |
|       auto it = p->glyphs_.find(c);
 | |
|       if (it == p->glyphs_.end())
 | |
|       {
 | |
|          logger_->info("Character not found: {}", static_cast<uint32_t>(c));
 | |
|          continue;
 | |
|       }
 | |
| 
 | |
|       TextureGlyph& glyph = it->second;
 | |
| 
 | |
|       if (i > 0)
 | |
|       {
 | |
|          x += Kerning(text[i - 1], c) * scale;
 | |
|       }
 | |
| 
 | |
|       x += glyph.advanceX_ * scale;
 | |
|    }
 | |
| 
 | |
|    return x;
 | |
| }
 | |
| 
 | |
| GLuint Font::GenerateTexture(gl::OpenGLFunctions& gl)
 | |
| {
 | |
|    gl.glGenTextures(1, &p->atlas_->id);
 | |
|    gl.glBindTexture(GL_TEXTURE_2D, p->atlas_->id);
 | |
| 
 | |
|    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
 | |
|    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 | |
|    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 | |
|    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 | |
| 
 | |
|    gl.glTexImage2D(GL_TEXTURE_2D,
 | |
|                    0,
 | |
|                    GL_RED,
 | |
|                    static_cast<GLsizei>(p->atlas_->width),
 | |
|                    static_cast<GLsizei>(p->atlas_->height),
 | |
|                    0,
 | |
|                    GL_RED,
 | |
|                    GL_UNSIGNED_BYTE,
 | |
|                    p->atlas_->data);
 | |
| 
 | |
|    return p->atlas_->id;
 | |
| }
 | |
| 
 | |
| std::shared_ptr<Font> Font::Create(const std::string& resource)
 | |
| {
 | |
|    logger_->debug("Loading font file: {}", resource);
 | |
| 
 | |
|    std::shared_ptr<Font>   font = nullptr;
 | |
|    boost::timer::cpu_timer timer;
 | |
| 
 | |
|    auto it = fontMap_.find(resource);
 | |
|    if (it != fontMap_.end())
 | |
|    {
 | |
|       logger_->debug("Font already created");
 | |
|       return it->second;
 | |
|    }
 | |
| 
 | |
|    QFile fontFile(resource.c_str());
 | |
|    fontFile.open(QIODevice::ReadOnly);
 | |
|    if (!fontFile.isOpen())
 | |
|    {
 | |
|       logger_->error("Could not read font file");
 | |
|       return font;
 | |
|    }
 | |
| 
 | |
|    font                = std::make_shared<Font>(resource);
 | |
|    QByteArray fontData = fontFile.readAll();
 | |
| 
 | |
|    font->p->atlas_                   = ftgl::texture_atlas_new(512, 512, 1);
 | |
|    ftgl::texture_font_t* textureFont = ftgl::texture_font_new_from_memory(
 | |
|       font->p->atlas_, BASE_POINT_SIZE, fontData.constData(), fontData.size());
 | |
| 
 | |
|    font->p->ParseNames(textureFont->face);
 | |
| 
 | |
|    textureFont->rendermode = ftgl::RENDER_SIGNED_DISTANCE_FIELD;
 | |
| 
 | |
|    timer.start();
 | |
|    texture_font_load_glyphs(textureFont, CODEPOINTS.c_str());
 | |
|    timer.stop();
 | |
| 
 | |
|    // Single-byte UTF-8 characters
 | |
|    for (const char& c : CODEPOINTS)
 | |
|    {
 | |
|       const ftgl::texture_glyph_t* glyph =
 | |
|          ftgl::texture_font_get_glyph(textureFont, &c);
 | |
| 
 | |
|       if (glyph != nullptr)
 | |
|       {
 | |
|          font->p->glyphs_.emplace(c,
 | |
|                                   TextureGlyph(glyph->offset_x,
 | |
|                                                glyph->offset_y,
 | |
|                                                static_cast<int>(glyph->width),
 | |
|                                                static_cast<int>(glyph->height),
 | |
|                                                glyph->s0,
 | |
|                                                glyph->t0,
 | |
|                                                glyph->s1,
 | |
|                                                glyph->t1,
 | |
|                                                glyph->advance_x));
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    logger_->debug("Font loaded in {}", timer.format(6, "%ws"));
 | |
| 
 | |
|    texture_font_delete(textureFont);
 | |
| 
 | |
|    if (font != nullptr)
 | |
|    {
 | |
|       fontMap_.insert({resource, font});
 | |
|    }
 | |
| 
 | |
|    return font;
 | |
| }
 | |
| 
 | |
| void FontImpl::ParseNames(FT_Face face)
 | |
| {
 | |
|    FT_SfntName sfntName;
 | |
|    FT_Error    error;
 | |
| 
 | |
|    FT_UInt nameCount = FT_Get_Sfnt_Name_Count(face);
 | |
| 
 | |
|    for (FT_UInt i = 0; i < nameCount; i++)
 | |
|    {
 | |
|       error = FT_Get_Sfnt_Name(face, i, &sfntName);
 | |
| 
 | |
|       if (error == 0)
 | |
|       {
 | |
|          switch (sfntName.name_id)
 | |
|          {
 | |
|          case TT_NAME_ID_FONT_FAMILY:
 | |
|             ParseSfntName(sfntName, fontData_.fontFamily_);
 | |
|             break;
 | |
| 
 | |
|          case TT_NAME_ID_FONT_SUBFAMILY:
 | |
|             ParseSfntName(sfntName, fontData_.fontSubfamily_);
 | |
|             break;
 | |
|          }
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    logger_->debug(
 | |
|       "Font family: {} ({})", fontData_.fontFamily_, fontData_.fontSubfamily_);
 | |
| }
 | |
| 
 | |
| static void ParseSfntName(const FT_SfntName& sfntName, std::string& str)
 | |
| {
 | |
|    if (str.empty())
 | |
|    {
 | |
|       if (sfntName.platform_id == TT_PLATFORM_MICROSOFT &&
 | |
|           sfntName.encoding_id == TT_MS_ID_UNICODE_CS)
 | |
|       {
 | |
|          char16_t* tempString = new char16_t[sfntName.string_len / 2];
 | |
|          memcpy(tempString, sfntName.string, sfntName.string_len);
 | |
| 
 | |
|          for (size_t j = 0; j < sfntName.string_len / 2; j++)
 | |
|          {
 | |
|             tempString[j] = ntohs(tempString[j]);
 | |
|          }
 | |
| 
 | |
|          str =
 | |
|             std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> {}
 | |
|                .to_bytes(tempString, tempString + sfntName.string_len / 2);
 | |
| 
 | |
|          delete[] tempString;
 | |
|       }
 | |
|       else if (sfntName.platform_id == TT_PLATFORM_MACINTOSH &&
 | |
|                sfntName.encoding_id == TT_MAC_ID_ROMAN)
 | |
|       {
 | |
|          str = std::string(reinterpret_cast<char*>(sfntName.string),
 | |
|                            sfntName.string_len);
 | |
|       }
 | |
|    }
 | |
| }
 | |
| 
 | |
| } // namespace util
 | |
| } // namespace qt
 | |
| } // namespace scwx
 | 
