1433 lines
58 KiB
C++
1433 lines
58 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 <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;
|
|
}
|
|
};
|
|
|
|
/// 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, int gpuTextureOffset, float worldSize = 1.0f, bool hinting = false) :
|
|
face(face),
|
|
hinting(hinting),
|
|
worldSize(worldSize),
|
|
gpuTextureOffset(gpuTextureOffset){
|
|
// 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(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;
|
|
}
|
|
|
|
//int amounters = 0;
|
|
//for(int i = 0; i < 1024; i++){
|
|
//testGL(10 + i);
|
|
//amounters = i + 1;
|
|
//cout << "added " << ofToString(amounters) << " LOL" << endl;
|
|
//bool hasError = false;
|
|
//GLenum err;
|
|
//while((err = glGetError()) != GL_NO_ERROR){
|
|
//hasError = true;
|
|
//// Process/log the error.
|
|
//}
|
|
//if(hasError){
|
|
//cout << "WHOOOPS" << endl;
|
|
//break;
|
|
//}
|
|
//}
|
|
|
|
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
|
|
glGenTextures(1, &glyphBufferName);
|
|
glGenTextures(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));
|
|
|
|
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 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,
|
|
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;
|
|
}
|
|
|
|
//cout << "yes, this is running" << endl;
|
|
if(charcode == '\r' || charcode == '\n'){
|
|
//cout << "but charcode is r or n" << endl;
|
|
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;
|
|
}
|
|
|
|
//text << (char)glyphIdentity.charcode
|
|
//<< " " << ofToString(glyphIdentity.coords[0]) << " "
|
|
//<< " (" << ofToString(bufferCurves.size()) << ")" << endl;
|
|
|
|
buildGlyph(glyphIdentity,
|
|
glyphIndex);
|
|
|
|
|
|
changed = true;
|
|
i++;
|
|
}
|
|
//cout << text.str() << endl;
|
|
|
|
if(changed){
|
|
dirtyBuffers = true;
|
|
glyphBufferUnit = 1;
|
|
curveBufferUnit = 2;
|
|
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();
|
|
}
|
|
}
|
|
|
|
private:
|
|
bool initializedGlyphsBufferTexture = false;
|
|
bool initializedCurvesBufferTexture = false;
|
|
bool dirtyBuffers = false;
|
|
int calcTextureOffset(){
|
|
return gpuTextureOffset * 2;
|
|
}
|
|
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());
|
|
|
|
//int bufferCurvesSize = bufferCurves.size() * bufferCurveSize;
|
|
//if(!isPowerOfTwo(bufferCurvesSize)){
|
|
//bufferCurvesSize = calculateUpperPowerOfTwo(bufferCurvesSize);
|
|
//}
|
|
//glActiveTexture(GL_TEXTURE0 + curveBufferUnit);
|
|
//glBindTexture(GL_TEXTURE_2D, curveBufferName);
|
|
//if(!initializedCurvesBufferTexture){
|
|
//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);
|
|
//initializedCurvesBufferTexture = true;
|
|
//}
|
|
//glTexImage2D(GL_TEXTURE_2D,
|
|
//0,
|
|
//GL_RG32F,
|
|
//bufferCurvesSize,
|
|
//1,
|
|
//0,
|
|
//GL_RG,
|
|
//GL_FLOAT,
|
|
//bufferCurves.data());
|
|
glActiveTexture(GL_TEXTURE0);
|
|
uploadCurveTextureLayers();
|
|
#endif
|
|
dirtyBuffers = false;
|
|
}
|
|
}
|
|
|
|
void buildGlyph(const GlyphIdentity & glyphIdentity,
|
|
FT_UInt glyphIndex){
|
|
BufferGlyph bufferGlyph;
|
|
bufferGlyph.start = static_cast <int32_t>(bufferCurves.size());
|
|
|
|
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.
|
|
convertContour(bufferCurves, &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> & all_curves, const FT_Outline * outline, short firstIndex, short lastIndex, float emSize){
|
|
// we might have to flip the direction
|
|
// so let's buffer our buffercurves in a buffer curves of buffercurves
|
|
std::vector <BufferCurve> curves;
|
|
|
|
// 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);
|
|
};
|
|
|
|
// keep track of collected direction
|
|
float direction = 0;
|
|
|
|
// 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](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);
|
|
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));
|
|
}
|
|
if(direction < 0){
|
|
for(BufferCurve & curve : curves){
|
|
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.begin(), curves.end());
|
|
}
|
|
all_curves.insert(all_curves.end(), curves.begin(), curves.end());
|
|
}
|
|
|
|
|
|
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);
|
|
curvesUniformLocation = glGetUniformLocation(program, "curves");
|
|
glUniform1i(curvesUniformLocation, curveBufferUnit);
|
|
|
|
glActiveTexture(GL_TEXTURE0 + glyphBufferUnit);
|
|
glBindTexture(GL_TEXTURE_2D, glyphBufferName);
|
|
|
|
glActiveTexture(GL_TEXTURE0 + curveBufferUnit);
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, curveBufferName);
|
|
|
|
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 == '\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;
|
|
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 <int> bufferCurveLayerPointers = {0};
|
|
std::unordered_map <GlyphIdentity, Glyph> glyphs;
|
|
|
|
static GLint gl_max_texture_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;
|
|
int gpuTextureOffset = 0;
|
|
};
|
|
|
|
static std::shared_ptr <Font> loadFont(FT_Library & library,
|
|
const std::string & filename,
|
|
int gpuTextureOffset = 0,
|
|
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, gpuTextureOffset, worldSize, hinting);
|
|
}
|
|
|
|
|
|
static void initializeFont(FT_Library & library,
|
|
const std::string & filename,
|
|
shared_ptr <Font> & mainFont,
|
|
int gpuTextureOffset){
|
|
cout << "initializeFont" << endl;
|
|
auto font = loadFont(library, filename, gpuTextureOffset);
|
|
if(!font){
|
|
return;
|
|
}
|
|
|
|
font->dilation = 0.1f;
|
|
|
|
mainFont = std::move(font);
|
|
}
|
|
}
|