/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield * * This library is open source and may be redistributed and/or modified under * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or * (at your option) any later version. The full license is in LICENSE file * included with this distribution, and on the openscenegraph.org website. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * OpenSceneGraph Public License for more details. */ #include #include #include #include #include #include #include #include #include using namespace osgText; using namespace std; static osg::ApplicationUsageProxy Font_e0(osg::ApplicationUsage::ENVIRONMENTAL_VARIABLE,"OSG_TEXT_INCREMENTAL_SUBLOADING ","ON | OFF"); std::string osgText::findFontFile(const std::string& str) { // try looking in OSGFILEPATH etc first for fonts. std::string filename = osgDB::findDataFile(str); if (!filename.empty()) return filename; static osgDB::FilePathList s_FontFilePath; static bool initialized = false; if (!initialized) { initialized = true; #if defined(WIN32) osgDB::convertStringPathIntoFilePathList( ".;C:/winnt/fonts;C:/windows/fonts", s_FontFilePath); char *ptr; if ((ptr = getenv( "windir" ))) { std::string winFontPath = ptr; winFontPath += "\\fonts"; s_FontFilePath.push_back(winFontPath); } #else osgDB::convertStringPathIntoFilePathList( ".:/usr/share/fonts/ttf:/usr/share/fonts/ttf/western:/usr/share/fonts/ttf/decoratives", s_FontFilePath); #endif } filename = osgDB::findFileInPath(str,s_FontFilePath); if (!filename.empty()) return filename; // Try filename without pathname, if it has a path filename = osgDB::getSimpleFileName(str); if(filename!=str) { filename = osgDB::findFileInPath(filename,s_FontFilePath); if (!filename.empty()) return filename; } else { filename = osgText::findFontFile(std::string("fonts/")+filename); if (!filename.empty()) return filename; } // Not found, return empty string osg::notify(osg::WARN)<<"Warning: font file \""< options = new osgDB::ReaderWriter::Options; options->setObjectCacheHint(osgDB::ReaderWriter::Options::CACHE_OBJECTS); osg::Object* object = osgDB::readObjectFile(foundFile, options.get()); // if the object is a font then return it. osgText::Font* font = dynamic_cast(object); if (font) return font; // otherwise if the object has zero references then delete it by doing another unref(). if (object && object->referenceCount()==0) object->unref(); return 0; } osgText::Font* osgText::readFontStream(std::istream& stream) { osg::ref_ptr options = new osgDB::ReaderWriter::Options; options->setObjectCacheHint(osgDB::ReaderWriter::Options::CACHE_OBJECTS); // there should be a better way to get the FreeType ReaderWriter by name... osgDB::ReaderWriter *reader = osgDB::Registry::instance()->getReaderWriterForExtension("ttf"); if (reader == 0) return 0; osgDB::ReaderWriter::ReadResult rr = reader->readObject(stream, options.get()); if (rr.error()) { osg::notify(osg::WARN) << rr.message() << std::endl; return 0; } if (!rr.validObject()) return 0; osg::Object *object = rr.takeObject(); // if the object is a font then return it. osgText::Font* font = dynamic_cast(object); if (font) return font; // otherwise if the object has zero references then delete it by doing another unref(). if (object && object->referenceCount()==0) object->unref(); return 0; } Font::Font(FontImplementation* implementation): _width(16), _height(16), _margin(2), _textureWidthHint(512), _textureHeightHint(512), _minFilterHint(osg::Texture::LINEAR_MIPMAP_LINEAR), _magFilterHint(osg::Texture::LINEAR) { setImplementation(implementation); _texenv = new osg::TexEnv; _stateset = new osg::StateSet; _stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); } Font::~Font() { if (_implementation.valid()) _implementation->_facade = 0; } void Font::setImplementation(FontImplementation* implementation) { if (_implementation.valid()) _implementation->_facade = 0; _implementation = implementation; if (_implementation.valid()) _implementation->_facade = this; } Font::FontImplementation* Font::getImplementation() { return _implementation.get(); } const Font::FontImplementation* Font::getImplementation() const { return _implementation.get(); } std::string Font::getFileName() const { if (_implementation.valid()) return _implementation->getFileName(); return ""; } void Font::setFontResolution(unsigned int width, unsigned int height) { if (_implementation.valid()) _implementation->setFontResolution(width, height); } unsigned int Font::getFontWidth() const { return _width; } unsigned int Font::getFontHeight() const { return _height; } void Font::setGlyphImageMargin(unsigned int margin) { _margin = margin; } unsigned int Font::getGlyphImageMargin() const { return _margin; } void Font::setTextureSizeHint(unsigned int width,unsigned int height) { _textureWidthHint = width; _textureHeightHint = height; } unsigned int Font::getTextureWidthHint() const { return _textureWidthHint; } unsigned int Font::getTextureHeightHint() const { return _textureHeightHint; } void Font::setMinFilterHint(osg::Texture::FilterMode mode) { _minFilterHint = mode; } osg::Texture::FilterMode Font::getMinFilterHint() const { return _minFilterHint; } /** Set the magnification texture filter to use when creating the texture to store the glyph images when rendering. * Note, this doesn't affect already created Texture Glhph's.*/ void Font::setMagFilterHint(osg::Texture::FilterMode mode) { _magFilterHint = mode; } osg::Texture::FilterMode Font::getMagFilterHint() const { return _magFilterHint; } Font::Glyph* Font::getGlyph(unsigned int charcode) { SizeGlyphMap::iterator itr = _sizeGlyphMap.find(SizePair(_width,_height)); if (itr!=_sizeGlyphMap.end()) { GlyphMap& glyphmap = itr->second; GlyphMap::iterator gitr = glyphmap.find(charcode); if (gitr!=glyphmap.end()) return gitr->second.get(); } if (_implementation.valid()) return _implementation->getGlyph(charcode); else return 0; } void Font::setThreadSafeRefUnref(bool threadSafe) { Object::setThreadSafeRefUnref(threadSafe); if (_texenv.valid()) _texenv->setThreadSafeRefUnref(threadSafe); if (_stateset.valid()) _stateset->setThreadSafeRefUnref(threadSafe); for(GlyphTextureList::const_iterator itr=_glyphTextureList.begin(); itr!=_glyphTextureList.end(); ++itr) { (*itr)->setThreadSafeRefUnref(threadSafe); } } void Font::resizeGLObjectBuffers(unsigned int maxSize) { if (_stateset.valid()) _stateset->resizeGLObjectBuffers(maxSize); for(GlyphTextureList::const_iterator itr=_glyphTextureList.begin(); itr!=_glyphTextureList.end(); ++itr) { (*itr)->resizeGLObjectBuffers(maxSize); } } void Font::releaseGLObjects(osg::State* state) const { if (_stateset.valid()) _stateset->releaseGLObjects(state); for(GlyphTextureList::const_iterator itr=_glyphTextureList.begin(); itr!=_glyphTextureList.end(); ++itr) { (*itr)->releaseGLObjects(state); } // const_cast(this)->_glyphTextureList.clear(); // const_cast(this)->_sizeGlyphMap.clear(); } osg::Vec2 Font::getKerning(unsigned int leftcharcode,unsigned int rightcharcode, KerningType kerningType) { if (_implementation.valid()) return _implementation->getKerning(leftcharcode,rightcharcode,kerningType); else return osg::Vec2(0.0f,0.0f); } bool Font::hasVertical() const { if (_implementation.valid()) return _implementation->hasVertical(); else return false; } void Font::addGlyph(unsigned int width, unsigned int height, unsigned int charcode, Glyph* glyph) { _sizeGlyphMap[SizePair(width,height)][charcode]=glyph; int posX=0,posY=0; GlyphTexture* glyphTexture = 0; for(GlyphTextureList::iterator itr=_glyphTextureList.begin(); itr!=_glyphTextureList.end() && !glyphTexture; ++itr) { if ((*itr)->getSpaceForGlyph(glyph,posX,posY)) glyphTexture = itr->get(); } if (glyphTexture) { //cout << " found space for texture "<setGlyphImageMargin(_margin); glyphTexture->setTextureSize(_textureWidthHint,_textureHeightHint); glyphTexture->setFilter(osg::Texture::MIN_FILTER,_minFilterHint); glyphTexture->setFilter(osg::Texture::MAG_FILTER,_magFilterHint); glyphTexture->setMaxAnisotropy(8); _glyphTextureList.push_back(glyphTexture); if (!glyphTexture->getSpaceForGlyph(glyph,posX,posY)) { osg::notify(osg::WARN)<<"Warning: unable to allocate texture big enough for glyph"<addGlyph(glyph,posX,posY); } Font::GlyphTexture::GlyphTexture(): _margin(2), _usedY(0), _partUsedX(0), _partUsedY(0) { } Font::GlyphTexture::~GlyphTexture() { } // return -1 if *this < *rhs, 0 if *this==*rhs, 1 if *this>*rhs. int Font::GlyphTexture::compare(const osg::StateAttribute& rhs) const { if (this<&rhs) return -1; else if (this>&rhs) return 1; return 0; } bool Font::GlyphTexture::getSpaceForGlyph(Glyph* glyph, int& posX, int& posY) { int width = glyph->s()+2*_margin; int height = glyph->t()+2*_margin; // first check box (_partUsedX,_usedY) to (width,height) if (width <= (getTextureWidth()-_partUsedX) && height <= (getTextureHeight()-_usedY)) { // can fit in existing row. // record the position in which the texture will be stored. posX = _partUsedX+_margin; posY = _usedY+_margin; // move used markers on. _partUsedX += width; if (_usedY+height>_partUsedY) _partUsedY = _usedY+height; return true; } // start an new row. if (width <= getTextureWidth() && height <= (getTextureHeight()-_partUsedY)) { // can fit next row. _partUsedX = 0; _usedY = _partUsedY; posX = _partUsedX+_margin; posY = _usedY+_margin; // move used markers on. _partUsedX += width; if (_usedY+height>_partUsedY) _partUsedY = _usedY+height; return true; } // doesn't fit into glyph. return false; } void Font::GlyphTexture::addGlyph(Glyph* glyph, int posX, int posY) { _glyphs.push_back(glyph); for(unsigned int i=0;i<_glyphsToSubload.size();++i) { _glyphsToSubload[i].push_back(glyph); } // set up the details of where to place glyph's image in the texture. glyph->setTexture(this); glyph->setTexturePosition(posX,posY); unsigned int sizeAdjustment = 0; // was 1. glyph->setMinTexCoord(osg::Vec2((float)(posX+_margin)/(float)(getTextureWidth()-sizeAdjustment),(float)(posY+_margin)/(float)(getTextureHeight()-sizeAdjustment))); glyph->setMaxTexCoord(osg::Vec2((float)(posX+glyph->s()-_margin)/(float)(getTextureWidth()-sizeAdjustment),(float)(posY+glyph->t()-_margin)/(float)(getTextureHeight()-sizeAdjustment))); } void Font::GlyphTexture::apply(osg::State& state) const { // get the contextID (user defined ID of 0 upwards) for the // current OpenGL context. const unsigned int contextID = state.getContextID(); if (contextID>=_glyphsToSubload.size()) { // graphics context is beyond the number of glyphsToSubloads, so // we must now copy the glyph list across, this is a potential // threading issue though is multiple applies are happening the // same time on this object - to avoid this condition number of // graphics contexts should be set before create text. for(unsigned int i=_glyphsToSubload.size();i<=contextID;++i) { GlyphPtrList& glyphPtrs = _glyphsToSubload[i]; for(GlyphRefList::const_iterator itr=_glyphs.begin(); itr!=_glyphs.end(); ++itr) { glyphPtrs.push_back(itr->get()); } } } const Extensions* extensions = getExtensions(contextID,true); bool generateMipMapSupported = extensions->isGenerateMipMapSupported(); // get the texture object for the current contextID. TextureObject* textureObject = getTextureObject(contextID); bool newTextureObject = (textureObject == 0); if (newTextureObject) { // being bound for the first time, need to allocate the texture _textureObjectBuffer[contextID] = textureObject = osg::Texture::generateTextureObject( contextID,GL_TEXTURE_2D,1,GL_ALPHA,getTextureWidth(), getTextureHeight(),1,0); textureObject->bind(); applyTexParameters(GL_TEXTURE_2D,state); // need to look at generate mip map extension if mip mapping required. switch(_min_filter) { case NEAREST_MIPMAP_NEAREST: case NEAREST_MIPMAP_LINEAR: case LINEAR_MIPMAP_NEAREST: case LINEAR_MIPMAP_LINEAR: if (generateMipMapSupported) { glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS,GL_TRUE); } else glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, LINEAR); break; default: // not mip mapping so no problems. break; } // allocate the texture memory. glTexImage2D( GL_TEXTURE_2D, 0, GL_ALPHA, getTextureWidth(), getTextureHeight(), 0, GL_ALPHA, GL_UNSIGNED_BYTE, 0 ); } else { // reuse texture by binding. textureObject->bind(); if (getTextureParameterDirty(contextID)) { applyTexParameters(GL_TEXTURE_2D,state); } } static const GLubyte* s_renderer = 0; static bool s_subloadAllGlyphsTogether = false; if (!s_renderer) { s_renderer = glGetString(GL_RENDERER); osg::notify(osg::INFO)<<"glGetString(GL_RENDERER)=="<subload(); } } else // just subload the new entries. { // default way of subloading as required. //std::cout<<"subloading"<subload(); } } // clear the list since we have now subloaded them. glyphsWereSubloading.clear(); } else { osg::notify(osg::INFO)<<"osgText::Font loading all glyphs as a single subload."<subload(); // Rather than subloading to graphics, we'll write the values // of the glyphs into some intermediate data and subload the // whole thing at the end for( int t = 0; t < (*itr)->t(); t++ ) { for( int s = 0; s < (*itr)->s(); s++ ) { int sindex = (t*(*itr)->s()+s); int dindex = ((((*itr)->getTexturePositionY()+t) * getTextureWidth()) + ((*itr)->getTexturePositionX()+s)); const unsigned char *sptr = &(*itr)->data()[sindex]; unsigned char *dptr = &local_data[dindex]; (*dptr) = (*sptr); } } } // clear the list since we have now subloaded them. glyphsWereSubloading.clear(); // Subload the image once glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, getTextureWidth(), getTextureHeight(), GL_ALPHA, GL_UNSIGNED_BYTE, local_data ); delete [] local_data; } } else { // osg::notify(osg::INFO) << "no need to subload "<