identify glyphs by character and variations
This commit is contained in:
parent
3a156be801
commit
1af76d8726
1 changed files with 293 additions and 221 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue