#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 "ofGraphics.h"
#include "utf8.h"
using namespace std;
const ofUnicode::range ofUnicode::Space {32, 32};
const ofUnicode::range ofUnicode::IdeographicSpace {0x3000, 0x3000};
const ofUnicode::range ofUnicode::Latin {32, 0x007F};
const ofUnicode::range ofUnicode::Latin1Supplement {32,0x00FF};
const ofUnicode::range ofUnicode::LatinA {0x0100,0x017F};
const ofUnicode::range ofUnicode::Greek {0x0370, 0x03FF};
const ofUnicode::range ofUnicode::Cyrillic {0x0400, 0x04FF};
const ofUnicode::range ofUnicode::Arabic {0x0600, 0x077F};
const ofUnicode::range ofUnicode::ArabicSupplement {0x0750, 0x077F};
const ofUnicode::range ofUnicode::ArabicExtendedA {0x08A0, 0x08FF};
const ofUnicode::range ofUnicode::Devanagari {0x0900, 0x097F};
const ofUnicode::range ofUnicode::HangulJamo {0x1100, 0x11FF};
const ofUnicode::range ofUnicode::VedicExtensions {0x1CD0, 0x1CFF};
const ofUnicode::range ofUnicode::LatinExtendedAdditional {0x1E00, 0x1EFF};
const ofUnicode::range ofUnicode::GreekExtended {0x1F00, 0x1FFF};
const ofUnicode::range ofUnicode::GeneralPunctuation {0x2000, 0x206F};
const ofUnicode::range ofUnicode::SuperAndSubScripts {0x2070, 0x209F};
const ofUnicode::range ofUnicode::CurrencySymbols {0x20A0, 0x20CF};
const ofUnicode::range ofUnicode::LetterLikeSymbols {0x2100, 0x214F};
const ofUnicode::range ofUnicode::NumberForms {0x2150, 0x218F};
const ofUnicode::range ofUnicode::Arrows {0x2190, 0x21FF};
const ofUnicode::range ofUnicode::MathOperators {0x2200, 0x22FF};
const ofUnicode::range ofUnicode::MiscTechnical {0x2300, 0x23FF};
const ofUnicode::range ofUnicode::BoxDrawing {0x2500, 0x257F};
const ofUnicode::range ofUnicode::BlockElement {0x2580, 0x259F};
const ofUnicode::range ofUnicode::GeometricShapes {0x25A0, 0x25FF};
const ofUnicode::range ofUnicode::MiscSymbols {0x2600, 0x26FF};
const ofUnicode::range ofUnicode::Dingbats {0x2700, 0x27BF};
const ofUnicode::range ofUnicode::Hiragana {0x3040, 0x309F};
const ofUnicode::range ofUnicode::Katakana {0x30A0, 0x30FF};
const ofUnicode::range ofUnicode::HangulCompatJamo {0x3130, 0x318F};
const ofUnicode::range ofUnicode::KatakanaPhoneticExtensions {0x31F0, 0x31FF};
const ofUnicode::range ofUnicode::CJKLettersAndMonths {0x3200, 0x32FF};
const ofUnicode::range ofUnicode::CJKUnified {0x4E00, 0x9FD5};
const ofUnicode::range ofUnicode::DevanagariExtended {0xA8E0, 0xA8FF};
const ofUnicode::range ofUnicode::HangulExtendedA {0xA960, 0xA97F};
const ofUnicode::range ofUnicode::HangulSyllables {0xAC00, 0xD7AF};
const ofUnicode::range ofUnicode::HangulExtendedB {0xD7B0, 0xD7FF};
const ofUnicode::range ofUnicode::AlphabeticPresentationForms {0xFB00, 0xFB4F};
const ofUnicode::range ofUnicode::ArabicPresFormsA {0xFB50, 0xFDFF};
const ofUnicode::range ofUnicode::ArabicPresFormsB {0xFE70, 0xFEFF};
const ofUnicode::range ofUnicode::KatakanaHalfAndFullwidthForms {0xFF00, 0xFFEF};
const ofUnicode::range ofUnicode::KanaSupplement {0x1B000, 0x1B0FF};
const ofUnicode::range ofUnicode::RumiNumericalSymbols {0x10E60, 0x10E7F};
const ofUnicode::range ofUnicode::ArabicMath {0x1EE00, 0x1EEFF};
const ofUnicode::range ofUnicode::MiscSymbolsAndPictographs {0x1F300, 0x1F5FF};
const ofUnicode::range ofUnicode::Emoticons {0x1F601, 0x1F64F};
const ofUnicode::range ofUnicode::TransportAndMap {0x1F680, 0x1F6FF};
const ofUnicode::range ofUnicode::EnclosedCharacters {0x24C2, 0x1F251};
const ofUnicode::range ofUnicode::Uncategorized {0x00A9, 0x1F5FF};
const ofUnicode::range ofUnicode::AdditionalEmoticons {0x1F600, 0x1F636};
const ofUnicode::range ofUnicode::AdditionalTransportAndMap {0x1F681, 0x1F6C5};
const ofUnicode::range ofUnicode::OtherAdditionalSymbols {0x1F30D, 0x1F567};
const std::initializer_list<ofUnicode::range> ofAlphabet::Emoji {
ofUnicode::Space,
ofUnicode::Emoticons,
ofUnicode::Dingbats,
ofUnicode::Uncategorized,
ofUnicode::TransportAndMap,
ofUnicode::EnclosedCharacters,
ofUnicode::OtherAdditionalSymbols,
};
const std::initializer_list<ofUnicode::range> ofAlphabet::Japanese {
ofUnicode::Space,
ofUnicode::IdeographicSpace,
ofUnicode::Hiragana,
ofUnicode::Katakana,
ofUnicode::KatakanaPhoneticExtensions,
ofUnicode::CJKLettersAndMonths,
ofUnicode::CJKUnified
};
const std::initializer_list<ofUnicode::range> ofAlphabet::Chinese {
ofUnicode::Space,
ofUnicode::IdeographicSpace,
ofUnicode::CJKLettersAndMonths,
ofUnicode::CJKUnified
};
const std::initializer_list<ofUnicode::range> ofAlphabet::Korean {
ofUnicode::Space,
ofUnicode::IdeographicSpace,
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,
ofUnicode::Latin,
ofUnicode::LatinA,
};
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{
0,
0,
0,
0,
0,0,
0,0,0,0,
0,
0.0f,0.0f,
0.0f,0.0f,0.0f,0.0f
};
const size_t TAB_WIDTH = 4;
static bool printVectorInfo = false;
static int ttfGlobalDpi = 96;
static bool librariesInitialized = false;
static FT_Library library;
void ofTrueTypeShutdown(){
#ifdef TARGET_LINUX
#endif
}
static ofPath makeContoursForCharacter(FT_Face face){
ofPath charOutlines;
charOutlines.setUseShapeColor(false);
charOutlines.setPolyWindingMode(OF_POLY_WINDING_NONZERO);
auto moveTo = [](const FT_Vector*to, void * userData){
ofPath * charOutlines = static_cast<ofPath*>(userData);
charOutlines->moveTo(to->x/64, -to->y/64);
return 0;
};
auto lineTo = [](const FT_Vector*to, void * userData){
ofPath * charOutlines = static_cast<ofPath*>(userData);
charOutlines->lineTo(to->x/64, -to->y/64);
return 0;
};
auto conicTo = [](const FT_Vector*cp, const FT_Vector*to, void * userData){
ofPath * charOutlines = static_cast<ofPath*>(userData);
auto lastP = charOutlines->getCommands().back().to;
charOutlines->quadBezierTo(lastP, {cp->x/64, -cp->y/64}, {to->x/64, -to->y/64});
return 0;
};
auto cubicTo = [](const FT_Vector*cp1, const FT_Vector*cp2, const FT_Vector*to, void * userData){
ofPath * charOutlines = static_cast<ofPath*>(userData);
charOutlines->bezierTo({cp1->x/64, -cp1->y/64}, {cp2->x/64, -cp2->y/64}, {to->x/64, -to->y/64});
return 0;
};
FT_Outline_Funcs funcs{
moveTo,
lineTo,
conicTo,
cubicTo,
0,
0,
};
FT_Outline_Decompose(&face->glyph->outline, &funcs, &charOutlines);
charOutlines.close();
return charOutlines;
}
#ifdef TARGET_OSX
static std::string osxFontPathByName(const std::string& fontname){
CFStringRef targetName = CFStringCreateWithCString(nullptr, 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 = std::string((char *)buffer);
CFRelease(targetURL);
}
CFRelease(targetName);
CFRelease(targetDescriptor);
return fontPath;
}
#endif
#ifdef TARGET_WIN32
#include <map>
static map<std::string, std::string> fonts_table;
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;
l_ret = RegQueryInfoKeyW(key_ft, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &value_count, nullptr, &max_data_len, nullptr, nullptr);
if(l_ret != ERROR_SUCCESS){
ofLogError("ofTrueTypeFont") << "initWindows(): couldn't query registery for fonts";
return;
}
if (value_count == 0){
ofLogError("ofTrueTypeFont") << "initWindows(): couldn't find any fonts in registery";
return;
}
value_data = static_cast<BYTE *>(HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, max_data_len));
if(value_data == nullptr) return;
char value_name_char[2048];
char value_data_char[2048];
std::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, nullptr, nullptr, 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);
std::string curr_face = value_name_char;
std::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 std::string winFontPathByName(const std::string& fontname ){
if(fonts_table.find(fontname)!=fonts_table.end()){
return fonts_table[fontname];
}
for(map<std::string,std::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 std::string linuxFontPathByName(const std::string& fontname){
std::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=nullptr;
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(const std::filesystem::path& _fontname, FT_Face & face, std::filesystem::path & filename){
std::filesystem::path fontname = _fontname;
filename = ofToDataPath(_fontname,true);
ofFile fontFile(filename,ofFile::Reference);
int fontID = 0;
if(!fontFile.exists()){
#ifdef TARGET_LINUX
filename = linuxFontPathByName(fontname.string());
#elif defined(TARGET_OSX)
if(fontname==OF_TTF_SANS){
fontname = "Helvetica Neue";
#if MAC_OS_X_VERSION_10_13 && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13
fontID = 0;
#else
fontID = 4;
#endif
}else if(fontname==OF_TTF_SERIF){
fontname = "Times New Roman";
}else if(fontname==OF_TTF_MONO){
fontname = "Menlo Regular";
}
filename = osxFontPathByName(fontname.string());
#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.string());
#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.string().c_str(), fontID, &face );
if (err) {
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
}
ofTrueTypeFont::ofTrueTypeFont(const ofTrueTypeFont& mom)
:settings(mom.settings){
#if defined(TARGET_ANDROID)
if(mom.isLoaded()){
ofAddListener(ofxAndroidEvents().unloadGL,this,&ofTrueTypeFont::unloadTextures);
ofAddListener(ofxAndroidEvents().reloadGL,this,&ofTrueTypeFont::reloadTextures);
}
#endif
bLoadedOk = mom.bLoadedOk;
charOutlines = mom.charOutlines;
charOutlinesNonVFlipped = mom.charOutlinesNonVFlipped;
charOutlinesContour = mom.charOutlinesContour;
charOutlinesNonVFlippedContour = mom.charOutlinesNonVFlippedContour;
lineHeight = mom.lineHeight;
ascenderHeight = mom.ascenderHeight;
descenderHeight = mom.descenderHeight;
glyphBBox = mom.glyphBBox;
letterSpacing = mom.letterSpacing;
spaceSize = mom.spaceSize;
fontUnitScale = mom.fontUnitScale;
cps = mom.cps;
settings = mom.settings;
glyphIndexMap = mom.glyphIndexMap;
texAtlas = mom.texAtlas;
face = mom.face;
}
ofTrueTypeFont & ofTrueTypeFont::operator=(const ofTrueTypeFont& mom){
if(this == &mom) return *this;
#if defined(TARGET_ANDROID)
if(mom.isLoaded()){
ofAddListener(ofxAndroidEvents().unloadGL,this,&ofTrueTypeFont::unloadTextures);
ofAddListener(ofxAndroidEvents().reloadGL,this,&ofTrueTypeFont::reloadTextures);
}
#endif
settings = mom.settings;
bLoadedOk = mom.bLoadedOk;
charOutlines = mom.charOutlines;
charOutlinesNonVFlipped = mom.charOutlinesNonVFlipped;
charOutlinesContour = mom.charOutlinesContour;
charOutlinesNonVFlippedContour = mom.charOutlinesNonVFlippedContour;
lineHeight = mom.lineHeight;
ascenderHeight = mom.ascenderHeight;
descenderHeight = mom.descenderHeight;
glyphBBox = mom.glyphBBox;
letterSpacing = mom.letterSpacing;
spaceSize = mom.spaceSize;
fontUnitScale = mom.fontUnitScale;
cps = mom.cps;
settings = mom.settings;
glyphIndexMap = mom.glyphIndexMap;
texAtlas = mom.texAtlas;
face = mom.face;
return *this;
}
ofTrueTypeFont::ofTrueTypeFont(ofTrueTypeFont&& mom)
:settings(std::move(mom.settings)){
#if defined(TARGET_ANDROID)
if(mom.isLoaded()){
ofAddListener(ofxAndroidEvents().unloadGL,this,&ofTrueTypeFont::unloadTextures);
ofAddListener(ofxAndroidEvents().reloadGL,this,&ofTrueTypeFont::reloadTextures);
}
#endif
bLoadedOk = mom.bLoadedOk;
charOutlines = std::move(mom.charOutlines);
charOutlinesNonVFlipped = std::move(mom.charOutlinesNonVFlipped);
charOutlinesContour = std::move(mom.charOutlinesContour);
charOutlinesNonVFlippedContour = std::move(mom.charOutlinesNonVFlippedContour);
lineHeight = mom.lineHeight;
ascenderHeight = mom.ascenderHeight;
descenderHeight = mom.descenderHeight;
glyphBBox = mom.glyphBBox;
letterSpacing = mom.letterSpacing;
spaceSize = mom.spaceSize;
fontUnitScale = mom.fontUnitScale;
cps = mom.cps;
settings = mom.settings;
glyphIndexMap = std::move(mom.glyphIndexMap);
texAtlas = mom.texAtlas;
face = mom.face;
}
ofTrueTypeFont & ofTrueTypeFont::operator=(ofTrueTypeFont&& mom){
if(this == &mom) return *this;
#if defined(TARGET_ANDROID)
if(mom.isLoaded()){
ofAddListener(ofxAndroidEvents().unloadGL,this,&ofTrueTypeFont::unloadTextures);
ofAddListener(ofxAndroidEvents().reloadGL,this,&ofTrueTypeFont::reloadTextures);
}
#endif
bLoadedOk = mom.bLoadedOk;
charOutlines = std::move(mom.charOutlines);
charOutlinesNonVFlipped = std::move(mom.charOutlinesNonVFlipped);
charOutlinesContour = std::move(mom.charOutlinesContour);
charOutlinesNonVFlippedContour = std::move(mom.charOutlinesNonVFlippedContour);
lineHeight = mom.lineHeight;
ascenderHeight = mom.ascenderHeight;
descenderHeight = mom.descenderHeight;
glyphBBox = mom.glyphBBox;
letterSpacing = mom.letterSpacing;
spaceSize = mom.spaceSize;
fontUnitScale = mom.fontUnitScale;
cps = mom.cps;
settings = mom.settings;
glyphIndexMap = std::move(mom.glyphIndexMap);
texAtlas = mom.texAtlas;
face = mom.face;
return *this;
}
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);
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;
aGlyph.pixels.allocate(width, height, OF_PIXELS_GRAY_ALPHA);
aGlyph.pixels.set(0,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 {
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(const std::filesystem::path& filename, int fontSize, bool antialiased, bool fullCharacterSet, bool makeContours, float simplifyAmt, int dpi) {
ofTrueTypeFontSettings 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 ofTrueTypeFontSettings & _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;
FT_Face loadFace;
if(!loadFontFace(settings.fontName, 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);
auto nGlyphs = std::accumulate(settings.ranges.begin(), settings.ranges.end(), 0u,
[](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;
auto i = 0u;
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;
ofUTF8Append(str,g);
ofLogNotice("ofTrueTypeFont") << "character " << str;
}
charOutlines[i] = makeContoursForCharacter( face.get() );
charOutlinesContour[i] = charOutlines[i];
charOutlinesContour[i].setFilled(false);
charOutlinesContour[i].setStrokeWidth(1);
charOutlinesNonVFlipped[i] = charOutlines[i];
charOutlinesNonVFlipped[i].translate({0,cps[i].height,0.f});
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;
});
bool packed = false;
float alpha = logf(areaSum)*1.44269f;
int w;
int h;
while(!packed){
w = pow(2,floor((alpha/2.f) + 0.5f));
h = w;
int x=0;
int y=0;
auto maxRowHeight = sortedCopy[0].tH + border*2;
packed = true;
for(auto & glyph: sortedCopy){
if(x+glyph.tW + border*2>w){
x = 0;
y += maxRowHeight;
maxRowHeight = glyph.tH + border*2;
if(y + maxRowHeight > h){
alpha++;
packed = false;
break;
}
}
x+= glyph.tW + border*2;
}
}
ofPixels atlasPixelsLuminanceAlpha;
atlasPixelsLuminanceAlpha.allocate(w,h,OF_PIXELS_GRAY_ALPHA);
atlasPixelsLuminanceAlpha.set(0,255);
atlasPixelsLuminanceAlpha.set(1,0);
int x=0;
int y=0;
auto maxRowHeight = sortedCopy[0].tH + border*2;
for(auto & glyph: sortedCopy){
ofPixels & charPixels = all_glyphs[glyph.characterIndex].pixels;
if(x+glyph.tW + border*2>w){
x = 0;
y += maxRowHeight;
maxRowHeight = glyph.tH + border*2;
}
cps[glyph.characterIndex].t1 = float(x + border)/float(w);
cps[glyph.characterIndex].v1 = float(y + border)/float(h);
cps[glyph.characterIndex].t2 = float(cps[glyph.characterIndex].tW + x + border)/float(w);
cps[glyph.characterIndex].v2 = float(cps[glyph.characterIndex].tH + y + border)/float(h);
charPixels.pasteInto(atlasPixelsLuminanceAlpha,x+border,y+border);
x+= glyph.tW + border*2;
}
int maxSize;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
if(w > maxSize || h > maxSize){
ofLogError("ofTruetypeFont") << "Trying to allocate texture of " << w << "x" << h << " which is bigger than supported in current platform: " << maxSize;
return false;
}else{
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;
}
ofPath 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 ofPath();
}
if (!isValidGlyph(character)){
return ofPath();
}
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)){
return;
}
long 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 = long(props.xmin+x);
ymin = props.ymin;
xmax = long(props.xmax+x);
ymax = props.ymax;
if(!vFlipped){
ymin *= -1;
ymax *= -1;
}
ymin += y;
ymax += y;
ofIndexType firstIndex = stringQuads.getVertices().size();
stringQuads.addVertex(glm::vec3(xmin,ymin,0.f));
stringQuads.addVertex(glm::vec3(xmax,ymin,0.f));
stringQuads.addVertex(glm::vec3(xmax,ymax,0.f));
stringQuads.addVertex(glm::vec3(xmin,ymax,0.f));
stringQuads.addTexCoord(glm::vec2(t1,v1));
stringQuads.addTexCoord(glm::vec2(t2,v1));
stringQuads.addTexCoord(glm::vec2(t2,v2));
stringQuads.addTexCoord(glm::vec2(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 leftC, uint32_t rightC) const{
if(FT_HAS_KERNING( face )){
FT_Vector kerning;
FT_Get_Kerning(face.get(), FT_Get_Char_Index(face.get(), leftC), FT_Get_Char_Index(face.get(), rightC), FT_KERNING_UNFITTED, &kerning);
return kerning.x >> 6;
}else{
return 0;
}
}
void ofTrueTypeFont::iterateString(const string & str, float x, float y, bool vFlipped, std::function<void(uint32_t, glm::vec2)> f) const{
glm::vec2 pos(x,y);
int newLineDirection = 1;
if(!vFlipped){
newLineDirection = -1;
}
int directionX = settings.direction == OF_TTF_LEFT_TO_RIGHT?1:-1;
uint32_t prevC = 0;
for(auto c: ofUTF8Iterator(str)){
try{
if (c == '\n') {
pos.y += lineHeight*newLineDirection;
pos.x = x ;
prevC = 0;
} else if (c == '\t') {
if (settings.direction == OF_TTF_LEFT_TO_RIGHT){
f(c, pos);
pos.x += getGlyphProperties(' ').advance * spaceSize * TAB_WIDTH * directionX;
} else{
pos.x += getGlyphProperties(' ').advance * spaceSize * TAB_WIDTH * directionX;
f(c, pos);
}
prevC = c;
}else if(c == ' '){
pos.x += getGlyphProperties(' ').advance * spaceSize * directionX;
f(c, pos);
prevC = c;
}else if(isValidGlyph(c)) {
const auto & props = getGlyphProperties(c);
if(prevC>0){
if(settings.direction == OF_TTF_LEFT_TO_RIGHT){
pos.x += getKerning(prevC, c);
}else{
pos.x += getKerning(c, prevC);
}
}
if(settings.direction == OF_TTF_LEFT_TO_RIGHT){
f(c,pos);
pos.x += props.advance * directionX;
pos.x += getGlyphProperties(' ').advance * (letterSpacing - 1.f) * directionX;
}else{
pos.x += props.advance * directionX;
pos.x += getGlyphProperties(' ').advance * (letterSpacing - 1.f) * directionX;
f(c,pos);
}
prevC = c;
}
}catch(...){
break;
}
}
}
void ofTrueTypeFont::setDirection(ofTrueTypeFontDirection direction){
settings.direction = direction;
}
vector<ofPath> ofTrueTypeFont::getStringAsPoints(const string & str, bool vflip, bool filled) const{
vector<ofPath> shapes;
if (!bLoadedOk){
ofLogError("ofxTrueTypeFont") << "getStringAsPoints(): font not allocated: line " << __LINE__ << " in " << __FILE__;
return shapes;
};
iterateString(str,0,0,vflip,[&](uint32_t c, glm::vec2 pos){
shapes.push_back(getCharacterAsPoints(c,vflip,filled));
shapes.back().translate(glm::vec3{pos, 0.f});
});
return shapes;
}
bool ofTrueTypeFont::isValidGlyph(uint32_t glyph) const{
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(const std::string& c) const{
ofRectangle rect = getStringBoundingBox(c, 0,0);
return rect.width;
}
ofRectangle ofTrueTypeFont::getStringBoundingBox(const std::string& c, float x, float y, bool vflip) const{
if ( c.empty() ){
return ofRectangle( x, y, 0.f, 0.f);
}
float minX = std::numeric_limits<float>::max();
float minY = std::numeric_limits<float>::max();
float maxX = -std::numeric_limits<float>::max();
float maxY = -std::numeric_limits<float>::max();
iterateString( c, x, y, vflip, [&]( uint32_t c, glm::vec2 pos ){
auto props = getGlyphProperties( c );
if ( c == '\t' ){
props.advance = getGlyphProperties(' ').advance * spaceSize * TAB_WIDTH;
}
maxX = max( maxX, pos.x + props.xmin + props.width );
minX = min( minX, pos.x );
if ( vflip ){
minY = min( minY, pos.y - ( props.ymax - props.ymin ) );
maxY = max( maxY, pos.y - ( props.bearingY - props.height ) );
} else{
minY = min( minY, pos.y - ( props.ymax) );
maxY = max( maxY, pos.y - ( props.ymin ) );
}
} );
float width = maxX - minX;
float height = maxY - minY;
return ofRectangle(minX, minY, width, height);
}
float ofTrueTypeFont::stringHeight(const std::string& c) const{
ofRectangle rect = getStringBoundingBox(c, 0,0);
return rect.height;
}
void ofTrueTypeFont::createStringMesh(const std::string& str, float x, float y, bool vflip) const{
iterateString(str,x,y,vflip,[&](uint32_t c, glm::vec2 pos){
drawChar(c, pos.x, pos.y, vflip);
});
}
const ofMesh & ofTrueTypeFont::getStringMesh(const std::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;
}
glm::vec2 ofTrueTypeFont::getFirstGlyphPosForTexture(const std::string & str, bool vflip) const{
if(!str.empty()){
try{
auto c = *ofUTF8Iterator(str).begin();
if(settings.direction == OF_TTF_LEFT_TO_RIGHT){
if (c != '\n') {
auto g = loadGlyph(c);
return {-float(g.props.xmin), getLineHeight() + g.props.ymin + getDescenderHeight()};
}
}else{
int width = 0;
int lineWidth = 0;
iterateString(str, 0, 0, vflip, [&](uint32_t c, ofVec2f){
try{
if (c != '\n') {
auto g = loadGlyph(c);
if(c == '\t')lineWidth += g.props.advance + getGlyphProperties(' ').advance * spaceSize * TAB_WIDTH;
else if(c == ' ')lineWidth += g.props.advance + getGlyphProperties(' ').advance * spaceSize;
else if(isValidGlyph(c))lineWidth += g.props.advance + getGlyphProperties(' ').advance * (letterSpacing - 1.f);
width = max(width, lineWidth);
}else{
lineWidth = 0;
}
}catch(...){
}
});
if (c != '\n') {
auto g = loadGlyph(c);
return {width - float(g.props.xmin), getLineHeight() + g.props.ymin + getDescenderHeight()};
}else{
return {float(width), 0.0};
}
}
}catch(...){}
}
return {0.f, 0.f};
}
ofTexture ofTrueTypeFont::getStringTexture(const std::string& str, bool vflip) const{
vector<glyph> glyphs;
vector<glm::vec2> glyphPositions;
long height = 0;
int width = 0;
int lineWidth = 0;
iterateString(str, 0, 0, vflip, [&](uint32_t c, ofVec2f pos){
try{
if (c != '\n') {
auto g = loadGlyph(c);
if (c == '\t'){
auto temp = loadGlyph(' ');
glyphs.push_back(temp);
}else{
glyphs.push_back(g);
}
int x = pos.x + g.props.xmin;
int y = pos.y;
glyphPositions.emplace_back(x, y);
if(c == '\t')lineWidth += g.props.advance + getGlyphProperties(' ').advance * spaceSize * TAB_WIDTH;
else if(c == ' ')lineWidth += g.props.advance + getGlyphProperties(' ').advance * spaceSize;
else if(isValidGlyph(c))lineWidth += g.props.advance + getGlyphProperties(' ').advance * (letterSpacing - 1.f);
width = max(width, lineWidth);
y += g.props.ymax;
height = max(height, y + long(getLineHeight()));
}else{
lineWidth = 0;
}
}catch(...){
}
});
ofTexture tex;
ofPixels totalPixels;
totalPixels.allocate(width, height, OF_PIXELS_GRAY_ALPHA);
totalPixels.set(0,255);
totalPixels.set(1,0);
size_t i = 0;
for(auto & g: glyphs){
if(settings.direction == OF_TTF_LEFT_TO_RIGHT){
g.pixels.blendInto(totalPixels, glyphPositions[i].x, glyphPositions[i].y + getLineHeight() + g.props.ymin + getDescenderHeight() );
}else{
g.pixels.blendInto(totalPixels, width-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(const std::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(const std::string& str, float x, float y) const{
if (!bLoadedOk){
ofLogError("ofTrueTypeFont") << "drawStringAsShapes(): font not allocated: line " << __LINE__ << " in " << __FILE__;
return;
};
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, glm::vec2 pos){
drawCharAsShape(c, pos.x, pos.y, ofIsVFlipped(), ofGetStyle().bFill);
});
}
std::size_t ofTrueTypeFont::getNumCharacters() const{
return cps.size();
}
Comments