ofxGPUFont/src/gpufont/font.hpp

1541 lines
64 KiB
C++

#pragma once
// Note: See "main.cpp" for headers.
// This file was extracted to improve the organization of the code,
// but it is still compiled in the "main.cpp" translation unit,
// because both files have mostly the same dependencies (OpenGL, GLM, FreeType).
#include "fwd.hpp"
#include "ofMain.h"
#include "ofUtils.h"
#include "shader_catalog.hpp"
#include <GL/gl.h>
#include <GL/glext.h>
#ifdef TARGET_OPENGLES
//#include <GL/gl.h>
//#include <GLES/gl.h>
//#include <GLES2/gl2ext.h>
//#include <GLES3/gl3.h>
#endif
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_MULTIPLE_MASTERS_H
#ifndef F26DOT6_TO_DOUBLE
#define F26DOT6_TO_DOUBLE(x) (1 / 64. * double(x))
#endif
#ifndef F16DOT16_TO_DOUBLE
#define F16DOT16_TO_DOUBLE(x) (1 / 65536. * double(x))
#endif
#ifndef DOUBLE_TO_F16DOT16
#define DOUBLE_TO_F16DOT16(x) FT_Fixed(65536. * x)
#endif
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;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return 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;
}
};
struct GlyphAppearance {
uint32_t charcode;
std::array <float, 4> color;
float fontSize_px;
float letterSpacing;
};
}
// 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 {
FT_UInt index;
int32_t bufferIndex;
int32_t curveCount;
// Important glyph metrics in font units.
FT_Pos width, height;
FT_Pos bearingX;
FT_Pos bearingY;
FT_Pos advance;
};
struct BufferGlyph {
int32_t start, count; // range of bezier curves belonging to this glyph
};
struct BufferCurve {
glm::float32_t x0, y0, x1, y1, x2, y2;
};
// keep this, just because I wrote it
// though, we don't need it as openGL takes care
// of managing the units
//
//static set <int> gl_texture_units;
//static set <int> gl_texture_units_freed;
//static int claimFreeTextureName(){
//if(gl_texture_units_freed.size() == 0){
//int unit = gl_texture_units.size();
//gl_texture_units.insert(unit);
//return unit;
//}else{
//auto unit_it = gl_texture_units_freed.begin();
//int unit = *unit_it;
//gl_texture_units.insert(unit);
//gl_texture_units_freed.erase(unit_it);
//return unit;
//}
//}
public:
struct BufferVertex {
float x, y, u, v;
int32_t bufferIndex;
float r, g, b, a;
};
//
// p0----------p1
// | |
// | /\ |
// | / \ |
// | /____\ |
// | / \ |
// | |
// p3----------p2
//
// also has bufferIndex & uv coords
struct BoundingBox {
glm::vec4 p0;
glm::vec4 p1;
glm::vec4 p2;
glm::vec4 p3;
float u0;
float u1;
float v0;
float v1;
int32_t bufferIndex;
void multiply(const glm::mat4 & mat){
p0 = mat * p0;
p1 = mat * p1;
p2 = mat * p2;
p3 = mat * p3;
}
};
// and here a mini variant
struct BBox {
float xMin;
float xMax;
float yMin;
float yMax;
};
/// A structure to model a given axis of a variable font.
struct FontVariationAxisParameters {
/// The name of the variation axis.
const char * name;
/// The axis's minimum coordinate value.
double minValue;
/// The axis's maximum coordinate value.
double maxValue;
/// The axis's default coordinate value. FreeType computes meaningful default values for Adobe MM fonts.
double defaultValue;
};
struct FontVariationAxis {
const char * name;
double value;
};
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;
if(FT_Get_MM_Var(face, &master)){
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])){
for(FT_UInt i = 0; i < master->num_axis; ++i){
if(!strcmp(name, master->axis[i].name)){
coords[i] = DOUBLE_TO_F16DOT16(coordinate);
success = true;
break;
}
}
}
if(FT_Set_Var_Design_Coordinates(face, FT_UInt(coords.size()), &coords[0])){
success = false;
}
}
FT_Done_MM_Var(library, master);
}
return success;
}
bool setFontVariationAxis(FT_Library & library, const std::string & name, float coordinate){
return Font::setFontVariationAxis(library, face, name.c_str(), coordinate);
}
static bool listFontVariationAxes(std::vector <FontVariationAxisParameters> & axes, FT_Library & library, FT_Face & face){
if(face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS){
FT_MM_Var * master = NULL;
if(FT_Get_MM_Var(face, &master)){
return false;
}
axes.resize(master->num_axis);
for(FT_UInt i = 0; i < master->num_axis; i++){
FontVariationAxisParameters & axis = axes[i];
axis.name = master->axis[i].name;
axis.minValue = F16DOT16_TO_DOUBLE(master->axis[i].minimum);
axis.maxValue = F16DOT16_TO_DOUBLE(master->axis[i].maximum);
axis.defaultValue = F16DOT16_TO_DOUBLE(master->axis[i].def);
}
FT_Done_MM_Var(library, master);
return true;
}
return false;
}
bool listFontVariationAxes(std::vector <FontVariationAxisParameters> & axes, FT_Library & library){
return Font::listFontVariationAxes(axes, library, face);
}
static FT_Face loadFace(FT_Library library, const std::string & filename, std::string & error){
FT_Face face = NULL;
//string pwd = ofSystem("pwd");
//pwd.erase(std::remove_if(pwd.begin(), pwd.end(), ::isspace), pwd.end());
//string fontPath = pwd + "/" + filename;
FT_Error ftError = FT_New_Face(library, filename.c_str(), 0, &face);
if(ftError){
const char * ftErrorStr = FT_Error_String(ftError);
if(ftErrorStr){
error = std::string(ftErrorStr);
}else{
// Fallback in case FT_Error_String returns NULL (e.g. if there
// was an error or FT was compiled without error strings).
std::stringstream stream;
stream << "Error " << ftError;
error = stream.str();
}
return NULL;
}
if(!(face->face_flags & FT_FACE_FLAG_SCALABLE)){
error = "non-scalable fonts are not supported";
FT_Done_Face(face);
return NULL;
}
return face;
}
// If hinting is enabled, worldSize must be an integer and defines the font size in pixels used for hinting.
// Otherwise, worldSize can be an arbitrary floating-point value.
Font(FT_Face face, float worldSize = 1.0f, bool hinting = false, GLint bufferTargetType = GL_TEXTURE_2D_ARRAY, GLuint shaderProgram = 0) :
face(face),
hinting(hinting),
BUFFER_TARGET_TYPE(bufferTargetType),
program(shaderProgram),
worldSize(worldSize){
// TODO: modularize init, so we can initialize with settings and text
if(gl_max_texture_size == -1){
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_max_texture_size);
cout << "GL_MAX_TEXTURE_SIZE: " << ofToString(gl_max_texture_size) << endl;
}
if(gl_max_uniform_block_size == -1){
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &gl_max_uniform_block_size);
cout << "GL_MAX_UNIFORM_BLOCK_SIZE: " << ofToString(gl_max_uniform_block_size) << endl;
}
if(hinting){
loadFlags = FT_LOAD_NO_BITMAP;
kerningMode = FT_KERNING_DEFAULT;
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;
}
}else{
loadFlags = FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP;
kerningMode = FT_KERNING_UNSCALED;
emSize = face->units_per_EM;
}
ubo_curves_index = glGetUniformBlockIndex(program, "curves_t");
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
glGenBuffers(1, &ebo);
#ifndef TARGET_OPENGLES
glGenTextures(1, &glyphTextureName);
glGenTextures(1, &curveTextureName);
glGenBuffers(1, &glyphBufferName);
glGenBuffers(1, &curveBufferName);
#else
if(BUFFER_TARGET_TYPE == GL_TEXTURE_2D_ARRAY){
glGenTextures(1, &glyphBufferName);
glGenTextures(1, &curveBufferName);
}else{
glGenTextures(1, &glyphBufferName);
glGenBuffers(1, &curveBufferName);
}
#endif
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, false, sizeof(BufferVertex), (void *)offsetof(BufferVertex, x));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(BufferVertex), (void *)offsetof(BufferVertex, u));
glEnableVertexAttribArray(2);
glVertexAttribIPointer(2, 1, GL_INT, sizeof(BufferVertex), (void *)offsetof(BufferVertex, bufferIndex));
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 4, GL_FLOAT, false, sizeof(BufferVertex), (void *)offsetof(BufferVertex, r));
glBindVertexArray(0);
//uploadBuffers();
#ifndef TARGET_OPENGLES
glBindTexture(GL_TEXTURE_BUFFER, glyphTextureName);
glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32I, glyphBufferName);
glBindTexture(GL_TEXTURE_BUFFER, 0);
glBindTexture(GL_TEXTURE_BUFFER, curveTextureName);
glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, curveBufferName);
glBindTexture(GL_TEXTURE_BUFFER, 0);
#else
#endif
}
int amountCurveTextureLayers = 1;
int widthCurveTextureLayer = 0;
int previousCurveBufferSize = 0;
int bufferCurveSize = (sizeof(BufferCurve) / (sizeof(glm::float32_t) * 1));
int shaderCurvesBufferSize = -1;
GLint ubo_curves_index;
void generateCurveBuffers(){
int shaderBufferByteSize = sizeof(BufferCurve) * shaderCurvesBufferSize;
shaderBufferCurves = bufferCurves;
shaderBufferCurves.resize(shaderCurvesBufferSize);
//cout << "generate curve buffer" << endl
//<< "\t shaderBufferByteSize: " << ofToString(shaderBufferByteSize)
//<< "\t bufferCurves.size(): " << ofToString(bufferCurves.size())
//<< "\t shaderBufferCurves.size(): " << ofToString(shaderBufferCurves.size())
//<< endl;
ubo_curves_index = glGetUniformBlockIndex(program, "curves_t");
glBindBufferBase(GL_UNIFORM_BUFFER, 0, curveBufferName);
glBufferData(GL_UNIFORM_BUFFER, shaderBufferByteSize, shaderBufferCurves.data(), GL_DYNAMIC_DRAW);
glUniformBlockBinding(program, ubo_curves_index, 0);
}
void generateCurveTextureLayers(int amountCurves){
//cout << "generateCurveTextureLayers()" << endl;
int curveBufferSize = amountCurves * bufferCurveSize;
curveBufferSize = calculateUpperPowerOfTwo(curveBufferSize);
int overshoot = curveBufferSize % gl_max_texture_size == 0 ? 0 : 1;
int neededLayers = (curveBufferSize / gl_max_texture_size) + overshoot;
// LOL
//cout << "\tlayers needed: " << ofToString(neededLayers) << endl
//<< "\tcurveBufferSize p2: " << ofToString(curveBufferSize) << endl
//<< "\tcurveBufferSize or: " << ofToString(bufferCurves.size()) << endl
//<< "\tsize of BufferCurve: " << ofToString(bufferCurveSize) << endl
//<< "\ttotal glyphs: " << ofToString(glyphs.size()) << endl
//;
if(neededLayers != amountCurveTextureLayers
|| curveBufferSize > previousCurveBufferSize){
glDeleteTextures(1, &curveBufferName);
glGenTextures(1, &curveBufferName);
//glActiveTexture(GL_TEXTURE0 + curveBufferUnit);
glBindTexture(GL_TEXTURE_2D_ARRAY, curveBufferName);
if(neededLayers == 1){
widthCurveTextureLayer = curveBufferSize;
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RG32F, curveBufferSize, 1, neededLayers);
}else{
//cout << ofToString(neededLayers) << "layers needed NEW" << endl;
if(neededLayers > 4){
//neededLayers = 4;
// TODO: implement multiple arrays
// LOL
}else{
}
widthCurveTextureLayer = gl_max_texture_size;
glTexStorage3D(GL_TEXTURE_2D_ARRAY,
1,
GL_RG32F,
gl_max_texture_size,
1,
neededLayers);
}
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
//glActiveTexture(GL_TEXTURE0);
amountCurveTextureLayers = neededLayers;
}
previousCurveBufferSize = curveBufferSize;
}
void uploadCurveBuffer(){
glBindBufferBase(GL_UNIFORM_BUFFER, 0, curveBufferName);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(BufferCurve) * bufferCurves.size(), bufferCurves.data());
}
void uploadCurveTextureLayers(){
glActiveTexture(GL_TEXTURE0 + curveBufferUnit);
glBindTexture(GL_TEXTURE_2D_ARRAY, curveBufferName);
for(int i = 0; i < amountCurveTextureLayers; i++){
int width = widthCurveTextureLayer;
int height = 1;
int third = (int)std::floor(gl_max_texture_size / 3);
int layerPointer = (i * third) - (third % bufferCurveSize) + 1;
//int layerPointer = i == 0 ? 0 :
////5461;
//(i * gl_max_texture_size / int(3)) - ((gl_max_texture_size / int(3)) % bufferCurveSize) + 1;
//cout << ofToString(i) << " - layer pointer " << ofToString(layerPointer) << endl;
glTexSubImage3D(GL_TEXTURE_2D_ARRAY,
0,
0,
0,
i,
width,
height,
1,
GL_RG,
GL_FLOAT,
&bufferCurves[layerPointer]);
}
glActiveTexture(GL_TEXTURE0);
}
~Font(){
glDeleteVertexArrays(1, &vao);
glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &ebo);
#ifndef TARGET_OPENGLES
glDeleteTextures(1, &glyphTextureName);
glDeleteTextures(1, &curveTextureName);
glDeleteBuffers(1, &glyphBufferName);
glDeleteBuffers(1, &curveBufferName);
#else
glDeleteTextures(1, &glyphBufferName);
glDeleteTextures(1, &curveBufferName);
#endif
FT_Done_Face(face);
}
public:
void setWorldSize(FT_Library & library, float worldSize){
if(worldSize == this->worldSize){
return;
}
this->worldSize = worldSize;
if(!hinting){
return;
}
// We have to rebuild our buffers, because the outline coordinates can
// change because of hinting.
emSize = worldSize * 64;
{ // 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.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;
it = glyphs.erase(it);
continue;
}
// 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(GlyphIdentity{charcode, it->first.coords}, glyphIndex);
it++;
}
dirtyBuffers = true;
//uploadBuffers();
}
void prepareGlyphsForText(const std::vector <GlyphIdentity> & variationText,
FT_Library & library,
shared_ptr <ShaderCatalog> shaderCatalog,
shared_ptr <ShaderCatalog::Entry> shader,
bool forceChange = false){
bool changed = false;
if(forceChange){
bufferGlyphs.clear();
bufferCurves.clear();
glyphs.clear();
}
int i = 0;
stringstream text;
for(const auto & glyphIdentity : variationText){
const uint32_t & charcode = glyphIdentity.charcode;
if(charcode == '\0'){ // do we need this?
break;
}
if(charcode == '\r' || charcode == '\n'){
continue;
}
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;
}
FT_Error error = FT_Load_Glyph(face, glyphIndex, loadFlags);
if(error){
std::cerr << "[font] error while loading glyph for character " << charcode << ": " << error << std::endl;
continue;
}
buildGlyph(glyphIdentity,
glyphIndex);
changed = true;
i++;
}
//cout << text.str() << endl;
if(changed){
dirtyBuffers = true;
glyphBufferUnit = 1;
curveBufferUnit = 2;
if(BUFFER_TARGET_TYPE == GL_UNIFORM_BUFFER){
int curvesBufferSize = getUboCurvesBufferSize(bufferCurves);
if(curvesBufferSize != shaderCurvesBufferSize){
shaderCurvesBufferSize = curvesBufferSize;
shaderCatalog->setReplacement("{{CURVES_BUFFER_SIZE}}", std::to_string(shaderCurvesBufferSize));
//cout << shaderCatalog->getReplacement("{{CURVES_BUFFER_SIZE}}") << " <<<< _ replacement for curves buffer size " << endl;
shaderCatalog->requestUpdate("font_ub", true);
shaderCatalog->update();
shader = shaderCatalog->get("font_ub");
program = shader->program;
generateCurveBuffers();
uploadBuffers();
}else{
uploadBuffers();
}
}else{
generateCurveTextureLayers(bufferCurves.size());
// Reupload the full buffer contents. To make this even more
// dynamic, the buffers could be overallocated and only the added
// data could be uploaded.
uploadBuffers();
}
}
}
int getUboCurvesBufferSize(const std::vector <BufferCurve> & bc){
int actualShaderBufferSize = ceil((bc.size() * 6.0) / 4.0);
int thresholdBase = 2048;
int n_max = gl_max_uniform_block_size / thresholdBase;
for(int i = 0; i < n_max + 1; i++){
int levelSize = thresholdBase * i;
// prevent flattering around a threshold
// when going a level down
// but immediately
if(actualShaderBufferSize <= levelSize){
if(shaderCurvesBufferSize == levelSize + thresholdBase){
return shaderCurvesBufferSize;
}
return levelSize;
}
}
// you want more? well, we don't have it :(
return gl_max_uniform_block_size;
}
private:
bool initializedGlyphsBufferTexture = false;
bool initializedCurvesBufferTexture = false;
bool dirtyBuffers = false;
void testGL(GLuint texture = 10){
//int size = 0;
//glGetIntegerv(GL_MAX_TEXTURE_SIZE, &size);
//cout << "GL_MAX_TEXTURE_SIZE: " << ofToString(size) << endl;
//size = 0;
//glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &size);
//cout << "GL_MAX_ARRAY_TEXTURE_LAYERS: " << ofToString(size) << endl;
//size = 0;
GLsizei width = gl_max_texture_size;
GLsizei height = 1;
GLsizei layerCount = 4;
GLsizei mipLevelCount = 1;
//GLsizei bufSize = 42;
//GLint * params[bufSize];
//for(int i = 0; i < bufSize; i++){
//params[i] = 0;
//}
//glGetInternalformativ(GL_TEXTURE_2D_ARRAY,
//GL_RG32F,
//GL_MAX_DEPTH,
//bufSize / 2,
//params[0]);
//glGetInternalformativ(GL_TEXTURE_2D_ARRAY,
//GL_RG32F,
//GL_MAX_LAYERS,
//bufSize / 2,
//params[21]);
//stringstream s;
//s << "and here:" << endl;
//for(int i = 0; i < bufSize; i++){
//s << ofToString(i) << ": " << ofToString(params[i]) << endl;
//}
//cout << s.str();
// Read you texels here. In the current example, we have 2*2*2 = 8 texels, with each texel being 4 GLubytes.
GLubyte texels[layerCount][width * height];
for(int l = 0; l < layerCount; l++){
for(int i = 0; i < width * height; i++){
texels[l][i] = 127;
}
}
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
// Allocate the storage.
glTexStorage3D(GL_TEXTURE_2D_ARRAY, mipLevelCount, GL_RG32F, width, height, layerCount);
// Upload pixel data.
// The first 0 refers to the mipmap level (level 0, since there's only 1)
// The following 2 zeroes refers to the x and y offsets in case you only want to specify a subrectangle.
// The final 0 refers to the layer index offset (we start from index 0 and have 2 levels).
// Altogether you can specify a 3D box subset of the overall texture, but only one mip level at a time.
for(int i = 0; i < (int)layerCount; i++){
//cout << "OFFSET " << ofToString(i) << endl;
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, layerCount, GL_RG, GL_FLOAT, texels[i]);
}
// Always set reasonable texture parameters
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
void uploadBuffers(){
if(dirtyBuffers){
//cout << "bufferGlyphs.size(): " << bufferGlyphs.size() << endl;
//cout << "bufferCurves.size(): " << bufferCurves.size() << endl;
#ifndef TARGET_OPENGLES
glBindBuffer(GL_TEXTURE_BUFFER, glyphBufferName);
glBufferData(GL_TEXTURE_BUFFER, sizeof(BufferGlyph) * bufferGlyphs.size(), bufferGlyphs.data(), GL_STATIC_DRAW);
glBindBuffer(GL_TEXTURE_BUFFER, 0);
glBindBuffer(GL_TEXTURE_BUFFER, curveBufferName);
glBufferData(GL_TEXTURE_BUFFER, sizeof(BufferCurve) * bufferCurves.size(), bufferCurves.data(), GL_STATIC_DRAW);
glBindBuffer(GL_TEXTURE_BUFFER, 0);
#else
int size;
//glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &size);
//cout << "GL_MAX_SHADER_STORAGE_BLOCK_SIZE: " << ofToString(size) << endl;
int bufferGlyphsSize = bufferGlyphs.size() * sizeof(BufferGlyph);
if(!isPowerOfTwo(bufferGlyphsSize)){
bufferGlyphsSize = calculateUpperPowerOfTwo(bufferGlyphsSize);
}
glActiveTexture(GL_TEXTURE0 + glyphBufferUnit);
glBindTexture(GL_TEXTURE_2D, glyphBufferName);
if(!initializedGlyphsBufferTexture){
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
initializedGlyphsBufferTexture = true;
}
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RG32I,
bufferGlyphsSize,
1,
0,
GL_RG_INTEGER,
GL_INT,
bufferGlyphs.data());
glActiveTexture(GL_TEXTURE0);
if(BUFFER_TARGET_TYPE == GL_TEXTURE_2D_ARRAY){
uploadCurveTextureLayers();
}else if(BUFFER_TARGET_TYPE == GL_UNIFORM_BUFFER){
uploadCurveBuffer();
}
#endif
dirtyBuffers = false;
}
}
void buildGlyph(const GlyphIdentity & glyphIdentity,
FT_UInt glyphIndex){
BufferGlyph bufferGlyph;
bufferGlyph.start = static_cast <int32_t>(bufferCurves.size());
bool autoFixOutline = true;
if(autoFixOutline){
short start = 0;
int n_contours = face->glyph->outline.n_contours;
std::vector <float> directions(n_contours, 0);
std::vector <std::vector <BufferCurve> > curves(n_contours);
std::vector <BBox> bbs(n_contours);
int largestIndex = 0;
float largestArea = 0;
bool flipAll = false;
for(int i = 0; i < n_contours; i++){
// Note: The end indices in face->glyph->outline.contours are inclusive.
bbs[i].xMin = FLT_MAX;
bbs[i].xMax = -FLT_MAX;
bbs[i].yMin = FLT_MAX;
bbs[i].yMax = -FLT_MAX;
convertContour(curves[i], directions[i], bbs[i], &face->glyph->outline, start, face->glyph->outline.contours[i], emSize);
start = face->glyph->outline.contours[i] + 1;
float area = abs(bbs[i].xMax - bbs[i].xMin) * abs(bbs[i].yMax - bbs[i].yMin);
if(area > largestArea){
largestArea = area;
largestIndex = i;
if(directions[i] < 0){
flipAll = true;
}else{
flipAll = false;
}
}
}
if(flipAll){
for(int i = 0; i < n_contours; i++){
for(BufferCurve & curve : curves[i]){
float tmp_x = curve.x0;
float tmp_y = curve.y0;
curve.x0 = curve.x2;
curve.y0 = curve.y2;
curve.x2 = tmp_x;
curve.y2 = tmp_y;
}
std::reverse(curves[i].begin(), curves[i].end());
bufferCurves.insert(bufferCurves.end(), curves[i].begin(), curves[i].end());
}
}else{
for(int i = 0; i < n_contours; i++){
if(directions[i] < 0){
bool flip = true;
for(int j = 0; j < n_contours; j++){
if(i != j){
bool inside = bbs[j].xMin <= bbs[i].xMin
&& bbs[j].xMax >= bbs[i].xMax
&& bbs[j].yMin <= bbs[i].yMin
&& bbs[j].yMax >= bbs[i].yMax;
if(inside){
flip = false;
break;
}
}
}
if(flip){
for(BufferCurve & curve : curves[i]){
float tmp_x = curve.x0;
float tmp_y = curve.y0;
curve.x0 = curve.x2;
curve.y0 = curve.y2;
curve.x2 = tmp_x;
curve.y2 = tmp_y;
}
std::reverse(curves[i].begin(), curves[i].end());
}
}
bufferCurves.insert(bufferCurves.end(), curves[i].begin(), curves[i].end());
}
}
}else{
short start = 0;
for(int i = 0; i < face->glyph->outline.n_contours; i++){
// Note: The end indices in face->glyph->outline.contours are inclusive.
float direction = 0; // ignored
BBox bb; // ignored
convertContour(bufferCurves, direction, bb, &face->glyph->outline, start, face->glyph->outline.contours[i], emSize);
start = face->glyph->outline.contours[i] + 1;
}
}
bufferGlyph.count = static_cast <int32_t>(bufferCurves.size()) - bufferGlyph.start;
int32_t bufferIndex = static_cast <int32_t>(bufferGlyphs.size());
bufferGlyphs.push_back(bufferGlyph);
Glyph glyph;
glyph.index = glyphIndex;
glyph.bufferIndex = bufferIndex;
glyph.curveCount = bufferGlyph.count;
glyph.width = face->glyph->metrics.width;
glyph.height = face->glyph->metrics.height;
glyph.bearingX = face->glyph->metrics.horiBearingX;
glyph.bearingY = face->glyph->metrics.horiBearingY;
glyph.advance = face->glyph->metrics.horiAdvance;
glyphs[glyphIdentity] = glyph;
}
// This function takes a single contour (defined by firstIndex and
// lastIndex, both inclusive) from outline and converts it into individual
// quadratic bezier curves, which are added to the curves vector.
void convertContour(std::vector <BufferCurve> & curves, float & direction, BBox & bb, const FT_Outline * outline, short firstIndex, short lastIndex, float emSize){
// See https://freetype.org/freetype2/docs/glyphs/glyphs-6.html
// for a detailed description of the outline format.
//
// In short, a contour is a list of points describing line segments
// and quadratic or cubic bezier curves that form a closed shape.
//
// TrueType fonts only contain quadratic bezier curves. OpenType fonts
// may contain outline data in TrueType format or in Compact Font
// Format, which also allows cubic beziers. However, in FreeType it is
// (theoretically) possible to mix the two types of bezier curves, so
// we handle both at the same time.
//
// Each point in the contour has a tag specifying its type
// (FT_CURVE_TAG_ON, FT_CURVE_TAG_CONIC or FT_CURVE_TAG_CUBIC).
// FT_CURVE_TAG_ON points sit exactly on the outline, whereas the
// other types are control points for quadratic/conic bezier curves,
// which in general do not sit exactly on the outline and are also
// called off points.
//
// Some examples of the basic segments:
// ON - ON ... line segment
// ON - CONIC - ON ... quadratic bezier curve
// ON - CUBIC - CUBIC - ON ... cubic bezier curve
//
// Cubic bezier curves must always be described by two CUBIC points
// inbetween two ON points. For the points used in the TrueType format
// (ON, CONIC) there is a special rule, that two consecutive points of
// the same type imply a virtual point of the opposite type at their
// exact midpoint.
//
// For example the sequence ON - CONIC - CONIC - ON describes two
// quadratic bezier curves where the virtual point forms the joining
// end point of the two curves: ON - CONIC - [ON] - CONIC - ON.
//
// Similarly the sequence ON - ON can be thought of as a line segment
// or a quadratic bezier curve (ON - [CONIC] - ON). Because the
// virtual point is at the exact middle of the two endpoints, the
// bezier curve is identical to the line segment.
//
// The font shader only supports quadratic bezier curves, so we use
// this virtual point rule to represent line segments as quadratic
// bezier curves.
//
// Cubic bezier curves are slightly more difficult, since they have a
// higher degree than the shader supports. Each cubic curve is
// approximated by two quadratic curves according to the following
// paper. This preserves C1-continuity (location of and tangents at
// the end points of the cubic curve) and the paper even proves that
// splitting at the parametric center minimizes the error due to the
// degree reduction. One could also analyze the approximation error
// and split the cubic curve, if the error is too large. However,
// almost all fonts use "nice" cubic curves, resulting in very small
// errors already (see also the section on Font Design in the paper).
//
// Quadratic Approximation of Cubic Curves
// Nghia Truong, Cem Yuksel, Larry Seiler
// https://ttnghia.github.io/pdf/QuadraticApproximation.pdf
// https://doi.org/10.1145/3406178
if(firstIndex == lastIndex){
return;
}
short dIndex = 1;
if(outline->flags & FT_OUTLINE_REVERSE_FILL){
short tmpIndex = lastIndex;
lastIndex = firstIndex;
firstIndex = tmpIndex;
dIndex = -1;
}
auto convert = [emSize](const FT_Vector & v){
return glm::vec2(
(float)v.x / emSize,
(float)v.y / emSize
);
};
auto makeMidpoint = [](const glm::vec2 & a, const glm::vec2 & b){
return 0.5f * (a + b);
};
// Get Direction of a point pair.
// Notice that we can ignore the middle point.
auto getDirection = [](const glm::vec2 & p0, const glm::vec2 & p1){
return (p1.x - p0.x) * (p1.y + p0.y);
};
// We will inject getDirection in makeCurve,
// so anytime the curve is made it updates the direction.
// This is less error prone than calling it separately.
// wisdom: Any error you cannot make, you will not make. :)
auto makeCurve = [&direction, getDirection, &bb](const glm::vec2 & p0, const glm::vec2 & p1, const glm::vec2 & p2){
BufferCurve result;
result.x0 = p0.x;
result.y0 = p0.y;
result.x1 = p1.x;
result.y1 = p1.y;
result.x2 = p2.x;
result.y2 = p2.y;
direction += getDirection(p0, p2);
bb.xMin = min(bb.xMin, min(p0.x, min(p1.x, p2.x)));
bb.xMax = max(bb.xMax, max(p0.x, max(p1.x, p2.x)));
bb.yMin = min(bb.yMin, min(p0.y, min(p1.y, p2.y)));
bb.yMax = max(bb.yMax, max(p0.y, max(p1.y, p2.y)));
return result;
};
// Find a point that is on the curve and remove it from the list.
glm::vec2 first;
bool firstOnCurve = (outline->tags[firstIndex] & FT_CURVE_TAG_ON);
if(firstOnCurve){
first = convert(outline->points[firstIndex]);
firstIndex += dIndex;
}else{
bool lastOnCurve = (outline->tags[lastIndex] & FT_CURVE_TAG_ON);
if(lastOnCurve){
first = convert(outline->points[lastIndex]);
lastIndex -= dIndex;
}else{
first = makeMidpoint(convert(outline->points[firstIndex]), convert(outline->points[lastIndex]));
// This is a virtual point, so we don't have to remove it.
}
}
glm::vec2 start = first;
glm::vec2 control = first;
glm::vec2 previous = first;
char previousTag = FT_CURVE_TAG_ON;
for(short index = firstIndex; index != lastIndex + dIndex; index += dIndex){
glm::vec2 current = convert(outline->points[index]);
char currentTag = FT_CURVE_TAG(outline->tags[index]);
if(currentTag == FT_CURVE_TAG_CUBIC){
// No-op, wait for more points.
control = previous;
}else if(currentTag == FT_CURVE_TAG_ON){
if(previousTag == FT_CURVE_TAG_CUBIC){
glm::vec2 & b0 = start;
glm::vec2 & b1 = control;
glm::vec2 & b2 = previous;
glm::vec2 & b3 = current;
glm::vec2 c0 = b0 + 0.75f * (b1 - b0);
glm::vec2 c1 = b3 + 0.75f * (b2 - b3);
glm::vec2 d = makeMidpoint(c0, c1);
curves.push_back(makeCurve(b0, c0, d));
curves.push_back(makeCurve(d, c1, b3));
}else if(previousTag == FT_CURVE_TAG_ON){
// Linear segment.
curves.push_back(makeCurve(previous, makeMidpoint(previous, current), current));
}else{
// Regular bezier curve.
curves.push_back(makeCurve(start, previous, current));
}
start = current;
control = current;
}else{ /* currentTag == FT_CURVE_TAG_CONIC */
if(previousTag == FT_CURVE_TAG_ON){
// No-op, wait for third point.
}else{
// Create virtual on point.
glm::vec2 mid = makeMidpoint(previous, current);
curves.push_back(makeCurve(start, previous, mid));
start = mid;
control = mid;
}
}
previous = current;
previousTag = currentTag;
}
// Close the contour.
if(previousTag == FT_CURVE_TAG_CUBIC){
glm::vec2 & b0 = start;
glm::vec2 & b1 = control;
glm::vec2 & b2 = previous;
glm::vec2 & b3 = first;
glm::vec2 c0 = b0 + 0.75f * (b1 - b0);
glm::vec2 c1 = b3 + 0.75f * (b2 - b3);
glm::vec2 d = makeMidpoint(c0, c1);
curves.push_back(makeCurve(b0, c0, d));
curves.push_back(makeCurve(d, c1, b3));
}else if(previousTag == FT_CURVE_TAG_ON){
// Linear segment.
curves.push_back(makeCurve(previous, makeMidpoint(previous, first), first));
}else{
curves.push_back(makeCurve(start, previous, first));
}
}
public:
void drawSetup(){
GLint location;
#ifndef TARGET_OPENGLES
location = glGetUniformLocation(program, "glyphs");
glUniform1i(location, 0);
location = glGetUniformLocation(program, "curves");
glUniform1i(location, 1);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_BUFFER, glyphTextureName);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_BUFFER, curveTextureName);
glActiveTexture(GL_TEXTURE0);
#else
glyphsUniformLocation = glGetUniformLocation(program, "glyphs");
glUniform1i(glyphsUniformLocation, glyphBufferUnit);
glActiveTexture(GL_TEXTURE0 + glyphBufferUnit);
glBindTexture(GL_TEXTURE_2D, glyphBufferName);
if(BUFFER_TARGET_TYPE == GL_TEXTURE_2D_ARRAY){
curvesUniformLocation = glGetUniformLocation(program, "curves");
glUniform1i(curvesUniformLocation, curveBufferUnit);
glActiveTexture(GL_TEXTURE0 + curveBufferUnit);
glBindTexture(GL_TEXTURE_2D_ARRAY, curveBufferName);
}else if(BUFFER_TARGET_TYPE == GL_UNIFORM_BUFFER){
glBindBuffer(GL_UNIFORM_BUFFER, curveBufferName);
glUniformBlockBinding(program, ubo_curves_index, 0);
}
glActiveTexture(GL_TEXTURE0);
#endif
}
float getLineHeight(float fontSize_px = 1){
float out = ((float)face->height * fontSize_px) / (float)face->units_per_EM * worldSize;
return out;
}
float getAscender(float fontSize_px = 1){
float out = ((float)face->ascender * fontSize_px) / (float)face->units_per_EM * worldSize;
return out;
}
float getDescender(float fontSize_px = 1){
float out = ((float)face->descender * fontSize_px) / (float)face->units_per_EM * worldSize;
return out;
}
void collectBoundingBoxes(const std::vector <GlyphIdentity> & variationText,
const std::vector <GlyphAppearance> & variationTextAppearance,
BoundingBox & boundingBox,
std::vector <BoundingBox> & boundingBoxes,
float & advanceY,
const bool vFlip = false,
const float fontSize_px = 42,
const float lineHeightMultiplier = 1,
const float wrapWidth = 0){
float advanceX = 0;
FT_UInt previous = 0;
float minX = FLT_MAX;
float minY = FLT_MAX;
float maxX = -FLT_MAX;
float maxY = -FLT_MAX;
int i = 0;
for(const GlyphIdentity & glyphIdentity : variationText){
const GlyphAppearance & glyphAppearance = variationTextAppearance[i];
if(glyphIdentity.charcode == '\0'){
break;
}
float letterFontSize_px = glyphAppearance.fontSize_px;
const uint32_t & charcode = glyphIdentity.charcode;
if(charcode == '\r'){
i++;
continue;
}
if(charcode == '\n'){
advanceX = 0;
advanceY += getLineHeight(fontSize_px) * lineHeightMultiplier;
if(!vFlip){
advanceY *= -1;
}
if(hinting){
advanceY = std::round(advanceY);
}
i++;
continue;
}
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;
FT_Error error = FT_Get_Kerning(face, previous, glyph.index, kerningMode, &kerning);
if(!error){
advanceX += (((float)kerning.x * letterFontSize_px)) / emSize * worldSize;
}
}
float letterSpacing = advanceX == 0 ? 0 : glyphAppearance.letterSpacing;
advanceX += (letterSpacing * letterFontSize_px) * 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 = advanceX + u0 * worldSize * letterFontSize_px;
float y0 = v0 * worldSize * letterFontSize_px;
float x1 = advanceX + u1 * worldSize * letterFontSize_px;
float y1 = v1 * worldSize * letterFontSize_px;
if(vFlip){
float _v = v0;
v0 = v1;
v1 = _v;
y0 = -v0 * worldSize * letterFontSize_px;
y1 = -v1 * worldSize * letterFontSize_px;
}
y0 += advanceY;
y1 += advanceY;
minX = min(x0, min(x1, minX));
maxX = max(x0, max(x1, maxX));
minY = min(y0, min(y1, minY));
maxY = max(y0, max(y1, maxY));
boundingBoxes.push_back({
glm::vec4(x0, y0, 0, 1),
glm::vec4(x1, y0, 0, 1),
glm::vec4(x1, y1, 0, 1),
glm::vec4(x0, y1, 0, 1),
u0, u1, v0, v1,
glyph.bufferIndex
});
}
advanceX += ((float)glyph.advance * letterFontSize_px) / emSize * worldSize;
previous = glyph.index;
i++;
}
boundingBox.p0 = glm::vec4(minX, minY, 0, 1);
boundingBox.p1 = glm::vec4(maxX, minY, 0, 1);
boundingBox.p2 = glm::vec4(maxX, maxY, 0, 1);
boundingBox.p3 = glm::vec4(minX, maxY, 0, 1);
advanceY += getLineHeight(fontSize_px) * lineHeightMultiplier;
if(!vFlip){
advanceY *= -1;
}
if(hinting){
advanceY = std::round(advanceY);
}
}
void collectVerticesAndIndices(const ofNode & node,
const std::vector <GlyphAppearance> & variationTextAppearance,
std::vector <BoundingBox> & boundingBoxes,
std::vector <BufferVertex> & vertices,
std::vector <int32_t> & indices){
int i = 0;
for(BoundingBox & bb : boundingBoxes){
bool hasGoodCharacter = false;
while(!hasGoodCharacter && i < variationTextAppearance.size() - 1){
if(variationTextAppearance[i].charcode == '\0'
|| variationTextAppearance[i].charcode == '\r'
|| variationTextAppearance[i].charcode == ' '
|| variationTextAppearance[i].charcode == '\n'){
i++;
}else{
hasGoodCharacter = true;
}
}
bb.multiply(node.getGlobalTransformMatrix());
int32_t base = static_cast <int32_t>(vertices.size());
const std::array <float, 4> & color = variationTextAppearance[i].color;
vertices.push_back(BufferVertex{bb.p0.x, bb.p0.y, bb.u0, bb.v0, bb.bufferIndex,
color[0],
color[1],
color[2],
color[3]});
vertices.push_back(BufferVertex{bb.p1.x, bb.p1.y, bb.u1, bb.v0, bb.bufferIndex,
color[0],
color[1],
color[2],
color[3]});
vertices.push_back(BufferVertex{bb.p2.x, bb.p2.y, bb.u1, bb.v1, bb.bufferIndex,
color[0],
color[1],
color[2],
color[3]});
vertices.push_back(BufferVertex{bb.p3.x, bb.p3.y, bb.u0, bb.v1, bb.bufferIndex,
color[0],
color[1],
color[2],
color[3]});
indices.insert(indices.end(), {base, base + 1, base + 2, base + 2, base + 3, base});
i++;
}
}
void draw(const std::vector <BufferVertex> & vertices,
const std::vector <int32_t> & indices){
glBindVertexArray(vao);
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);
}
private:
FT_Face face;
// Whether hinting is enabled for this instance.
// Note that hinting changes how we operate FreeType:
// If hinting is not enabled, we scale all coordinates ourselves (see comment for emSize).
// If hinting is enabled, we must let FreeType scale the outlines for the hinting to work properly.
// The variables loadFlags and kerningMode are set in the constructor and control this scaling behavior.
bool hinting;
GLint BUFFER_TARGET_TYPE;
FT_Int32 loadFlags;
FT_Kerning_Mode kerningMode;
// Size of the em square used to convert metrics into em-relative values,
// which can then be scaled to the worldSize. We do the scaling ourselves in
// floating point to support arbitrary world sizes (whereas the fixed-point
// numbers used by FreeType do not have enough resolution if the world size
// is small).
// Following the FreeType convention, if hinting (and therefore scaling) is enabled,
// this value is in 1/64th of a pixel (compatible with 26.6 fixed point numbers).
// If hinting/scaling is not enabled, this value is in font units.
float emSize;
float worldSize;
GLuint vao, vbo, ebo;
#ifndef TARGET_OPENGLES
GLuint glyphTextureName, curveTextureName;
#else
GLint glyphsUniformLocation, curvesUniformLocation;
#endif
GLuint glyphBufferName, curveBufferName;
GLuint glyphBufferUnit, curveBufferUnit;
std::vector <BufferGlyph> bufferGlyphs;
std::vector <BufferCurve> bufferCurves;
std::vector <BufferCurve> shaderBufferCurves; // ubo
std::vector <int> bufferCurveLayerPointers = {0}; // t2a
std::unordered_map <GlyphIdentity, Glyph> glyphs;
static GLint gl_max_texture_size;
static GLint gl_max_uniform_block_size;
public:
// ID of the shader program to use.
GLuint program = 0;
// The glyph quads are expanded by this amount to enable proper
// anti-aliasing. Value is relative to emSize.
float dilation = 0;
};
static std::shared_ptr <Font> loadFont(FT_Library & library,
const std::string & filename,
GLint bufferTargetType,
GLuint shaderProgram,
float worldSize = 1.0f,
bool hinting = false){
std::string error;
FT_Face face = Font::loadFace(library, filename, error);
if(error != ""){
std::cerr << "ofxGPUFont::font.hpp[" << __LINE__ << "] FT failed to load " << filename << ": " << error << std::endl;
return std::shared_ptr <Font>{};
}else{
std::cout << "ofxGPUFont::font.hpp[" << __LINE__ << "] FT loaded " << filename << ": " << error << std::endl;
}
return std::make_shared <Font>(face, worldSize, hinting, bufferTargetType, shaderProgram);
}
static void initializeFont(FT_Library & library,
const std::string & filename,
shared_ptr <Font> & mainFont,
GLuint shaderProgram,
GLint bufferTargetType = GL_TEXTURE_2D_ARRAY){
cout << "initializeFont" << endl;
auto font = loadFont(library, filename, bufferTargetType, shaderProgram);
if(!font){
return;
}
font->dilation = 0.1f;
mainFont = std::move(font);
}
}