/************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 * only, as published by the Free Software Foundation. * * OpenOffice.org 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 * GNU Lesser General Public License version 3 for more details * (a copy is included in the LICENSE file that accompanied this code). * * You should have received a copy of the GNU Lesser General Public License * version 3 along with OpenOffice.org. If not, see * * for a copy of the LGPLv3 License. * ************************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_vcl.hxx" #include #include #include #include #include #include #include #include #ifdef ENABLE_GRAPHITE #include #endif #include // used only for string=>hashvalue #include #include // ======================================================================= // GlyphCache // ======================================================================= static GlyphCache* pInstance = NULL; GlyphCache::GlyphCache( GlyphCachePeer& rPeer ) : mrPeer( rPeer ), mnMaxSize( 1500000 ), mnBytesUsed(sizeof(GlyphCache)), mnLruIndex(0), mnGlyphCount(0), mpCurrentGCFont(NULL), mpFtManager(NULL) { pInstance = this; mpFtManager = new FreetypeManager; } // ----------------------------------------------------------------------- GlyphCache::~GlyphCache() { InvalidateAllGlyphs(); if( mpFtManager ) delete mpFtManager; } // ----------------------------------------------------------------------- void GlyphCache::InvalidateAllGlyphs() { #if 0 // TODO: implement uncaching of all glyph shapes and metrics for( FontList::iterator it = maFontList.begin(); it != maFontList.end(); ++it ) delete const_cast( it->second ); maFontList.clear(); mpCurrentGCFont = NULL; #endif } // ----------------------------------------------------------------------- inline size_t GlyphCache::IFSD_Hash::operator()( const ImplFontSelectData& rFontSelData ) const { // TODO: is it worth to improve this hash function? sal_IntPtr nFontId = reinterpret_cast( rFontSelData.mpFontData ); #ifdef ENABLE_GRAPHITE if (rFontSelData.maTargetName.Search(grutils::GrFeatureParser::FEAT_PREFIX) != STRING_NOTFOUND) { rtl::OString aFeatName = rtl::OUStringToOString( rFontSelData.maTargetName, RTL_TEXTENCODING_UTF8 ); nFontId ^= aFeatName.hashCode(); } #endif size_t nHash = nFontId << 8; nHash += rFontSelData.mnHeight; nHash += rFontSelData.mnOrientation; nHash += rFontSelData.mbVertical; nHash += rFontSelData.meItalic; nHash += rFontSelData.meWeight; #ifdef ENABLE_GRAPHITE nHash += rFontSelData.meLanguage; #endif return nHash; } // ----------------------------------------------------------------------- bool GlyphCache::IFSD_Equal::operator()( const ImplFontSelectData& rA, const ImplFontSelectData& rB) const { // check font ids sal_IntPtr nFontIdA = reinterpret_cast( rA.mpFontData ); sal_IntPtr nFontIdB = reinterpret_cast( rB.mpFontData ); if( nFontIdA != nFontIdB ) return false; // compare with the requested metrics if( (rA.mnHeight != rB.mnHeight) || (rA.mnOrientation != rB.mnOrientation) || (rA.mbVertical != rB.mbVertical) || (rA.mbNonAntialiased != rB.mbNonAntialiased) ) return false; if( (rA.meItalic != rB.meItalic) || (rA.meWeight != rB.meWeight) ) return false; // NOTE: ignoring meFamily deliberately // compare with the requested width, allow default width if( (rA.mnWidth != rB.mnWidth) && ((rA.mnHeight != rB.mnWidth) || (rA.mnWidth != 0)) ) return false; #ifdef ENABLE_GRAPHITE if (rA.meLanguage != rB.meLanguage) return false; // check for features if ((rA.maTargetName.Search(grutils::GrFeatureParser::FEAT_PREFIX) != STRING_NOTFOUND || rB.maTargetName.Search(grutils::GrFeatureParser::FEAT_PREFIX) != STRING_NOTFOUND) && rA.maTargetName != rB.maTargetName) return false; #endif return true; } // ----------------------------------------------------------------------- GlyphCache& GlyphCache::GetInstance() { return *pInstance; } // ----------------------------------------------------------------------- void GlyphCache::LoadFonts() { if( const char* pFontPath = ::getenv( "SAL_FONTPATH_PRIVATE" ) ) AddFontPath( String::CreateFromAscii( pFontPath ) ); const String& rFontPath = Application::GetFontPath(); if( rFontPath.Len() > 0 ) AddFontPath( rFontPath ); } // ----------------------------------------------------------------------- void GlyphCache::ClearFontPath() { if( mpFtManager ) mpFtManager->ClearFontList(); } // ----------------------------------------------------------------------- void GlyphCache::AddFontPath( const String& rFontPath ) { if( !mpFtManager ) return; for( xub_StrLen nBreaker1 = 0, nBreaker2 = 0; nBreaker2 != STRING_LEN; nBreaker1 = nBreaker2 + 1 ) { nBreaker2 = rFontPath.Search( ';', nBreaker1 ); if( nBreaker2 == STRING_NOTFOUND ) nBreaker2 = STRING_LEN; ::rtl::OUString aUrlName; osl::FileBase::getFileURLFromSystemPath( rFontPath.Copy( nBreaker1, nBreaker2 ), aUrlName ); mpFtManager->AddFontDir( aUrlName ); } } // ----------------------------------------------------------------------- void GlyphCache::AddFontFile( const rtl::OString& rNormalizedName, int nFaceNum, sal_IntPtr nFontId, const ImplDevFontAttributes& rDFA, const ExtraKernInfo* pExtraKern ) { if( mpFtManager ) mpFtManager->AddFontFile( rNormalizedName, nFaceNum, nFontId, rDFA, pExtraKern ); } // ----------------------------------------------------------------------- void GlyphCache::AnnounceFonts( ImplDevFontList* pList ) const { if( mpFtManager ) mpFtManager->AnnounceFonts( pList ); // VirtDevServerFont::AnnounceFonts( pList ); } // ----------------------------------------------------------------------- ServerFont* GlyphCache::CacheFont( const ImplFontSelectData& rFontSelData ) { // a serverfont request has pFontData if( rFontSelData.mpFontData == NULL ) return NULL; // a serverfont request has a fontid > 0 sal_IntPtr nFontId = rFontSelData.mpFontData->GetFontId(); if( nFontId <= 0 ) return NULL; // the FontList's key mpFontData member is reinterpreted as font id ImplFontSelectData aFontSelData = rFontSelData; aFontSelData.mpFontData = reinterpret_cast( nFontId ); FontList::iterator it = maFontList.find( aFontSelData ); if( it != maFontList.end() ) { ServerFont* pFound = it->second; if( pFound ) pFound->AddRef(); return pFound; } // font not cached yet => create new font item ServerFont* pNew = NULL; if( mpFtManager ) pNew = mpFtManager->CreateFont( aFontSelData ); // TODO: pNew = VirtDevServerFont::CreateFont( aFontSelData ); if( pNew ) { maFontList[ aFontSelData ] = pNew; mnBytesUsed += pNew->GetByteCount(); // enable garbage collection for new font if( !mpCurrentGCFont ) { mpCurrentGCFont = pNew; pNew->mpNextGCFont = pNew; pNew->mpPrevGCFont = pNew; } else { pNew->mpNextGCFont = mpCurrentGCFont; pNew->mpPrevGCFont = mpCurrentGCFont->mpPrevGCFont; pNew->mpPrevGCFont->mpNextGCFont = pNew; mpCurrentGCFont->mpPrevGCFont = pNew; } } return pNew; } // ----------------------------------------------------------------------- void GlyphCache::UncacheFont( ServerFont& rServerFont ) { // the interface for rServerFont must be const because a // user who wants to release it only got const ServerFonts. // The caching algorithm needs a non-const object ServerFont* pFont = const_cast( &rServerFont ); if( (pFont->Release() <= 0) && (mnMaxSize <= (mnBytesUsed + mrPeer.GetByteCount())) ) { mpCurrentGCFont = pFont; GarbageCollect(); } } // ----------------------------------------------------------------------- ULONG GlyphCache::CalcByteCount() const { ULONG nCacheSize = sizeof(*this); for( FontList::const_iterator it = maFontList.begin(); it != maFontList.end(); ++it ) { const ServerFont* pSF = it->second; if( pSF ) nCacheSize += pSF->GetByteCount(); } // TODO: also account something for hashtable management return nCacheSize; } // ----------------------------------------------------------------------- void GlyphCache::GarbageCollect() { // when current GC font has been destroyed get another one if( !mpCurrentGCFont ) { FontList::iterator it = maFontList.begin(); if( it != maFontList.end() ) mpCurrentGCFont = it->second; } // unless there is no other font to collect if( !mpCurrentGCFont ) return; // prepare advance to next font for garbage collection ServerFont* const pServerFont = mpCurrentGCFont; mpCurrentGCFont = pServerFont->mpNextGCFont; if( (pServerFont == mpCurrentGCFont) // no other fonts || (pServerFont->GetRefCount() > 0) ) // font still used { // try to garbage collect at least a few bytes pServerFont->GarbageCollect( mnLruIndex - mnGlyphCount/2 ); } else // current GC font is unreferenced { DBG_ASSERT( (pServerFont->GetRefCount() == 0), "GlyphCache::GC detected RefCount underflow" ); // free all pServerFont related data pServerFont->GarbageCollect( mnLruIndex+0x10000000 ); if( pServerFont == mpCurrentGCFont ) mpCurrentGCFont = NULL; const ImplFontSelectData& rIFSD = pServerFont->GetFontSelData(); maFontList.erase( rIFSD ); mrPeer.RemovingFont( *pServerFont ); mnBytesUsed -= pServerFont->GetByteCount(); // remove font from list of garbage collected fonts if( pServerFont->mpPrevGCFont ) pServerFont->mpPrevGCFont->mpNextGCFont = pServerFont->mpNextGCFont; if( pServerFont->mpNextGCFont ) pServerFont->mpNextGCFont->mpPrevGCFont = pServerFont->mpPrevGCFont; if( pServerFont == mpCurrentGCFont ) mpCurrentGCFont = NULL; delete pServerFont; } } // ----------------------------------------------------------------------- inline void GlyphCache::UsingGlyph( ServerFont&, GlyphData& rGlyphData ) { rGlyphData.SetLruValue( mnLruIndex++ ); } // ----------------------------------------------------------------------- inline void GlyphCache::AddedGlyph( ServerFont& rServerFont, GlyphData& rGlyphData ) { ++mnGlyphCount; mnBytesUsed += sizeof( rGlyphData ); UsingGlyph( rServerFont, rGlyphData ); GrowNotify(); } // ----------------------------------------------------------------------- void GlyphCache::GrowNotify() { if( (mnBytesUsed + mrPeer.GetByteCount()) > mnMaxSize ) GarbageCollect(); } // ----------------------------------------------------------------------- inline void GlyphCache::RemovingGlyph( ServerFont& rSF, GlyphData& rGD, int nGlyphIndex ) { mrPeer.RemovingGlyph( rSF, rGD, nGlyphIndex ); mnBytesUsed -= sizeof( GlyphData ); --mnGlyphCount; } // ======================================================================= // ServerFont // ======================================================================= ServerFont::ServerFont( const ImplFontSelectData& rFSD ) : maGlyphList( 0), maFontSelData(rFSD), mnExtInfo(0), mnRefCount(1), mnBytesUsed( sizeof(ServerFont) ), mpPrevGCFont( NULL ), mpNextGCFont( NULL ), mnCos( 0x10000), mnSin( 0 ), mnZWJ( 0 ), mnZWNJ( 0 ), mbCollectedZW( false ) { // TODO: move update of mpFontEntry into FontEntry class when // it becomes reponsible for the ServerFont instantiation ((ImplServerFontEntry*)rFSD.mpFontEntry)->SetServerFont( this ); if( rFSD.mnOrientation != 0 ) { const double dRad = rFSD.mnOrientation * ( F_2PI / 3600.0 ); mnCos = static_cast( 0x10000 * cos( dRad ) + 0.5 ); mnSin = static_cast( 0x10000 * sin( dRad ) + 0.5 ); } } // ----------------------------------------------------------------------- ServerFont::~ServerFont() { ReleaseFromGarbageCollect(); } // ----------------------------------------------------------------------- void ServerFont::ReleaseFromGarbageCollect() { // remove from GC list ServerFont* pPrev = mpPrevGCFont; ServerFont* pNext = mpNextGCFont; if( pPrev ) pPrev->mpNextGCFont = pNext; if( pNext ) pNext->mpPrevGCFont = pPrev; mpPrevGCFont = NULL; mpNextGCFont = NULL; } // ----------------------------------------------------------------------- long ServerFont::Release() const { DBG_ASSERT( mnRefCount > 0, "ServerFont: RefCount underflow" ); return --mnRefCount; } // ----------------------------------------------------------------------- GlyphData& ServerFont::GetGlyphData( int nGlyphIndex ) { // usually the GlyphData is cached GlyphList::iterator it = maGlyphList.find( nGlyphIndex ); if( it != maGlyphList.end() ) { GlyphData& rGlyphData = it->second; GlyphCache::GetInstance().UsingGlyph( *this, rGlyphData ); return rGlyphData; } // sometimes not => we need to create and initialize it ourselves GlyphData& rGlyphData = maGlyphList[ nGlyphIndex ]; mnBytesUsed += sizeof( GlyphData ); InitGlyphData( nGlyphIndex, rGlyphData ); GlyphCache::GetInstance().AddedGlyph( *this, rGlyphData ); return rGlyphData; } // ----------------------------------------------------------------------- void ServerFont::GarbageCollect( long nMinLruIndex ) { GlyphList::iterator it_next = maGlyphList.begin(); while( it_next != maGlyphList.end() ) { GlyphList::iterator it = it_next++; GlyphData& rGD = it->second; if( (nMinLruIndex - rGD.GetLruValue()) > 0 ) { OSL_ASSERT( mnBytesUsed >= sizeof(GlyphData) ); mnBytesUsed -= sizeof( GlyphData ); GlyphCache::GetInstance().RemovingGlyph( *this, rGD, it->first ); maGlyphList.erase( it ); it_next = maGlyphList.begin(); } } } // ----------------------------------------------------------------------- Point ServerFont::TransformPoint( const Point& rPoint ) const { if( mnCos == 0x10000 ) return rPoint; // TODO: use 32x32=>64bit intermediate const double dCos = mnCos * (1.0 / 0x10000); const double dSin = mnSin * (1.0 / 0x10000); long nX = (long)(rPoint.X() * dCos + rPoint.Y() * dSin); long nY = (long)(rPoint.Y() * dCos - rPoint.X() * dSin); return Point( nX, nY ); } bool ServerFont::IsGlyphInvisible( int nGlyphIndex ) { if (!mbCollectedZW) { mnZWJ = GetGlyphIndex( 0x200D ); mnZWNJ = GetGlyphIndex( 0x200C ); mbCollectedZW = true; } if( !nGlyphIndex ) // don't hide the NotDef glyph return false; if( (nGlyphIndex == mnZWNJ) || (nGlyphIndex == mnZWJ) ) return true; return false; } // ======================================================================= ImplServerFontEntry::ImplServerFontEntry( ImplFontSelectData& rFSD ) : ImplFontEntry( rFSD ), mpServerFont( NULL ) {} // ----------------------------------------------------------------------- ImplServerFontEntry::~ImplServerFontEntry() { // TODO: remove the ServerFont here instead of in the GlyphCache } // ======================================================================= ExtraKernInfo::ExtraKernInfo( sal_IntPtr nFontId ) : mbInitialized( false ), mnFontId( nFontId ), maUnicodeKernPairs( 0 ) {} //-------------------------------------------------------------------------- bool ExtraKernInfo::HasKernPairs() const { if( !mbInitialized ) Initialize(); return !maUnicodeKernPairs.empty(); } //-------------------------------------------------------------------------- int ExtraKernInfo::GetUnscaledKernPairs( ImplKernPairData** ppKernPairs ) const { if( !mbInitialized ) Initialize(); // return early if no kerning available if( maUnicodeKernPairs.empty() ) return 0; // allocate kern pair table int nKernCount = maUnicodeKernPairs.size(); *ppKernPairs = new ImplKernPairData[ nKernCount ]; // fill in unicode kern pairs with the kern value scaled to the font width ImplKernPairData* pKernData = *ppKernPairs; UnicodeKernPairs::const_iterator it = maUnicodeKernPairs.begin(); for(; it != maUnicodeKernPairs.end(); ++it ) *(pKernData++) = *it; return nKernCount; } //-------------------------------------------------------------------------- int ExtraKernInfo::GetUnscaledKernValue( sal_Unicode cLeft, sal_Unicode cRight ) const { if( !mbInitialized ) Initialize(); if( maUnicodeKernPairs.empty() ) return 0; ImplKernPairData aKernPair = { cLeft, cRight, 0 }; UnicodeKernPairs::const_iterator it = maUnicodeKernPairs.find( aKernPair ); if( it == maUnicodeKernPairs.end() ) return 0; int nUnscaledValue = (*it).mnKern; return nUnscaledValue; } // =======================================================================