From 1af76d872689edbc31d941684047f28f029726de Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Sun, 16 Apr 2023 14:51:41 +0200 Subject: [PATCH] identify glyphs by character and variations --- src/gpufont/font.hpp | 514 ++++++++++++++++++++++++------------------- 1 file changed, 293 insertions(+), 221 deletions(-) diff --git a/src/gpufont/font.hpp b/src/gpufont/font.hpp index 175e702..9945957 100644 --- a/src/gpufont/font.hpp +++ b/src/gpufont/font.hpp @@ -27,6 +27,13 @@ namespace ofxGPUFont { +inline FT_Fixed double2f16dot16(double x){ + return DOUBLE_TO_F16DOT16(x); +} +inline FT_Fixed float2f16dot16(float x){ + return DOUBLE_TO_F16DOT16(x); +} + inline int32_t calculateUpperPowerOfTwo(int32_t v){ v--; v |= v >> 1; @@ -41,6 +48,122 @@ inline int32_t calculateUpperPowerOfTwo(int32_t v){ inline bool isPowerOfTwo(int i){ return (i & (i - 1)) == 0; } +// Decodes the first Unicode code point from the null-terminated UTF-8 string *text and advances *text to point at the next code point. +// If the encoding is invalid, advances *text by one byte and returns 0. +// *text should not be empty, because it will be advanced past the null terminator. +inline uint32_t decodeCharcodes(const char * * text){ + uint8_t first = static_cast ((*text)[0]); + + // Fast-path for ASCII. + if(first < 128){ + (*text)++; + return static_cast (first); + } + + // This could probably be optimized a bit. + uint32_t result; + int size; + if((first & 0xE0) == 0xC0){ // 110xxxxx + result = first & 0x1F; + size = 2; + }else if((first & 0xF0) == 0xE0){ // 1110xxxx + result = first & 0x0F; + size = 3; + }else if((first & 0xF8) == 0xF0){ // 11110xxx + result = first & 0x07; + size = 4; + }else{ + // Invalid encoding. + (*text)++; + return 0; + } + + for(int i = 1; i < size; i++){ + uint8_t value = static_cast ((*text)[i]); + // Invalid encoding (also catches a null terminator in the middle of a code point). + if((value & 0xC0) != 0x80){ // 10xxxxxx + (*text)++; + return 0; + } + result = (result << 6) | (value & 0x3F); + } + + (*text) += size; + return result; +} +inline uint32_t decodeCharcode(const char * text){ + uint8_t first = static_cast ((text)[0]); + + // Fast-path for ASCII. + if(first < 128){ + return static_cast (first); + } + + // This could probably be optimized a bit. + uint32_t result; + int size; + if((first & 0xE0) == 0xC0){ // 110xxxxx + result = first & 0x1F; + size = 2; + }else if((first & 0xF0) == 0xE0){ // 1110xxxx + result = first & 0x0F; + size = 3; + }else if((first & 0xF8) == 0xF0){ // 11110xxx + result = first & 0x07; + size = 4; + }else{ + // Invalid encoding. + return 0; + } + + for(int i = 1; i < size; i++){ + uint8_t value = static_cast ((text)[i]); + // Invalid encoding (also catches a null terminator in the middle of a code point). + if((value & 0xC0) != 0x80){ // 10xxxxxx + return 0; + } + result = (result << 6) | (value & 0x3F); + } + + return result; +} + +template +inline void hash_combine(std::size_t & seed, const T & v){ + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +typedef std::vector FontVariationCoords; +struct GlyphIdentity { + uint32_t charcode; + FontVariationCoords coords; + // we need a comparison operator, to use GlyphIdentity in our unordered_map as key + bool operator==(const GlyphIdentity & other) const { + return this->charcode == other.charcode && this->coords == other.coords; + } +}; +} +// create hash for GlyphIdentity, so we can use it in our unordered_map as key +namespace std { +template <> +struct hash { + std::size_t operator()(const ofxGPUFont::GlyphIdentity & k) const { + using std::size_t; + using std::hash; + using std::string; + + size_t seed = 0; + ofxGPUFont::hash_combine(seed, k.charcode); + for(const auto & coord : k.coords){ + ofxGPUFont::hash_combine(seed, coord); + } + + return seed; + } +}; +} +namespace ofxGPUFont { class Font { struct Glyph { @@ -87,7 +210,68 @@ class Font { double value; }; - static bool setFontVariationAxis(FT_Library & library, FT_Face & face, const char * name, double coordinate){ + static bool setFontVariationAxes(FT_Library & library, + FT_Face & face, + FontVariationCoords & coords){ + bool success = false; + if(face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS){ + FT_MM_Var * master = NULL; + if(FT_Get_MM_Var(face, &master) != 0){ + return false; + } + if(master && master->num_axis){ + if(FT_Set_Var_Design_Coordinates(face, FT_UInt(coords.size()), &coords[0]) != 0){ + success = false; + } + } + FT_Done_MM_Var(library, master); + } + return success; + } + + bool setFontVariationAxes(FT_Library & library, FontVariationCoords & coords){ + return Font::setFontVariationAxes(library, face, coords); + } + + static bool setFontVariationAxes(FT_Library & library, + FT_Face & face, + const std::vector & variationAxes){ + bool success = false; + if(face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS){ + FT_MM_Var * master = NULL; + if(FT_Get_MM_Var(face, &master) != 0){ + return false; + } + if(master && master->num_axis){ + std::vector coords(master->num_axis); + if(FT_Get_Var_Design_Coordinates(face, FT_UInt(coords.size()), &coords[0]) == 0){ + for(FT_UInt i = 0; i < master->num_axis; ++i){ + for(const auto & variationAxis : variationAxes){ + if(strcmp(variationAxis.name, master->axis[i].name) == 0){ + coords[i] = DOUBLE_TO_F16DOT16(variationAxis.value); + success = true; + break; + } + } + } + } + if(FT_Set_Var_Design_Coordinates(face, FT_UInt(coords.size()), &coords[0]) != 0){ + success = false; + } + } + FT_Done_MM_Var(library, master); + } + return success; + } + + bool setFontVariationAxes(FT_Library & library, + const std::vector & variationAxes){ + return Font::setFontVariationAxes(library, face, variationAxes); + } + + static bool setFontVariationAxis(FT_Library & library, + FT_Face & face, + const char * name, double coordinate){ bool success = false; if(face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS){ FT_MM_Var * master = NULL; @@ -260,7 +444,7 @@ class Font { } public: - void setWorldSize(float worldSize){ + void setWorldSize(FT_Library & library, float worldSize){ if(worldSize == this->worldSize){ return; } @@ -274,18 +458,22 @@ class Font { // change because of hinting. emSize = worldSize * 64; - FT_Error error = FT_Set_Pixel_Sizes(face, 0, static_cast (std::ceil(worldSize))); - if(error){ - std::cerr << "[font] error while setting pixel size: " << error << std::endl; + { // error scope + FT_Error error = FT_Set_Pixel_Sizes(face, 0, static_cast (std::ceil(worldSize))); + if(error){ + std::cerr << "[font] error while setting pixel size: " << error << std::endl; + } } bufferGlyphs.clear(); bufferCurves.clear(); for(auto it = glyphs.begin(); it != glyphs.end();){ - uint32_t charcode = it->first; + uint32_t charcode = it->first.charcode; FT_UInt glyphIndex = it->second.index; + setFontVariationAxes(library, + const_cast (it->first.coords)); // danger? FT_Error error = FT_Load_Glyph(face, glyphIndex, loadFlags); if(error){ std::cerr << "[font] error while loading glyph for character " << charcode << ": " << error << std::endl; @@ -296,14 +484,15 @@ class Font { // This call will overwrite the glyph in the glyphs map. However, it // cannot invalidate the iterator because the glyph is already in // the map if we are here. - buildGlyph(charcode, glyphIndex); + buildGlyph(GlyphIdentity{charcode, it->first.coords}, glyphIndex); it++; } uploadBuffers(); } - - void prepareGlyphsForText(const std::string & text, bool forceChange = false){ + void prepareGlyphsForText(const std::vector & variationText, + FT_Library & library, + bool forceChange = false){ bool changed = false; if(forceChange){ @@ -312,18 +501,25 @@ class Font { glyphs.clear(); } - for(const char * textIt = text.c_str(); *textIt != '\0';){ - uint32_t charcode = decodeCharcode(&textIt); + int i = 0; + for(const auto & glyphIdentity : variationText){ + const uint32_t & charcode = glyphIdentity.charcode; + if(charcode == '\0'){ // do we need this? + break; + } //cout << "yes, this is running" << endl; if(charcode == '\r' || charcode == '\n'){ //cout << "but charcode is r or n" << endl; continue; } - if(glyphs.count(charcode) != 0){ + if(glyphs.count(glyphIdentity) != 0){ continue; } + setFontVariationAxes(library, + const_cast (glyphIdentity.coords)); // danger? + FT_UInt glyphIndex = FT_Get_Char_Index(face, charcode); if(!glyphIndex){ continue; @@ -335,8 +531,10 @@ class Font { continue; } - buildGlyph(charcode, glyphIndex); + buildGlyph(glyphIdentity, + glyphIndex); changed = true; + i++; } if(changed){ @@ -422,7 +620,8 @@ class Font { #endif } - void buildGlyph(uint32_t charcode, FT_UInt glyphIndex){ + void buildGlyph(const GlyphIdentity & glyphIdentity, + FT_UInt glyphIndex){ BufferGlyph bufferGlyph; bufferGlyph.start = static_cast (bufferCurves.size()); @@ -447,7 +646,7 @@ class Font { glyph.bearingX = face->glyph->metrics.horiBearingX; glyph.bearingY = face->glyph->metrics.horiBearingY; glyph.advance = face->glyph->metrics.horiAdvance; - glyphs[charcode] = glyph; + glyphs[glyphIdentity] = glyph; } // This function takes a single contour (defined by firstIndex and @@ -597,7 +796,7 @@ class Font { } start = current; control = current; - }else{ /* currentTag == FT_CURVE_TAG_CONIC */ + }else{ /* currentTag == FT_CURVE_TAG_CONIC */ if(previousTag == FT_CURVE_TAG_ON){ // No-op, wait for third point. }else{ @@ -635,49 +834,6 @@ class Font { } } - // Decodes the first Unicode code point from the null-terminated UTF-8 string *text and advances *text to point at the next code point. - // If the encoding is invalid, advances *text by one byte and returns 0. - // *text should not be empty, because it will be advanced past the null terminator. - uint32_t decodeCharcode(const char * * text){ - uint8_t first = static_cast ((*text)[0]); - - // Fast-path for ASCII. - if(first < 128){ - (*text)++; - return static_cast (first); - } - - // This could probably be optimized a bit. - uint32_t result; - int size; - if((first & 0xE0) == 0xC0){ // 110xxxxx - result = first & 0x1F; - size = 2; - }else if((first & 0xF0) == 0xE0){ // 1110xxxx - result = first & 0x0F; - size = 3; - }else if((first & 0xF8) == 0xF0){ // 11110xxx - result = first & 0x07; - size = 4; - }else{ - // Invalid encoding. - (*text)++; - return 0; - } - - for(int i = 1; i < size; i++){ - uint8_t value = static_cast ((*text)[i]); - // Invalid encoding (also catches a null terminator in the middle of a code point). - if((value & 0xC0) != 0x80){ // 10xxxxxx - (*text)++; - return 0; - } - result = (result << 6) | (value & 0x3F); - } - - (*text) += size; - return result; - } public: void drawSetup(){ @@ -715,7 +871,7 @@ class Font { } void collectVerticesAndIndices(const glm::vec3 & position, - const std::string & text, + const std::vector & variationText, std::vector & vertices, std::vector & indices, const bool vFlip = false, @@ -725,9 +881,12 @@ class Font { float originalX = x; FT_UInt previous = 0; - for(const char * textIt = text.c_str(); *textIt != '\0';){ - float letterFontSize_px = fontSize_px; // can be individual - uint32_t charcode = decodeCharcode(&textIt); + for(const GlyphIdentity & glyphIdentity : variationText){ + if(glyphIdentity.charcode == '\0'){ + break; + } + float letterFontSize_px = fontSize_px; // can be individual + const uint32_t & charcode = glyphIdentity.charcode; if(charcode == '\r'){ continue; @@ -742,8 +901,9 @@ class Font { continue; } - auto glyphIt = glyphs.find(charcode); - Glyph & glyph = (glyphIt == glyphs.end()) ? glyphs[0] : glyphIt->second; + auto glyphIt = glyphs.find(glyphIdentity); + Glyph & glyph = (glyphIt == glyphs.end()) ? glyphs.begin()->second : glyphIt->second; // in case we have no glyph, draw first one? + // NOTE: should we do this? if(previous != 0 && glyph.index != 0){ FT_Vector kerning; @@ -805,159 +965,76 @@ class Font { glBindVertexArray(0); } - void draw(float x, float y, float z, const std::string & text, bool vFlip = false, float fontSize_px = 42){ - float originalX = x; - - glBindVertexArray(vao); - - std::vector vertices; - std::vector indices; - - FT_UInt previous = 0; - for(const char * textIt = text.c_str(); *textIt != '\0';){ - float letterFontSize_px = fontSize_px; // can be individual - uint32_t charcode = decodeCharcode(&textIt); - - if(charcode == '\r'){ - continue; - } - - if(charcode == '\n'){ - x = originalX; - y -= (float)face->height / (float)face->units_per_EM * worldSize; - if(hinting){ - y = std::round(y); - } - continue; - } - - auto glyphIt = glyphs.find(charcode); - Glyph & glyph = (glyphIt == glyphs.end()) ? glyphs[0] : glyphIt->second; - - if(previous != 0 && glyph.index != 0){ - FT_Vector kerning; - FT_Error error = FT_Get_Kerning(face, previous, glyph.index, kerningMode, &kerning); - if(!error){ - x += (float)kerning.x / emSize * worldSize; - } - } - - // Do not emit quad for empty glyphs (whitespace). - if(glyph.curveCount){ - FT_Pos d = (FT_Pos)(emSize * dilation); - - float u0 = (float)(glyph.bearingX - d) / emSize; - float v0 = (float)(glyph.bearingY - glyph.height - d) / emSize; - float u1 = (float)(glyph.bearingX + glyph.width + d) / emSize; - float v1 = (float)(glyph.bearingY + d) / emSize; - - - float x0 = x + u0 * worldSize * letterFontSize_px; - float y0 = y + v0 * worldSize * letterFontSize_px; - float x1 = x + u1 * worldSize * letterFontSize_px; - float y1 = y + v1 * worldSize * letterFontSize_px; - - if(vFlip){ - float _v = v0; - v0 = v1; - v1 = _v; - - y0 = y - v0 * worldSize * letterFontSize_px; - y1 = y - v1 * worldSize * letterFontSize_px; - } - - int32_t base = static_cast (vertices.size()); - vertices.push_back(BufferVertex{x0, y0, u0, v0, glyph.bufferIndex}); - vertices.push_back(BufferVertex{x1, y0, u1, v0, glyph.bufferIndex}); - vertices.push_back(BufferVertex{x1, y1, u1, v1, glyph.bufferIndex}); - vertices.push_back(BufferVertex{x0, y1, u0, v1, glyph.bufferIndex}); - - indices.insert(indices.end(), {base, base + 1, base + 2, base + 2, base + 3, base}); - } - - x += ((float)glyph.advance * letterFontSize_px) / emSize * worldSize; - previous = glyph.index; - } - - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(BufferVertex) * vertices.size(), vertices.data(), GL_STREAM_DRAW); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int32_t) * indices.size(), indices.data(), GL_STREAM_DRAW); - - glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0); - - glBindVertexArray(0); - } - struct BoundingBox { float minX, minY, maxX, maxY; }; BoundingBox measure(float x, float y, const std::string & text){ BoundingBox bb; - bb.minX = +std::numeric_limits ::infinity(); - bb.minY = +std::numeric_limits ::infinity(); - bb.maxX = -std::numeric_limits ::infinity(); - bb.maxY = -std::numeric_limits ::infinity(); + cerr << "ofxGPUFont::Font::BoundingBox not implemented" << endl; + cout << "ofxGPUFont::Font::BoundingBox not implemented" << endl; + //bb.minX = +std::numeric_limits ::infinity(); + //bb.minY = +std::numeric_limits ::infinity(); + //bb.maxX = -std::numeric_limits ::infinity(); + //bb.maxY = -std::numeric_limits ::infinity(); - float originalX = x; + //float originalX = x; - FT_UInt previous = 0; - for(const char * textIt = text.c_str(); *textIt != '\0';){ - uint32_t charcode = decodeCharcode(&textIt); + //FT_UInt previous = 0; + //for(const char * textIt = text.c_str(); *textIt != '\0';){ + //uint32_t charcode = decodeCharcodes(&textIt); - if(charcode == '\r'){ - continue; - } + //if(charcode == '\r'){ + //continue; + //} - if(charcode == '\n'){ - x = originalX; - y -= (float)face->height / (float)face->units_per_EM * worldSize; - if(hinting){ - y = std::round(y); - } - continue; - } + //if(charcode == '\n'){ + //x = originalX; + //y -= (float)face->height / (float)face->units_per_EM * worldSize; + //if(hinting){ + //y = std::round(y); + //} + //continue; + //} - auto glyphIt = glyphs.find(charcode); - Glyph & glyph = (glyphIt == glyphs.end()) ? glyphs[0] : glyphIt->second; + //auto glyphIt = glyphs.find(charcode); + //Glyph & glyph = (glyphIt == glyphs.end()) ? glyphs[0] : glyphIt->second; - if(previous != 0 && glyph.index != 0){ - FT_Vector kerning; - FT_Error error = FT_Get_Kerning(face, previous, glyph.index, kerningMode, &kerning); - if(!error){ - x += (float)kerning.x / emSize * worldSize; - } - } + //if(previous != 0 && glyph.index != 0){ + //FT_Vector kerning; + //FT_Error error = FT_Get_Kerning(face, previous, glyph.index, kerningMode, &kerning); + //if(!error){ + //x += (float)kerning.x / emSize * worldSize; + //} + //} - // Note: Do not apply dilation here, we want to calculate exact bounds. - float u0 = (float)(glyph.bearingX) / emSize; - float v0 = (float)(glyph.bearingY - glyph.height) / emSize; - float u1 = (float)(glyph.bearingX + glyph.width) / emSize; - float v1 = (float)(glyph.bearingY) / emSize; + //// Note: Do not apply dilation here, we want to calculate exact bounds. + //float u0 = (float)(glyph.bearingX) / emSize; + //float v0 = (float)(glyph.bearingY - glyph.height) / emSize; + //float u1 = (float)(glyph.bearingX + glyph.width) / emSize; + //float v1 = (float)(glyph.bearingY) / emSize; - float x0 = x + u0 * worldSize; - float y0 = y + v0 * worldSize; - float x1 = x + u1 * worldSize; - float y1 = y + v1 * worldSize; + //float x0 = x + u0 * worldSize; + //float y0 = y + v0 * worldSize; + //float x1 = x + u1 * worldSize; + //float y1 = y + v1 * worldSize; - if(x0 < bb.minX){ - bb.minX = x0; - } - if(y0 < bb.minY){ - bb.minY = y0; - } - if(x1 > bb.maxX){ - bb.maxX = x1; - } - if(y1 > bb.maxY){ - bb.maxY = y1; - } + //if(x0 < bb.minX){ + //bb.minX = x0; + //} + //if(y0 < bb.minY){ + //bb.minY = y0; + //} + //if(x1 > bb.maxX){ + //bb.maxX = x1; + //} + //if(y1 > bb.maxY){ + //bb.maxY = y1; + //} - x += (float)glyph.advance / emSize * worldSize; - previous = glyph.index; - } + //x += (float)glyph.advance / emSize * worldSize; + //previous = glyph.index; + //} return bb; } @@ -996,7 +1073,8 @@ class Font { std::vector bufferGlyphs; std::vector bufferCurves; - std::unordered_map glyphs; + //std::unordered_map glyphs; + std::unordered_map glyphs; public: // ID of the shader program to use. @@ -1023,25 +1101,19 @@ static std::shared_ptr loadFont(FT_Library & library, return std::make_shared (face, gpuTextureOffset, worldSize, hinting); } -static void tryUpdateMainFont(FT_Library & library, - const std::string & filename, - const string & mainText, - shared_ptr & mainFont, - int gpuTextureOffset){ - { - cout << "tryUpdateMainFont" << endl; - auto font = loadFont(library, filename, gpuTextureOffset); - if(!font){ - return; - } - font->dilation = 0.1f; - - if(mainText != ""){ - font->prepareGlyphsForText(mainText); - } - - mainFont = std::move(font); +static void initializeFont(FT_Library & library, + const std::string & filename, + shared_ptr & mainFont, + int gpuTextureOffset){ + cout << "tryUpdateMainFont" << endl; + auto font = loadFont(library, filename, gpuTextureOffset); + if(!font){ + return; } + + font->dilation = 0.1f; + + mainFont = std::move(font); } }