//----------------------------------------------------------- bool ofTrueTypeFont::load(string _filename, int _fontSize, bool _bAntiAliased, bool _bFullCharacterSet, bool _makeContours, float _simplifyAmt, int _dpi) { #if defined(TARGET_ANDROID) ofAddListener(ofxAndroidEvents().unloadGL,this,&ofTrueTypeFont::unloadTextures); ofAddListener(ofxAndroidEvents().reloadGL,this,&ofTrueTypeFont::reloadTextures); #endif int border = 1; initLibraries(); // if we've already been loaded, try to clean up : unloadTextures(); if( _dpi == 0 ){ _dpi = ttfGlobalDpi; } bLoadedOk = false; bAntiAliased = _bAntiAliased; bFullCharacterSet = _bFullCharacterSet; fontSize = _fontSize; bMakeContours = _makeContours; simplifyAmt = _simplifyAmt; dpi = _dpi; //--------------- load the library and typeface if(!loadFontFace(_filename,_fontSize,face,filename)){ return false; } FT_Set_Char_Size( face, fontSize << 6, fontSize << 6, dpi, dpi); float fontUnitScale = ((float)fontSize * 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); //------------------------------------------------------ //kerning would be great to support: //ofLogNotice("ofTrueTypeFont") << "FT_HAS_KERNING ? " << FT_HAS_KERNING(face); //------------------------------------------------------ nCharacters = (bFullCharacterSet ? 256 : 128) - NUM_CHARACTER_TO_START; //--------------- initialize character info and textures cps.resize(nCharacters); if(bMakeContours){ charOutlines.assign(nCharacters, ofTTFCharacter()); charOutlinesNonVFlipped.assign(nCharacters, ofTTFCharacter()); charOutlinesContour.assign(nCharacters, ofTTFCharacter()); charOutlinesNonVFlippedContour.assign(nCharacters, ofTTFCharacter()); }else{ charOutlines.resize(1); } vector<ofPixels> expanded_data(nCharacters); long areaSum=0; FT_Error err; //--------------------- load each char ----------------------- for (int i = 0 ; i < nCharacters; i++){ //------------------------------------------ anti aliased or not: int glyph = (unsigned char)(i+NUM_CHARACTER_TO_START); if (glyph == 0xA4) glyph = 0x20AC; // hack to load the euro sign, all codes in 8859-15 match with utf-32 except for this one err = FT_Load_Glyph( face, FT_Get_Char_Index( face, glyph ), bAntiAliased ? FT_LOAD_FORCE_AUTOHINT : FT_LOAD_DEFAULT ); if(err){ ofLogError("ofTrueTypeFont") << "loadFont(): FT_Load_Glyph failed for char " << i << ": FT_Error " << err; } if (bAntiAliased == true) FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); else FT_Render_Glyph(face->glyph, FT_RENDER_MODE_MONO); //------------------------------------------ if(bMakeContours){ if(printVectorInfo){ ofLogNotice("ofTrueTypeFont") << "character " << char(i+NUM_CHARACTER_TO_START); } //int character = i + NUM_CHARACTER_TO_START; charOutlines[i] = makeContoursForCharacter( face ); 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(simplifyAmt>0){ charOutlines[i].simplify(simplifyAmt); charOutlinesNonVFlipped[i].simplify(simplifyAmt); charOutlinesContour[i].simplify(simplifyAmt); charOutlinesNonVFlippedContour[i].simplify(simplifyAmt); } } // ------------------------- // info about the character: FT_Bitmap& bitmap= face->glyph->bitmap; int width = bitmap.width; int height = bitmap.rows; cps[i].characterIndex = i; cps[i].glyph = glyph; cps[i].height = face->glyph->metrics.height>>6; cps[i].width = face->glyph->metrics.width>>6; cps[i].bearingX = face->glyph->metrics.horiBearingX>>6; cps[i].bearingY = face->glyph->metrics.horiBearingY>>6; cps[i].xmin = face->glyph->bitmap_left; cps[i].xmax = cps[i].xmin + cps[i].width; cps[i].ymin = -face->glyph->bitmap_top; cps[i].ymax = cps[i].ymin + cps[i].height; cps[i].advance = face->glyph->metrics.horiAdvance>>6; cps[i].tW = cps[i].width; cps[i].tH = cps[i].height; areaSum += (cps[i].tW+border*2)*(cps[i].tH+border*2); if(width==0 || height==0) continue; // Allocate Memory For The Texture Data. expanded_data[i].allocate(width, height, OF_PIXELS_GRAY_ALPHA); //-------------------------------- clear data: expanded_data[i].set(0,255); // every luminance pixel = 255 expanded_data[i].set(1,0); if (bAntiAliased == true){ ofPixels bitmapPixels; bitmapPixels.setFromExternalPixels(bitmap.buffer,bitmap.width,bitmap.rows,OF_PIXELS_GRAY); expanded_data[i].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++){ expanded_data[i][2*(k+j*width)] = 255; if (k%8==0){ b = (*bptr++); } expanded_data[i][2*(k+j*width) + 1] = b&0x80 ? 255 : 0; b <<= 1; } src += bitmap.pitch; } //----------------------------------- } } vector<charProps> sortedCopy = cps; sort(sortedCopy.begin(),sortedCopy.end(),&compare_cps); // 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 = expanded_data[sortedCopy[i].characterIndex]; 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(bAntiAliased && fontSize>20){ texAtlas.setTextureMinMagFilter(GL_LINEAR,GL_LINEAR); }else{ texAtlas.setTextureMinMagFilter(GL_NEAREST,GL_NEAREST); } texAtlas.loadData(atlasPixelsLuminanceAlpha); // ------------- close the library and typeface bLoadedOk = true; return true; }
//----------------------------------------------------------- bool ofTrueTypeFont::loadFont(string _filename, int _fontSize, bool _bAntiAliased, bool _bFullCharacterSet, bool _makeContours, float _simplifyAmt, int _dpi) { initLibraries(); //------------------------------------------------ if (bLoadedOk == true){ // we've already been loaded, try to clean up : unloadTextures(); } //------------------------------------------------ if( _dpi == 0 ){ _dpi = ttfGlobalDpi; } bLoadedOk = false; bAntiAliased = _bAntiAliased; bFullCharacterSet = _bFullCharacterSet; fontSize = _fontSize; bMakeContours = _makeContours; simplifyAmt = _simplifyAmt; dpi = _dpi; //--------------- load the library and typeface FT_Face face; if(!loadFontFace(_filename,_fontSize,face,filename)){ return false; } FT_Set_Char_Size( face, fontSize << 6, fontSize << 6, dpi, dpi); lineHeight = fontSize * 1.43f; //------------------------------------------------------ //kerning would be great to support: //ofLogNotice("ofTrueTypeFont") << "FT_HAS_KERNING ? " << FT_HAS_KERNING(face); //------------------------------------------------------ nCharacters = (bFullCharacterSet ? 256 : 128) - NUM_CHARACTER_TO_START; //--------------- initialize character info and textures cps.resize(nCharacters); if(bMakeContours){ charOutlines.clear(); charOutlines.assign(nCharacters, ofTTFCharacter()); charOutlinesNonVFlipped.assign(nCharacters, ofTTFCharacter()); } vector<ofPixels> expanded_data(nCharacters); long areaSum=0; FT_Error err; //--------------------- load each char ----------------------- for (int i = 0 ; i < nCharacters; i++){ //------------------------------------------ anti aliased or not: int glyph = (unsigned char)(i+NUM_CHARACTER_TO_START); if (glyph == 0xA4) glyph = 0x20AC; // hack to load the euro sign, all codes in 8859-15 match with utf-32 except for this one err = FT_Load_Glyph( face, FT_Get_Char_Index( face, glyph ), FT_LOAD_DEFAULT ); if(err){ ofLogError("ofTrueTypeFont") << "loadFont(): FT_Load_Glyph failed for char " << i << ": FT_Error " << err; } if (bAntiAliased == true) FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); else FT_Render_Glyph(face->glyph, FT_RENDER_MODE_MONO); //------------------------------------------ FT_Bitmap& bitmap= face->glyph->bitmap; // prepare the texture: /*int width = ofNextPow2( bitmap.width + border*2 ); int height = ofNextPow2( bitmap.rows + border*2 ); // ------------------------- this is fixing a bug with small type // ------------------------- appearantly, opengl has trouble with // ------------------------- width or height textures of 1, so we // ------------------------- we just set it to 2... if (width == 1) width = 2; if (height == 1) height = 2;*/ if(bMakeContours){ if(printVectorInfo){ ofLogNotice("ofTrueTypeFont") << "character " << char(i+NUM_CHARACTER_TO_START); } //int character = i + NUM_CHARACTER_TO_START; charOutlines[i] = makeContoursForCharacter( face ); charOutlinesNonVFlipped[i] = charOutlines[i]; charOutlinesNonVFlipped[i].translate(ofVec3f(0,cps[i].height)); charOutlinesNonVFlipped[i].scale(1,-1); if(simplifyAmt>0) charOutlines[i].simplify(simplifyAmt); charOutlines[i].getTessellation(); if(simplifyAmt>0) charOutlinesNonVFlipped[i].simplify(simplifyAmt); charOutlinesNonVFlipped[i].getTessellation(); } // ------------------------- // info about the character: cps[i].character = i; cps[i].height = face->glyph->bitmap_top; cps[i].width = face->glyph->bitmap.width; cps[i].setWidth = face->glyph->advance.x >> 6; cps[i].topExtent = face->glyph->bitmap.rows; cps[i].leftExtent = face->glyph->bitmap_left; int width = cps[i].width; int height = bitmap.rows; cps[i].tW = width; cps[i].tH = height; GLint fheight = cps[i].height; GLint bwidth = cps[i].width; GLint top = cps[i].topExtent - cps[i].height; GLint lextent = cps[i].leftExtent; GLfloat corr, stretch; //this accounts for the fact that we are showing 2*visibleBorder extra pixels //so we make the size of each char that many pixels bigger stretch = 0;//(float)(visibleBorder * 2); corr = (float)(( (fontSize - fheight) + top) - fontSize); cps[i].x1 = lextent + bwidth + stretch; cps[i].y1 = fheight + corr + stretch; cps[i].x2 = (float) lextent; cps[i].y2 = -top + corr; // Allocate Memory For The Texture Data. expanded_data[i].allocate(width, height, 2); //-------------------------------- clear data: expanded_data[i].set(0,255); // every luminance pixel = 255 expanded_data[i].set(1,0); if (bAntiAliased == true){ ofPixels bitmapPixels; bitmapPixels.setFromExternalPixels(bitmap.buffer,bitmap.width,bitmap.rows,1); expanded_data[i].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(int j=0; j <bitmap.rows;j++) { unsigned char b=0; unsigned char *bptr = src; for(int k=0; k < bitmap.width ; k++){ expanded_data[i][2*(k+j*width)] = 255; if (k%8==0){ b = (*bptr++); } expanded_data[i][2*(k+j*width) + 1] = b&0x80 ? 255 : 0; b <<= 1; } src += bitmap.pitch; } //----------------------------------- } areaSum += (cps[i].width+border*2)*(cps[i].height+border*2); } vector<charProps> sortedCopy = cps; sort(sortedCopy.begin(),sortedCopy.end(),&compare_cps); // 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,2); 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 = expanded_data[sortedCopy[i].character]; if(x+sortedCopy[i].tW + border*2>w){ x = 0; y += maxRowHeight; maxRowHeight = sortedCopy[i].tH + border*2; } cps[sortedCopy[i].character].t2 = float(x + border)/float(w); cps[sortedCopy[i].character].v2 = float(y + border)/float(h); cps[sortedCopy[i].character].t1 = float(cps[sortedCopy[i].character].tW + x + border)/float(w); cps[sortedCopy[i].character].v1 = float(cps[sortedCopy[i].character].tH + y + border)/float(h); charPixels.pasteInto(atlasPixelsLuminanceAlpha,x+border,y+border); x+= sortedCopy[i].tW + border*2; } ofPixels atlasPixels; atlasPixels.allocate(atlasPixelsLuminanceAlpha.getWidth(),atlasPixelsLuminanceAlpha.getHeight(),4); atlasPixels.setChannel(0,atlasPixelsLuminanceAlpha.getChannel(0)); atlasPixels.setChannel(1,atlasPixelsLuminanceAlpha.getChannel(0)); atlasPixels.setChannel(2,atlasPixelsLuminanceAlpha.getChannel(0)); atlasPixels.setChannel(3,atlasPixelsLuminanceAlpha.getChannel(1)); texAtlas.allocate(atlasPixels,false); if(bAntiAliased && fontSize>20){ texAtlas.setTextureMinMagFilter(GL_LINEAR,GL_LINEAR); }else{ texAtlas.setTextureMinMagFilter(GL_NEAREST,GL_NEAREST); } texAtlas.loadData(atlasPixels); // ------------- close the library and typeface FT_Done_Face(face); bLoadedOk = true; return true; }
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(), 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; //--------------------- load each char ----------------------- 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; 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.44269f; int w; int h; while(!packed){ w = pow(2,floor((alpha/2.f) + 0.5f)); // 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; 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; } 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; }