Skip to content

Instantly share code, notes, and snippets.

@2bbb
Created December 13, 2015 07:09
Show Gist options
  • Select an option

  • Save 2bbb/1b158d1941ecd9cff460 to your computer and use it in GitHub Desktop.

Select an option

Save 2bbb/1b158d1941ecd9cff460 to your computer and use it in GitHub Desktop.
ofTrueTypeFont
#include "ofTrueTypeFont.h"
//--------------------------
#include <ft2build.h>
#ifdef TARGET_LINUX
#include <fontconfig/fontconfig.h>
#endif
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_OUTLINE_H
#include FT_TRIGONOMETRY_H
#include <algorithm>
#include <numeric>
#include "ofUtils.h"
#include "ofGraphics.h"
#include "ofAppRunner.h"
#include "utf8.h"
//constexpr ofUnicode::range ofUnicode::Latin;
//constexpr ofUnicode::range ofUnicode::Latin1Supplement;
//constexpr ofUnicode::range ofUnicode::Greek;
//constexpr ofUnicode::range ofUnicode::Cyrillic;
//constexpr ofUnicode::range ofUnicode::Arabic;
//constexpr ofUnicode::range ofUnicode::ArabicSupplement;
//constexpr ofUnicode::range ofUnicode::ArabicExtendedA;
//constexpr ofUnicode::range ofUnicode::Devanagari;
//constexpr ofUnicode::range ofUnicode::HangulJamo;
//constexpr ofUnicode::range ofUnicode::VedicExtensions;
//constexpr ofUnicode::range ofUnicode::LatinExtendedAdditional;
//constexpr ofUnicode::range ofUnicode::GreekExtended;
//constexpr ofUnicode::range ofUnicode::GeneralPunctuation;
//constexpr ofUnicode::range ofUnicode::SuperAndSubScripts;
//constexpr ofUnicode::range ofUnicode::CurrencySymbols;
//constexpr ofUnicode::range ofUnicode::LetterLikeSymbols;
//constexpr ofUnicode::range ofUnicode::NumberForms;
//constexpr ofUnicode::range ofUnicode::Arrows;
//constexpr ofUnicode::range ofUnicode::MathOperators;
//constexpr ofUnicode::range ofUnicode::MiscTechnical;
//constexpr ofUnicode::range ofUnicode::BoxDrawing;
//constexpr ofUnicode::range ofUnicode::BlockElement;
//constexpr ofUnicode::range ofUnicode::GeometricShapes;
//constexpr ofUnicode::range ofUnicode::MiscSymbols;
//constexpr ofUnicode::range ofUnicode::Dingbats;
//constexpr ofUnicode::range ofUnicode::Hiragana;
//constexpr ofUnicode::range ofUnicode::Katakana;
//constexpr ofUnicode::range ofUnicode::HangulCompatJamo;
//constexpr ofUnicode::range ofUnicode::KatakanaPhoneticExtensions;
//constexpr ofUnicode::range ofUnicode::CJKLettersAndMonths;
//constexpr ofUnicode::range ofUnicode::CJKUnified;
//constexpr ofUnicode::range ofUnicode::DevanagariExtended;
//constexpr ofUnicode::range ofUnicode::HangulExtendedA;
//constexpr ofUnicode::range ofUnicode::HangulSyllables;
//constexpr ofUnicode::range ofUnicode::HangulExtendedB;
//constexpr ofUnicode::range ofUnicode::AlphabeticPresentationForms;
//constexpr ofUnicode::range ofUnicode::ArabicPresFormsA;
//constexpr ofUnicode::range ofUnicode::ArabicPresFormsB;
//constexpr ofUnicode::range ofUnicode::KatakanaHalfAndFullwidthForms;
//constexpr ofUnicode::range ofUnicode::KanaSupplement;
//constexpr ofUnicode::range ofUnicode::RumiNumericalSymbols;
//constexpr ofUnicode::range ofUnicode::ArabicMath;
//constexpr ofUnicode::range ofUnicode::MiscSymbolsAndPictographs;
//constexpr ofUnicode::range ofUnicode::Emoticons;
//constexpr ofUnicode::range ofUnicode::TransportAndMap;
//constexpr ofUnicode::range ofUnicode::Space;
const std::initializer_list<ofUnicode::range> ofAlphabet::Emoji {
ofUnicode::Space,
ofUnicode::Hiragana,
ofUnicode::Katakana,
ofUnicode::KatakanaPhoneticExtensions,
ofUnicode::CJKLettersAndMonths,
ofUnicode::CJKUnified
};
const std::initializer_list<ofUnicode::range> ofAlphabet::Japanese {
ofUnicode::Space,
ofUnicode::Hiragana,
ofUnicode::Katakana,
ofUnicode::KatakanaPhoneticExtensions,
ofUnicode::CJKLettersAndMonths,
ofUnicode::CJKUnified
};
const std::initializer_list<ofUnicode::range> ofAlphabet::Chinese {
ofUnicode::Space,
ofUnicode::CJKLettersAndMonths,
ofUnicode::CJKUnified
};
const std::initializer_list<ofUnicode::range> ofAlphabet::Korean {
ofUnicode::Space,
ofUnicode::HangulJamo,
ofUnicode::HangulCompatJamo,
ofUnicode::HangulExtendedA,
ofUnicode::HangulExtendedB,
ofUnicode::HangulSyllables
};
const std::initializer_list<ofUnicode::range> ofAlphabet::Arabic {
ofUnicode::Space,
ofUnicode::Arabic,
ofUnicode::ArabicExtendedA,
ofUnicode::ArabicMath,
ofUnicode::ArabicPresFormsA,
ofUnicode::ArabicPresFormsB
};
const std::initializer_list<ofUnicode::range> ofAlphabet::Devanagari {
ofUnicode::Devanagari,
ofUnicode::DevanagariExtended,
ofUnicode::VedicExtensions
};
const std::initializer_list<ofUnicode::range> ofAlphabet::Latin {
ofUnicode::Latin1Supplement,
ofUnicode::LatinExtendedAdditional
};
const std::initializer_list<ofUnicode::range> ofAlphabet::Greek {
ofUnicode::Space,
ofUnicode::Greek,
ofUnicode::GreekExtended
};
const std::initializer_list<ofUnicode::range> ofAlphabet::Cyrillic {
ofUnicode::Space,
ofUnicode::Cyrillic
};
const ofTrueTypeFont::glyphProps ofTrueTypeFont::invalidProps{
-1,
0,
0,
0,
0,0,
0,0,0,0,
0,
0.0f,0.0f,
0.0f,0.0f,0.0f,0.0f
};
static bool printVectorInfo = false;
static int ttfGlobalDpi = 96;
static bool librariesInitialized = false;
static FT_Library library;
//--------------------------------------------------------
void ofTrueTypeShutdown(){
#ifdef TARGET_LINUX
FcFini();
#endif
}
//--------------------------------------------------------
static ofTTFCharacter makeContoursForCharacter(FT_Face face){
//int num = face->glyph->outline.n_points;
int nContours = face->glyph->outline.n_contours;
int startPos = 0;
char * tags = face->glyph->outline.tags;
FT_Vector * vec = face->glyph->outline.points;
ofTTFCharacter charOutlines;
charOutlines.setUseShapeColor(false);
for(int k = 0; k < nContours; k++){
if( k > 0 ){
startPos = face->glyph->outline.contours[k-1]+1;
}
int endPos = face->glyph->outline.contours[k]+1;
if(printVectorInfo){
ofLogNotice("ofTrueTypeFont") << "--NEW CONTOUR";
}
//vector <ofPoint> testOutline;
ofPoint lastPoint;
for(int j = startPos; j < endPos; j++){
if( FT_CURVE_TAG(tags[j]) == FT_CURVE_TAG_ON ){
lastPoint.set((float)vec[j].x, (float)-vec[j].y, 0);
if(printVectorInfo){
ofLogNotice("ofTrueTypeFont") << "flag[" << j << "] is set to 1 - regular point - " << lastPoint.x << lastPoint.y;
}
charOutlines.lineTo(lastPoint/64);
}else{
if(printVectorInfo){
ofLogNotice("ofTrueTypeFont") << "flag[" << j << "] is set to 0 - control point";
}
if( FT_CURVE_TAG(tags[j]) == FT_CURVE_TAG_CUBIC ){
if(printVectorInfo){
ofLogNotice("ofTrueTypeFont") << "- bit 2 is set to 2 - CUBIC";
}
int prevPoint = j-1;
if( j == 0){
prevPoint = endPos-1;
}
int nextIndex = j+1;
if( nextIndex >= endPos){
nextIndex = startPos;
}
ofPoint nextPoint( (float)vec[nextIndex].x, -(float)vec[nextIndex].y );
//we need two control points to draw a cubic bezier
bool lastPointCubic = ( FT_CURVE_TAG(tags[prevPoint]) != FT_CURVE_TAG_ON ) && ( FT_CURVE_TAG(tags[prevPoint]) == FT_CURVE_TAG_CUBIC);
if( lastPointCubic ){
ofPoint controlPoint1((float)vec[prevPoint].x, (float)-vec[prevPoint].y);
ofPoint controlPoint2((float)vec[j].x, (float)-vec[j].y);
ofPoint nextPoint((float) vec[nextIndex].x, -(float) vec[nextIndex].y);
//cubic_bezier(testOutline, lastPoint.x, lastPoint.y, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, nextPoint.x, nextPoint.y, 8);
charOutlines.bezierTo(controlPoint1.x/64, controlPoint1.y/64, controlPoint2.x/64, controlPoint2.y/64, nextPoint.x/64, nextPoint.y/64);
}
}else{
ofPoint conicPoint( (float)vec[j].x, -(float)vec[j].y );
if(printVectorInfo){
ofLogNotice("ofTrueTypeFont") << "- bit 2 is set to 0 - conic- ";
ofLogNotice("ofTrueTypeFont") << "--- conicPoint point is " << conicPoint.x << conicPoint.y;
}
//If the first point is connic and the last point is connic then we need to create a virutal point which acts as a wrap around
if( j == startPos ){
bool prevIsConnic = ( FT_CURVE_TAG( tags[endPos-1] ) != FT_CURVE_TAG_ON ) && ( FT_CURVE_TAG( tags[endPos-1]) != FT_CURVE_TAG_CUBIC );
if( prevIsConnic ){
ofPoint lastConnic((float)vec[endPos - 1].x, (float)-vec[endPos - 1].y);
lastPoint = (conicPoint + lastConnic) / 2;
if(printVectorInfo){
ofLogNotice("ofTrueTypeFont") << "NEED TO MIX WITH LAST";
ofLogNotice("ofTrueTypeFont") << "last is " << lastPoint.x << " " << lastPoint.y;
}
}
}
//bool doubleConic = false;
int nextIndex = j+1;
if( nextIndex >= endPos){
nextIndex = startPos;
}
ofPoint nextPoint( (float)vec[nextIndex].x, -(float)vec[nextIndex].y );
if(printVectorInfo){
ofLogNotice("ofTrueTypeFont") << "--- last point is " << lastPoint.x << " " << lastPoint.y;
}
bool nextIsConnic = ( FT_CURVE_TAG( tags[nextIndex] ) != FT_CURVE_TAG_ON ) && ( FT_CURVE_TAG( tags[nextIndex]) != FT_CURVE_TAG_CUBIC );
//create a 'virtual on point' if we have two connic points
if( nextIsConnic ){
nextPoint = (conicPoint + nextPoint) / 2;
if(printVectorInfo){
ofLogNotice("ofTrueTypeFont") << "|_______ double connic!";
}
}
if(printVectorInfo){
ofLogNotice("ofTrueTypeFont") << "--- next point is " << nextPoint.x << " " << nextPoint.y;
}
//quad_bezier(testOutline, lastPoint.x, lastPoint.y, conicPoint.x, conicPoint.y, nextPoint.x, nextPoint.y, 8);
charOutlines.quadBezierTo(lastPoint.x/64, lastPoint.y/64, conicPoint.x/64, conicPoint.y/64, nextPoint.x/64, nextPoint.y/64);
if( nextIsConnic ){
lastPoint = nextPoint;
}
}
}
//end for
}
charOutlines.close();
}
return charOutlines;
}
#ifdef TARGET_OSX
//------------------------------------------------------------------
static string osxFontPathByName( string fontname ){
CFStringRef targetName = CFStringCreateWithCString(NULL, fontname.c_str(), kCFStringEncodingUTF8);
CTFontDescriptorRef targetDescriptor = CTFontDescriptorCreateWithNameAndSize(targetName, 0.0);
CFURLRef targetURL = (CFURLRef) CTFontDescriptorCopyAttribute(targetDescriptor, kCTFontURLAttribute);
string fontPath = "";
if(targetURL) {
UInt8 buffer[PATH_MAX];
CFURLGetFileSystemRepresentation(targetURL, true, buffer, PATH_MAX);
fontPath = string((char *)buffer);
CFRelease(targetURL);
}
CFRelease(targetName);
CFRelease(targetDescriptor);
return fontPath;
}
#endif
#ifdef TARGET_WIN32
#include <map>
// font font face -> file name name mapping
static map<string, string> fonts_table;
// read font linking information from registry, and store in std::map
//------------------------------------------------------------------
void initWindows(){
LONG l_ret;
const wchar_t *Fonts = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts";
HKEY key_ft;
l_ret = RegOpenKeyExW(HKEY_LOCAL_MACHINE, Fonts, 0, KEY_QUERY_VALUE, &key_ft);
if (l_ret != ERROR_SUCCESS){
ofLogError("ofTrueTypeFont") << "initWindows(): couldn't find fonts registery key";
return;
}
DWORD value_count;
DWORD max_data_len;
wchar_t value_name[2048];
BYTE *value_data;
// get font_file_name -> font_face mapping from the "Fonts" registry key
l_ret = RegQueryInfoKeyW(key_ft, NULL, NULL, NULL, NULL, NULL, NULL, &value_count, NULL, &max_data_len, NULL, NULL);
if(l_ret != ERROR_SUCCESS){
ofLogError("ofTrueTypeFont") << "initWindows(): couldn't query registery for fonts";
return;
}
// no font installed
if (value_count == 0){
ofLogError("ofTrueTypeFont") << "initWindows(): couldn't find any fonts in registery";
return;
}
// max_data_len is in BYTE
value_data = static_cast<BYTE *>(HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, max_data_len));
if(value_data == NULL) return;
char value_name_char[2048];
char value_data_char[2048];
/*char ppidl[2048];
char fontsPath[2048];
SHGetKnownFolderIDList(FOLDERID_Fonts, 0, NULL, &ppidl);
SHGetPathFromIDList(ppidl,&fontsPath);*/
string fontsDir = getenv ("windir");
fontsDir += "\\Fonts\\";
for (DWORD i = 0; i < value_count; ++i)
{
DWORD name_len = 2048;
DWORD data_len = max_data_len;
l_ret = RegEnumValueW(key_ft, i, value_name, &name_len, NULL, NULL, value_data, &data_len);
if(l_ret != ERROR_SUCCESS){
ofLogError("ofTrueTypeFont") << "initWindows(): couldn't read registry key for font type";
continue;
}
wcstombs(value_name_char,value_name,2048);
wcstombs(value_data_char,reinterpret_cast<wchar_t *>(value_data),2048);
string curr_face = value_name_char;
string font_file = value_data_char;
curr_face = curr_face.substr(0, curr_face.find('(') - 1);
fonts_table[curr_face] = fontsDir + font_file;
}
HeapFree(GetProcessHeap(), 0, value_data);
l_ret = RegCloseKey(key_ft);
}
static string winFontPathByName( string fontname ){
if(fonts_table.find(fontname)!=fonts_table.end()){
return fonts_table[fontname];
}
for(map<string,string>::iterator it = fonts_table.begin(); it!=fonts_table.end(); it++){
if(ofIsStringInString(ofToLower(it->first),ofToLower(fontname))) return it->second;
}
return "";
}
#endif
#ifdef TARGET_LINUX
//------------------------------------------------------------------
static string linuxFontPathByName(string fontname){
string filename;
FcPattern * pattern = FcNameParse((const FcChar8*)fontname.c_str());
FcBool ret = FcConfigSubstitute(0,pattern,FcMatchPattern);
if(!ret){
ofLogError() << "linuxFontPathByName(): couldn't find font file or system font with name \"" << fontname << "\"";
return "";
}
FcDefaultSubstitute(pattern);
FcResult result;
FcPattern * fontMatch=NULL;
fontMatch = FcFontMatch(0,pattern,&result);
if(!fontMatch){
ofLogError() << "linuxFontPathByName(): couldn't match font file or system font with name \"" << fontname << "\"";
FcPatternDestroy(fontMatch);
FcPatternDestroy(pattern);
return "";
}
FcChar8 *file;
if (FcPatternGetString (fontMatch, FC_FILE, 0, &file) == FcResultMatch){
filename = (const char*)file;
}else{
ofLogError() << "linuxFontPathByName(): couldn't find font match for \"" << fontname << "\"";
FcPatternDestroy(fontMatch);
FcPatternDestroy(pattern);
return "";
}
FcPatternDestroy(fontMatch);
FcPatternDestroy(pattern);
return filename;
}
#endif
//-----------------------------------------------------------
static bool loadFontFace(string fontname, int _fontSize, FT_Face & face, string & filename){
filename = ofToDataPath(fontname,true);
ofFile fontFile(filename,ofFile::Reference);
int fontID = 0;
if(!fontFile.exists()){
#ifdef TARGET_LINUX
filename = linuxFontPathByName(fontname);
#elif defined(TARGET_OSX)
if(fontname==OF_TTF_SANS){
fontname = "Helvetica Neue";
fontID = 4;
}else if(fontname==OF_TTF_SERIF){
fontname = "Times New Roman";
}else if(fontname==OF_TTF_MONO){
fontname = "Menlo Regular";
}
filename = osxFontPathByName(fontname);
#elif defined(TARGET_WIN32)
if(fontname==OF_TTF_SANS){
fontname = "Arial";
}else if(fontname==OF_TTF_SERIF){
fontname = "Times New Roman";
}else if(fontname==OF_TTF_MONO){
fontname = "Courier New";
}
filename = winFontPathByName(fontname);
#endif
if(filename == "" ){
ofLogError("ofTrueTypeFont") << "loadFontFace(): couldn't find font \"" << fontname << "\"";
return false;
}
ofLogVerbose("ofTrueTypeFont") << "loadFontFace(): \"" << fontname << "\" not a file in data loading system font from \"" << filename << "\"";
}
FT_Error err;
err = FT_New_Face( library, filename.c_str(), fontID, &face );
if (err) {
// simple error table in lieu of full table (see fterrors.h)
string errorString = "unknown freetype";
if(err == 1) errorString = "INVALID FILENAME";
ofLogError("ofTrueTypeFont") << "loadFontFace(): couldn't create new face for \"" << fontname << "\": FT_Error " << err << " " << errorString;
return false;
}
return true;
}
//--------------------------------------------------------
void ofTrueTypeFont::setGlobalDpi(int newDpi){
ttfGlobalDpi = newDpi;
}
#if defined(TARGET_ANDROID)
#include "ofxAndroidUtils.h"
#endif
//------------------------------------------------------------------
bool ofTrueTypeFont::initLibraries(){
if(!librariesInitialized){
FT_Error err;
err = FT_Init_FreeType( &library );
if (err){
ofLogError("ofTrueTypeFont") << "loadFont(): couldn't initialize Freetype lib: FT_Error " << err;
return false;
}
#ifdef TARGET_LINUX
FcBool result = FcInit();
if(!result){
return false;
}
#endif
#ifdef TARGET_WIN32
initWindows();
#endif
librariesInitialized = true;
}
return true;
}
//------------------------------------------------------------------
ofTrueTypeFont::ofTrueTypeFont()
:settings("",0){
bLoadedOk = false;
letterSpacing = 1;
spaceSize = 1;
fontUnitScale = 1;
stringQuads.setMode(OF_PRIMITIVE_TRIANGLES);
ascenderHeight = 0;
descenderHeight = 0;
lineHeight = 0;
}
//------------------------------------------------------------------
ofTrueTypeFont::~ofTrueTypeFont(){
#if defined(TARGET_ANDROID)
ofRemoveListener(ofxAndroidEvents().unloadGL,this,&ofTrueTypeFont::unloadTextures);
ofRemoveListener(ofxAndroidEvents().reloadGL,this,&ofTrueTypeFont::reloadTextures);
#endif
}
//-----------------------------------------------------------
void ofTrueTypeFont::unloadTextures(){
if(!bLoadedOk) return;
texAtlas.clear();
}
//-----------------------------------------------------------
void ofTrueTypeFont::reloadTextures(){
if(bLoadedOk) load(settings);
}
//-----------------------------------------------------------
bool ofTrueTypeFont::loadFont(string filename, int fontSize, bool bAntiAliased, bool bFullCharacterSet, bool makeContours, float simplifyAmt, int dpi) {
return load(filename, fontSize, bAntiAliased, bFullCharacterSet, makeContours, simplifyAmt, dpi);
}
//-----------------------------------------------------------
ofTrueTypeFont::glyph ofTrueTypeFont::loadGlyph(uint32_t utf8) const{
glyph aGlyph;
auto err = FT_Load_Glyph( face.get(), FT_Get_Char_Index( face.get(), utf8 ), settings.antialiased ? FT_LOAD_FORCE_AUTOHINT : FT_LOAD_DEFAULT );
if(err){
ofLogError("ofTrueTypeFont") << "loadFont(): FT_Load_Glyph failed for utf8 code " << utf8 << ": FT_Error " << err;
return aGlyph;
}
if (settings.antialiased) FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
else FT_Render_Glyph(face->glyph, FT_RENDER_MODE_MONO);
// -------------------------
// info about the character:
aGlyph.props.glyph = utf8;
aGlyph.props.height = face->glyph->metrics.height>>6;
aGlyph.props.width = face->glyph->metrics.width>>6;
aGlyph.props.bearingX = face->glyph->metrics.horiBearingX>>6;
aGlyph.props.bearingY = face->glyph->metrics.horiBearingY>>6;
aGlyph.props.xmin = face->glyph->bitmap_left;
aGlyph.props.xmax = aGlyph.props.xmin + aGlyph.props.width;
aGlyph.props.ymin = -face->glyph->bitmap_top;
aGlyph.props.ymax = aGlyph.props.ymin + aGlyph.props.height;
aGlyph.props.advance = face->glyph->metrics.horiAdvance>>6;
aGlyph.props.tW = aGlyph.props.width;
aGlyph.props.tH = aGlyph.props.height;
FT_Bitmap& bitmap= face->glyph->bitmap;
int width = bitmap.width;
int height = bitmap.rows;
if(width==0 || height==0) return aGlyph;
// Allocate Memory For The Texture Data.
aGlyph.pixels.allocate(width, height, OF_PIXELS_GRAY_ALPHA);
//-------------------------------- clear data:
aGlyph.pixels.set(0,255); // every luminance pixel = 255
aGlyph.pixels.set(1,0);
if (settings.antialiased == true){
ofPixels bitmapPixels;
bitmapPixels.setFromExternalPixels(bitmap.buffer,bitmap.width,bitmap.rows,OF_PIXELS_GRAY);
aGlyph.pixels.setChannel(1,bitmapPixels);
} else {
//-----------------------------------
// true type packs monochrome info in a
// 1-bit format, hella funky
// here we unpack it:
unsigned char *src = bitmap.buffer;
for(unsigned int j=0; j <bitmap.rows;j++) {
unsigned char b=0;
unsigned char *bptr = src;
for(unsigned int k=0; k < bitmap.width ; k++){
aGlyph.pixels[2*(k+j*width)] = 255;
if (k%8==0){
b = (*bptr++);
}
aGlyph.pixels[2*(k+j*width) + 1] = b&0x80 ? 255 : 0;
b <<= 1;
}
src += bitmap.pitch;
}
//-----------------------------------
}
return aGlyph;
}
//-----------------------------------------------------------
bool ofTrueTypeFont::load(string filename, int fontSize, bool antialiased, bool fullCharacterSet, bool makeContours, float simplifyAmt, int dpi) {
ofTtfSettings settings(filename,fontSize);
settings.antialiased = antialiased;
settings.contours = makeContours;
settings.simplifyAmt = simplifyAmt;
settings.dpi = dpi;
if(fullCharacterSet){
settings.ranges = {ofUnicode::Latin1Supplement};
}else{
settings.ranges = {ofUnicode::Latin};
}
return load(settings);
}
bool ofTrueTypeFont::load(const ofTtfSettings & _settings){
#if defined(TARGET_ANDROID)
ofAddListener(ofxAndroidEvents().unloadGL,this,&ofTrueTypeFont::unloadTextures);
ofAddListener(ofxAndroidEvents().reloadGL,this,&ofTrueTypeFont::reloadTextures);
#endif
initLibraries();
settings = _settings;
if( settings.dpi == 0 ){
settings.dpi = ttfGlobalDpi;
}
bLoadedOk = false;
//--------------- load the library and typeface
FT_Face loadFace;
if(!loadFontFace(settings.fontName,settings.fontSize,loadFace,settings.fontName)){
return false;
}
face = std::shared_ptr<struct FT_FaceRec_>(loadFace,FT_Done_Face);
if(settings.ranges.empty()){
settings.ranges.push_back(ofUnicode::Latin1Supplement);
}
int border = 1;
FT_Set_Char_Size( face.get(), settings.fontSize << 6, settings.fontSize << 6, settings.dpi, settings.dpi);
fontUnitScale = ((float)settings.fontSize * settings.dpi) / (72 * face->units_per_EM);
lineHeight = face->height * fontUnitScale;
ascenderHeight = face->ascender * fontUnitScale;
descenderHeight = face->descender * fontUnitScale;
glyphBBox.set(face->bbox.xMin * fontUnitScale,
face->bbox.yMin * fontUnitScale,
(face->bbox.xMax - face->bbox.xMin) * fontUnitScale,
(face->bbox.yMax - face->bbox.yMin) * fontUnitScale);
//--------------- initialize character info and textures
auto nGlyphs = std::accumulate(settings.ranges.begin(), settings.ranges.end(), 0,
[](uint32_t acc, ofUnicode::range range){
return acc + range.getNumGlyphs();
});
cps.resize(nGlyphs);
if(settings.contours){
charOutlines.resize(nGlyphs);
charOutlinesNonVFlipped.resize(nGlyphs);
charOutlinesContour.resize(nGlyphs);
charOutlinesNonVFlippedContour.resize(nGlyphs);
}else{
charOutlines.resize(1);
}
vector<ofTrueTypeFont::glyph> all_glyphs;
uint32_t areaSum=0;
//--------------------- load each char -----------------------
auto i = 0;
for(auto & range: settings.ranges){
for (uint32_t g = range.begin; g <= range.end; g++, i++){
all_glyphs.push_back(loadGlyph(g));
all_glyphs[i].props.characterIndex = i;
glyphIndexMap[g] = i;
cps[i] = all_glyphs[i].props;
areaSum += (cps[i].tW+border*2)*(cps[i].tH+border*2);
if(settings.contours){
if(printVectorInfo){
std::string str;
ofAppendUTF8(str,g);
ofLogNotice("ofTrueTypeFont") << "character " << str;
}
//int character = i + NUM_CHARACTER_TO_START;
charOutlines[i] = makeContoursForCharacter( face.get() );
charOutlinesContour[i] = charOutlines[i];
charOutlinesContour[i].setFilled(false);
charOutlinesContour[i].setStrokeWidth(1);
charOutlinesNonVFlipped[i] = charOutlines[i];
charOutlinesNonVFlipped[i].translate(ofVec3f(0,cps[i].height));
charOutlinesNonVFlipped[i].scale(1,-1);
charOutlinesNonVFlippedContour[i] = charOutlines[i];
charOutlinesNonVFlippedContour[i].setFilled(false);
charOutlinesNonVFlippedContour[i].setStrokeWidth(1);
if(settings.simplifyAmt>0){
charOutlines[i].simplify(settings.simplifyAmt);
charOutlinesNonVFlipped[i].simplify(settings.simplifyAmt);
charOutlinesContour[i].simplify(settings.simplifyAmt);
charOutlinesNonVFlippedContour[i].simplify(settings.simplifyAmt);
}
}
}
}
vector<ofTrueTypeFont::glyphProps> sortedCopy = cps;
sort(sortedCopy.begin(),sortedCopy.end(),[](const ofTrueTypeFont::glyphProps & c1, const ofTrueTypeFont::glyphProps & c2){
if(c1.tH == c2.tH) return c1.tW > c2.tW;
else return c1.tH > c2.tH;
});
// pack in a texture, algorithm to calculate min w/h from
// http://upcommons.upc.edu/pfc/bitstream/2099.1/7720/1/TesiMasterJonas.pdf
//ofLogNotice("ofTrueTypeFont") << "loadFont(): areaSum: " << areaSum
bool packed = false;
float alpha = logf(areaSum)*1.44269;
int w;
int h;
while(!packed){
w = pow(2,floor((alpha/2.f) + 0.5)); // there doesn't seem to be a round in cmath for windows.
//w = pow(2,round(alpha/2.f));
h = w;//pow(2,round(alpha - round(alpha/2.f)));
int x=0;
int y=0;
int maxRowHeight = sortedCopy[0].tH + border*2;
for(int i=0;i<(int)cps.size();i++){
if(x+sortedCopy[i].tW + border*2>w){
x = 0;
y += maxRowHeight;
maxRowHeight = sortedCopy[i].tH + border*2;
if(y + maxRowHeight > h){
alpha++;
break;
}
}
x+= sortedCopy[i].tW + border*2;
if(i==(int)cps.size()-1) packed = true;
}
}
ofPixels atlasPixelsLuminanceAlpha;
atlasPixelsLuminanceAlpha.allocate(w,h,OF_PIXELS_GRAY_ALPHA);
atlasPixelsLuminanceAlpha.set(0,255);
atlasPixelsLuminanceAlpha.set(1,0);
int x=0;
int y=0;
int maxRowHeight = sortedCopy[0].tH + border*2;
for(int i=0;i<(int)cps.size();i++){
ofPixels & charPixels = all_glyphs[sortedCopy[i].characterIndex].pixels;
if(x+sortedCopy[i].tW + border*2>w){
x = 0;
y += maxRowHeight;
maxRowHeight = sortedCopy[i].tH + border*2;
}
cps[sortedCopy[i].characterIndex].t1 = float(x + border)/float(w);
cps[sortedCopy[i].characterIndex].v1 = float(y + border)/float(h);
cps[sortedCopy[i].characterIndex].t2 = float(cps[sortedCopy[i].characterIndex].tW + x + border)/float(w);
cps[sortedCopy[i].characterIndex].v2 = float(cps[sortedCopy[i].characterIndex].tH + y + border)/float(h);
charPixels.pasteInto(atlasPixelsLuminanceAlpha,x+border,y+border);
x+= sortedCopy[i].tW + border*2;
}
texAtlas.allocate(atlasPixelsLuminanceAlpha,false);
texAtlas.setRGToRGBASwizzles(true);
if(settings.antialiased && settings.fontSize>20){
texAtlas.setTextureMinMagFilter(GL_LINEAR,GL_LINEAR);
}else{
texAtlas.setTextureMinMagFilter(GL_NEAREST,GL_NEAREST);
}
texAtlas.loadData(atlasPixelsLuminanceAlpha);
bLoadedOk = true;
return true;
}
//-----------------------------------------------------------
bool ofTrueTypeFont::isLoaded() const{
return bLoadedOk;
}
//-----------------------------------------------------------
bool ofTrueTypeFont::isAntiAliased() const{
return settings.antialiased;
}
//-----------------------------------------------------------
bool ofTrueTypeFont::hasFullCharacterSet() const{
return true;
}
//-----------------------------------------------------------
int ofTrueTypeFont::getSize() const{
return settings.fontSize;
}
//-----------------------------------------------------------
void ofTrueTypeFont::setLineHeight(float _newLineHeight) {
lineHeight = _newLineHeight;
}
//-----------------------------------------------------------
float ofTrueTypeFont::getLineHeight() const{
return lineHeight;
}
//-----------------------------------------------------------
float ofTrueTypeFont::getAscenderHeight() const {
return ascenderHeight;
}
//-----------------------------------------------------------
float ofTrueTypeFont::getDescenderHeight() const {
return descenderHeight;
}
//-----------------------------------------------------------
const ofRectangle & ofTrueTypeFont::getGlyphBBox() const {
return glyphBBox;
}
//-----------------------------------------------------------
void ofTrueTypeFont::setLetterSpacing(float _newletterSpacing) {
letterSpacing = _newletterSpacing;
}
//-----------------------------------------------------------
float ofTrueTypeFont::getLetterSpacing() const{
return letterSpacing;
}
//-----------------------------------------------------------
void ofTrueTypeFont::setSpaceSize(float _newspaceSize) {
spaceSize = _newspaceSize;
}
//-----------------------------------------------------------
float ofTrueTypeFont::getSpaceSize() const{
return spaceSize;
}
//------------------------------------------------------------------
ofTTFCharacter ofTrueTypeFont::getCharacterAsPoints(uint32_t character, bool vflip, bool filled) const{
if( settings.contours == false ){
ofLogError("ofxTrueTypeFont") << "getCharacterAsPoints(): contours not created, call loadFont() with makeContours set to true";
return ofTTFCharacter();
}
if (!isValidGlyph(character)){
return ofTTFCharacter();
}
if(vflip){
if(filled){
return charOutlines[indexForGlyph(character)];
}else{
return charOutlinesContour[indexForGlyph(character)];
}
}else{
if(filled){
return charOutlinesNonVFlipped[indexForGlyph(character)];
}else{
return charOutlinesNonVFlippedContour[indexForGlyph(character)];
}
}
}
//-----------------------------------------------------------
void ofTrueTypeFont::drawChar(uint32_t c, float x, float y, bool vFlipped) const{
if (!isValidGlyph(c)){
//ofLogError("ofTrueTypeFont") << "drawChar(): char " << c + NUM_CHARACTER_TO_START << " not allocated: line " << __LINE__ << " in " << __FILE__;
return;
}
int xmin, ymin, xmax, ymax;
float t1, v1, t2, v2;
auto props = getGlyphProperties(c);
t1 = props.t1;
t2 = props.t2;
v2 = props.v2;
v1 = props.v1;
xmin = props.xmin+x;
ymin = props.ymin;
xmax = props.xmax+x;
ymax = props.ymax;
if(!vFlipped){
ymin *= -1;
ymax *= -1;
}
ymin += y;
ymax += y;
int firstIndex = stringQuads.getVertices().size();
stringQuads.addVertex(ofVec3f(xmin,ymin));
stringQuads.addVertex(ofVec3f(xmax,ymin));
stringQuads.addVertex(ofVec3f(xmax,ymax));
stringQuads.addVertex(ofVec3f(xmin,ymax));
stringQuads.addTexCoord(ofVec2f(t1,v1));
stringQuads.addTexCoord(ofVec2f(t2,v1));
stringQuads.addTexCoord(ofVec2f(t2,v2));
stringQuads.addTexCoord(ofVec2f(t1,v2));
stringQuads.addIndex(firstIndex);
stringQuads.addIndex(firstIndex+1);
stringQuads.addIndex(firstIndex+2);
stringQuads.addIndex(firstIndex+2);
stringQuads.addIndex(firstIndex+3);
stringQuads.addIndex(firstIndex);
}
//-----------------------------------------------------------
int ofTrueTypeFont::getKerning(uint32_t c, uint32_t prevC) const{
if(FT_HAS_KERNING( face )){
FT_Vector kerning;
FT_Get_Kerning(face.get(), FT_Get_Char_Index(face.get(), c), FT_Get_Char_Index(face.get(), prevC), FT_KERNING_UNFITTED, &kerning);
return kerning.x * fontUnitScale;
}else{
return 0;
}
}
void ofTrueTypeFont::iterateString(const string & str, float x, float y, bool vFlipped, std::function<void(uint32_t, ofVec2f)> f) const{
ofVec2f pos(x,y);
int newLineDirection = 1;
if(!vFlipped){
// this would align multiline texts to the last line when vflip is disabled
//int lines = ofStringTimesInString(c,"\n");
//Y = lines*lineHeight;
newLineDirection = -1;
}
int directionX = settings.direction == ofTtfSettings::LeftToRight?1:-1;
string str_valid;
utf8::replace_invalid(str.begin(),str.end(),back_inserter(str_valid));
utf8::iterator<const char*> it(&str_valid.front(), &str_valid.front(), (&str_valid.back())+1);
utf8::iterator<const char*> end((&str_valid.back())+1, &str_valid.front(), (&str_valid.back())+1);
uint32_t prevC = 0;
while(it != end){
try{
auto c = *it;
if (c == '\n') {
pos.y += lineHeight*newLineDirection;
pos.x = x ; //reset X Pos back to zero
prevC = 0;
}if (c == '\t') {
pos.x += getGlyphProperties(' ').advance * letterSpacing * 4 * directionX;
prevC = c;
} else if(isValidGlyph(c)) {
const auto & props = getGlyphProperties(c);
if(prevC>0){
pos.x += getKerning(c,prevC);// * directionX;
}
f(c,pos);
pos.x += props.advance * letterSpacing * directionX;
prevC = c;
}
++it;
}catch(...){
break;
}
}
}
//-----------------------------------------------------------
void ofTrueTypeFont::setDirection(ofTtfSettings::Direction direction){
settings.direction = direction;
}
//-----------------------------------------------------------
vector<ofTTFCharacter> ofTrueTypeFont::getStringAsPoints(string str, bool vflip, bool filled) const{
vector<ofTTFCharacter> shapes;
if (!bLoadedOk){
ofLogError("ofxTrueTypeFont") << "getStringAsPoints(): font not allocated: line " << __LINE__ << " in " << __FILE__;
return shapes;
};
iterateString(str,0,0,vflip,[vflip, filled, &shapes, this](uint32_t c, ofVec2f pos){
shapes.push_back(getCharacterAsPoints(c,vflip,filled));
shapes.back().translate(pos);
});
return shapes;
}
bool ofTrueTypeFont::isValidGlyph(uint32_t glyph) const{
//return glyphIndexMap.find(glyph) != glyphIndexMap.end();
return std::any_of(settings.ranges.begin(), settings.ranges.end(),
[&](ofUnicode::range range){
return glyph >= range.begin && glyph <= range.end;
});
}
size_t ofTrueTypeFont::indexForGlyph(uint32_t glyph) const{
return glyphIndexMap.find(glyph)->second;
}
const ofTrueTypeFont::glyphProps & ofTrueTypeFont::getGlyphProperties(uint32_t glyph) const{
if(isValidGlyph(glyph)){
return cps[indexForGlyph(glyph)];
}else{
return invalidProps;
}
}
//-----------------------------------------------------------
void ofTrueTypeFont::drawCharAsShape(uint32_t c, float x, float y, bool vFlipped, bool filled) const{
if(vFlipped){
if(filled){
charOutlines[indexForGlyph(c)].draw(x,y);
}else{
charOutlinesContour[indexForGlyph(c)].draw(x,y);
}
}else{
if(filled){
charOutlinesNonVFlipped[indexForGlyph(c)].draw(x,y);
}else{
charOutlinesNonVFlippedContour[indexForGlyph(c)].draw(x,y);
}
}
}
//-----------------------------------------------------------
float ofTrueTypeFont::stringWidth(string c) const{
ofRectangle rect = getStringBoundingBox(c, 0,0);
return rect.width;
}
//-----------------------------------------------------------
ofRectangle ofTrueTypeFont::getStringBoundingBox(string c, float x, float y, bool vflip) const{
ofMesh mesh = getStringMesh(c,x,y,vflip);
ofRectangle bb(std::numeric_limits<float>::max(),std::numeric_limits<float>::max(),0,0);
float maxX = std::numeric_limits<float>::min();
float maxY = std::numeric_limits<float>::min();
for(const auto & v: mesh.getVertices()){
bb.x = min(v.x,bb.x);
bb.y = min(v.y,bb.y);
maxX = max(v.x,maxX);
maxY = max(v.y,maxY);
}
bb.width = maxX - bb.x;
bb.height = maxY - bb.y;
return bb;
}
//-----------------------------------------------------------
float ofTrueTypeFont::stringHeight(string c) const{
ofRectangle rect = getStringBoundingBox(c, 0,0);
return rect.height;
}
//-----------------------------------------------------------
void ofTrueTypeFont::createStringMesh(string str, float x, float y, bool vflip) const{
iterateString(str,x,y,vflip,[vflip, this](uint32_t c, ofVec2f pos){
drawChar(c, pos.x, pos.y, vflip);
});
}
//-----------------------------------------------------------
const ofMesh & ofTrueTypeFont::getStringMesh(string c, float x, float y, bool vFlipped) const{
stringQuads.clear();
createStringMesh(c,x,y,vFlipped);
return stringQuads;
}
//-----------------------------------------------------------
const ofTexture & ofTrueTypeFont::getFontTexture() const{
return texAtlas;
}
//-----------------------------------------------------------
ofTexture ofTrueTypeFont::getStringTexture(string str, bool vflip) const{
string str_valid;
utf8::replace_invalid(str.begin(),str.end(),back_inserter(str_valid));
vector<glyph> glyphs;
vector<ofVec2f> glyphPositions;
utf8::iterator<const char*> it(&str_valid.front(), &str_valid.front(), (&str_valid.back())+1);
utf8::iterator<const char*> end((&str_valid.back())+1, &str_valid.front(), (&str_valid.back())+1);
int x = 0;
int y = 0;
int height = 0;
uint32_t prevC = 0;
while(it != end){
try{
auto c = *it;
if (c == '\n') {
y += lineHeight;
x = 0 ; //reset X Pos back to zero
prevC = 0;
} else {
glyphs.push_back(loadGlyph(c));
if(prevC>0){
x += getKerning(c,prevC);
}else if(settings.direction == ofTtfSettings::RightToLeft){
x += glyphs.back().props.width;
}
glyphPositions.emplace_back(x,y);
x += glyphs.back().props.advance * letterSpacing;
height = max(height,glyphs.back().props.ymax + y + (int)getLineHeight());
}
++it;
prevC = c;
}catch(...){
break;
}
}
ofTexture tex;
ofPixels totalPixels;
totalPixels.allocate(x, height, OF_PIXELS_GRAY_ALPHA);
//-------------------------------- clear data:
totalPixels.set(0,255); // every luminance pixel = 255
totalPixels.set(1,0);
size_t i = 0;
for(auto & g: glyphs){
if(settings.direction == ofTtfSettings::LeftToRight){
g.pixels.blendInto(totalPixels, glyphPositions[i].x, glyphPositions[i].y + getLineHeight() + g.props.ymin + getDescenderHeight() );
}else{
g.pixels.blendInto(totalPixels, x-glyphPositions[i].x, glyphPositions[i].y + getLineHeight() + g.props.ymin + getDescenderHeight() );
}
i++;
if(i==glyphPositions.size()){
break;
}
}
tex.allocate(totalPixels);
return tex;
}
//-----------------------------------------------------------
void ofTrueTypeFont::drawString(string c, float x, float y) const{
if (!bLoadedOk){
ofLogError("ofTrueTypeFont") << "drawString(): font not allocated";
return;
}
ofGetCurrentRenderer()->drawString(*this,c,x,y);
}
//-----------------------------------------------------------
void ofTrueTypeFont::drawStringAsShapes(string str, float x, float y) const{
if (!bLoadedOk){
ofLogError("ofTrueTypeFont") << "drawStringAsShapes(): font not allocated: line " << __LINE__ << " in " << __FILE__;
return;
};
//----------------------- error checking
if (!settings.contours){
ofLogError("ofTrueTypeFont") << "drawStringAsShapes(): contours not created for this font, call loadFont() with makeContours set to true";
return;
}
iterateString(str,x,y,true,[&](uint32_t c, ofVec2f pos){
drawCharAsShape(c, pos.x, pos.y, ofIsVFlipped(), ofGetStyle().bFill);
});
}
//-----------------------------------------------------------
int ofTrueTypeFont::getNumCharacters() const{
return cps.size();
}
#pragma once
#include <vector>
#include "ofPoint.h"
#include "ofRectangle.h"
#include "ofConstants.h"
#include "ofPath.h"
#include "ofTexture.h"
#include "ofMesh.h"
/// \file
/// The ofTrueTypeFont class provides an interface to load fonts into
/// openFrameworks. The fonts are converted to textures, and can be drawn on
/// screen. There are some options when you load the font - what size the
/// font is rendered at, whether or not it is anti-aliased, and whether the
/// font object will be the full character set or a subset (i.e., extended
/// ASCII, which can include diacritics like umlauts, or ASCII). The default
/// is anti-aliased, non-full character set. The library uses freetype, which
/// has certain patent problems in regards to true type hinting, especially
/// at small sizes, so non-anti-aliased type doesn't always render
/// beautifully. But we find it quite adequate, and at larger sizes it seems
/// to works well.
/// \cond INTERNAL
typedef ofPath ofTTFCharacter;
typedef struct FT_FaceRec_* FT_Face;
/// \endcond
/// \name Fonts
/// \{
static const string OF_TTF_SANS = "sans-serif";
static const string OF_TTF_SERIF = "serif";
static const string OF_TTF_MONO = "monospace";
/// \}
void ofTrueTypeShutdown();
class ofUnicode{
public:
struct range{
std::uint32_t begin;
std::uint32_t end;
constexpr std::uint32_t getNumGlyphs() const{
return end - begin + 1;
}
range &operator=(const range &rhs) {
begin = rhs.begin;
end = rhs.end;
return *this;
}
};
static constexpr range Space {32, 32};
static constexpr range Latin {32, 0x007F};
static constexpr range Latin1Supplement {32,0x00FF};
static constexpr range Greek {0x0370, 0x03FF};
static constexpr range Cyrillic {0x0400, 0x04FF};
static constexpr range Arabic {0x0600, 0x077F};
static constexpr range ArabicSupplement {0x0750, 0x077F};
static constexpr range ArabicExtendedA {0x08A0, 0x08FF};
static constexpr range Devanagari {0x0900, 0x097F};
static constexpr range HangulJamo {0x1100, 0x11FF};
static constexpr range VedicExtensions {0x1CD0, 0x1CFF};
static constexpr range LatinExtendedAdditional {0x1E00, 0x1EFF};
static constexpr range GreekExtended {0x1F00, 0x1FFF};
static constexpr range GeneralPunctuation {0x2000, 0x206F};
static constexpr range SuperAndSubScripts {0x2070, 0x209F};
static constexpr range CurrencySymbols {0x20A0, 0x20CF};
static constexpr range LetterLikeSymbols {0x2100, 0x214F};
static constexpr range NumberForms {0x2150, 0x218F};
static constexpr range Arrows {0x2190, 0x21FF};
static constexpr range MathOperators {0x2200, 0x22FF};
static constexpr range MiscTechnical {0x2300, 0x23FF};
static constexpr range BoxDrawing {0x2500, 0x257F};
static constexpr range BlockElement {0x2580, 0x259F};
static constexpr range GeometricShapes {0x25A0, 0x25FF};
static constexpr range MiscSymbols {0x2600, 0x26FF};
static constexpr range Dingbats {0x2700, 0x27BF};
static constexpr range Hiragana {0x3040, 0x309F};
static constexpr range Katakana {0x30A0, 0x30FF};
static constexpr range HangulCompatJamo {0x3130, 0x318F};
static constexpr range KatakanaPhoneticExtensions {0x31F0, 0x31FF};
static constexpr range CJKLettersAndMonths {0x3200, 0x32FF};
static constexpr range CJKUnified {0x4E00, 0x9FD5};
static constexpr range DevanagariExtended {0xA8E0, 0xA8FF};
static constexpr range HangulExtendedA {0xA960, 0xA97F};
static constexpr range HangulSyllables {0xAC00, 0xD7AF};
static constexpr range HangulExtendedB {0xD7B0, 0xD7FF};
static constexpr range AlphabeticPresentationForms {0xFB00, 0xFB4F};
static constexpr range ArabicPresFormsA {0xFB50, 0xFDFF};
static constexpr range ArabicPresFormsB {0xFE70, 0xFEFF};
static constexpr range KatakanaHalfAndFullwidthForms {0xFF00, 0xFFEF};
static constexpr range KanaSupplement {0x1B000, 0x1B0FF};
static constexpr range RumiNumericalSymbols {0x10E60, 0x10E7F};
static constexpr range ArabicMath {0x1EE00, 0x1EEFF};
static constexpr range MiscSymbolsAndPictographs {0x1F300, 0x1F5FF};
static constexpr range Emoticons {0x1F600, 0x1F64F};
static constexpr range TransportAndMap {0x1F680, 0x1F6FF};
};
class ofAlphabet{
public:
static const std::initializer_list<ofUnicode::range> Emoji;
static const std::initializer_list<ofUnicode::range> Japanese;
static const std::initializer_list<ofUnicode::range> Chinese;
static const std::initializer_list<ofUnicode::range> Korean;
static const std::initializer_list<ofUnicode::range> Arabic;
static const std::initializer_list<ofUnicode::range> Devanagari;
static const std::initializer_list<ofUnicode::range> Latin;
static const std::initializer_list<ofUnicode::range> Greek;
static const std::initializer_list<ofUnicode::range> Cyrillic;
};
class ofTtfSettings{
friend class ofTrueTypeFont;
vector<ofUnicode::range> ranges;
public:
ofTtfSettings(const string & name, int size)
:fontName(name)
,fontSize(size){}
string fontName;
int fontSize;
bool antialiased = true;
bool contours = false;
bool simplifyAmt = 0.3;
int dpi = 0;
enum Direction{
LeftToRight,
RightToLeft
};
Direction direction = LeftToRight;
void add(std::initializer_list<ofUnicode::range> alphabet){
ranges.insert(ranges.end(), alphabet);
}
void add(const ofUnicode::range & range){
ranges.push_back(range);
}
};
class ofTrueTypeFont{
public:
/// \todo
ofTrueTypeFont();
/// \todo
virtual ~ofTrueTypeFont();
/// \name Load Font
/// \{
/// \brief Loads the font specified by filename, allows you to control size, aliasing, and other parameters.
///
/// loads a font, and allows you to set the following parameters: the filename, the size, if the font is anti-aliased,
/// if it has a full character set, if you need it to have contours (for getStringPoints) and parameters that control
/// the simplification amount for those contours and the dpi of the font.
///
/// default (without dpi), non-full char set, anti aliased, 96 dpi
///
/// \param filename The name of the font file to load.
/// \param fontsize The size in pixels to load the font.
/// \param _bAntiAliased true if the font should be anti-aliased.
/// \param _bFullCharacterSet true if the full character set should be cached.
/// \param makeControus true if the vector contours should be cached.
/// \param simplifyAmt the amount to simplify the vector contours. Larger number means more simplified.
/// \param dpi the dots per inch used to specify rendering size.
/// \returns true if the font was loaded correctly.
bool load(string filename,
int fontsize,
bool _bAntiAliased=true,
bool _bFullCharacterSet=true,
bool makeContours=false,
float simplifyAmt=0.3,
int dpi=0);
OF_DEPRECATED_MSG("Use load instead",bool loadFont(string filename,
int fontsize,
bool _bAntiAliased=true,
bool _bFullCharacterSet=false,
bool makeContours=false,
float simplifyAmt=0.3,
int dpi=0));
bool load(const ofTtfSettings & settings);
/// \brief Has the font been loaded successfully?
/// \returns true if the font was loaded.
bool isLoaded() const;
/// \}
/// \name Font Settings
/// \{
// set the default dpi for all typefaces.
/// \todo
static void setGlobalDpi(int newDpi);
/// \brief Is the font anti-aliased?
/// \returns true if the font was set to be anti-aliased.
bool isAntiAliased() const;
/// \brief Does the font have a full character set?
/// \returns true if the font was allocated with a full character set.
bool hasFullCharacterSet() const;
/// \brief Get the number characters in the loaded character set.
///
/// If you allocate the font using different parameters, you can load in partial
/// and full character sets, this helps you know how many characters it can represent.
///
/// \returns Number of characters in loaded character set.
int getNumCharacters();
/// \}
/// \name Font Size
/// \{
/// \brief Returns the size of the font.
/// \returns Size of font, set when font was loaded.
int getSize() const;
/// \brief Computes line height based on font size.
/// \returns Returns current line height.
float getLineHeight() const;
/// \brief Sets line height for text drawn on screen.
///
/// Note the line height is automatically computed based on the font size, when you load in the font.
///
/// \param height Line height for text drawn on screen.
void setLineHeight(float height);
/// \brief Get the ascender distance for this font.
///
/// The ascender is the vertical distance from the baseline to the highest "character" coordinate.
/// The meaning of "character" coordinate depends on the font. Some fonts take accents into account,
/// others do not, and still others define it simply to be the highest coordinate over all glyphs.
///
/// \returns Returns font ascender height in pixels.
float getAscenderHeight() const;
/// \brief Get the descender distance for this font.
///
/// The descender is the vertical distance from the baseline to the lowest "character" coordinate.
/// The meaning of "character" coordinate depends on the font. Some fonts take accents into account,
/// others do not, and still others define it simply to be the lowest coordinate over all glyphs.
/// This value will be negative for descenders below the baseline (which is typical).
///
/// \returns Returns font descender height in pixels.
float getDescenderHeight() const;
/// \brief Get the global bounding box for this font.
///
/// The global bounding box is the rectangle inside of which all glyphs in the font can fit.
/// Glyphs are drawn starting from (0,0) in the returned box (though note that the box can
/// extend in any direction out from the origin).
///
/// \returns Returns font descender height in pixels.
const ofRectangle & getGlyphBBox() const;
/// \brief Returns letter spacing of font object.
///
/// You can control this by the ofTrueTypeFont::setLetterSpacing() function. 1.0 = default spacing,
/// less then 1.0 would be tighter spacing, greater then 1.0 would be wider spacing.
///
/// \returns Returns letter spacing of font object.
float getLetterSpacing() const;
/// \brief Sets the letter spacing of the font object.
///
/// 1.0 = default spacing, less then 1.0 would be tighter spacing, greater then 1.0 would be wider spacing.
/// \param spacing Spacing of font object.
void setLetterSpacing(float spacing);
/// \brief Returns a variable that represents how wide spaces are.
///
/// It's a scalar for the width of the letter 'p', so 1.0 means that a space will be the size of the lower
/// case 'p' of that font. 2.0 means that it's 2 times the size of the lower case 'p', etc.
///
/// \returns Returns a variable that represents how wide spaces are.
float getSpaceSize() const;
/// \brief Sets the size of the space ' ' character.
///
/// This number, which defaults to 1.0, scales the width of the letter 'p' for the space.
///
/// \param size Scales the width of the letter 'p' for the space.
void setSpaceSize(float size);
/// \brief Returns the string width.
///
/// This is essentially the width component of the ofTrueTypeFont::getStringBoundingBox() rectangle.
///
/// \param s The string to get the width of.
/// \returns Returns the string width.
float stringWidth(string s) const;
/// \brief Returns the string height.
///
/// This is essentially the height component of the ofTrueTypeFont::getStringBoundingBox() rectangle.
///
/// \param s The string to get the height of.
/// \returns Returns the string height.
float stringHeight(string s) const;
/// \brief Returns the bounding box of a string as a rectangle.
/// \param s The string to get bounding box of.
/// \param x X position of returned rectangle.
/// \param y Y position of returned rectangle.
/// \returns Returns the bounding box of a string as a rectangle.
ofRectangle getStringBoundingBox(string s, float x, float y, bool vflip=true) const;
/// \}
/// \name Drawing
/// \{
/// \brief Draw a string s at position x,y
/// \param s String to draw
/// \param x X position of string
/// \param y Y position of string
void drawString(string s, float x, float y) const;
/// \brief Draws the string as if it was geometrical shapes.
///
/// Uses the information contained in ofTTFContour and ofTTFCharacter.
///
/// \param x X position of shapes
/// \param y Y position of shapes
void drawStringAsShapes(string s, float x, float y) const;
/// \brief Get the num chars in the loaded character set.
///
/// If you allocate the font using different paramters, you can load in partial
/// and full character sets, this helps you know how many characters it can represent.
///
/// \returns Number of characters in loaded character set.
int getNumCharacters() const;
/// \todo
ofTTFCharacter getCharacterAsPoints(uint32_t character, bool vflip=true, bool filled=true) const;
vector<ofTTFCharacter> getStringAsPoints(string str, bool vflip=true, bool filled=true) const;
const ofMesh & getStringMesh(string s, float x, float y, bool vflip=true) const;
const ofTexture & getFontTexture() const;
ofTexture getStringTexture(string s, bool vflip=true) const;
bool isValidGlyph(uint32_t) const;
/// \}
void setDirection(ofTtfSettings::Direction direction);
protected:
/// \cond INTERNAL
bool bLoadedOk;
vector <ofTTFCharacter> charOutlines;
vector <ofTTFCharacter> charOutlinesNonVFlipped;
vector <ofTTFCharacter> charOutlinesContour;
vector <ofTTFCharacter> charOutlinesNonVFlippedContour;
float lineHeight;
float ascenderHeight;
float descenderHeight;
ofRectangle glyphBBox;
float letterSpacing;
float spaceSize;
float fontUnitScale;
struct glyphProps{
int64_t characterIndex;
uint32_t glyph;
int height;
int width;
int bearingX, bearingY;
int xmin, xmax, ymin, ymax;
int advance;
float tW,tH;
float t1,t2,v1,v2;
};
struct glyph{
glyphProps props;
ofPixels pixels;
};
vector<glyphProps> cps; // properties for each character
ofTtfSettings settings;
unordered_map<uint32_t,size_t> glyphIndexMap;
int getKerning(uint32_t c, uint32_t prevC) const;
void drawChar(uint32_t c, float x, float y, bool vFlipped) const;
void drawCharAsShape(uint32_t c, float x, float y, bool vFlipped, bool filled) const;
void createStringMesh(string s, float x, float y, bool vFlipped) const;
glyph loadGlyph(uint32_t utf8) const;
const glyphProps & getGlyphProperties(uint32_t glyph) const;
void iterateString(const string & str, float x, float y, bool vFlipped, std::function<void(uint32_t, ofVec2f)> f) const;
size_t indexForGlyph(uint32_t glyph) const;
ofTexture texAtlas;
mutable ofMesh stringQuads;
/// \endcond
private:
#if defined(TARGET_ANDROID) || defined(TARGET_OF_IOS)
friend void ofUnloadAllFontTextures();
friend void ofReloadAllFontTextures();
#endif
shared_ptr<struct FT_FaceRec_> face;
static const glyphProps invalidProps;
void unloadTextures();
void reloadTextures();
static bool initLibraries();
static void finishLibraries();
friend void ofExitCallback();
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment