Created
December 13, 2015 07:09
-
-
Save 2bbb/1b158d1941ecd9cff460 to your computer and use it in GitHub Desktop.
ofTrueTypeFont
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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(); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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