slight refactor

This commit is contained in:
jrkb 2023-04-11 17:45:20 +02:00
parent e1557285de
commit 470b831c61
9 changed files with 45 additions and 41 deletions

View file

@ -1,9 +0,0 @@
// Source: https://stackoverflow.com/a/42060129
#ifndef defer
struct defer_dummy {};
template <class F> struct deferrer { F f; ~deferrer() { f(); } };
template <class F> deferrer<F> operator*(defer_dummy, F f) { return {f}; }
#define DEFER_(LINE) zz_defer##LINE
#define DEFER(LINE) DEFER_(LINE)
#define defer auto DEFER(__LINE__) = defer_dummy{} *[&]()
#endif // defer

View file

@ -1,918 +0,0 @@
// 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 "ofMain.h"
#include <GL/glext.h>
#include <ft2build.h>
#ifdef TARGET_OPENGLES
#include <GL/gl.h>
#include <GLES/gl.h>
#endif
#include FT_FREETYPE_H
#include FT_MULTIPLE_MASTERS_H
#define F26DOT6_TO_DOUBLE(x) (1 / 64. * double(x))
#define F16DOT16_TO_DOUBLE(x) (1 / 65536. * double(x))
#define DOUBLE_TO_F16DOT16(x) FT_Fixed(65536. * 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;
}
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 {
float x0, y0, x1, y1, x2, y2;
};
struct BufferVertex {
float x, y, u, v;
int32_t bufferIndex;
};
public:
/// 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 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) : face(face), worldSize(worldSize), hinting(hinting){
// TODO: modularize init, so we can initialize with settings and text
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;
}
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
glGenBuffers(1, &ebo);
#ifndef TARGET_OPENGLES
glGenTextures(1, &glyphTexture);
glGenTextures(1, &curveTexture);
glGenBuffers(1, &glyphBuffer);
glGenBuffers(1, &curveBuffer);
#else
glGenTextures(1, &glyphBuffer);
glGenTextures(1, &curveBuffer);
#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));
glBindVertexArray(0);
//{
//uint32_t charcode = 0;
//FT_UInt glyphIndex = 0;
//FT_Error error = FT_Load_Glyph(face, glyphIndex, loadFlags);
//if(error){
//std::cerr << "[font] error while loading undefined glyph: " << error << std::endl;
//// Continue, because we always want an entry for the undefined glyph in our glyphs map!
//}
//buildGlyph(charcode, glyphIndex);
//}
//for(uint32_t charcode = 32; charcode < 128; charcode++){
//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(charcode, glyphIndex);
//}
uploadBuffers();
#ifndef TARGET_OPENGLES
glBindTexture(GL_TEXTURE_BUFFER, glyphTexture);
glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32I, glyphBuffer);
glBindTexture(GL_TEXTURE_BUFFER, 0);
glBindTexture(GL_TEXTURE_BUFFER, curveTexture);
glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, curveBuffer);
glBindTexture(GL_TEXTURE_BUFFER, 0);
#else
#endif
}
~Font(){
glDeleteVertexArrays(1, &vao);
glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &ebo);
#ifndef TARGET_OPENGLES
glDeleteTextures(1, &glyphTexture);
glDeleteTextures(1, &curveTexture);
glDeleteBuffers(1, &glyphBuffer);
glDeleteBuffers(1, &curveBuffer);
#else
glDeleteTextures(1, &glyphBuffer);
glDeleteTextures(1, &curveBuffer);
#endif
FT_Done_Face(face);
}
public:
void setWorldSize(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;
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;
FT_UInt glyphIndex = it->second.index;
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(charcode, glyphIndex);
it++;
}
uploadBuffers();
}
void prepareGlyphsForText(const std::string & text, bool forceChange = false){
// TODO: do not duplicate glyphs
bool changed = false;
if(forceChange){
bufferGlyphs.clear();
bufferCurves.clear();
glyphs.clear();
}
for(const char * textIt = text.c_str(); *textIt != '\0';){
uint32_t charcode = decodeCharcode(&textIt);
//cout << "yes, this is running" << endl;
if(charcode == '\r' || charcode == '\n'){
//cout << "but charcode is r or n" << endl;
continue;
}
if(glyphs.count(charcode) != 0){
//cout << "but count is not 0?" << endl;
continue;
}
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(charcode, glyphIndex);
changed = true;
}
if(changed){
// 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;
void uploadBuffers(){
//cout << "bufferGlyphs.size(): " << bufferGlyphs.size() << endl;
//cout << "bufferCurves.size(): " << bufferCurves.size() << endl;
#ifndef TARGET_OPENGLES
glBindBuffer(GL_TEXTURE_BUFFER, glyphBuffer);
glBufferData(GL_TEXTURE_BUFFER, sizeof(BufferGlyph) * bufferGlyphs.size(), bufferGlyphs.data(), GL_STATIC_DRAW);
glBindBuffer(GL_TEXTURE_BUFFER, 0);
glBindBuffer(GL_TEXTURE_BUFFER, curveBuffer);
glBufferData(GL_TEXTURE_BUFFER, sizeof(BufferCurve) * bufferCurves.size(), bufferCurves.data(), GL_STATIC_DRAW);
glBindBuffer(GL_TEXTURE_BUFFER, 0);
#else
int bufferGlyphsSize = bufferGlyphs.size();
if(!isPowerOfTwo(bufferGlyphsSize)){
bufferGlyphsSize = calculateUpperPowerOfTwo(bufferGlyphsSize);
}
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, glyphBuffer);
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();
if(!isPowerOfTwo(bufferCurvesSize)){
bufferCurvesSize = calculateUpperPowerOfTwo(bufferCurvesSize);
}
glActiveTexture(GL_TEXTURE4);
glBindTexture(GL_TEXTURE_2D, curveBuffer);
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 * 2,
1,
0,
GL_RG,
GL_FLOAT,
bufferCurves.data());
glActiveTexture(GL_TEXTURE0);
#endif
}
void buildGlyph(uint32_t charcode, 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[charcode] = 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, 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);
};
auto makeCurve = [](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;
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));
}
}
// Decodes the first Unicode code point from the null-terminated UTF-8 string *text and advances *text to point at the next code point.
// If the encoding is invalid, advances *text by one byte and returns 0.
// *text should not be empty, because it will be advanced past the null terminator.
uint32_t decodeCharcode(const char * * text){
uint8_t first = static_cast <uint8_t>((*text)[0]);
// Fast-path for ASCII.
if(first < 128){
(*text)++;
return static_cast <uint32_t>(first);
}
// This could probably be optimized a bit.
uint32_t result;
int size;
if((first & 0xE0) == 0xC0){ // 110xxxxx
result = first & 0x1F;
size = 2;
}else if((first & 0xF0) == 0xE0){ // 1110xxxx
result = first & 0x0F;
size = 3;
}else if((first & 0xF8) == 0xF0){ // 11110xxx
result = first & 0x07;
size = 4;
}else{
// Invalid encoding.
(*text)++;
return 0;
}
for(int i = 1; i < size; i++){
uint8_t value = static_cast <uint8_t>((*text)[i]);
// Invalid encoding (also catches a null terminator in the middle of a code point).
if((value & 0xC0) != 0x80){ // 10xxxxxx
(*text)++;
return 0;
}
result = (result << 6) | (value & 0x3F);
}
(*text) += size;
return result;
}
public:
void drawSetup(){
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, glyphTexture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_BUFFER, curveTexture);
glActiveTexture(GL_TEXTURE0);
#else
// Important! The GLint for the location needs to stick around
// we cannot reuse a location as in Desktop for some reason
glyphBufferLocation = glGetUniformLocation(program, "glyphs");
glUniform1i(glyphBufferLocation, 3);
curveBufferLocation = glGetUniformLocation(program, "curves");
glUniform1i(curveBufferLocation, 4);
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, glyphBuffer);
glActiveTexture(GL_TEXTURE4);
glBindTexture(GL_TEXTURE_2D, curveBuffer);
glActiveTexture(GL_TEXTURE0);
#endif
}
void draw(float x, float y, float z, const std::string & text){
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';){
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;
float y0 = y + v0 * worldSize;
float x1 = x + u1 * worldSize;
float y1 = y + v1 * worldSize;
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 / emSize * worldSize;
previous = glyph.index;
}
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(BufferVertex) * vertices.size(), vertices.data(), GL_STREAM_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int32_t) * indices.size(), indices.data(), GL_STREAM_DRAW);
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
struct BoundingBox {
float minX, minY, maxX, maxY;
};
BoundingBox measure(float x, float y, const std::string & text){
BoundingBox bb;
bb.minX = +std::numeric_limits <float>::infinity();
bb.minY = +std::numeric_limits <float>::infinity();
bb.maxX = -std::numeric_limits <float>::infinity();
bb.maxY = -std::numeric_limits <float>::infinity();
float originalX = x;
FT_UInt previous = 0;
for(const char * textIt = text.c_str(); *textIt != '\0';){
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;
}
}
// Note: Do not apply dilation here, we want to calculate exact bounds.
float u0 = (float)(glyph.bearingX) / emSize;
float v0 = (float)(glyph.bearingY - glyph.height) / emSize;
float u1 = (float)(glyph.bearingX + glyph.width) / emSize;
float v1 = (float)(glyph.bearingY) / emSize;
float x0 = x + u0 * worldSize;
float y0 = y + v0 * worldSize;
float x1 = x + u1 * worldSize;
float y1 = y + v1 * worldSize;
if(x0 < bb.minX){
bb.minX = x0;
}
if(y0 < bb.minY){
bb.minY = y0;
}
if(x1 > bb.maxX){
bb.maxX = x1;
}
if(y1 > bb.maxY){
bb.maxY = y1;
}
x += (float)glyph.advance / emSize * worldSize;
previous = glyph.index;
}
return bb;
}
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 glyphTexture, curveTexture;
#else
GLint glyphBufferLocation, curveBufferLocation;
#endif
GLuint glyphBuffer, curveBuffer;
std::vector <BufferGlyph> bufferGlyphs;
std::vector <BufferCurve> bufferCurves;
std::unordered_map <uint32_t, Glyph> glyphs;
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;
};

View file

@ -1,18 +0,0 @@
#pragma once
#define GLM_FORCE_RADIANS
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/mat4x4.hpp>
#include <glm/trigonometric.hpp>
#include <glm/gtc/constants.hpp>
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/hash.hpp>
#include <glm/gtx/string_cast.hpp>
#include <glm/gtx/transform.hpp>

View file

@ -1,717 +0,0 @@
#include <cmath>
#include <cstdint>
#include <iostream>
#include <limits>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#include <defer.hpp>
#include "ofMain.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_MULTIPLE_MASTERS_H
#include "glm.hpp"
#include "shader_catalog.hpp"
#define F26DOT6_TO_DOUBLE(x) (1 / 64. * double(x))
#define F16DOT16_TO_DOUBLE(x) (1 / 65536. * double(x))
#define DOUBLE_TO_F16DOT16(x) FT_Fixed(65536. * x)
#include "font.hpp"
float wght = 700.0;
float wghtStep = 10.0;
std::string currentFontPath = "";
struct Transform {
float fovy = glm::radians(60.0f);
float distance = 0.42f;
glm::mat3 rotation = glm::mat3(1.0f);
glm::vec3 position = glm::vec3(0.0f);
glm::mat4 getProjectionMatrix(float aspect){
return glm::perspective( /* fovy = */ glm::radians(60.0f), aspect, 0.002f, 12.000f);
}
glm::mat4 getViewMatrix(){
auto translation = glm::translate(position);
return glm::lookAt(glm::vec3(0, 0, distance), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0)) * glm::mat4(rotation) * translation;
}
};
struct DragController {
enum class Action {
NONE,
TRANSLATE,
ROTATE_TURNTABLE,
ROTATE_TRACKBALL
};
Transform * transform = nullptr;
int activeButton = -1;
Action activeAction = Action::NONE;
double dragX, dragY;
double wrapX, wrapY;
double virtualX, virtualY;
glm::vec3 dragTarget;
void reset(){
// Reset transform.
*transform = Transform{};
// Cancel active action, if any.
activeButton = -1;
activeAction = Action::NONE;
}
bool unprojectMousePositionToXYPlane(GLFWwindow * window, double x, double y, glm::vec3 & result){
int iwidth = 0, iheight = 0;
glfwGetWindowSize(window, &iwidth, &iheight);
double width = iwidth;
double height = iheight;
glm::mat4 projection = transform->getProjectionMatrix(float(width / height));
glm::mat4 view = transform->getViewMatrix();
double relX = x / width * 2.0 - 1.0;
double relY = y / height * 2.0 - 1.0;
glm::vec4 clipPos = glm::vec4(float(relX), -float(relY), 0.5f, 1.0f);
glm::vec4 worldPos = glm::inverse(projection * view) * clipPos;
worldPos *= 1.0f / worldPos.w;
glm::vec3 pos = glm::vec3(glm::column(glm::inverse(view), 3));
glm::vec3 dir = glm::normalize(glm::vec3(worldPos) - pos);
float t = -pos.z / dir.z;
result = pos + t * dir;
return t > 0.0f;
}
void onMouseButton(GLFWwindow * window, int button, int action, int mods){
if(action == GLFW_PRESS && activeButton == -1){
activeButton = button;
if(mods & GLFW_MOD_CONTROL){
activeAction = Action::TRANSLATE;
}else{
if(activeButton == GLFW_MOUSE_BUTTON_2){
activeAction = Action::TRANSLATE;
}else if(activeButton == GLFW_MOUSE_BUTTON_3){
activeAction = Action::ROTATE_TURNTABLE;
}else{
activeAction = Action::ROTATE_TRACKBALL;
}
}
glfwGetCursorPos(window, &dragX, &dragY);
wrapX = std::numeric_limits <double>::quiet_NaN();
wrapY = std::numeric_limits <double>::quiet_NaN();
virtualX = dragX;
virtualY = dragY;
glm::vec3 target;
bool ok = unprojectMousePositionToXYPlane(window, dragX, dragY, target);
dragTarget = ok ? target : glm::vec3();
}else if(action == GLFW_RELEASE && activeButton == button){
activeButton = -1;
activeAction = Action::NONE;
dragX = 0.0;
dragY = 0.0;
wrapX = std::numeric_limits <double>::quiet_NaN();
wrapY = std::numeric_limits <double>::quiet_NaN();
virtualX = 0.0;
virtualY = 0.0;
dragTarget = glm::vec3();
}
}
void onCursorPos(GLFWwindow * window, double x, double y){
if(activeAction == Action::NONE){
return;
}
int iwidth = 0, iheight = 0;
glfwGetWindowSize(window, &iwidth, &iheight);
double width = iwidth;
double height = iheight;
double deltaX = x - dragX;
double deltaY = y - dragY;
if(!std::isnan(wrapX) && !std::isnan(wrapY)){
double wrapDeltaX = x - wrapX;
double wrapDeltaY = y - wrapY;
if(wrapDeltaX * wrapDeltaX + wrapDeltaY * wrapDeltaY < deltaX * deltaX + deltaY * deltaY){
deltaX = wrapDeltaX;
deltaY = wrapDeltaY;
wrapX = std::numeric_limits <double>::quiet_NaN();
wrapY = std::numeric_limits <double>::quiet_NaN();
}
}
dragX = x;
dragY = y;
double targetX = x;
double targetY = y;
bool changed = false;
if(targetX < 0){
targetX += width - 1;
changed = true;
}else if(targetX >= width){
targetX -= width - 1;
changed = true;
}
if(targetY < 0){
targetY += height - 1;
changed = true;
}else if(targetY >= height){
targetY -= height - 1;
changed = true;
}
if(changed){
glfwSetCursorPos(window, targetX, targetY);
wrapX = targetX;
wrapY = targetY;
}
if(activeAction == Action::TRANSLATE){
virtualX += deltaX;
virtualY += deltaY;
glm::vec3 target;
bool ok = unprojectMousePositionToXYPlane(window, virtualX, virtualY, target);
if(ok){
float x = transform->position.x;
float y = transform->position.y;
glm::vec3 delta = target - dragTarget;
transform->position.x = glm::clamp(x + delta.x, -4.0f, 4.0f);
transform->position.y = glm::clamp(y + delta.y, -4.0f, 4.0f);
}
}else if(activeAction == Action::ROTATE_TURNTABLE){
double size = glm::min(width, height);
glm::mat3 rx = glm::rotate(float(deltaX / size * glm::pi <double>()), glm::vec3(0, 0, 1));
glm::mat3 ry = glm::rotate(float(deltaY / size * glm::pi <double>()), glm::vec3(1, 0, 0));
transform->rotation = ry * transform->rotation * rx;
}else if(activeAction == Action::ROTATE_TRACKBALL){
double size = glm::min(width, height);
glm::mat3 rx = glm::rotate(float(deltaX / size * glm::pi <double>()), glm::vec3(0, 1, 0));
glm::mat3 ry = glm::rotate(float(deltaY / size * glm::pi <double>()), glm::vec3(1, 0, 0));
transform->rotation = ry * rx * transform->rotation;
}
}
void onScroll(GLFWwindow * window, double xOffset, double yOffset){
float factor = glm::clamp(1.0 - float(yOffset) / 10.0, 0.1, 1.9);
transform->distance = glm::clamp(transform->distance * factor, 0.010f, 10.000f);
}
};
namespace {
FT_Library library;
Transform transform;
DragController dragController;
// Empty VAO used when the vertex shader has no input and only uses gl_VertexID,
// because OpenGL still requires a non-zero VAO to be bound for the draw call.
GLuint emptyVAO;
std::unique_ptr <ShaderCatalog> shaderCatalog;
std::shared_ptr <ShaderCatalog::Entry> backgroundShader;
std::shared_ptr <ShaderCatalog::Entry> fontShader;
std::unique_ptr <Font> mainFont;
std::unique_ptr <Font> helpFont;
std::vector <std::unique_ptr <Font> > otherFonts;
std::vector <float> otherWghts;
std::vector <float> otherSteps;
static const int N_OTHER_FONTS = 5;
constexpr float helpFontBaseSize = 20.0f;
int antiAliasingWindowSize = 1;
bool enableSuperSamplingAntiAliasing = true;
bool enableControlPointsVisualization = false;
bool showHelp = true;
Font::BoundingBox bb;
std::string mainTexta =
R"DONE(AVM AAIn the center of Fedora, that gray stone metropolis, stands a metal building
[from Invisible Cities by Italo Calvino])DONE";
std::string mainText =
R"DONE(In the center of Fedora, that gray stone metropolis, stands a metal building
with a crystal globe in every room. Looking into each globe, you see a blue
city, the model of a different Fedora. These are the forms the city could have
taken if, for one reason or another, it had not become what we see today. In
every age someone, looking at Fedora as it was, imagined a way of making it the
ideal city, but while he constructed his miniature model, Fedora was already no
longer the same as before, and what had been until yesterday a possible future
became only a toy in a glass globe.
The building with the globes is now Fedora's museum: every inhabitant visits it,
chooses the city that corresponds to his desires, contemplates it, imagining his
reflection in the medusa pond that would have collected the waters of the canal
(if it had not been dried up), the view from the high canopied box along the
avenue reserved for elephants (now banished from the city), the fun of sliding
down the spiral, twisting minaret (which never found a pedestal from which to
rise).
On the map of your empire, O Great Khan, there must be room both for the big,
stone Fedora and the little Fedoras in glass globes. Not because they are all
equally real, but because they are only assumptions. The one contains what is
accepted as necessary when it is not yet so; the others, what is imagined as
possible and, a moment later, is possible no longer.
On the map of your empire, O Great Khan, there must be room both for the big,
stone Fedora and the little Fedoras in glass globes. Not because they are all
equally real, but because they are only assumptions. The one contains what is
accepted as necessary when it is not yet so; the others, what is imagined as
possible and, a moment later, is possible no longer.
On the map of your empire, O Great Khan, there must be room both for the big,
stone Fedora and the little Fedoras in glass globes. Not because they are all
equally real, but because they are only assumptions. The one contains what is
accepted as necessary when it is not yet so; the others, what is imagined as
possible and, a moment later, is possible no longer.
On the map of your empire, O Great Khan, there must be room both for the big,
stone Fedora and the little Fedoras in glass globes. Not because they are all
equally real, but because they are only assumptions. The one contains what is
accepted as necessary when it is not yet so; the others, what is imagined as
possible and, a moment later, is possible no longer.
The building with the globes is now Fedora's museum: every inhabitant visits it,
chooses the city that corresponds to his desires, contemplates it, imagining his
reflection in the medusa pond that would have collected the waters of the canal
(if it had not been dried up), the view from the high canopied box along the
avenue reserved for elephants (now banished from the city), the fun of sliding
down the spiral, twisting minaret (which never found a pedestal from which to
rise).
The building with the globes is now Fedora's museum: every inhabitant visits it,
chooses the city that corresponds to his desires, contemplates it, imagining his
reflection in the medusa pond that would have collected the waters of the canal
(if it had not been dried up), the view from the high canopied box along the
avenue reserved for elephants (now banished from the city), the fun of sliding
down the spiral, twisting minaret (which never found a pedestal from which to
rise).
On the map of your empire, O Great Khan, there must be room both for the big,
stone Fedora and the little Fedoras in glass globes. Not because they are all
equally real, but because they are only assumptions. The one contains what is
accepted as necessary when it is not yet so; the others, what is imagined as
possible and, a moment later, is possible no longer.
On the map of your empire, O Great Khan, there must be room both for the big,
stone Fedora and the little Fedoras in glass globes. Not because they are all
equally real, but because they are only assumptions. The one contains what is
accepted as necessary when it is not yet so; the others, what is imagined as
possible and, a moment later, is possible no longer.
On the map of your empire, O Great Khan, there must be room both for the big,
stone Fedora and the little Fedoras in glass globes. Not because they are all
equally real, but because they are only assumptions. The one contains what is
accepted as necessary when it is not yet so; the others, what is imagined as
possible and, a moment later, is possible no longer.
On the map of your empire, O Great Khan, there must be room both for the big,
stone Fedora and the little Fedoras in glass globes. Not because they are all
equally real, but because they are only assumptions. The one contains what is
accepted as necessary when it is not yet so; the others, what is imagined as
possible and, a moment later, is possible no longer.
[from Invisible Cities by Italo Calvino])DONE";
}
void updateWght(float & value, float & step){
value += step;
if(value >= 700.0){
if(step > 0){
step *= -1;
}
value += step;
}
if(value <= 100.0){
if(step < 0){
step *= -1;
}
value += step;
}
}
int currentIndexx = -1;
static std::unique_ptr <Font> loadFont(const std::string & filename, float worldSize = 1.0f, bool hinting = false){
std::string error;
FT_Face face = Font::loadFace(library, filename, error);
float & wghtValue = currentIndexx < 0 ? wght : otherWghts[currentIndexx];
bool success = Font::setFontVariationAxis(library, face, "Weight", wghtValue);
//if(success){
//std::cout << "lol, success?" << wght << std::endl;
//}else{
//std::cout << "godverdomme" << wght << std::endl;
//}
if(error != ""){
std::cerr << "[font] failed to load " << filename << ": " << error << std::endl;
return std::unique_ptr <Font>{};
}
return std::make_unique <Font>(face, worldSize, hinting);
}
static void tryUpdateMainFont(const std::string & filename){
{
currentIndexx = -1;
auto font = loadFont(filename, 0.05f);
if(!font){
return;
}
font->dilation = 0.1f;
font->prepareGlyphsForText(mainText);
mainFont = std::move(font);
bb = mainFont->measure(0, 0, mainText);
}
updateWght(wght, wghtStep);
for(int i = 0; i < N_OTHER_FONTS; ++i){
if(otherWghts.size() == i){
int w = i * 213456;
while(w > 700){
w -= 700;
}
otherWghts.push_back(float(w));
otherSteps.push_back(float(wghtStep));
}
currentIndexx = i;
auto font = loadFont(filename, 0.05f);
if(!font){
return;
}
updateWght(otherWghts[i], otherSteps[i]);
//std::cout << "updateWght(" << otherWghts[i] << "," << otherSteps[i] << ");" << std::endl;
font->dilation = 0.1f;
font->prepareGlyphsForText(mainText);
if(otherFonts.size() == i){
otherFonts.push_back(std::move(font));
}else{
otherFonts[i] = std::move(font);
}
}
}
static void mouseButtonCallback(GLFWwindow * window, int button, int action, int mods){
dragController.onMouseButton(window, button, action, mods);
}
static void cursorPosCallback(GLFWwindow * window, double x, double y){
dragController.onCursorPos(window, x, y);
}
static void scrollCallback(GLFWwindow * window, double xOffset, double yOffset){
dragController.onScroll(window, xOffset, yOffset);
}
static void keyCallback(GLFWwindow * window, int key, int scancode, int action, int mods){
if(action != GLFW_PRESS){
return;
}
switch(key){
case GLFW_KEY_R:
dragController.reset();
break;
case GLFW_KEY_C:
enableControlPointsVisualization = !enableControlPointsVisualization;
break;
case GLFW_KEY_A:
enableSuperSamplingAntiAliasing = !enableSuperSamplingAntiAliasing;
break;
case GLFW_KEY_0:
antiAliasingWindowSize = 0;
break;
case GLFW_KEY_1:
antiAliasingWindowSize = 1;
break;
case GLFW_KEY_2:
antiAliasingWindowSize = 20;
break;
case GLFW_KEY_3:
antiAliasingWindowSize = 40;
break;
case GLFW_KEY_S:
antiAliasingWindowSize = 1;
enableSuperSamplingAntiAliasing = true;
break;
case GLFW_KEY_H:
showHelp = !showHelp;
break;
}
}
static void dropCallback(GLFWwindow * window, int pathCount, const char * paths[]){
if(pathCount == 0){
return;
}
currentFontPath = paths[0];
tryUpdateMainFont(paths[0]);
}
int main(int argc, char * argv[]){
if(!glfwInit()){
std::cerr << "ERROR: failed to initialize GLFW" << std::endl;
return 1;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
glfwWindowHint(GLFW_SRGB_CAPABLE, GLFW_TRUE);
GLFWwindow * window = glfwCreateWindow(1600, 900, "GPU Font Rendering Demo", nullptr, nullptr);
if(!window){
std::cerr << "ERROR: failed to create GLFW window" << std::endl;
glfwTerminate();
return 1;
}
glfwMakeContextCurrent(window);
if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){
std::cerr << "ERROR: failed to initialize OpenGL context" << std::endl;
glfwTerminate();
return 1;
}
{
FT_Error error = FT_Init_FreeType(&library);
if(error){
std::cerr << "ERROR: failed to initialize FreeType" << std::endl;
glfwTerminate();
return 1;
}
}
dragController.transform = &transform;
glfwSetMouseButtonCallback(window, mouseButtonCallback);
glfwSetCursorPosCallback(window, cursorPosCallback);
glfwSetScrollCallback(window, scrollCallback);
glfwSetKeyCallback(window, keyCallback);
glfwSetDropCallback(window, dropCallback);
glGenVertexArrays(1, &emptyVAO);
shaderCatalog = std::make_unique <ShaderCatalog>("shaders");
backgroundShader = shaderCatalog->get("background");
fontShader = shaderCatalog->get("font");
currentFontPath = "fonts/SourceSerifPro-Regular.otf";
tryUpdateMainFont(currentFontPath);
{
float xscale, yscale;
glfwGetWindowContentScale(window, &xscale, &yscale);
float worldSize = std::ceil(helpFontBaseSize * yscale);
helpFont = loadFont("fonts/SourceSansPro-Semibold.otf", worldSize, true);
}
while(!glfwWindowShouldClose(window)){
tryUpdateMainFont(currentFontPath);
shaderCatalog->update();
glfwPollEvents();
int width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
GLuint location;
glm::mat4 projection = transform.getProjectionMatrix((float)width / height);
glm::mat4 view = transform.getViewMatrix();
glm::mat4 model = glm::mat4(1.0f);
{ // Draw background.
GLuint program = backgroundShader->program;
glUseProgram(program);
glBindVertexArray(emptyVAO);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
glUseProgram(0);
}
// Uses premultiplied-alpha.
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
for(int i = N_OTHER_FONTS - 1; i >= 0; i--){
if(otherFonts.size() > i){
//std::cout << "drawing " << i << " with " << otherWghts[i] << std::endl;
auto & otherFont = otherFonts[i];
GLuint program = fontShader->program;
glUseProgram(program);
otherFont->program = program;
otherFont->drawSetup();
location = glGetUniformLocation(program, "projection");
glUniformMatrix4fv(location, 1, false, glm::value_ptr(projection));
location = glGetUniformLocation(program, "view");
glUniformMatrix4fv(location, 1, false, glm::value_ptr(view));
location = glGetUniformLocation(program, "model");
glUniformMatrix4fv(location, 1, false, glm::value_ptr(model));
location = glGetUniformLocation(program, "color");
float r = float(i) / N_OTHER_FONTS;
float g = sin(42.143 * float(i) + otherWghts[i] * 0.0001) * 0.5 + 0.5;
float b = cos(3.143 * float(i) + otherWghts[i] * 0.0005) * 0.1 + 0.1;
glUniform4f(location, r, 1.0f - r, 1.0f - r, 1.0f);
float z = (i + 1) * -0.1;
location = glGetUniformLocation(program, "z");
glUniform1f(location, z);
location = glGetUniformLocation(program, "antiAliasingWindowSize");
glUniform1f(location, (float)antiAliasingWindowSize);
location = glGetUniformLocation(program, "enableSuperSamplingAntiAliasing");
glUniform1i(location, enableSuperSamplingAntiAliasing);
location = glGetUniformLocation(program, "enableControlPointsVisualization");
glUniform1i(location, enableControlPointsVisualization);
float cx = 0.5f * (bb.minX + bb.maxX);
float cy = 0.5f * (bb.minY + bb.maxY);
otherFont->draw(-cx, -cy, 0, mainText);
glUseProgram(0);
}
}
if(mainFont){
GLuint program = fontShader->program;
glUseProgram(program);
mainFont->program = program;
mainFont->drawSetup();
location = glGetUniformLocation(program, "projection");
glUniformMatrix4fv(location, 1, false, glm::value_ptr(projection));
location = glGetUniformLocation(program, "view");
glUniformMatrix4fv(location, 1, false, glm::value_ptr(view));
location = glGetUniformLocation(program, "model");
glUniformMatrix4fv(location, 1, false, glm::value_ptr(model));
float z = 0;
location = glGetUniformLocation(program, "z");
glUniform1f(location, z);
location = glGetUniformLocation(program, "color");
glUniform4f(location, 1.0f, 1.0f, 1.0f, 1.0f);
location = glGetUniformLocation(program, "antiAliasingWindowSize");
glUniform1f(location, (float)antiAliasingWindowSize);
location = glGetUniformLocation(program, "enableSuperSamplingAntiAliasing");
glUniform1i(location, enableSuperSamplingAntiAliasing);
location = glGetUniformLocation(program, "enableControlPointsVisualization");
glUniform1i(location, enableControlPointsVisualization);
float cx = 0.5f * (bb.minX + bb.maxX);
float cy = 0.5f * (bb.minY + bb.maxY);
mainFont->draw(-cx, -cy, 0, mainText);
glUseProgram(0);
}
if(helpFont && showHelp){
GLuint program = fontShader->program;
glUseProgram(program);
helpFont->program = program;
helpFont->drawSetup();
glm::mat4 projection = glm::ortho(0.0f, (float)width, 0.0f, (float)height, -1.0f, 1.0f);
glm::mat4 view = glm::mat4(1.0f);
glm::mat4 model = glm::mat4(1.0f);
location = glGetUniformLocation(program, "projection");
glUniformMatrix4fv(location, 1, false, glm::value_ptr(projection));
location = glGetUniformLocation(program, "view");
glUniformMatrix4fv(location, 1, false, glm::value_ptr(view));
location = glGetUniformLocation(program, "model");
glUniformMatrix4fv(location, 1, false, glm::value_ptr(model));
location = glGetUniformLocation(program, "color");
float r = 200, g = 35, b = 220, a = 0.8;
glUniform4f(location, r * a / 255.0f, g * a / 255.0f, b * a / 255.0f, a);
location = glGetUniformLocation(program, "antiAliasingWindowSize");
glUniform1f(location, 1.0f);
location = glGetUniformLocation(program, "enableSuperSamplingAntiAliasing");
glUniform1i(location, true);
location = glGetUniformLocation(program, "enableControlPointsVisualization");
glUniform1i(location, false);
std::stringstream stream;
stream << "Drag and drop a .ttf or .otf file to change the font\n";
stream << "\n";
stream << "right drag (or CTRL drag) - move\n";
stream << "left drag - trackball rotate\n";
stream << "middle drag - turntable rotate\n";
stream << "scroll wheel - zoom\n";
stream << "\n";
stream << "0, 1, 2, 3 - change anti-aliasing window size: " << antiAliasingWindowSize << " pixel" << ((antiAliasingWindowSize != 1) ? "s" : "") << "\n";
stream << glfwGetKeyName(GLFW_KEY_A, 0) << " - " << (enableSuperSamplingAntiAliasing ? "disable" : "enable") << " 2D anti-aliasing\n";
stream << "(using another ray along the y-axis)\n";
stream << glfwGetKeyName(GLFW_KEY_S, 0) << " - reset anti-aliasing settings\n";
stream << glfwGetKeyName(GLFW_KEY_C, 0) << " - " << (enableControlPointsVisualization ? "disable" : "enable") << " control points\n";
stream << glfwGetKeyName(GLFW_KEY_R, 0) << " - reset view\n";
stream << glfwGetKeyName(GLFW_KEY_H, 0) << " - toggle help\n";
std::string helpText = stream.str();
helpFont->prepareGlyphsForText(helpText);
float xscale, yscale;
glfwGetWindowContentScale(window, &xscale, &yscale);
helpFont->setWorldSize(std::ceil(helpFontBaseSize * yscale));
auto bb = helpFont->measure(0, 0, helpText);
helpFont->draw(10 - bb.minX, height - 10 - bb.maxY, 0, helpText);
glUseProgram(0);
}
glDisable(GL_BLEND);
glfwSwapBuffers(window);
}
// Clean up OpenGL resources before termination.
mainFont = nullptr;
helpFont = nullptr;
glfwTerminate();
return 0;
}

View file

@ -1,191 +0,0 @@
#include "shader_catalog.hpp"
#include <chrono>
#include <fstream>
#include <iostream>
#include <mutex>
#include <unordered_map>
#include <vector>
#include <defer.hpp>
//#include <glad/glad.h>
// UpdateList keeps track of which entries need to be updated.
// The actual update is slightly delayed to avoid reading a partially written file.
// It is threadsafe to allow safe communication with the asynchronous file watcher callback.
class UpdateList {
std::mutex mutex;
std::unordered_map <std::string, std::chrono::steady_clock::time_point> updates;
public:
void requestUpdate(const std::string & name){
using namespace std::chrono_literals;
std::lock_guard <std::mutex> guard(mutex);
updates[name] = std::chrono::steady_clock::now() + 50ms;
}
std::vector <std::string> collectDueUpdates(){
std::lock_guard <std::mutex> guard(mutex);
std::vector <std::string> result;
auto now = std::chrono::steady_clock::now();
for(auto it = updates.begin(); it != updates.end();){
if(it->second < now){
result.push_back(it->first);
it = updates.erase(it);
}else{
++it;
}
}
return result;
}
};
class ShaderCatalog::Impl {
private:
std::string dir;
std::unordered_map <std::string, std::shared_ptr <Entry> > entries;
UpdateList list;
public:
Impl(const std::string & dir) : dir(dir){
}
private:
std::string readFile(const std::string & filename, std::string & error){
std::ifstream stream(filename, std::ios::binary);
if(!stream){
error = "failed to open: " + filename;
return "";
}
stream.seekg(0, std::istream::end);
size_t size = stream.tellg();
stream.seekg(0, std::istream::beg);
std::string result = std::string(size, 0);
stream.read(&result[0], size);
if(!stream){
error = "failed to read: " + filename;
return "";
}
return result;
}
GLuint compile(const std::string & name, std::string & error){
std::string vertexData = readFile(dir + "/" + name + ".vert", error);
if(error != ""){
return 0;
}
std::string fragmentData = readFile(dir + "/" + name + ".frag", error);
if(error != ""){
return 0;
}
GLint success = 0;
const char * vertexSource = vertexData.c_str();
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
defer {glDeleteShader(vertexShader);
};
glShaderSource(vertexShader, 1, &vertexSource, nullptr);
glCompileShader(vertexShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if(!success){
char log[1024];
GLsizei length = 0;
glGetShaderInfoLog(vertexShader, sizeof(log), &length, log);
error = "failed to compile vertex shader " + name + ":\n\n" + log;
return 0;
}
const char * fragmentSource = fragmentData.c_str();
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
defer {glDeleteShader(fragmentShader);
};
glShaderSource(fragmentShader, 1, &fragmentSource, nullptr);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if(!success){
char log[1024];
GLsizei length = 0;
glGetShaderInfoLog(fragmentShader, sizeof(log), &length, log);
error = "failed to compile fragment shader " + name + ":\n\n" + log;
return 0;
}
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
glGetProgramiv(program, GL_LINK_STATUS, &success);
if(!success){
char log[1024];
GLsizei length = 0;
glGetProgramInfoLog(program, sizeof(log), &length, log);
glDeleteProgram(program);
error = "failed to compile program " + name + ":\n\n" + log;
return 0;
}
return program;
}
public:
std::shared_ptr <Entry> get(const std::string & name){
auto it = entries.find(name);
if(it != entries.end()){
return it->second;
}
std::string error;
GLuint program = compile(name, error);
if(error != ""){
std::cerr << "[shader] " << error << std::endl;
}
auto entry = std::make_shared <Entry>(program);
entries[name] = entry;
return entry;
}
void update(){
std::vector <std::string> updates = list.collectDueUpdates();
for(const std::string & name : updates){
auto it = entries.find(name);
if(it == entries.end()){
continue;
}
std::string error;
GLuint program = compile(name, error);
if(error != ""){
std::cerr << "[shader] " << error << std::endl;
}else{
std::cerr << "[shader] reloaded " << name << std::endl;
glDeleteProgram(it->second->program);
it->second->program = program;
}
}
}
};
ShaderCatalog::ShaderCatalog(const std::string & dir) : impl(std::make_unique <Impl>(dir)){
}
ShaderCatalog::~ShaderCatalog(){
}
std::shared_ptr <ShaderCatalog::Entry> ShaderCatalog::get(const std::string & name){
return impl->get(name);
}
void ShaderCatalog::update(){
impl->update();
}

View file

@ -1,32 +0,0 @@
#pragma once
#include <string>
#include <memory>
#include "ofMain.h"
// A shader catalog loads and compiles shaders from a directory. Vertex and
// fragment shaders are matched based on their filename (e.g. example.vert and
// example.frag are loaded and linked together to form the "example" program).
// Whenever a shader file changes on disk, the corresponding program is
// recompiled and relinked.
class ShaderCatalog {
public:
struct Entry {
unsigned int program;
Entry() : program(0){
}
Entry(unsigned int program) : program(program){
}
};
ShaderCatalog(const std::string & dir);
~ShaderCatalog();
std::shared_ptr <Entry> get(const std::string & name);
void update();
private:
class Impl;
std::unique_ptr <Impl> impl;
};

View file

@ -148,8 +148,8 @@ void ofApp::update(){
float animationSpeed = ofMap(sin(ofGetElapsedTimef() * 0.01), -1, 1, 0.0, 2);
float threshold = 5.0;
for(int i = 0; i < fontVariationAxes.size(); i++){
Font::FontVariationAxis & axis = fontVariationAxes[i];
Font::FontVariationAxisParameters & axisParams = fontVariationAxesParameters[i];
ofxGPUFont::Font::FontVariationAxis & axis = fontVariationAxes[i];
ofxGPUFont::Font::FontVariationAxisParameters & axisParams = fontVariationAxesParameters[i];
double newValue = ofMap(sin(ofGetElapsedTimef() * animationSpeed),
-1, 1,
axisParams.minValue, axisParams.maxValue);

View file

@ -2,39 +2,7 @@
#include "ofMain.h"
#include "ofxGPUFont.h"
#include "gpufont/font.hpp"
#include "gpufont/shader_catalog.hpp"
static std::unique_ptr <Font> loadFont(FT_Library & library, const std::string & filename, float worldSize = 1.0f, bool hinting = false){
std::string error;
FT_Face face = Font::loadFace(library, filename, error);
if(error != ""){
std::cerr << "[font] failed to load " << filename << ": " << error << std::endl;
return std::unique_ptr <Font>{};
}
return std::make_unique <Font>(face, worldSize, hinting);
}
static void tryUpdateMainFont(FT_Library & library,
const std::string & filename,
const string & mainText,
unique_ptr <Font> & mainFont,
Font::BoundingBox & bb){
{
auto font = loadFont(library, filename, 0.05f);
if(!font){
return;
}
font->dilation = 0.1f;
font->prepareGlyphsForText(mainText);
mainFont = std::move(font);
bb = mainFont->measure(0, 0, mainText);
}
}
class ofApp : public ofBaseApp {
public:
@ -71,7 +39,7 @@ class ofApp : public ofBaseApp {
void exit();
unique_ptr <Font> font;
unique_ptr <ofxGPUFont::Font> font;
FT_Library library;
// Empty VAO used when the vertex shader has no input and only uses gl_VertexID,
@ -82,7 +50,7 @@ class ofApp : public ofBaseApp {
//std::shared_ptr <ShaderCatalog::Entry> backgroundShader;
std::shared_ptr <ShaderCatalog::Entry> fontShader;
Font::BoundingBox bb;
ofxGPUFont::Font::BoundingBox bb;
Transform transform;
@ -152,7 +120,7 @@ class ofApp : public ofBaseApp {
"data/celines-fonts/Version-3-var.ttf"
};
std::vector <Font::FontVariationAxisParameters> fontVariationAxesParameters;
std::vector <Font::FontVariationAxis> fontVariationAxes;
std::vector <ofxGPUFont::Font::FontVariationAxisParameters> fontVariationAxesParameters;
std::vector <ofxGPUFont::Font::FontVariationAxis> fontVariationAxes;
};