//----------------------------------------------------------- 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(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; }
//------------------------------------------------------------------ void ofTrueTypeFont::loadFont(string filename, int fontsize, bool _bAntiAliased, bool _bFullCharacterSet, bool makeContours){ bMakeContours = makeContours; //------------------------------------------------ if (bLoadedOk == true){ // we've already been loaded, try to clean up : if (cps != NULL){ delete[] cps; } if (texNames != NULL){ for (int i = 0; i < nCharacters; i++){ glDeleteTextures(1, &texNames[i]); } delete[] texNames; } bLoadedOk = false; } //------------------------------------------------ filename = ofToDataPath(filename); bLoadedOk = false; bAntiAlised = _bAntiAliased; bFullCharacterSet = _bFullCharacterSet; fontSize = fontsize; //--------------- load the library and typeface FT_Library library; if (FT_Init_FreeType( &library )){ ofLog(OF_LOG_ERROR," PROBLEM WITH FT lib"); return; } FT_Face face; if (FT_New_Face( library, filename.c_str(), 0, &face )) { return; } FT_Set_Char_Size( face, fontsize << 6, fontsize << 6, 96, 96); lineHeight = fontsize * 1.43f; //------------------------------------------------------ //kerning would be great to support: //ofLog(OF_LOG_NOTICE,"FT_HAS_KERNING ? %i", FT_HAS_KERNING(face)); //------------------------------------------------------ nCharacters = bFullCharacterSet ? 256 : 128 - NUM_CHARACTER_TO_START; //--------------- initialize character info and textures cps = new charProps[nCharacters]; texNames = new GLuint[nCharacters]; glGenTextures(nCharacters, texNames); if(bMakeContours){ charOutlines.clear(); charOutlines.assign(nCharacters, ofTTFCharacter()); } //--------------------- load each char ----------------------- for (int i = 0 ; i < nCharacters; i++){ //------------------------------------------ anti aliased or not: if(FT_Load_Glyph( face, FT_Get_Char_Index( face, (unsigned char)(i+NUM_CHARACTER_TO_START) ), FT_LOAD_DEFAULT )){ ofLog(OF_LOG_ERROR,"error with FT_Load_Glyph %i", i); } if (bAntiAlised == 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; // 3 pixel border around the glyph // We show 2 pixels of this, so that blending looks good. // 1 pixels is hidden because we don't want to see the real edge of the texture border = 3; visibleBorder = 2; if(bMakeContours){ if( printVectorInfo )printf("\n\ncharacter %c: \n", char( i+NUM_CHARACTER_TO_START ) ); //int character = i + NUM_CHARACTER_TO_START; charOutlines[i] = makeContoursForCharacter( face ); } // 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; // ------------------------- // info about the character: cps[i].value = 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; // texture internals cps[i].tTex = (float)(bitmap.width + visibleBorder*2) / (float)width; cps[i].vTex = (float)(bitmap.rows + visibleBorder*2) / (float)height; cps[i].xOff = (float)(border - visibleBorder) / (float)width; cps[i].yOff = (float)(border - visibleBorder) / (float)height; /* sanity check: ofLog(OF_LOG_NOTICE,"%i %i %i %i %i %i", cps[i].value , cps[i].height , cps[i].width , cps[i].setWidth , cps[i].topExtent , cps[i].leftExtent ); */ // Allocate Memory For The Texture Data. unsigned char* expanded_data = new unsigned char[ 2 * width * height]; //-------------------------------- clear data: for(int j=0; j <height;j++) { for(int k=0; k < width; k++){ expanded_data[2*(k+j*width) ] = 255; // every luminance pixel = 255 expanded_data[2*(k+j*width)+1] = 0; } } if (bAntiAlised == true){ //----------------------------------- for(int j=0; j <height; j++) { for(int k=0; k < width; k++){ if ((k<bitmap.width) && (j<bitmap.rows)){ expanded_data[2*((k+border)+(j+border)*width)+1] = bitmap.buffer[k + bitmap.width*(j)]; } } } //----------------------------------- } 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[2*((k+1)+(j+1)*width)] = 255; if (k%8==0){ b = (*bptr++);} expanded_data[2*((k+1)+(j+1)*width) + 1] = b&0x80 ? 255 : 0; b <<= 1; } src += bitmap.pitch; } //----------------------------------- } //Now we just setup some texture paramaters. glBindTexture( GL_TEXTURE_2D, texNames[i]); #ifndef TARGET_OF_IPHONE glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); #endif if (bAntiAlised == true){ glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); } else { glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); } glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); //Here we actually create the texture itself, notice //that we are using GL_LUMINANCE_ALPHA to indicate that //we are using 2 channel data. #ifndef TARGET_OF_IPHONE // gluBuild2DMipmaps doesn't seem to exist in anything i had in the iphone build... so i commented it out bool b_use_mipmaps = false; // FOR now this is fixed to false, could be an option, left in for legacy... if (b_use_mipmaps){ gluBuild2DMipmaps( GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, width, height, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data); } else #endif { glTexImage2D( GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, width, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data ); } //With the texture created, we don't need to expanded data anymore delete [] expanded_data; } // ------------- close the library and typeface FT_Done_Face(face); FT_Done_FreeType(library); bLoadedOk = true; }
//------------------------------------------------------------------ void ofTrueTypeFont::loadFont(string filename, int fontsize, bool _bAntiAliased, bool _bFullCharacterSet, bool makeContours, float simplifyAmt){ bMakeContours = makeContours; //------------------------------------------------ if (bLoadedOk == true){ // we've already been loaded, try to clean up : unloadTextures(); } //------------------------------------------------ filename = ofToDataPath(filename); bLoadedOk = false; bAntiAlised = _bAntiAliased; bFullCharacterSet = _bFullCharacterSet; fontSize = fontsize; //--------------- load the library and typeface FT_Library library; if (FT_Init_FreeType( &library )){ ofLog(OF_LOG_ERROR," PROBLEM WITH FT lib"); return; } FT_Face face; if (FT_New_Face( library, filename.c_str(), 0, &face )) { return; } FT_Set_Char_Size( face, fontsize << 6, fontsize << 6, 96, 96); lineHeight = fontsize * 1.43f; //------------------------------------------------------ //kerning would be great to support: //ofLog(OF_LOG_NOTICE,"FT_HAS_KERNING ? %i", 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()); } vector<ofPixels> expanded_data(nCharacters); long areaSum=0; //--------------------- load each char ----------------------- for (int i = 0 ; i < nCharacters; i++){ //------------------------------------------ anti aliased or not: if(FT_Load_Glyph( face, FT_Get_Char_Index( face, (unsigned char)(i+NUM_CHARACTER_TO_START) ), FT_LOAD_DEFAULT )){ ofLog(OF_LOG_ERROR,"error with FT_Load_Glyph %i", i); } if (bAntiAlised == 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 )printf("\n\ncharacter %c: \n", char( i+NUM_CHARACTER_TO_START ) ); //int character = i + NUM_CHARACTER_TO_START; charOutlines[i] = makeContoursForCharacter( face ); if(simplifyAmt>0) charOutlines[i].simplify(simplifyAmt); charOutlines[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 (bAntiAlised == 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 //cout << areaSum << endl; 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 atlasPixels; atlasPixels.allocate(w,h,2); atlasPixels.set(0,255); atlasPixels.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(atlasPixels,x+border,y+border); x+= sortedCopy[i].tW + border*2; } texAtlas.allocate(atlasPixels.getWidth(),atlasPixels.getHeight(),GL_LUMINANCE_ALPHA,false); if(bAntiAlised && fontsize>14){ texAtlas.setTextureMinMagFilter(GL_LINEAR,GL_LINEAR); }else{ texAtlas.setTextureMinMagFilter(GL_NEAREST,GL_NEAREST); } texAtlas.loadData(atlasPixels.getPixels(),atlasPixels.getWidth(),atlasPixels.getHeight(),GL_LUMINANCE_ALPHA); // ------------- close the library and typeface FT_Done_Face(face); FT_Done_FreeType(library); bLoadedOk = 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; }
//----------------------------------------------------------- void ofTrueTypeFontWS::loadEachChar(int char_id) { //----------------------- error checking if (!bLoadedOk){ ofLog(OF_LOG_ERROR,"Error : font not allocated -- line %d in %s", __LINE__,__FILE__); return; } int i = char_id; //------------------------------------------ anti aliased or not: if(FT_Load_Glyph( face, FT_Get_Char_Index( face, (int)(loaded_chars[i]+NUM_CHARACTER_TO_START) ), FT_LOAD_DEFAULT )){ ofLog(OF_LOG_ERROR,"error with FT_Load_Glyph %i", loaded_chars[i]); } if (bAntiAlised == 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; // 3 pixel border around the glyph // We show 2 pixels of this, so that blending looks good. // 1 pixels is hidden because we don't want to see the real edge of the texture border = 3; visibleBorder = 2; if(bMakeContours){ if( printVectorInfo )printf("\n\ncharacter %c: \n", wchar_t( loaded_chars[i]+NUM_CHARACTER_TO_START ) ); //int character = i + NUM_CHARACTER_TO_START; charOutlines[i] = makeContoursForCharacter( face ); } // 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; // ------------------------- // info about the character: cps[i].value = loaded_chars[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; // texture internals cps[i].tTex = (float)(bitmap.width + visibleBorder*2) / (float)width; cps[i].vTex = (float)(bitmap.rows + visibleBorder*2) / (float)height; cps[i].xOff = (float)(border - visibleBorder) / (float)width; cps[i].yOff = (float)(border - visibleBorder) / (float)height; /* sanity check: ofLog(OF_LOG_NOTICE,"%i %i %i %i %i %i", cps[i].value , cps[i].height , cps[i].width , cps[i].setWidth , cps[i].topExtent , cps[i].leftExtent ); */ // Allocate Memory For The Texture Data. unsigned char* expanded_data = new unsigned char[ 2 * width * height]; //-------------------------------- clear data: for(int j=0; j <height;j++) { for(int k=0; k < width; k++){ expanded_data[2*(k+j*width) ] = 255; // every luminance pixel = 255 expanded_data[2*(k+j*width)+1] = 0; } } if (bAntiAlised == true){ //----------------------------------- for(int j=0; j <height; j++) { for(int k=0; k < width; k++){ if ((k<bitmap.width) && (j<bitmap.rows)){ expanded_data[2*((k+border)+(j+border)*width)+1] = bitmap.buffer[k + bitmap.width*(j)]; } } } //----------------------------------- } 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[2*((k+1)+(j+1)*width)] = 255; if (k%8==0){ b = (*bptr++);} expanded_data[2*((k+1)+(j+1)*width) + 1] = b&0x80 ? 255 : 0; b <<= 1; } src += bitmap.pitch; } //----------------------------------- } //Now we just setup some texture paramaters. glBindTexture( GL_TEXTURE_2D, texNames[i]); #ifndef TARGET_OF_IPHONE glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); #endif if (bAntiAlised == true){ glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); } else { glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); } glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); //Here we actually create the texture itself, notice //that we are using GL_LUMINANCE_ALPHA to indicate that //we are using 2 channel data. #ifndef TARGET_OF_IPHONE // gluBuild2DMipmaps doesn't seem to exist in anything i had in the iphone build... so i commented it out bool b_use_mipmaps = false; // FOR now this is fixed to false, could be an option, left in for legacy... if (b_use_mipmaps){ gluBuild2DMipmaps( GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, width, height, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data); } else #endif { glTexImage2D( GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, width, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data ); } //With the texture created, we don't need to expanded data anymore delete [] expanded_data; }
//----------------------------------------------------------- bool ofxSosoTrueTypeFont::loadFont(string filename, int fontsize, bool _bAntiAliased, bool _bFullCharacterSet, bool makeContours, bool makeMipMaps, float simplifyAmt, int dpi){ //soso - added makeMipMaps (see below) bMakeContours = makeContours; //------------------------------------------------ if (bLoadedOk == true){ // we've already been loaded, try to clean up : unloadTextures(); } //------------------------------------------------ if( dpi == 0 ){ dpi = ttfGlobalDpi; } filename = ofToDataPath(filename); bLoadedOk = false; bAntiAliased = _bAntiAliased; bFullCharacterSet = _bFullCharacterSet; fontSize = fontsize; //--------------- load the library and typeface FT_Error err; FT_Library library; if (err = FT_Init_FreeType( &library )){ ofLog(OF_LOG_ERROR,"ofTrueTypeFont::loadFont - Error initializing freetype lib: FT_Error = %d", err); return false; } FT_Face face; if (err = FT_New_Face( library, filename.c_str(), 0, &face )) { // simple error table in lieu of full table (see fterrors.h) string errorString = "unknown freetype"; if(err == 1) errorString = "INVALID FILENAME"; ofLog(OF_LOG_ERROR,"ofTrueTypeFont::loadFont - %s: %s: FT_Error = %d", errorString.c_str(), filename.c_str(), err); return false; } //FT_Set_Char_Size( face, fontsize << 6, fontsize << 6, dpi, dpi); //of //FT_Set_Char_Size( face, 0, fontsize*dpi, 0, dpi); //soso FT_Set_Char_Size( face, 0, fontsize*64, 0, dpi); //soso lineHeight = fontsize * 1.43f; //------------------------------------------------------ //kerning would be great to support: //ofLog(OF_LOG_NOTICE,"FT_HAS_KERNING ? %i", FT_HAS_KERNING(face)); //------------------------------------------------------ //nCharacters = bFullCharacterSet ? 256 : 128 - NUM_CHARACTER_TO_START; nCharacters = bFullCharacterSet ? 512 : 128 - NUM_CHARACTER_TO_START; //--------------- initialize character info and textures cps.resize(nCharacters); if(bMakeContours){ charOutlines.clear(); charOutlines.assign(nCharacters, ofTTFCharacter()); } vector<ofPixels> expanded_data(nCharacters); long areaSum=0; //--------------------- load each char ----------------------- for (int i = 0 ; i < nCharacters; i++){ //------------------------------------------ anti aliased or not: //if(err = FT_Load_Glyph( face, FT_Get_Char_Index( face, (unsigned char)(i+NUM_CHARACTER_TO_START) ), FT_LOAD_DEFAULT )){ if(err = FT_Load_Glyph( face, getFTCharIndex( face, (unsigned char)(i+NUM_CHARACTER_TO_START) ), FT_LOAD_DEFAULT )){ //soso replaced FT_Get_Char_Index with our custom version ofLog(OF_LOG_ERROR,"ofTrueTypeFont::loadFont - Error with FT_Load_Glyph %i: FT_Error = %d", i, 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 )printf("\n\ncharacter %c: \n", char( i+NUM_CHARACTER_TO_START ) ); //int character = i + NUM_CHARACTER_TO_START; charOutlines[i] = makeContoursForCharacter( face ); if(simplifyAmt>0) charOutlines[i].simplify(simplifyAmt); charOutlines[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 //cout << areaSum << endl; 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 atlasPixels; atlasPixels.allocate(w,h,2); atlasPixels.set(0,255); atlasPixels.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(atlasPixels,x+border,y+border); x+= sortedCopy[i].tW + border*2; } texAtlas.allocate(atlasPixels.getWidth(),atlasPixels.getHeight(),GL_LUMINANCE_ALPHA,false); if(bAntiAliased && fontsize>20){ if (makeMipMaps) { //soso //texAtlas.enableMipmaps(); //texAtlas.setTextureMinMagFilter(GL_LINEAR_MIPMAP_LINEAR,GL_LINEAR_MIPMAP_LINEAR); //soso } else //soso texAtlas.setTextureMinMagFilter(GL_LINEAR,GL_LINEAR); }else{ texAtlas.setTextureMinMagFilter(GL_NEAREST,GL_NEAREST); } texAtlas.loadData(atlasPixels.getPixels(),atlasPixels.getWidth(),atlasPixels.getHeight(),GL_LUMINANCE_ALPHA); ///////////////////////////////////////////////////////////////////////sosoAddon //until ofTexture fully supports mipmaps, we gotta do it manually here - AFTER loadData is called on the texture //it's a redo of what happens inside tex.loadData(), but instead we build the mipmaps if(makeMipMaps){ glEnable(texAtlas.getTextureData().textureTarget); glBindTexture(texAtlas.getTextureData().textureTarget, (GLuint) texAtlas.getTextureData().textureID); glTexParameteri(texAtlas.getTextureData().textureTarget, GL_GENERATE_MIPMAP_SGIS, true); glTexParameteri( texAtlas.getTextureData().textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri( texAtlas.getTextureData().textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri( texAtlas.getTextureData().textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri( texAtlas.getTextureData().textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); gluBuild2DMipmaps(texAtlas.getTextureData().textureTarget, texAtlas.getTextureData().glType, w, h, texAtlas.getTextureData().glType, texAtlas.getTextureData().pixelType, atlasPixels.getPixels()); glDisable(texAtlas.getTextureData().textureTarget); } ////////////////////////////////////////////////////////////////////// //Sosolimited - load kerning pairs //initialize all pairs to 0 for (int i = 0; i < FONT_NUM_CHARS; i++) { for (int j = 0; j < FONT_NUM_CHARS; j++) { kerningPairs[i][j] = 0; } } //find out if the face has kerning FT_Bool use_kerning = (FT_Bool)FT_HAS_KERNING(face); if(use_kerning) printf("ofxSosoTrueTypeFont::loadFont() - kerning is supported\n"); else printf("ofxSosoTrueTypeFont::loadFont() - kerning is NOT supported\n"); FT_UInt glyph_index_r, glyph_index_l; for (int i = 0; i < FONT_NUM_CHARS; i++) { // convert character code to glyph index glyph_index_r = FT_Get_Char_Index(face, i + NUM_CHARACTER_TO_START); for (int j = 0; j < FONT_NUM_CHARS; j++) { // convert character code to glyph index glyph_index_l = FT_Get_Char_Index(face, j + NUM_CHARACTER_TO_START); // retrieve kerning distance if (use_kerning && glyph_index_l && glyph_index_r) { FT_Vector delta; FT_Get_Kerning( face, glyph_index_l, glyph_index_r, FT_KERNING_DEFAULT, &delta ); kerningPairs[i][j] = delta.x >> 6; //if(i<127) //if(fabs((float)kerningPairs[i][j]) > 0) printf("kerningPairs: %c%c = %d, delta = %d\n", i + NUM_CHARACTER_TO_START, j + NUM_CHARACTER_TO_START, kerningPairs[i][j], delta.x); } } }
//-------------------------------------------------------------- void ofxFreeType2::loadFont(string filepath, int size, bool _bAntiAlias, bool _bFullCharacterSet, bool _bMakeContours) { //Init vars fontSize = size; lineHeight = fontSize; bAntiAlias = _bAntiAlias; bFullCharacterSet = _bFullCharacterSet; bMakeContours = _bMakeContours; //Clear chars if a font has already been loaded if (bFaceLoaded){ if (chars != NULL){ delete[] chars; } if (charTextures != NULL){ for (int i = 0; i < nChars; i++){ glDeleteTextures(1, &charTextures[i]); } delete[] charTextures; } } bFaceLoaded = false; //FreeType Vars FT_Library library; FT_Face face; FT_Error error; //Init library if (FT_Init_FreeType(&library)) { ofLog(OF_LOG_ERROR,"Freetype Error: Unable to Initialize Library"); return; } //Load Font Face error = FT_New_Face(library, ofToDataPath(filepath).c_str(), 0, &face ); if (error == FT_Err_Unknown_File_Format) { //Font opened but unsupported ofLog(OF_LOG_ERROR,"Freetype Error: Unknown Format"); return; } else if (error) { //Font can not be read/is broken ofLog(OF_LOG_ERROR,"Freetype Error: Couldn't open font."); return; } //Set Size (pts or pixels) if(resolution) error = FT_Set_Char_Size(face, 0, (float(size) * (72.0/float(resolution)))*64.0, resolution, resolution); else error = FT_Set_Pixel_Sizes(face, size, size); if(error) { ofLog(OF_LOG_ERROR,"Freetype Error: Couldn't set font size. Font may be fixed width?"); return; } //Get total Chars from Face nChars = bFullCharacterSet ? 256 : 128; nChars -= START_CHAR; //Init arrays, generate textures chars = new charInfo[nChars]; charTextures = new GLuint[nChars]; glGenTextures(nChars, charTextures); //Clear/Create contour array if necessary if(bMakeContours){ charOutlines.clear(); charOutlines.assign(nChars, ofTTCharacter()); } //Create Characters FT_Glyph glyph; FT_BBox bbox; for(int i=START_CHAR; i<nChars+START_CHAR; i++) { //Load Glyph int charIndex = i - START_CHAR; if(bAntiAlias) { error = FT_Load_Char(face, i, FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT); } else { error = FT_Load_Char(face, i, FT_LOAD_RENDER | FT_LOAD_MONOCHROME); } if(error) { ofLog(OF_LOG_ERROR,"Freetype Error: Couldn't load glyph."); return; } //Create Outline if necessary if(bMakeContours){ if( printVectorInfo )printf("\n\ncharacter %c: \n", char( i ) ); //int character = i + NUM_CHARACTER_TO_START; charOutlines[charIndex] = makeContoursForCharacter( face ); } //Get Size of Bitmap, convert from fractional pixel format float bmpW = face->glyph->metrics.width >> 6; float bmpH = face->glyph->metrics.height >> 6; //Create Texture Sizes //Add 2px to avoid edges on either side int width = ofNextPow2(bmpW+1); int height = ofNextPow2(bmpH+1); if (width == 1) width = 2; if (height == 1) height = 2; //Set Info for This Glyph FT_Get_Glyph(face->glyph, &glyph ); FT_Glyph_Get_CBox(glyph, 0, &bbox ); float xMin = bbox.xMin >> 6; float xMax = bbox.xMax >> 6; float yMin = bbox.yMin >> 6; float yMax = bbox.yMax >> 6; chars[charIndex].value = i; chars[charIndex].width = xMax - xMin; chars[charIndex].height = yMax - yMin; chars[charIndex].left = face->glyph->bitmap_left; chars[charIndex].top = yMax; chars[charIndex].bottom = yMin; chars[charIndex].advance = face->glyph->metrics.horiAdvance >> 6; //Set the position of the texture, centered with pow2 tex chars[charIndex].texWidth = bmpW; chars[charIndex].texHeight = bmpH; chars[charIndex].texXDiff = (float)bmpW/(float)width; chars[charIndex].texYDiff = (float)bmpH/(float)height; // Allocate Memory For The Texture Data. unsigned char* expanded_data = new unsigned char[2 * width * height]; //Clear Texture Data for(int j=0; j <height;j++) { for(int k=0; k < width; k++){ expanded_data[2*(k+(j*width))] = 255; // every luminance pixel = 255 expanded_data[2*(k+(j*width))+1] = 0; } } //Create texture from bitmap of glyph FT_Bitmap& bitmap= face->glyph->bitmap; if (bAntiAlias){ //----------------------------------- for(int j=0; j <height; j++) { for(int k=0; k < width; k++){ if ((k<bitmap.width) && (j<bitmap.rows)){ //Offset pixels into texture by 1px to avoid edges //expanded_data[2*(((j+1)*width)+k +1) + 1] = bitmap.buffer[k + bitmap.width*(j)]; int thisPixel = (((j * width) + k) * 2) + 1; expanded_data[thisPixel] = bitmap.buffer[k + bitmap.width*(j)]; } } } //----------------------------------- } 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++){ if (k%8==0) b=(*bptr++); //Offset pixels into texture by 1px to avoid edges expanded_data[2*(k+j*width)+1] = b&0x80 ? 255 : 0; b <<= 1; } src += bitmap.pitch; } //----------------------------------- } //Now we just setup some texture paramaters. glBindTexture( GL_TEXTURE_2D, charTextures[charIndex]); if (bAntiAlias){ glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); } else { glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); } glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); //Here we actually create the texture itself, notice //that we are using GL_LUMINANCE_ALPHA to indicate that //we are using 2 channel data. glTexImage2D( GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, width, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data ); //Clear Expanded Data delete [] expanded_data; } bFaceLoaded = true; }