initital commit

This commit is contained in:
jrkb 2023-03-31 16:45:23 +02:00
commit 6661f7eb0f
19 changed files with 2400 additions and 0 deletions

56
.gitignore vendored Normal file
View file

@ -0,0 +1,56 @@
# don't push project files
*.cbp
*.sln
*.vcx*
*.workspace*
config.make
Makefile
*.xcodeproj
*.plist
*.xcconfig
*.suo
*.depend
*.layout
*.mode*v3
*.pbxuser
*.app*
*.DS_*
*.xcworkspacedata
xcuserdata/
project.xcworkspace
*.opensdf
*.sdf
*.suo
*.ipch
.svn/
obj/
bin/
build/
!data/
.cache
.ccls-cache
# don't push backup files/directories
*.bk
# vim
*.swp
*.swo
# qtcreator
*.qbs
*.qbs.user
build-*
compile_commands.json
compile_commands*
.vroot
.ycm_extra_conf.py
*.nohup
# prevent accidental leaks
celines-fonts

65
addon_config.mk Normal file
View file

@ -0,0 +1,65 @@
# All variables and this file are optional, if they are not present the PG and the
# makefiles will try to parse the correct values from the file system.
#
# Variables that specify exclusions can use % as a wildcard to specify that anything in
# that position will match. A partial path can also be specified to, for example, exclude
# a whole folder from the parsed paths from the file system
#
# Variables can be specified using = or +=
# = will clear the contents of that variable both specified from the file or the ones parsed
# from the file system
# += will add the values to the previous ones in the file or the ones parsed from the file
# system
#
# The PG can be used to detect errors in this file, just create a new project with this addon
# and the PG will write to the console the kind of error and in which line it is
meta:
ADDON_NAME = ofxGPUFont
ADDON_DESCRIPTION = draw font shapes with gpu
ADDON_AUTHOR = Jakob Schloetter
ADDON_TAGS = "typography font"
ADDON_URL = https://gitlab.com/pointerstudio/utils/ofxGPUFont
common:
# dependencies with other addons, a list of them separated by spaces
# or use += in several lines
# ADDON_DEPENDENCIES =
# include search paths, this will be usually parsed from the file system
# but if the addon or addon libraries need special search paths they can be
# specified here separated by spaces or one per line using +=
# ADDON_INCLUDES =
# any special flag that should be passed to the compiler when using this
# addon
# ADDON_CFLAGS =
# any special flag that should be passed to the linker when using this
# addon, also used for system libraries with -lname
# ADDON_LDFLAGS =
# linux only, any library that should be included in the project using
# pkg-config
# ADDON_PKG_CONFIG_LIBRARIES =
# osx/iOS only, any framework that should be included in the project
# ADDON_FRAMEWORKS =
# source files, these will be usually parsed from the file system looking
# in the src folders in libs and the root of the addon. if your addon needs
# to include files in different places or a different set of files per platform
# they can be specified here
# ADDON_SOURCES =
# some addons need resources to be copied to the bin/data folder of the project
# specify here any files that need to be copied, you can use wildcards like * and ?
# ADDON_DATA =
ADDON_DATA = data/ofxGPUFont
# when parsing the file system looking for libraries exclude this for all or
# a specific platform
# ADDON_LIBS_EXCLUDE =
emscripten:
linux64:

View file

@ -0,0 +1,12 @@
#version 330 core
in vec2 position;
out vec3 color;
void main() {
float t = (position.y + 1.0) / 2.0;
vec3 bottom = vec3(75.0, 55.0, 201.0) / 255.0;
vec3 top = vec3(0.0, 12.0, 0.0) / 255.0;
color = mix(bottom, top, t);
}

View file

@ -0,0 +1,15 @@
#version 330 core
const vec2 vertices[4] = vec2[4](
vec2(-1.0, -1.0),
vec2( 1.0, -1.0),
vec2(-1.0, 1.0),
vec2( 1.0, 1.0)
);
out vec2 position;
void main() {
position = vertices[gl_VertexID];
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
}

View file

@ -0,0 +1,156 @@
#version 330 core
// Based on: http://wdobbie.com/post/gpu-text-rendering-with-vector-textures/
struct Glyph {
int start, count;
};
struct Curve {
vec2 p0, p1, p2;
};
uniform isamplerBuffer glyphs;
uniform samplerBuffer curves;
uniform vec4 color;
// Controls for debugging and exploring:
// Size of the window (in pixels) used for 1-dimensional anti-aliasing along each rays.
// 0 - no anti-aliasing
// 1 - normal anti-aliasing
// >=2 - exaggerated effect
uniform float antiAliasingWindowSize = 1.0;
// Enable a second ray along the y-axis to achieve 2-dimensional anti-aliasing.
uniform bool enableSuperSamplingAntiAliasing = true;
// Draw control points for debugging (green - on curve, magenta - off curve).
uniform bool enableControlPointsVisualization = false;
in vec2 uv;
flat in int bufferIndex;
out vec4 result;
Glyph loadGlyph(int index) {
Glyph result;
ivec2 data = texelFetch(glyphs, index).xy;
result.start = data.x;
result.count = data.y;
return result;
}
Curve loadCurve(int index) {
Curve result;
result.p0 = texelFetch(curves, 3*index+0).xy;
result.p1 = texelFetch(curves, 3*index+1).xy;
result.p2 = texelFetch(curves, 3*index+2).xy;
return result;
}
float computeCoverage(float inverseDiameter, vec2 p0, vec2 p1, vec2 p2) {
if (p0.y > 0 && p1.y > 0 && p2.y > 0) return 0.0;
if (p0.y < 0 && p1.y < 0 && p2.y < 0) return 0.0;
// Note: Simplified from abc formula by extracting a factor of (-2) from b.
vec2 a = p0 - 2*p1 + p2;
vec2 b = p0 - p1;
vec2 c = p0;
float t0, t1;
if (abs(a.y) >= 1e-5) {
// Quadratic segment, solve abc formula to find roots.
float radicand = b.y*b.y - a.y*c.y;
if (radicand <= 0) return 0.0;
float s = sqrt(radicand);
t0 = (b.y - s) / a.y;
t1 = (b.y + s) / a.y;
} else {
// Linear segment, avoid division by a.y, which is near zero.
// There is only one root, so we have to decide which variable to
// assign it to based on the direction of the segment, to ensure that
// the ray always exits the shape at t0 and enters at t1. For a
// quadratic segment this works 'automatically', see readme.
float t = p0.y / (p0.y - p2.y);
if (p0.y < p2.y) {
t0 = -1.0;
t1 = t;
} else {
t0 = t;
t1 = -1.0;
}
}
float alpha = 0;
if (t0 >= 0 && t0 < 1) {
float x = (a.x*t0 - 2.0*b.x)*t0 + c.x;
alpha += clamp(x * inverseDiameter + 0.5, 0, 1);
}
if (t1 >= 0 && t1 < 1) {
float x = (a.x*t1 - 2.0*b.x)*t1 + c.x;
alpha -= clamp(x * inverseDiameter + 0.5, 0, 1);
}
return alpha;
}
vec2 rotate(vec2 v) {
return vec2(v.y, -v.x);
}
void main() {
float alpha = 0;
// Inverse of the diameter of a pixel in uv units for anti-aliasing.
vec2 inverseDiameter = 1.0 / (antiAliasingWindowSize * fwidth(uv));
Glyph glyph = loadGlyph(bufferIndex);
for (int i = 0; i < glyph.count; i++) {
Curve curve = loadCurve(glyph.start + i);
vec2 p0 = curve.p0 - uv;
vec2 p1 = curve.p1 - uv;
vec2 p2 = curve.p2 - uv;
alpha += computeCoverage(inverseDiameter.x, p0, p1, p2);
if (enableSuperSamplingAntiAliasing) {
alpha += computeCoverage(inverseDiameter.y, rotate(p0), rotate(p1), rotate(p2));
}
}
if (enableSuperSamplingAntiAliasing) {
alpha *= 0.5;
}
alpha = clamp(alpha, 0.0, 1.0);
result = color * alpha;
if (enableControlPointsVisualization) {
// Visualize control points.
vec2 fw = fwidth(uv);
float r = 4.0 * 0.5 * (fw.x + fw.y);
for (int i = 0; i < glyph.count; i++) {
Curve curve = loadCurve(glyph.start + i);
vec2 p0 = curve.p0 - uv;
vec2 p1 = curve.p1 - uv;
vec2 p2 = curve.p2 - uv;
if (dot(p0, p0) < r*r || dot(p2, p2) < r*r) {
result = vec4(0, 1, 0, 1);
return;
}
if (dot(p1, p1) < r*r) {
result = vec4(1, 0, 1, 1);
return;
}
}
}
}

View file

@ -0,0 +1,19 @@
#version 330 core
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
uniform float z;
layout (location = 0) in vec2 vertexPosition;
layout (location = 1) in vec2 vertexUV;
layout (location = 2) in int vertexIndex;
out vec2 uv;
flat out int bufferIndex;
void main() {
gl_Position = projection * view * model * vec4(vertexPosition, z, 1);
uv = vertexUV;
bufferIndex = vertexIndex;
}

1
example/addons.make Normal file
View file

@ -0,0 +1 @@
ofxGPUFont

14
example/clean.sh Executable file
View file

@ -0,0 +1,14 @@
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
PREVIOUS_DIR=$(pwd)
cd $DIR
emmake make clean && make clean && rm -rf obj && rm -rf ../../obj
rm -rf ../../../libs/openFrameworksCompiled/lib/linux64/obj
rm -rf ../../../libs/openFrameworksCompiled/lib/linux64/libopenFrameworks.a
rm -rf ../../../libs/openFrameworksCompiled/lib/emscripten/obj
rm -rf ../../../libs/openFrameworksCompiled/lib/emscripten/libopenFrameworks.bc
cd $PREVIOUS_DIR

9
example/src/defer.hpp Normal file
View file

@ -0,0 +1,9 @@
// 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

@ -0,0 +1,767 @@
// 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 <ft2build.h>
#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)
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:
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 listFontVariationAxes(std::vector<FontVariationAxis> &axes, FreetypeHandle *library, FontHandle *font) {
//if (font->face->face_flags&FT_FACE_FLAG_MULTIPLE_MASTERS) {
//FT_MM_Var *master = NULL;
//if (FT_Get_MM_Var(font->face, &master))
//return false;
//axes.resize(master->num_axis);
//for (FT_UInt i = 0; i < master->num_axis; i++) {
//FontVariationAxis &axis = axes[i];
//axis.name = master->axis[i].name;
//axis.minValue = master->axis[i].minimum;
//axis.maxValue = master->axis[i].maximum;
//axis.defaultValue = master->axis[i].def;
//}
//FT_Done_MM_Var(library->library, master);
//return true;
//}
//return false;
//}
static FT_Face loadFace(FT_Library library, const std::string & filename, std::string & error){
FT_Face face = NULL;
string pwd = ofSystem("pwd");
cout << "pwd: " << pwd << endl;
pwd.erase(std::remove_if(pwd.begin(), pwd.end(), ::isspace), pwd.end());
cout << "fontPAth: " << pwd << "/" << filename << endl;
string fontPath = pwd + "/" + filename;
FT_Error ftError = FT_New_Face(library, fontPath.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){
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);
glGenTextures(1, &glyphTexture);
glGenTextures(1, &curveTexture);
glGenBuffers(1, &glyphBuffer);
glGenBuffers(1, &curveBuffer);
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();
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);
}
~Font(){
glDeleteVertexArrays(1, &vao);
glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &ebo);
glDeleteTextures(1, &glyphTexture);
glDeleteTextures(1, &curveTexture);
glDeleteBuffers(1, &glyphBuffer);
glDeleteBuffers(1, &curveBuffer);
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 changed = false;
for(const char * textIt = text.c_str(); *textIt != '\0';){
uint32_t charcode = decodeCharcode(&textIt);
if(charcode == '\r' || charcode == '\n'){
continue;
}
if(glyphs.count(charcode) != 0){
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:
void uploadBuffers(){
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);
}
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;
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);
}
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;
GLuint glyphTexture, curveTexture;
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

@ -0,0 +1,18 @@
#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

@ -0,0 +1,717 @@
#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

@ -0,0 +1,191 @@
#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

@ -0,0 +1,32 @@
#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;
};

21
example/src/main.cpp Normal file
View file

@ -0,0 +1,21 @@
#include "ofMain.h"
#include "ofApp.h"
//========================================================================
int main(){
#ifdef OF_TARGET_OPENGLES
ofGLESWindowSettings settings;
//settings.setSize(1920, 1080);
settings.glesVersion = 3;
#else
ofGLWindowSettings settings;
settings.setSize(1920, 1080);
settings.setGLVersion(3, 3);
#endif
ofCreateWindow(settings);
// this kicks off the running of my app
// can be OF_WINDOW or OF_FULLSCREEN
// pass in width and height too:
ofRunApp(new ofApp());
}

191
example/src/ofApp.cpp Normal file
View file

@ -0,0 +1,191 @@
#include "ofApp.h"
//--------------------------------------------------------------
void ofApp::setup(){
mainText = R"DONE(Some things are hard to write about. Take soil,
for instance. Soil, Oxford dictionary reads, is the
upper layer of earth in which plants grow, a black or
dark brown material typically consisting of a mixture
of organic remains, clay, and rock particles.
I wonder why I have chosen to write
about soil, as I dont seem to have had many
encounters with it. Perhaps because of that?
Intuiting its importance, but never minding it?
So far it has appeared rather distant. Absent
from my thoughts and words. But now, when
I think of it, I see a charismatic substance.
Soil, according to Merriam-Webster, is
1. firm land: earth;
2. a) the upper layer of earth that may be dug
or plowed and in which plants grow; and
b) the superficial unconsolidated and
usually weathered part of the mantle of
a planet and especially of the earth.
The Soil Science Society of America defines soil
as a mixture of minerals, dead and living
organisms (organic materials), air, and water.
The soil entry in Encyclopaedia Britannica starts
with soil is the biologically active,
porous medium [] serving as a reservoir of
water and nutrients, as a medium for the filtration
and breakdown of injurious wastes, and as
a participant in the cycling of carbon and other
elements through the global ecosystem.
Such nebulous definitions, though, arent
most definitions unsettled? (all definitions are
blasphemy!) like soil is (a mix of) all and nothing,
doing anything and everything, everywhere. How
one defines or translates soil by posing a question
always affects the answer. Soil can mean earth,
ground, dirt, clay, turf, humus, silt, loam, land,
clod, terra, territory, landscape, country, a political
power base, an aspect of divinity, a terrain
to cultivate, or a resource to be exploited This
is going to be difficult. I dont know yet where to
start. I hope to capture something essential.)DONE";
shaderCatalog = std::make_unique <ShaderCatalog>("data/ofxGPUFont/shaders/GL3");
backgroundShader = shaderCatalog->get("background");
fontShader = shaderCatalog->get("font");
{
FT_Error error = FT_Init_FreeType(&library);
if(error){
std::cerr << "ERROR: failed to initialize FreeType" << std::endl;
ofExit();
}
}
currentFontPath = "data/celines-fonts/Version-1-var.ttf";
tryUpdateMainFont(library,
currentFontPath,
mainText,
font,
bb);
transform = Transform();
}
//--------------------------------------------------------------
void ofApp::update(){
}
//--------------------------------------------------------------
void ofApp::draw(){
GLuint location;
int width = ofGetWidth();
int height = ofGetHeight();
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);
if(font){
GLuint program = fontShader->program;
glUseProgram(program);
font->program = program;
font->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);
font->draw(-cx, -cy, 0, mainText);
glUseProgram(0);
}
glDisable(GL_BLEND);
}
//--------------------------------------------------------------
void ofApp::keyPressed(int key){
}
//--------------------------------------------------------------
void ofApp::keyReleased(int key){
}
//--------------------------------------------------------------
void ofApp::mouseMoved(int x, int y){
}
//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button){
}
//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button){
}
//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button){
}
//--------------------------------------------------------------
void ofApp::mouseEntered(int x, int y){
}
//--------------------------------------------------------------
void ofApp::mouseExited(int x, int y){
}
//--------------------------------------------------------------
void ofApp::windowResized(int w, int h){
}
//--------------------------------------------------------------
void ofApp::gotMessage(ofMessage msg){
}
//--------------------------------------------------------------
void ofApp::dragEvent(ofDragInfo dragInfo){
}

97
example/src/ofApp.h Normal file
View file

@ -0,0 +1,97 @@
#pragma once
#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:
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;
}
};
void setup();
void update();
void draw();
void keyPressed(int key);
void keyReleased(int key);
void mouseMoved(int x, int y);
void mouseDragged(int x, int y, int button);
void mousePressed(int x, int y, int button);
void mouseReleased(int x, int y, int button);
void mouseEntered(int x, int y);
void mouseExited(int x, int y);
void windowResized(int w, int h);
void dragEvent(ofDragInfo dragInfo);
void gotMessage(ofMessage msg);
unique_ptr <Font> font;
FT_Library library;
// 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;
Font::BoundingBox bb;
Transform transform;
string currentFontPath;
string mainText;
float helpFontBaseSize = 20.0f;
int antiAliasingWindowSize = 1;
bool enableSuperSamplingAntiAliasing = true;
bool enableControlPointsVisualization = false;
bool showHelp = true;
};

18
src/glm.hpp Normal file
View file

@ -0,0 +1,18 @@
#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>

1
src/ofxGPUFont.h Normal file
View file

@ -0,0 +1 @@
#pragma one