blob: cc1fbda807ac380f05d58bce1ddba30b9238c948 [file] [log] [blame]
/**************************************************************
*
* 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;
}
// =======================================================================