| /************************************************************** |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_vcl.hxx" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <math.h> |
| |
| #include <gcach_ftyp.hxx> |
| |
| #include <vcl/svapp.hxx> |
| #include <vcl/bitmap.hxx> |
| #include <vcl/salbtype.hxx> |
| |
| #include <outfont.hxx> |
| |
| #ifdef ENABLE_GRAPHITE |
| #include <graphite_features.hxx> |
| #endif |
| |
| #include <rtl/ustring.hxx> // used only for string=>hashvalue |
| #include <osl/file.hxx> |
| #include <tools/debug.hxx> |
| |
| // ======================================================================= |
| // 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() |
| { |
| // an application about to exit can omit garbage collecting the heap |
| // since it makes things slower and introduces risks if the heap was not perfect |
| // for debugging, for memory grinding or leak checking the env allows to force GC |
| const char* pEnv = getenv( "SAL_FORCE_GC_ON_EXIT" ); |
| if( pEnv && (*pEnv != '0') ) |
| { |
| // uncache of all glyph shapes and metrics |
| for( FontList::iterator it = maFontList.begin(); it != maFontList.end(); ++it ) |
| delete const_cast<ServerFont*>( it->second ); |
| maFontList.clear(); |
| mpCurrentGCFont = NULL; |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| 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<sal_IntPtr>( 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<sal_IntPtr>( rA.mpFontData ); |
| sal_IntPtr nFontIdB = reinterpret_cast<sal_IntPtr>( 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<ImplFontData*>( 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<ServerFont*>( &rServerFont ); |
| if( (pFont->Release() <= 0) |
| && (mnMaxSize <= (mnBytesUsed + mrPeer.GetByteCount())) ) |
| { |
| mpCurrentGCFont = pFont; |
| GarbageCollect(); |
| } |
| } |
| |
| // ----------------------------------------------------------------------- |
| |
| sal_uLong GlyphCache::CalcByteCount() const |
| { |
| sal_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, sal_GlyphId aGlyphId ) |
| { |
| mrPeer.RemovingGlyph( rSF, rGD, aGlyphId ); |
| 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<long>( 0x10000 * cos( dRad ) + 0.5 ); |
| mnSin = static_cast<long>( 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( sal_GlyphId aGlyphId ) |
| { |
| // usually the GlyphData is cached |
| GlyphList::iterator it = maGlyphList.find( aGlyphId ); |
| 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[ aGlyphId ]; |
| mnBytesUsed += sizeof( GlyphData ); |
| InitGlyphData( aGlyphId, 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( sal_GlyphId aGlyphId ) |
| { |
| if (!mbCollectedZW) |
| { |
| mnZWJ = GetGlyphIndex( 0x200D ); |
| mnZWNJ = GetGlyphIndex( 0x200C ); |
| mbCollectedZW = true; |
| } |
| |
| if( !aGlyphId ) // don't hide the NotDef glyph |
| return false; |
| if( (aGlyphId == mnZWNJ) || (aGlyphId == mnZWJ) ) |
| return true; |
| |
| return false; |
| } |
| |
| // ======================================================================= |
| |
| ImplServerFontEntry::ImplServerFontEntry( ImplFontSelectData& rFSD ) |
| : ImplFontEntry( rFSD ) |
| , mpServerFont( NULL ) |
| , mbGotFontOptions( false ) |
| , mbValidFontOptions( false ) |
| {} |
| |
| // ----------------------------------------------------------------------- |
| |
| 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; |
| } |
| |
| // ======================================================================= |
| |