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 {
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 <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 {
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 <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;
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 <FT_UInt>(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 <FT_UInt>(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 <FontVariationCoords &>(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 <GlyphIdentity> & 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 <FontVariationCoords &>(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 <int32_t>(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 <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:
void drawSetup(){
@ -715,7 +871,7 @@ class Font {
}
void collectVerticesAndIndices(const glm::vec3 & position,
const std::string & text,
const std::vector <GlyphIdentity> & variationText,
std::vector <BufferVertex> & vertices,
std::vector <int32_t> & 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 <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 {
float minX, minY, maxX, maxY;
};
BoundingBox measure(float x, float y, const std::string & text){
BoundingBox bb;
bb.minX = +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();
cerr << "ofxGPUFont::Font::BoundingBox not implemented" << endl;
cout << "ofxGPUFont::Font::BoundingBox not implemented" << endl;
//bb.minX = +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;
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 <BufferGlyph> bufferGlyphs;
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:
// 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);
}
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;
if(mainText != ""){
font->prepareGlyphsForText(mainText);
}
mainFont = std::move(font);
static void initializeFont(FT_Library & library,
const std::string & filename,
shared_ptr <Font> & mainFont,
int gpuTextureOffset){
cout << "tryUpdateMainFont" << endl;
auto font = loadFont(library, filename, gpuTextureOffset);
if(!font){
return;
}
font->dilation = 0.1f;
mainFont = std::move(font);
}
}