identify glyphs by character and variations

This commit is contained in:
jrkb 2023-04-16 14:51:41 +02:00
parent 3a156be801
commit 1af76d8726

View file

@ -27,6 +27,13 @@
namespace ofxGPUFont { 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){ inline int32_t calculateUpperPowerOfTwo(int32_t v){
v--; v--;
v |= v >> 1; v |= v >> 1;
@ -41,6 +48,122 @@ inline int32_t calculateUpperPowerOfTwo(int32_t v){
inline bool isPowerOfTwo(int i){ inline bool isPowerOfTwo(int i){
return (i & (i - 1)) == 0; 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 <uint8_t>((*text)[0]);
// Fast-path for ASCII.
if(first < 128){
(*text)++;
return static_cast <uint32_t>(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 <uint8_t>((*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 <uint8_t>((text)[0]);
// Fast-path for ASCII.
if(first < 128){
return static_cast <uint32_t>(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 <uint8_t>((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 <class T>
inline void hash_combine(std::size_t & seed, const T & v){
std::hash <T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
typedef std::vector <FT_Fixed> 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 <ofxGPUFont::GlyphIdentity> {
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 { class Font {
struct Glyph { struct Glyph {
@ -87,7 +210,68 @@ class Font {
double value; 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 <FontVariationAxis> & 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 <FT_Fixed> 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 <FontVariationAxis> & variationAxes){
return Font::setFontVariationAxes(library, face, variationAxes);
}
static bool setFontVariationAxis(FT_Library & library,
FT_Face & face,
const char * name, double coordinate){
bool success = false; bool success = false;
if(face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS){ if(face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS){
FT_MM_Var * master = NULL; FT_MM_Var * master = NULL;
@ -260,7 +444,7 @@ class Font {
} }
public: public:
void setWorldSize(float worldSize){ void setWorldSize(FT_Library & library, float worldSize){
if(worldSize == this->worldSize){ if(worldSize == this->worldSize){
return; return;
} }
@ -274,18 +458,22 @@ class Font {
// change because of hinting. // change because of hinting.
emSize = worldSize * 64; emSize = worldSize * 64;
FT_Error error = FT_Set_Pixel_Sizes(face, 0, static_cast <FT_UInt>(std::ceil(worldSize))); { // error scope
if(error){ FT_Error error = FT_Set_Pixel_Sizes(face, 0, static_cast <FT_UInt>(std::ceil(worldSize)));
std::cerr << "[font] error while setting pixel size: " << error << std::endl; if(error){
std::cerr << "[font] error while setting pixel size: " << error << std::endl;
}
} }
bufferGlyphs.clear(); bufferGlyphs.clear();
bufferCurves.clear(); bufferCurves.clear();
for(auto it = glyphs.begin(); it != glyphs.end();){ 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; FT_UInt glyphIndex = it->second.index;
setFontVariationAxes(library,
const_cast <FontVariationCoords &>(it->first.coords)); // danger?
FT_Error error = FT_Load_Glyph(face, glyphIndex, loadFlags); FT_Error error = FT_Load_Glyph(face, glyphIndex, loadFlags);
if(error){ if(error){
std::cerr << "[font] error while loading glyph for character " << charcode << ": " << error << std::endl; 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 // This call will overwrite the glyph in the glyphs map. However, it
// cannot invalidate the iterator because the glyph is already in // cannot invalidate the iterator because the glyph is already in
// the map if we are here. // the map if we are here.
buildGlyph(charcode, glyphIndex); buildGlyph(GlyphIdentity{charcode, it->first.coords}, glyphIndex);
it++; it++;
} }
uploadBuffers(); uploadBuffers();
} }
void prepareGlyphsForText(const std::vector <GlyphIdentity> & variationText,
void prepareGlyphsForText(const std::string & text, bool forceChange = false){ FT_Library & library,
bool forceChange = false){
bool changed = false; bool changed = false;
if(forceChange){ if(forceChange){
@ -312,18 +501,25 @@ class Font {
glyphs.clear(); glyphs.clear();
} }
for(const char * textIt = text.c_str(); *textIt != '\0';){ int i = 0;
uint32_t charcode = decodeCharcode(&textIt); 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; //cout << "yes, this is running" << endl;
if(charcode == '\r' || charcode == '\n'){ if(charcode == '\r' || charcode == '\n'){
//cout << "but charcode is r or n" << endl; //cout << "but charcode is r or n" << endl;
continue; continue;
} }
if(glyphs.count(charcode) != 0){ if(glyphs.count(glyphIdentity) != 0){
continue; continue;
} }
setFontVariationAxes(library,
const_cast <FontVariationCoords &>(glyphIdentity.coords)); // danger?
FT_UInt glyphIndex = FT_Get_Char_Index(face, charcode); FT_UInt glyphIndex = FT_Get_Char_Index(face, charcode);
if(!glyphIndex){ if(!glyphIndex){
continue; continue;
@ -335,8 +531,10 @@ class Font {
continue; continue;
} }
buildGlyph(charcode, glyphIndex); buildGlyph(glyphIdentity,
glyphIndex);
changed = true; changed = true;
i++;
} }
if(changed){ if(changed){
@ -422,7 +620,8 @@ class Font {
#endif #endif
} }
void buildGlyph(uint32_t charcode, FT_UInt glyphIndex){ void buildGlyph(const GlyphIdentity & glyphIdentity,
FT_UInt glyphIndex){
BufferGlyph bufferGlyph; BufferGlyph bufferGlyph;
bufferGlyph.start = static_cast <int32_t>(bufferCurves.size()); bufferGlyph.start = static_cast <int32_t>(bufferCurves.size());
@ -447,7 +646,7 @@ class Font {
glyph.bearingX = face->glyph->metrics.horiBearingX; glyph.bearingX = face->glyph->metrics.horiBearingX;
glyph.bearingY = face->glyph->metrics.horiBearingY; glyph.bearingY = face->glyph->metrics.horiBearingY;
glyph.advance = face->glyph->metrics.horiAdvance; glyph.advance = face->glyph->metrics.horiAdvance;
glyphs[charcode] = glyph; glyphs[glyphIdentity] = glyph;
} }
// This function takes a single contour (defined by firstIndex and // This function takes a single contour (defined by firstIndex and
@ -597,7 +796,7 @@ class Font {
} }
start = current; start = current;
control = current; control = current;
}else{ /* currentTag == FT_CURVE_TAG_CONIC */ }else{ /* currentTag == FT_CURVE_TAG_CONIC */
if(previousTag == FT_CURVE_TAG_ON){ if(previousTag == FT_CURVE_TAG_ON){
// No-op, wait for third point. // No-op, wait for third point.
}else{ }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 <uint8_t>((*text)[0]);
// Fast-path for ASCII.
if(first < 128){
(*text)++;
return static_cast <uint32_t>(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 <uint8_t>((*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: public:
void drawSetup(){ void drawSetup(){
@ -715,7 +871,7 @@ class Font {
} }
void collectVerticesAndIndices(const glm::vec3 & position, void collectVerticesAndIndices(const glm::vec3 & position,
const std::string & text, const std::vector <GlyphIdentity> & variationText,
std::vector <BufferVertex> & vertices, std::vector <BufferVertex> & vertices,
std::vector <int32_t> & indices, std::vector <int32_t> & indices,
const bool vFlip = false, const bool vFlip = false,
@ -725,9 +881,12 @@ class Font {
float originalX = x; float originalX = x;
FT_UInt previous = 0; FT_UInt previous = 0;
for(const char * textIt = text.c_str(); *textIt != '\0';){ for(const GlyphIdentity & glyphIdentity : variationText){
float letterFontSize_px = fontSize_px; // can be individual if(glyphIdentity.charcode == '\0'){
uint32_t charcode = decodeCharcode(&textIt); break;
}
float letterFontSize_px = fontSize_px; // can be individual
const uint32_t & charcode = glyphIdentity.charcode;
if(charcode == '\r'){ if(charcode == '\r'){
continue; continue;
@ -742,8 +901,9 @@ class Font {
continue; continue;
} }
auto glyphIt = glyphs.find(charcode); auto glyphIt = glyphs.find(glyphIdentity);
Glyph & glyph = (glyphIt == glyphs.end()) ? glyphs[0] : glyphIt->second; 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){ if(previous != 0 && glyph.index != 0){
FT_Vector kerning; FT_Vector kerning;
@ -805,159 +965,76 @@ class Font {
glBindVertexArray(0); 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 <BufferVertex> vertices;
std::vector <int32_t> 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 <int32_t>(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 { struct BoundingBox {
float minX, minY, maxX, maxY; float minX, minY, maxX, maxY;
}; };
BoundingBox measure(float x, float y, const std::string & text){ BoundingBox measure(float x, float y, const std::string & text){
BoundingBox bb; BoundingBox bb;
bb.minX = +std::numeric_limits <float>::infinity(); cerr << "ofxGPUFont::Font::BoundingBox not implemented" << endl;
bb.minY = +std::numeric_limits <float>::infinity(); cout << "ofxGPUFont::Font::BoundingBox not implemented" << endl;
bb.maxX = -std::numeric_limits <float>::infinity(); //bb.minX = +std::numeric_limits <float>::infinity();
bb.maxY = -std::numeric_limits <float>::infinity(); //bb.minY = +std::numeric_limits <float>::infinity();
//bb.maxX = -std::numeric_limits <float>::infinity();
//bb.maxY = -std::numeric_limits <float>::infinity();
float originalX = x; //float originalX = x;
FT_UInt previous = 0; //FT_UInt previous = 0;
for(const char * textIt = text.c_str(); *textIt != '\0';){ //for(const char * textIt = text.c_str(); *textIt != '\0';){
uint32_t charcode = decodeCharcode(&textIt); //uint32_t charcode = decodeCharcodes(&textIt);
if(charcode == '\r'){ //if(charcode == '\r'){
continue; //continue;
} //}
if(charcode == '\n'){ //if(charcode == '\n'){
x = originalX; //x = originalX;
y -= (float)face->height / (float)face->units_per_EM * worldSize; //y -= (float)face->height / (float)face->units_per_EM * worldSize;
if(hinting){ //if(hinting){
y = std::round(y); //y = std::round(y);
} //}
continue; //continue;
} //}
auto glyphIt = glyphs.find(charcode); //auto glyphIt = glyphs.find(charcode);
Glyph & glyph = (glyphIt == glyphs.end()) ? glyphs[0] : glyphIt->second; //Glyph & glyph = (glyphIt == glyphs.end()) ? glyphs[0] : glyphIt->second;
if(previous != 0 && glyph.index != 0){ //if(previous != 0 && glyph.index != 0){
FT_Vector kerning; //FT_Vector kerning;
FT_Error error = FT_Get_Kerning(face, previous, glyph.index, kerningMode, &kerning); //FT_Error error = FT_Get_Kerning(face, previous, glyph.index, kerningMode, &kerning);
if(!error){ //if(!error){
x += (float)kerning.x / emSize * worldSize; //x += (float)kerning.x / emSize * worldSize;
} //}
} //}
// Note: Do not apply dilation here, we want to calculate exact bounds. //// Note: Do not apply dilation here, we want to calculate exact bounds.
float u0 = (float)(glyph.bearingX) / emSize; //float u0 = (float)(glyph.bearingX) / emSize;
float v0 = (float)(glyph.bearingY - glyph.height) / emSize; //float v0 = (float)(glyph.bearingY - glyph.height) / emSize;
float u1 = (float)(glyph.bearingX + glyph.width) / emSize; //float u1 = (float)(glyph.bearingX + glyph.width) / emSize;
float v1 = (float)(glyph.bearingY) / emSize; //float v1 = (float)(glyph.bearingY) / emSize;
float x0 = x + u0 * worldSize; //float x0 = x + u0 * worldSize;
float y0 = y + v0 * worldSize; //float y0 = y + v0 * worldSize;
float x1 = x + u1 * worldSize; //float x1 = x + u1 * worldSize;
float y1 = y + v1 * worldSize; //float y1 = y + v1 * worldSize;
if(x0 < bb.minX){ //if(x0 < bb.minX){
bb.minX = x0; //bb.minX = x0;
} //}
if(y0 < bb.minY){ //if(y0 < bb.minY){
bb.minY = y0; //bb.minY = y0;
} //}
if(x1 > bb.maxX){ //if(x1 > bb.maxX){
bb.maxX = x1; //bb.maxX = x1;
} //}
if(y1 > bb.maxY){ //if(y1 > bb.maxY){
bb.maxY = y1; //bb.maxY = y1;
} //}
x += (float)glyph.advance / emSize * worldSize; //x += (float)glyph.advance / emSize * worldSize;
previous = glyph.index; //previous = glyph.index;
} //}
return bb; return bb;
} }
@ -996,7 +1073,8 @@ class Font {
std::vector <BufferGlyph> bufferGlyphs; std::vector <BufferGlyph> bufferGlyphs;
std::vector <BufferCurve> bufferCurves; std::vector <BufferCurve> bufferCurves;
std::unordered_map <uint32_t, Glyph> glyphs; //std::unordered_map <uint32_t, Glyph> glyphs;
std::unordered_map <GlyphIdentity, Glyph> glyphs;
public: public:
// ID of the shader program to use. // ID of the shader program to use.
@ -1023,25 +1101,19 @@ static std::shared_ptr <Font> loadFont(FT_Library & library,
return std::make_shared <Font>(face, gpuTextureOffset, worldSize, hinting); return std::make_shared <Font>(face, gpuTextureOffset, worldSize, hinting);
} }
static void tryUpdateMainFont(FT_Library & library,
const std::string & filename,
const string & mainText,
shared_ptr <Font> & mainFont,
int gpuTextureOffset){
{
cout << "tryUpdateMainFont" << endl;
auto font = loadFont(library, filename, gpuTextureOffset);
if(!font){
return;
}
font->dilation = 0.1f; static void initializeFont(FT_Library & library,
const std::string & filename,
if(mainText != ""){ shared_ptr <Font> & mainFont,
font->prepareGlyphsForText(mainText); int gpuTextureOffset){
} cout << "tryUpdateMainFont" << endl;
auto font = loadFont(library, filename, gpuTextureOffset);
mainFont = std::move(font); if(!font){
return;
} }
font->dilation = 0.1f;
mainFont = std::move(font);
} }
} }