diff options
Diffstat (limited to 'vcl/generic/glyphs')
-rw-r--r-- | vcl/generic/glyphs/gcach_ftyp.cxx | 2649 | ||||
-rw-r--r-- | vcl/generic/glyphs/gcach_ftyp.hxx | 201 | ||||
-rw-r--r-- | vcl/generic/glyphs/gcach_layout.cxx | 669 | ||||
-rw-r--r-- | vcl/generic/glyphs/gcach_rbmp.cxx | 277 | ||||
-rw-r--r-- | vcl/generic/glyphs/glyphcache.cxx | 503 | ||||
-rw-r--r-- | vcl/generic/glyphs/graphite_serverfont.cxx | 154 |
6 files changed, 4453 insertions, 0 deletions
diff --git a/vcl/generic/glyphs/gcach_ftyp.cxx b/vcl/generic/glyphs/gcach_ftyp.cxx new file mode 100644 index 000000000000..ffa4aa33b642 --- /dev/null +++ b/vcl/generic/glyphs/gcach_ftyp.cxx @@ -0,0 +1,2649 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * 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 + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +#ifdef WNT +#include <svsys.h> +#undef CreateFont +#endif + +#include "gcach_ftyp.hxx" + +#include "vcl/svapp.hxx" +#include <outfont.hxx> +#include <impfont.hxx> +#ifdef ENABLE_GRAPHITE +#include <graphite2/Font.h> +#include <graphite_layout.hxx> +#endif + +#include "tools/poly.hxx" +#include "basegfx/matrix/b2dhommatrix.hxx" +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include "basegfx/polygon/b2dpolypolygon.hxx" + +#include "osl/file.hxx" +#include "osl/thread.hxx" + +#include "sft.hxx" + +#include <ft2build.h> +#include FT_FREETYPE_H +#include FT_GLYPH_H +#include FT_OUTLINE_H +#include FT_TRUETYPE_TABLES_H +#include FT_TRUETYPE_TAGS_H +#include FT_TRUETYPE_IDS_H + +#ifndef FT_RENDER_MODE_MONO // happens in the MACOSX build + #define FT_RENDER_MODE_MONO ft_render_mode_mono +#endif +#include "rtl/instance.hxx" + +#ifndef FREETYPE_PATCH + // VERSION_MINOR in freetype.h is too coarse + // if patch-level is not available we need to fine-tune the version ourselves + #define FTVERSION 2005 +#else + #define FTVERSION (1000*FREETYPE_MAJOR + 100*FREETYPE_MINOR + FREETYPE_PATCH) +#endif +#if FTVERSION >= 2200 +typedef const FT_Vector* FT_Vector_CPtr; +#else // FTVERSION < 2200 +typedef FT_Vector* FT_Vector_CPtr; +#endif + +#include <vector> + +// TODO: move file mapping stuff to OSL +#if defined(UNX) + // PORTERS: dlfcn is used for getting symbols from FT versions newer than baseline + #include <dlfcn.h> + #include <unistd.h> + #include <fcntl.h> + #include <sys/stat.h> + #include <sys/mman.h> + #include "vcl/fontmanager.hxx" +#elif defined(WNT) + #include <io.h> + #define strncasecmp strnicmp +#endif + +typedef const unsigned char* CPU8; +inline sal_uInt16 NEXT_U16( CPU8& p ) { p+=2; return (p[-2]<<8)|p[-1]; } +inline sal_Int16 NEXT_S16( CPU8& p ) { return (sal_Int16)NEXT_U16(p); } +inline sal_uInt32 NEXT_U32( CPU8& p ) { p+=4; return (p[-4]<<24)|(p[-3]<<16)|(p[-2]<<8)|p[-1]; } +//inline sal_Int32 NEXT_S32( U8*& p ) { return (sal_Int32)NEXT_U32(p); } + +// ----------------------------------------------------------------------- + +// the gamma table makes artificial bold look better for CJK glyphs +static unsigned char aGammaTable[257]; + +static void InitGammaTable() +{ + static const int M_MAX = 255; + static const int M_X = 128; + static const int M_Y = 208; + + int x, a; + for( x = 0; x < 256; x++) + { + if ( x <= M_X ) + a = ( x * M_Y + M_X / 2) / M_X; + else + a = M_Y + ( ( x - M_X ) * ( M_MAX - M_Y ) + + ( M_MAX - M_X ) / 2 ) / ( M_MAX - M_X ); + + aGammaTable[x] = (unsigned char)a; + } +} + +// ----------------------------------------------------------------------- + +static FT_Library aLibFT = 0; + +// #110607# enable linking with old FT versions +static int nFTVERSION = 0; +static FT_Error (*pFTNewSize)(FT_Face,FT_Size*); +static FT_Error (*pFTActivateSize)(FT_Size); +static FT_Error (*pFTDoneSize)(FT_Size); +FT_Error (*pFTEmbolden)(FT_GlyphSlot); +FT_Error (*pFTOblique)(FT_GlyphSlot); +static bool bEnableSizeFT = false; + +struct EqStr{ bool operator()(const char* a, const char* b) const { return !strcmp(a,b); } }; +struct HashStr { size_t operator()( const char* s ) const { return rtl_str_hashCode(s); } }; +typedef ::boost::unordered_map<const char*,boost::shared_ptr<FtFontFile>,HashStr, EqStr> FontFileList; +namespace { struct vclFontFileList : public rtl::Static< FontFileList, vclFontFileList > {}; } + +// ----------------------------------------------------------------------- + +// TODO: remove when the priorities are selected by UI +// if (AH==0) => disable autohinting +// if (AA==0) => disable antialiasing +// if (EB==0) => disable embedded bitmaps +// if (AA prio <= AH prio) => antialias + autohint +// if (AH<AA) => do not autohint when antialiasing +// if (EB<AH) => do not autohint for monochrome +static int nDefaultPrioEmbedded = 2; +static int nDefaultPrioAutoHint = 1; +static int nDefaultPrioAntiAlias = 1; + +// ======================================================================= +// FreetypeManager +// ======================================================================= + +FtFontFile::FtFontFile( const ::rtl::OString& rNativeFileName ) +: maNativeFileName( rNativeFileName ), + mpFileMap( NULL ), + mnFileSize( 0 ), + mnRefCount( 0 ), + mnLangBoost( 0 ) +{ + // boost font preference if UI language is mentioned in filename + int nPos = maNativeFileName.lastIndexOf( '_' ); + if( nPos == -1 || maNativeFileName[nPos+1] == '.' ) + mnLangBoost += 0x1000; // no langinfo => good + else + { + static const char* pLangBoost = NULL; + static bool bOnce = true; + if( bOnce ) + { + bOnce = false; + LanguageType aLang = Application::GetSettings().GetUILanguage(); + switch( aLang ) + { + case LANGUAGE_JAPANESE: + pLangBoost = "jan"; + break; + case LANGUAGE_CHINESE: + case LANGUAGE_CHINESE_SIMPLIFIED: + case LANGUAGE_CHINESE_SINGAPORE: + pLangBoost = "zhs"; + break; + case LANGUAGE_CHINESE_TRADITIONAL: + case LANGUAGE_CHINESE_HONGKONG: + case LANGUAGE_CHINESE_MACAU: + pLangBoost = "zht"; + break; + case LANGUAGE_KOREAN: + case LANGUAGE_KOREAN_JOHAB: + pLangBoost = "kor"; + break; + } + } + + if( pLangBoost && !strncasecmp( pLangBoost, &maNativeFileName.getStr()[nPos+1], 3 ) ) + mnLangBoost += 0x2000; // matching langinfo => better + } +} + +// ----------------------------------------------------------------------- + +FtFontFile* FtFontFile::FindFontFile( const ::rtl::OString& rNativeFileName ) +{ + // font file already known? (e.g. for ttc, synthetic, aliased fonts) + const char* pFileName = rNativeFileName.getStr(); + FontFileList &rFontFileList = vclFontFileList::get(); + FontFileList::const_iterator it = rFontFileList.find( pFileName ); + if( it != rFontFileList.end() ) + return it->second.get(); + + // no => create new one + FtFontFile* pFontFile = new FtFontFile( rNativeFileName ); + pFileName = pFontFile->maNativeFileName.getStr(); + rFontFileList[pFileName].reset(pFontFile); + return pFontFile; +} + +// ----------------------------------------------------------------------- + +bool FtFontFile::Map() +{ + if( mnRefCount++ <= 0 ) + { + const char* pFileName = maNativeFileName.getStr(); +#if defined(UNX) + int nFile = open( pFileName, O_RDONLY ); + if( nFile < 0 ) + return false; + + struct stat aStat; + fstat( nFile, &aStat ); + mnFileSize = aStat.st_size; + mpFileMap = (const unsigned char*) + mmap( NULL, mnFileSize, PROT_READ, MAP_SHARED, nFile, 0 ); + if( mpFileMap == MAP_FAILED ) + mpFileMap = NULL; + close( nFile ); +#elif defined(WNT) + void* pFileDesc = ::CreateFile( pFileName, GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 ); + if( pFileDesc == INVALID_HANDLE_VALUE) + return false; + + mnFileSize = ::GetFileSize( pFileDesc, NULL ); + HANDLE aHandle = ::CreateFileMapping( pFileDesc, NULL, PAGE_READONLY, 0, mnFileSize, "TTF" ); + mpFileMap = (const unsigned char*)::MapViewOfFile( aHandle, FILE_MAP_READ, 0, 0, mnFileSize ); + ::CloseHandle( pFileDesc ); +#else + FILE* pFile = fopen( pFileName, "rb" ); + if( !pFile ) + return false; + + struct stat aStat; + stat( pFileName, &aStat ); + mnFileSize = aStat.st_size; + mpFileMap = new unsigned char[ mnFileSize ]; + if( mnFileSize != fread( mpFileMap, 1, mnFileSize, pFile ) ) + { + delete[] mpFileMap; + mpFileMap = NULL; + } + fclose( pFile ); +#endif + } + + return (mpFileMap != NULL); +} + +// ----------------------------------------------------------------------- + +void FtFontFile::Unmap() +{ + if( (--mnRefCount > 0) || (mpFileMap == NULL) ) + return; + +#if defined(UNX) + munmap( (char*)mpFileMap, mnFileSize ); +#elif defined(WNT) + UnmapViewOfFile( (LPCVOID)mpFileMap ); +#else + delete[] mpFileMap; +#endif + + mpFileMap = NULL; +} + +#ifdef ENABLE_GRAPHITE +// wrap FtFontInfo's table function +const void * graphiteFontTable(const void* appFaceHandle, unsigned int name, size_t *len) +{ + const FtFontInfo * pFontInfo = reinterpret_cast<const FtFontInfo*>(appFaceHandle); + typedef union { + char m_c[5]; + unsigned int m_id; + } TableId; + TableId tableId; + tableId.m_id = name; +#ifndef WORDS_BIGENDIAN + TableId swapped; + swapped.m_c[3] = tableId.m_c[0]; + swapped.m_c[2] = tableId.m_c[1]; + swapped.m_c[1] = tableId.m_c[2]; + swapped.m_c[0] = tableId.m_c[3]; + tableId.m_id = swapped.m_id; +#endif + tableId.m_c[4] = '\0'; + sal_uLong nLength = 0; + const void * pTable = static_cast<const void*>(pFontInfo->GetTable(tableId.m_c, &nLength)); + if (len) *len = static_cast<size_t>(nLength); + return pTable; +} +#endif + +// ======================================================================= + +FtFontInfo::FtFontInfo( const ImplDevFontAttributes& rDevFontAttributes, + const ::rtl::OString& rNativeFileName, int nFaceNum, sal_IntPtr nFontId, int nSynthetic, + const ExtraKernInfo* pExtraKernInfo ) +: + maFaceFT( NULL ), + mpFontFile( FtFontFile::FindFontFile( rNativeFileName ) ), + mnFaceNum( nFaceNum ), + mnRefCount( 0 ), + mnSynthetic( nSynthetic ), +#ifdef ENABLE_GRAPHITE + mbCheckedGraphite(false), + mpGraphiteFace(NULL), +#endif + mnFontId( nFontId ), + maDevFontAttributes( rDevFontAttributes ), + mpFontCharMap( NULL ), + mpChar2Glyph( NULL ), + mpGlyph2Char( NULL ), + mpExtraKernInfo( pExtraKernInfo ) +{ + // prefer font with low ID + maDevFontAttributes.mnQuality += 10000 - nFontId; + // prefer font with matching file names + maDevFontAttributes.mnQuality += mpFontFile->GetLangBoost(); + // prefer font with more external info + if( pExtraKernInfo ) + maDevFontAttributes.mnQuality += 100; +} + +// ----------------------------------------------------------------------- + +FtFontInfo::~FtFontInfo() +{ + if( mpFontCharMap ) + mpFontCharMap->DeReference(); + delete mpExtraKernInfo; + delete mpChar2Glyph; + delete mpGlyph2Char; +#ifdef ENABLE_GRAPHITE + if (mpGraphiteFace) + delete mpGraphiteFace; +#endif +} + +void FtFontInfo::InitHashes() const +{ + // TODO: avoid pointers when empty stl::hash_* objects become cheap + mpChar2Glyph = new Int2IntMap(); + mpGlyph2Char = new Int2IntMap(); +} + +// ----------------------------------------------------------------------- + +FT_FaceRec_* FtFontInfo::GetFaceFT() +{ + // get faceFT once/multiple depending on availability of SizeFT APIs + if( (mnRefCount++ <= 0) || !bEnableSizeFT ) + { + if( !mpFontFile->Map() ) + return NULL; + FT_Error rc = FT_New_Memory_Face( aLibFT, + (FT_Byte*)mpFontFile->GetBuffer(), + mpFontFile->GetFileSize(), mnFaceNum, &maFaceFT ); + if( (rc != FT_Err_Ok) || (maFaceFT->num_glyphs <= 0) ) + maFaceFT = NULL; + } + + return maFaceFT; +} + +#ifdef ENABLE_GRAPHITE +GraphiteFaceWrapper * FtFontInfo::GetGraphiteFace() +{ + if (mbCheckedGraphite) + return mpGraphiteFace; + // test for graphite here so that it is cached most efficiently + if (GetTable("Silf", 0)) + { + int graphiteSegCacheSize = 10000; + static const char* pGraphiteCacheStr = getenv( "SAL_GRAPHITE_CACHE_SIZE" ); + graphiteSegCacheSize = pGraphiteCacheStr ? (atoi(pGraphiteCacheStr)) : 0; + gr_face * pGraphiteFace; + if (graphiteSegCacheSize > 500) + pGraphiteFace = gr_make_face_with_seg_cache(this, graphiteFontTable, graphiteSegCacheSize, gr_face_cacheCmap); + else + pGraphiteFace = gr_make_face(this, graphiteFontTable, gr_face_cacheCmap); + if (pGraphiteFace) + mpGraphiteFace = new GraphiteFaceWrapper(pGraphiteFace); + } + mbCheckedGraphite = true; + return mpGraphiteFace; +} +#endif + +// ----------------------------------------------------------------------- + +void FtFontInfo::ReleaseFaceFT( FT_FaceRec_* pFaceFT ) +{ + // release last/each depending on SizeFT availability + if( (--mnRefCount <= 0) || !bEnableSizeFT ) + { + FT_Done_Face( pFaceFT ); + maFaceFT = NULL; + mpFontFile->Unmap(); + } +} + +// ----------------------------------------------------------------------- + +bool FtFontInfo::HasExtraKerning() const +{ + if( !mpExtraKernInfo ) + return false; + // TODO: how to enable the line below without getting #i29881# back? + // on the other hand being to optimistic doesn't cause problems + // return mpExtraKernInfo->HasKernPairs(); + return true; +} + +// ----------------------------------------------------------------------- + +int FtFontInfo::GetExtraKernPairs( ImplKernPairData** ppKernPairs ) const +{ + if( !mpExtraKernInfo ) + return 0; + return mpExtraKernInfo->GetUnscaledKernPairs( ppKernPairs ); +} + +// ----------------------------------------------------------------------- + +int FtFontInfo::GetExtraGlyphKernValue( int nLeftGlyph, int nRightGlyph ) const +{ + if( !mpExtraKernInfo ) + return 0; + if( !mpGlyph2Char ) + return 0; + sal_Unicode cLeftChar = (*mpGlyph2Char)[ nLeftGlyph ]; + sal_Unicode cRightChar = (*mpGlyph2Char)[ nRightGlyph ]; + return mpExtraKernInfo->GetUnscaledKernValue( cLeftChar, cRightChar ); +} + +// ----------------------------------------------------------------------- + +static unsigned GetUInt( const unsigned char* p ) { return((p[0]<<24)+(p[1]<<16)+(p[2]<<8)+p[3]);} +static unsigned GetUShort( const unsigned char* p ){ return((p[0]<<8)+p[1]);} +//static signed GetSShort( const unsigned char* p ){ return((short)((p[0]<<8)+p[1]));} + +// ----------------------------------------------------------------------- + +const unsigned char* FtFontInfo::GetTable( const char* pTag, sal_uLong* pLength ) const +{ + const unsigned char* pBuffer = mpFontFile->GetBuffer(); + int nFileSize = mpFontFile->GetFileSize(); + if( !pBuffer || nFileSize<1024 ) + return NULL; + + // we currently only handle TTF and TTC headers + unsigned nFormat = GetUInt( pBuffer ); + const unsigned char* p = pBuffer + 12; + if( nFormat == 0x74746366 ) // TTC_MAGIC + p += GetUInt( p + 4 * mnFaceNum ); + else if( (nFormat!=0x00010000) && (nFormat!=0x74727565) ) // TTF_MAGIC and Apple TTF Magic + return NULL; + + // walk table directory until match + int nTables = GetUShort( p - 8 ); + if( nTables >= 64 ) // something fishy? + return NULL; + for( int i = 0; i < nTables; ++i, p+=16 ) + { + if( p[0]==pTag[0] && p[1]==pTag[1] && p[2]==pTag[2] && p[3]==pTag[3] ) + { + sal_uLong nLength = GetUInt( p + 12 ); + if( pLength != NULL ) + *pLength = nLength; + const unsigned char* pTable = pBuffer + GetUInt( p + 8 ); + if( (pTable + nLength) <= (mpFontFile->GetBuffer() + nFileSize) ) + return pTable; + } + } + + return NULL; +} + +// ----------------------------------------------------------------------- + +void FtFontInfo::AnnounceFont( ImplDevFontList* pFontList ) +{ + ImplFTSFontData* pFD = new ImplFTSFontData( this, maDevFontAttributes ); + pFontList->Add( pFD ); +} + +// ======================================================================= + +FreetypeManager::FreetypeManager() +: mnMaxFontId( 0 ), mnNextFontId( 0x1000 ) +{ + /*FT_Error rcFT =*/ FT_Init_FreeType( &aLibFT ); + +#ifdef RTLD_DEFAULT // true if a good dlfcn.h header was included + // Get version of freetype library to enable workarounds. + // Freetype <= 2.0.9 does not have FT_Library_Version(). + // Using dl_sym() instead of osl_getSymbol() because latter + // isn't designed to work with oslModule=NULL + void (*pFTLibraryVersion)(FT_Library library, + FT_Int *amajor, FT_Int *aminor, FT_Int *apatch); + pFTLibraryVersion = (void (*)(FT_Library library, + FT_Int *amajor, FT_Int *aminor, FT_Int *apatch))(sal_IntPtr)dlsym( RTLD_DEFAULT, "FT_Library_Version" ); + + pFTNewSize = (FT_Error(*)(FT_Face,FT_Size*))(sal_IntPtr)dlsym( RTLD_DEFAULT, "FT_New_Size" ); + pFTActivateSize = (FT_Error(*)(FT_Size))(sal_IntPtr)dlsym( RTLD_DEFAULT, "FT_Activate_Size" ); + pFTDoneSize = (FT_Error(*)(FT_Size))(sal_IntPtr)dlsym( RTLD_DEFAULT, "FT_Done_Size" ); + pFTEmbolden = (FT_Error(*)(FT_GlyphSlot))(sal_IntPtr)dlsym( RTLD_DEFAULT, "FT_GlyphSlot_Embolden" ); + pFTOblique = (FT_Error(*)(FT_GlyphSlot))(sal_IntPtr)dlsym( RTLD_DEFAULT, "FT_GlyphSlot_Oblique" ); + + bEnableSizeFT = (pFTNewSize!=NULL) && (pFTActivateSize!=NULL) && (pFTDoneSize!=NULL); + + FT_Int nMajor = 0, nMinor = 0, nPatch = 0; + if( pFTLibraryVersion ) + pFTLibraryVersion( aLibFT, &nMajor, &nMinor, &nPatch ); + nFTVERSION = nMajor * 1000 + nMinor * 100 + nPatch; + + // disable embedded bitmaps for Freetype-2.1.3 unless explicitly + // requested by env var below because it crashes StarOffice on RH9 + // reason: double free in freetype's embedded bitmap handling + if( nFTVERSION == 2103 ) + nDefaultPrioEmbedded = 0; + // disable artificial emboldening with the Freetype API for older versions + if( nFTVERSION < 2110 ) + pFTEmbolden = NULL; + +#else // RTLD_DEFAULT + // assume systems where dlsym is not possible use supplied library + nFTVERSION = FTVERSION; +#endif + + // TODO: remove when the priorities are selected by UI + char* pEnv; + pEnv = ::getenv( "SAL_EMBEDDED_BITMAP_PRIORITY" ); + if( pEnv ) + nDefaultPrioEmbedded = pEnv[0] - '0'; + pEnv = ::getenv( "SAL_ANTIALIASED_TEXT_PRIORITY" ); + if( pEnv ) + nDefaultPrioAntiAlias = pEnv[0] - '0'; + pEnv = ::getenv( "SAL_AUTOHINTING_PRIORITY" ); + if( pEnv ) + nDefaultPrioAutoHint = pEnv[0] - '0'; + + InitGammaTable(); + vclFontFileList::get(); +} + +// ----------------------------------------------------------------------- + +FT_Face ServerFont::GetFtFace() const +{ + if( maSizeFT ) + pFTActivateSize( maSizeFT ); + + return maFaceFT; +} + +// ----------------------------------------------------------------------- + +FreetypeManager::~FreetypeManager() +{ + ClearFontList(); +// This crashes on Solaris 10 +// TODO: check which versions have this problem +// +// FT_Error rcFT = FT_Done_FreeType( aLibFT ); +} + +// ----------------------------------------------------------------------- + +void FreetypeManager::AddFontFile( const rtl::OString& rNormalizedName, + int nFaceNum, sal_IntPtr nFontId, const ImplDevFontAttributes& rDevFontAttr, + const ExtraKernInfo* pExtraKernInfo ) +{ + if( !rNormalizedName.getLength() ) + return; + + if( maFontList.find( nFontId ) != maFontList.end() ) + return; + + FtFontInfo* pFontInfo = new FtFontInfo( rDevFontAttr, + rNormalizedName, nFaceNum, nFontId, 0, pExtraKernInfo ); + maFontList[ nFontId ] = pFontInfo; + if( mnMaxFontId < nFontId ) + mnMaxFontId = nFontId; +} + +// ----------------------------------------------------------------------- + +void FreetypeManager::AnnounceFonts( ImplDevFontList* pToAdd ) const +{ + for( FontList::const_iterator it = maFontList.begin(); it != maFontList.end(); ++it ) + { + FtFontInfo* pFtFontInfo = it->second; + pFtFontInfo->AnnounceFont( pToAdd ); + } +} + +// ----------------------------------------------------------------------- + +void FreetypeManager::ClearFontList( ) +{ + for( FontList::iterator it = maFontList.begin(); it != maFontList.end(); ++it ) + { + FtFontInfo* pFtFontInfo = it->second; + delete pFtFontInfo; + } + maFontList.clear(); +} + +// ----------------------------------------------------------------------- + +ServerFont* FreetypeManager::CreateFont( const ImplFontSelectData& rFSD ) +{ + FtFontInfo* pFontInfo = NULL; + + // find a FontInfo matching to the font id + sal_IntPtr nFontId = reinterpret_cast<sal_IntPtr>( rFSD.mpFontData ); + FontList::iterator it = maFontList.find( nFontId ); + if( it != maFontList.end() ) + pFontInfo = it->second; + + if( !pFontInfo ) + return NULL; + + ServerFont* pNew = new ServerFont( rFSD, pFontInfo ); + + return pNew; +} + +// ======================================================================= + +ImplFTSFontData::ImplFTSFontData( FtFontInfo* pFI, const ImplDevFontAttributes& rDFA ) +: ImplFontData( rDFA, IFTSFONT_MAGIC ), + mpFtFontInfo( pFI ) +{ + mbDevice = false; + mbOrientation = true; +} + +// ----------------------------------------------------------------------- + +ImplFontEntry* ImplFTSFontData::CreateFontInstance( ImplFontSelectData& rFSD ) const +{ + ImplServerFontEntry* pEntry = new ImplServerFontEntry( rFSD ); + return pEntry; +} + +// ======================================================================= +// ServerFont +// ======================================================================= + +ServerFont::ServerFont( const ImplFontSelectData& rFSD, FtFontInfo* pFI ) +: 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 ), + mnPrioEmbedded(nDefaultPrioEmbedded), + mnPrioAntiAlias(nDefaultPrioAntiAlias), + mnPrioAutoHint(nDefaultPrioAutoHint), + mpFontInfo( pFI ), + maFaceFT( NULL ), + maSizeFT( NULL ), + mbFaceOk( false ), + maRecodeConverter( NULL ), + mpLayoutEngine( NULL ) +{ + // 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 ); + } + + maFaceFT = pFI->GetFaceFT(); + + if( !maFaceFT ) + return; + + // set the pixel size of the font instance + mnWidth = rFSD.mnWidth; + if( !mnWidth ) + mnWidth = rFSD.mnHeight; + mfStretch = (double)mnWidth / rFSD.mnHeight; + // sanity check (e.g. #i66394#, #i66244#, #66537#) + if( (mnWidth < 0) || (mfStretch > +64.0) || (mfStretch < -64.0) ) + return; + + // perf: use maSizeFT if available + if( bEnableSizeFT ) + { + pFTNewSize( maFaceFT, &maSizeFT ); + pFTActivateSize( maSizeFT ); + } + FT_Error rc = FT_Set_Pixel_Sizes( maFaceFT, mnWidth, rFSD.mnHeight ); + if( rc != FT_Err_Ok ) + return; + + // prepare for font encodings other than unicode or symbol + FT_Encoding eEncoding = FT_ENCODING_UNICODE; + if( mpFontInfo->IsSymbolFont() ) + { +#if (FTVERSION < 2000) + eEncoding = FT_ENCODING_NONE; +#else + if( FT_IS_SFNT( maFaceFT ) ) + eEncoding = ft_encoding_symbol; + else + eEncoding = FT_ENCODING_ADOBE_CUSTOM; // freetype wants this for PS symbol fonts +#endif + } + rc = FT_Select_Charmap( maFaceFT, eEncoding ); + // no standard encoding applies => we need an encoding converter + if( rc != FT_Err_Ok ) + { + rtl_TextEncoding eRecodeFrom = RTL_TEXTENCODING_UNICODE; + for( int i = maFaceFT->num_charmaps; --i >= 0; ) + { + const FT_CharMap aCM = maFaceFT->charmaps[i]; + if( aCM->platform_id == TT_PLATFORM_MICROSOFT ) + { + switch( aCM->encoding_id ) + { + case TT_MS_ID_SJIS: + eEncoding = FT_ENCODING_SJIS; + eRecodeFrom = RTL_TEXTENCODING_SHIFT_JIS; + break; + case TT_MS_ID_GB2312: + eEncoding = FT_ENCODING_GB2312; + eRecodeFrom = RTL_TEXTENCODING_GB_2312; + break; + case TT_MS_ID_BIG_5: + eEncoding = FT_ENCODING_BIG5; + eRecodeFrom = RTL_TEXTENCODING_BIG5; + break; + case TT_MS_ID_WANSUNG: + eEncoding = FT_ENCODING_WANSUNG; + eRecodeFrom = RTL_TEXTENCODING_MS_949; + break; + case TT_MS_ID_JOHAB: + eEncoding = FT_ENCODING_JOHAB; + eRecodeFrom = RTL_TEXTENCODING_MS_1361; + break; + } + } + else if( aCM->platform_id == TT_PLATFORM_MACINTOSH ) + { + switch( aCM->encoding_id ) + { + case TT_MAC_ID_ROMAN: + eEncoding = FT_ENCODING_APPLE_ROMAN; + eRecodeFrom = RTL_TEXTENCODING_UNICODE; // TODO: use better match + break; + // TODO: add other encodings when Mac-only + // non-unicode fonts show up + } + } + else if( aCM->platform_id == TT_PLATFORM_ADOBE ) + { + switch( aCM->encoding_id ) + { +#ifdef TT_ADOBE_ID_LATIN1 + case TT_ADOBE_ID_LATIN1: // better unicode than nothing + eEncoding = FT_ENCODING_ADOBE_LATIN_1; + eRecodeFrom = RTL_TEXTENCODING_ISO_8859_1; + break; +#endif // TT_ADOBE_ID_LATIN1 + case TT_ADOBE_ID_STANDARD: // better unicode than nothing + eEncoding = FT_ENCODING_ADOBE_STANDARD; + eRecodeFrom = RTL_TEXTENCODING_UNICODE; // TODO: use better match + break; + } + } + } + + if( FT_Err_Ok != FT_Select_Charmap( maFaceFT, eEncoding ) ) + return; + + if( eRecodeFrom != RTL_TEXTENCODING_UNICODE ) + maRecodeConverter = rtl_createUnicodeToTextConverter( eRecodeFrom ); + } + + mbFaceOk = true; + + ApplyGSUB( rFSD ); + + // TODO: query GASP table for load flags + mnLoadFlags = FT_LOAD_DEFAULT; +#if 1 // #i97326# cairo sometimes uses FT_Set_Transform() on our FT_FACE + // we are not using FT_Set_Transform() yet, so just ignore it for now + mnLoadFlags |= FT_LOAD_IGNORE_TRANSFORM; +#endif + + mbArtItalic = (rFSD.meItalic != ITALIC_NONE && pFI->GetFontAttributes().GetSlant() == ITALIC_NONE); + mbArtBold = (rFSD.meWeight > WEIGHT_MEDIUM && pFI->GetFontAttributes().GetWeight() <= WEIGHT_MEDIUM); + mbUseGamma = false; + if( mbArtBold ) + { + //static const int TT_CODEPAGE_RANGE_874 = (1L << 16); // Thai + //static const int TT_CODEPAGE_RANGE_932 = (1L << 17); // JIS/Japan + //static const int TT_CODEPAGE_RANGE_936 = (1L << 18); // Chinese: Simplified + //static const int TT_CODEPAGE_RANGE_949 = (1L << 19); // Korean Wansung + //static const int TT_CODEPAGE_RANGE_950 = (1L << 20); // Chinese: Traditional + //static const int TT_CODEPAGE_RANGE_1361 = (1L << 21); // Korean Johab + static const int TT_CODEPAGE_RANGES1_CJKT = 0x3F0000; // all of the above + const TT_OS2* pOs2 = (const TT_OS2*)FT_Get_Sfnt_Table( maFaceFT, ft_sfnt_os2 ); + if ((pOs2) && (pOs2->ulCodePageRange1 & TT_CODEPAGE_RANGES1_CJKT ) + && rFSD.mnHeight < 20) + mbUseGamma = true; + } + + if( ((mnCos != 0) && (mnSin != 0)) || (mnPrioEmbedded <= 0) ) + mnLoadFlags |= FT_LOAD_NO_BITMAP; +} + +void ServerFont::SetFontOptions( boost::shared_ptr<ImplFontOptions> pFontOptions) +{ + mpFontOptions = pFontOptions; + + if (!mpFontOptions) + return; + + FontAutoHint eHint = mpFontOptions->GetUseAutoHint(); + if( eHint == AUTOHINT_DONTKNOW ) + eHint = mbUseGamma ? AUTOHINT_TRUE : AUTOHINT_FALSE; + + if( eHint == AUTOHINT_TRUE ) + mnLoadFlags |= FT_LOAD_FORCE_AUTOHINT; + + if( (mnSin != 0) && (mnCos != 0) ) // hinting for 0/90/180/270 degrees only + mnLoadFlags |= FT_LOAD_NO_HINTING; + mnLoadFlags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; //#88334# + + if( mpFontOptions->DontUseAntiAlias() ) + mnPrioAntiAlias = 0; + if( mpFontOptions->DontUseEmbeddedBitmaps() ) + mnPrioEmbedded = 0; + if( mpFontOptions->DontUseHinting() ) + mnPrioAutoHint = 0; + +#if (FTVERSION >= 2005) || defined(TT_CONFIG_OPTION_BYTECODE_INTERPRETER) + if( mnPrioAutoHint <= 0 ) +#endif + mnLoadFlags |= FT_LOAD_NO_HINTING; + +#if defined(FT_LOAD_TARGET_LIGHT) && defined(FT_LOAD_TARGET_NORMAL) + if( !(mnLoadFlags & FT_LOAD_NO_HINTING) && (nFTVERSION >= 2103)) + { + mnLoadFlags |= FT_LOAD_TARGET_NORMAL; + switch( mpFontOptions->GetHintStyle() ) + { + case HINT_NONE: + mnLoadFlags |= FT_LOAD_NO_HINTING; + break; + case HINT_SLIGHT: + mnLoadFlags |= FT_LOAD_TARGET_LIGHT; + break; + case HINT_MEDIUM: + break; + case HINT_FULL: + default: + break; + } + } +#endif + + if( mnPrioEmbedded <= 0 ) + mnLoadFlags |= FT_LOAD_NO_BITMAP; +} + +boost::shared_ptr<ImplFontOptions> ServerFont::GetFontOptions() const +{ + return mpFontOptions; +} + +// ----------------------------------------------------------------------- + +bool ServerFont::TestFont() const +{ + return mbFaceOk; +} + +// ----------------------------------------------------------------------- + +ServerFont::~ServerFont() +{ + if( mpLayoutEngine ) + delete mpLayoutEngine; + + if( maRecodeConverter ) + rtl_destroyUnicodeToTextConverter( maRecodeConverter ); + + if( maSizeFT ) + pFTDoneSize( maSizeFT ); + + mpFontInfo->ReleaseFaceFT( maFaceFT ); + + ReleaseFromGarbageCollect(); +} + + // ----------------------------------------------------------------------- + +int ServerFont::GetEmUnits() const +{ + return maFaceFT->units_per_EM; +} + +// ----------------------------------------------------------------------- + +void ServerFont::FetchFontMetric( ImplFontMetricData& rTo, long& rFactor ) const +{ + const int UNDETERMINED = 0xFEED; + static int nUseNewLineHeight = UNDETERMINED; + if (nUseNewLineHeight == UNDETERMINED) + { + osl::MutexGuard aGuard( osl::Mutex::getGlobalMutex()); + if (nUseNewLineHeight == UNDETERMINED) + { + const char* pEnv = getenv( "SAL_USE_NEW_LINEHEIGHT"); + nUseNewLineHeight = (pEnv ? atoi(pEnv) : 0); + } + } + + static_cast<ImplFontAttributes&>(rTo) = mpFontInfo->GetFontAttributes(); + + rTo.mbScalableFont = true; + rTo.mbDevice = true; + rTo.mbKernableFont = (FT_HAS_KERNING( maFaceFT ) != 0) || mpFontInfo->HasExtraKerning(); + rTo.mnOrientation = GetFontSelData().mnOrientation; + + //Always consider [star]symbol as symbol fonts + if ( + (rTo.GetFamilyName().EqualsAscii("OpenSymbol")) || + (rTo.GetFamilyName().EqualsAscii("StarSymbol")) + ) + { + rTo.mbSymbolFlag = true; + } + + if( maSizeFT ) + pFTActivateSize( maSizeFT ); + + rFactor = 0x100; + + rTo.mnWidth = mnWidth; + + const FT_Size_Metrics& rMetrics = maFaceFT->size->metrics; + rTo.mnAscent = (+rMetrics.ascender + 32) >> 6; + rTo.mnDescent = (-rMetrics.descender + 32) >> 6; + if (nUseNewLineHeight) + { + rTo.mnExtLeading = ((rMetrics.height + 32) >> 6) - (rTo.mnAscent + rTo.mnDescent); + rTo.mnIntLeading = (rTo.mnAscent + rTo.mnDescent) - ((maFaceFT->units_per_EM + 32) >> 6); + } + else + { + rTo.mnIntLeading = ((rMetrics.height + 32) >> 6) - (rTo.mnAscent + rTo.mnDescent); + } + rTo.mnSlant = 0; + + const TT_OS2* pOS2 = (const TT_OS2*)FT_Get_Sfnt_Table( maFaceFT, ft_sfnt_os2 ); + if( pOS2 && (pOS2->version != 0xFFFF) ) + { + // map the panose info from the OS2 table to their VCL counterparts + switch( pOS2->panose[0] ) + { + case 1: rTo.meFamily = FAMILY_ROMAN; break; + case 2: rTo.meFamily = FAMILY_SWISS; break; + case 3: rTo.meFamily = FAMILY_MODERN; break; + case 4: rTo.meFamily = FAMILY_SCRIPT; break; + case 5: rTo.meFamily = FAMILY_DECORATIVE; break; + // TODO: is it reasonable to override the attribute with DONTKNOW? + case 0: // fall through + default: rTo.meFamilyType = FAMILY_DONTKNOW; break; + } + + switch( pOS2->panose[3] ) + { + case 2: // fall through + case 3: // fall through + case 4: // fall through + case 5: // fall through + case 6: // fall through + case 7: // fall through + case 8: rTo.mePitch = PITCH_VARIABLE; break; + case 9: rTo.mePitch = PITCH_FIXED; break; + // TODO: is it reasonable to override the attribute with DONTKNOW? + case 0: // fall through + case 1: // fall through + default: rTo.mePitch = PITCH_DONTKNOW; break; + } + + const double fScale = (double)GetFontSelData().mnHeight / maFaceFT->units_per_EM; + if (nUseNewLineHeight) + { + if( pOS2->sTypoAscender || pOS2->sTypoDescender ) + { + rTo.mnAscent = (long)( pOS2->sTypoAscender * fScale + 0.5 ); + rTo.mnDescent = (long)( -pOS2->sTypoDescender * fScale + 0.5 ); + rTo.mnExtLeading = (long)( pOS2->sTypoLineGap * fScale + 0.5 ); + rTo.mnIntLeading = (long)( (pOS2->sTypoAscender - pOS2->sTypoDescender - maFaceFT->units_per_EM) * fScale + 0.5 ); + } + } + else + { + // #108862# sanity check, some fonts treat descent as signed !!! + int nDescent = pOS2->usWinDescent; + if( nDescent > 5*maFaceFT->units_per_EM ) + nDescent = (short)pOS2->usWinDescent; // interpret it as signed! + + if( pOS2->usWinAscent || pOS2->usWinDescent ) // #i30551# + { + rTo.mnAscent = (long)( +pOS2->usWinAscent * fScale + 0.5 ); + rTo.mnDescent = (long)( +nDescent * fScale + 0.5 ); + rTo.mnIntLeading = (long)( (+pOS2->usWinAscent + pOS2->usWinDescent - maFaceFT->units_per_EM) * fScale + 0.5 ); + } + rTo.mnExtLeading = 0; + const TT_HoriHeader* pHHEA = (const TT_HoriHeader*)FT_Get_Sfnt_Table( maFaceFT, ft_sfnt_hhea ); + if( (pHHEA != NULL) && (pOS2->usWinAscent || pOS2->usWinDescent) ) + { + int nExtLeading = pHHEA->Line_Gap; + nExtLeading -= (pOS2->usWinAscent + pOS2->usWinDescent); + nExtLeading += (pHHEA->Ascender - pHHEA->Descender); + if( nExtLeading > 0 ) + rTo.mnExtLeading = (long)(nExtLeading * fScale + 0.5); + } + + // Check for CJK capabilities of the current font + // #107888# workaround for Asian... + // TODO: remove when ExtLeading fully implemented + sal_Bool bCJKCapable = ((pOS2->ulUnicodeRange2 & 0x2DF00000) != 0); + + if ( bCJKCapable && (pOS2->usWinAscent || pOS2->usWinDescent) ) + { + rTo.mnIntLeading += rTo.mnExtLeading; + + // #109280# The line height for Asian fonts is too small. + // Therefore we add half of the external leading to the + // ascent, the other half is added to the descent. + const long nHalfTmpExtLeading = rTo.mnExtLeading / 2; + const long nOtherHalfTmpExtLeading = rTo.mnExtLeading - nHalfTmpExtLeading; + + // #110641# external leading for Asian fonts. + // The factor 0.3 has been verified during experiments. + const long nCJKExtLeading = (long)(0.30 * (rTo.mnAscent + rTo.mnDescent)); + + if ( nCJKExtLeading > rTo.mnExtLeading ) + rTo.mnExtLeading = nCJKExtLeading - rTo.mnExtLeading; + else + rTo.mnExtLeading = 0; + + rTo.mnAscent += nHalfTmpExtLeading; + rTo.mnDescent += nOtherHalfTmpExtLeading; + } + } + } + + // initialize kashida width + // TODO: what if there are different versions of this glyph available + rTo.mnMinKashida = rTo.mnAscent / 4; // a reasonable default + const int nKashidaGlyphId = GetRawGlyphIndex( 0x0640 ); + if( nKashidaGlyphId ) + { + GlyphData aGlyphData; + InitGlyphData( nKashidaGlyphId, aGlyphData ); + rTo.mnMinKashida = aGlyphData.GetMetric().GetCharWidth(); + } +} + +// ----------------------------------------------------------------------- + +static inline void SplitGlyphFlags( const ServerFont& rFont, int& nGlyphIndex, int& nGlyphFlags ) +{ + nGlyphFlags = nGlyphIndex & GF_FLAGMASK; + nGlyphIndex &= GF_IDXMASK; + + if( nGlyphIndex & GF_ISCHAR ) + nGlyphIndex = rFont.GetRawGlyphIndex( nGlyphIndex ); +} + +// ----------------------------------------------------------------------- + +int ServerFont::ApplyGlyphTransform( int nGlyphFlags, + FT_Glyph pGlyphFT, bool bForBitmapProcessing ) const +{ + int nAngle = GetFontSelData().mnOrientation; + // shortcut most common case + if( !nAngle && !nGlyphFlags ) + return nAngle; + + const FT_Size_Metrics& rMetrics = maFaceFT->size->metrics; + FT_Vector aVector; + FT_Matrix aMatrix; + + bool bStretched = false; + + switch( nGlyphFlags & GF_ROTMASK ) + { + default: // straight + aVector.x = 0; + aVector.y = 0; + aMatrix.xx = +mnCos; + aMatrix.yy = +mnCos; + aMatrix.xy = -mnSin; + aMatrix.yx = +mnSin; + break; + case GF_ROTL: // left + nAngle += 900; + bStretched = (mfStretch != 1.0); + aVector.x = (FT_Pos)(+rMetrics.descender * mfStretch); + aVector.y = -rMetrics.ascender; + aMatrix.xx = (FT_Pos)(-mnSin / mfStretch); + aMatrix.yy = (FT_Pos)(-mnSin * mfStretch); + aMatrix.xy = (FT_Pos)(-mnCos * mfStretch); + aMatrix.yx = (FT_Pos)(+mnCos / mfStretch); + break; + case GF_ROTR: // right + nAngle -= 900; + bStretched = (mfStretch != 1.0); + aVector.x = -maFaceFT->glyph->metrics.horiAdvance; + aVector.x += (FT_Pos)(rMetrics.descender * mnSin/65536.0); + aVector.y = (FT_Pos)(-rMetrics.descender * mfStretch * mnCos/65536.0); + aMatrix.xx = (FT_Pos)(+mnSin / mfStretch); + aMatrix.yy = (FT_Pos)(+mnSin * mfStretch); + aMatrix.xy = (FT_Pos)(+mnCos * mfStretch); + aMatrix.yx = (FT_Pos)(-mnCos / mfStretch); + break; + } + + while( nAngle < 0 ) + nAngle += 3600; + + if( pGlyphFT->format != FT_GLYPH_FORMAT_BITMAP ) + { + FT_Glyph_Transform( pGlyphFT, NULL, &aVector ); + + // orthogonal transforms are better handled by bitmap operations + if( bStretched || (bForBitmapProcessing && (nAngle % 900) != 0) ) + { + // workaround for compatibility with older FT versions + if( nFTVERSION < 2102 ) + { + FT_Fixed t = aMatrix.xy; + aMatrix.xy = aMatrix.yx; + aMatrix.yx = t; + } + + // apply non-orthogonal or stretch transformations + FT_Glyph_Transform( pGlyphFT, &aMatrix, NULL ); + nAngle = 0; + } + } + else + { + // FT<=2005 ignores transforms for bitmaps, so do it manually + FT_BitmapGlyph pBmpGlyphFT = reinterpret_cast<FT_BitmapGlyph>(pGlyphFT); + pBmpGlyphFT->left += (aVector.x + 32) >> 6; + pBmpGlyphFT->top += (aVector.y + 32) >> 6; + } + + return nAngle; +} + +// ----------------------------------------------------------------------- + +int ServerFont::GetRawGlyphIndex( sal_UCS4 aChar ) const +{ + if( mpFontInfo->IsSymbolFont() ) + { + if( !FT_IS_SFNT( maFaceFT ) ) + { + if( (aChar & 0xFF00) == 0xF000 ) + aChar &= 0xFF; // PS font symbol mapping + else if( aChar > 0xFF ) + return 0; + } + } + + // if needed recode from unicode to font encoding + if( maRecodeConverter ) + { + sal_Char aTempArray[8]; + sal_Size nTempSize; + sal_uInt32 nCvtInfo; + + // assume that modern UCS4 fonts have unicode CMAPs + // => no encoding remapping to unicode is needed + if( aChar > 0xFFFF ) + return 0; + + sal_Unicode aUCS2Char = static_cast<sal_Unicode>(aChar); + rtl_UnicodeToTextContext aContext = rtl_createUnicodeToTextContext( maRecodeConverter ); + int nChars = rtl_convertUnicodeToText( maRecodeConverter, aContext, + &aUCS2Char, 1, aTempArray, sizeof(aTempArray), + RTL_UNICODETOTEXT_FLAGS_UNDEFINED_QUESTIONMARK + | RTL_UNICODETOTEXT_FLAGS_INVALID_QUESTIONMARK, + &nCvtInfo, &nTempSize ); + rtl_destroyUnicodeToTextContext( maRecodeConverter, aContext ); + + aChar = 0; + for( int i = 0; i < nChars; ++i ) + aChar = aChar*256 + (aTempArray[i] & 0xFF); + } + + // cache glyph indexes in font info to share between different sizes + int nGlyphIndex = mpFontInfo->GetGlyphIndex( aChar ); + if( nGlyphIndex < 0 ) + { + nGlyphIndex = FT_Get_Char_Index( maFaceFT, aChar ); + if( !nGlyphIndex) + { + // check if symbol aliasing helps + if( (aChar <= 0x00FF) && mpFontInfo->IsSymbolFont() ) + nGlyphIndex = FT_Get_Char_Index( maFaceFT, aChar | 0xF000 ); + } + mpFontInfo->CacheGlyphIndex( aChar, nGlyphIndex ); + } + + return nGlyphIndex; +} + +// ----------------------------------------------------------------------- + +int ServerFont::FixupGlyphIndex( int nGlyphIndex, sal_UCS4 aChar ) const +{ + int nGlyphFlags = GF_NONE; + + // do glyph substitution if necessary + // CJK vertical writing needs special treatment + if( GetFontSelData().mbVertical ) + { + // TODO: rethink when GSUB is used for non-vertical case + GlyphSubstitution::const_iterator it = maGlyphSubstitution.find( nGlyphIndex ); + if( it == maGlyphSubstitution.end() ) + { + int nTemp = GetVerticalChar( aChar ); + if( nTemp ) // is substitution possible + nTemp = GetRawGlyphIndex( nTemp ); + if( nTemp ) // substitute manually if sensible + nGlyphIndex = nTemp | (GF_GSUB | GF_ROTL); + else + nGlyphFlags |= GetVerticalFlags( aChar ); + } + else + { + // for vertical GSUB also compensate for nOrientation=2700 + nGlyphIndex = (*it).second; + nGlyphFlags |= GF_GSUB | GF_ROTL; + } + } + + if( nGlyphIndex != 0 ) + nGlyphIndex |= nGlyphFlags; + + return nGlyphIndex; +} + + +// ----------------------------------------------------------------------- + +int ServerFont::GetGlyphIndex( sal_UCS4 aChar ) const +{ + int nGlyphIndex = GetRawGlyphIndex( aChar ); + nGlyphIndex = FixupGlyphIndex( nGlyphIndex, aChar ); + return nGlyphIndex; +} + +// ----------------------------------------------------------------------- + +static int lcl_GetCharWidth( FT_FaceRec_* pFaceFT, double fStretch, int nGlyphFlags ) +{ + int nCharWidth = pFaceFT->glyph->metrics.horiAdvance; + + if( nGlyphFlags & GF_ROTMASK ) // for bVertical rotated glyphs + { + const FT_Size_Metrics& rMetrics = pFaceFT->size->metrics; +#if (FTVERSION < 2000) + nCharWidth = (int)((rMetrics.height - rMetrics.descender) * fStretch); +#else + nCharWidth = (int)((rMetrics.height + rMetrics.descender) * fStretch); +#endif + } + + return (nCharWidth + 32) >> 6; +} + +// ----------------------------------------------------------------------- + +void ServerFont::InitGlyphData( int nGlyphIndex, GlyphData& rGD ) const +{ + if( maSizeFT ) + pFTActivateSize( maSizeFT ); + + int nGlyphFlags; + SplitGlyphFlags( *this, nGlyphIndex, nGlyphFlags ); + + int nLoadFlags = mnLoadFlags; + +// if( mbArtItalic ) +// nLoadFlags |= FT_LOAD_NO_BITMAP; + + FT_Error rc = -1; +#if (FTVERSION <= 2008) + // #88364# freetype<=2005 prefers autohinting to embedded bitmaps + // => first we have to try without hinting + if( (nLoadFlags & (FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP)) == 0 ) + { + rc = FT_Load_Glyph( maFaceFT, nGlyphIndex, nLoadFlags|FT_LOAD_NO_HINTING ); + if( (rc==FT_Err_Ok) && (maFaceFT->glyph->format!=FT_GLYPH_FORMAT_BITMAP) ) + rc = -1; // mark as "loading embedded bitmap" was unsuccessful + nLoadFlags |= FT_LOAD_NO_BITMAP; + } + + if( rc != FT_Err_Ok ) +#endif + rc = FT_Load_Glyph( maFaceFT, nGlyphIndex, nLoadFlags ); + + if( rc != FT_Err_Ok ) + { + // we get here e.g. when a PS font lacks the default glyph + rGD.SetCharWidth( 0 ); + rGD.SetDelta( 0, 0 ); + rGD.SetOffset( 0, 0 ); + rGD.SetSize( Size( 0, 0 ) ); + return; + } + + const bool bOriginallyZeroWidth = (maFaceFT->glyph->metrics.horiAdvance == 0); + if( mbArtBold && pFTEmbolden ) + (*pFTEmbolden)( maFaceFT->glyph ); + + const int nCharWidth = bOriginallyZeroWidth ? 0 : lcl_GetCharWidth( maFaceFT, mfStretch, nGlyphFlags ); + rGD.SetCharWidth( nCharWidth ); + + FT_Glyph pGlyphFT; + rc = FT_Get_Glyph( maFaceFT->glyph, &pGlyphFT ); + + ApplyGlyphTransform( nGlyphFlags, pGlyphFT, false ); + if( mbArtBold && pFTEmbolden && (nFTVERSION < 2200) ) // #i71094# workaround staircase bug + pGlyphFT->advance.y = 0; + rGD.SetDelta( (pGlyphFT->advance.x + 0x8000) >> 16, -((pGlyphFT->advance.y + 0x8000) >> 16) ); + + FT_BBox aBbox; + FT_Glyph_Get_CBox( pGlyphFT, FT_GLYPH_BBOX_PIXELS, &aBbox ); + if( aBbox.yMin > aBbox.yMax ) // circumvent freetype bug + { + int t=aBbox.yMin; aBbox.yMin=aBbox.yMax, aBbox.yMax=t; + } + + rGD.SetOffset( aBbox.xMin, -aBbox.yMax ); + rGD.SetSize( Size( (aBbox.xMax-aBbox.xMin+1), (aBbox.yMax-aBbox.yMin) ) ); + + FT_Done_Glyph( pGlyphFT ); +} + +// ----------------------------------------------------------------------- + +bool ServerFont::GetAntialiasAdvice( void ) const +{ + if( GetFontSelData().mbNonAntialiased || (mnPrioAntiAlias<=0) ) + return false; + bool bAdviseAA = true; + // TODO: also use GASP info + return bAdviseAA; +} + +// ----------------------------------------------------------------------- + +bool ServerFont::GetGlyphBitmap1( int nGlyphIndex, RawBitmap& rRawBitmap ) const +{ + if( maSizeFT ) + pFTActivateSize( maSizeFT ); + + int nGlyphFlags; + SplitGlyphFlags( *this, nGlyphIndex, nGlyphFlags ); + + FT_Int nLoadFlags = mnLoadFlags; + // #i70930# force mono-hinting for monochrome text + if( nFTVERSION >= 2110 ) //#i71947# unless it looks worse + { + nLoadFlags &= ~0xF0000; + nLoadFlags |= FT_LOAD_TARGET_MONO; + } + + if( mbArtItalic ) + nLoadFlags |= FT_LOAD_NO_BITMAP; + +#if (FTVERSION >= 2002) + // for 0/90/180/270 degree fonts enable hinting even if not advisable + // non-hinted and non-antialiased bitmaps just look too ugly + if( (mnCos==0 || mnSin==0) && (mnPrioAutoHint > 0) ) + nLoadFlags &= ~FT_LOAD_NO_HINTING; +#endif + + if( mnPrioEmbedded <= mnPrioAutoHint ) + nLoadFlags |= FT_LOAD_NO_BITMAP; + + FT_Error rc = -1; +#if (FTVERSION <= 2008) + // #88364# freetype<=2005 prefers autohinting to embedded bitmaps + // => first we have to try without hinting + if( (nLoadFlags & (FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP)) == 0 ) + { + rc = FT_Load_Glyph( maFaceFT, nGlyphIndex, nLoadFlags|FT_LOAD_NO_HINTING ); + if( (rc==FT_Err_Ok) && (maFaceFT->glyph->format != FT_GLYPH_FORMAT_BITMAP) ) + rc = -1; // mark as "loading embedded bitmap" was unsuccessful + nLoadFlags |= FT_LOAD_NO_BITMAP; + } + + if( rc != FT_Err_Ok ) +#endif + rc = FT_Load_Glyph( maFaceFT, nGlyphIndex, nLoadFlags ); + if( rc != FT_Err_Ok ) + return false; + + if( mbArtBold && pFTEmbolden ) + (*pFTEmbolden)( maFaceFT->glyph ); + + FT_Glyph pGlyphFT; + rc = FT_Get_Glyph( maFaceFT->glyph, &pGlyphFT ); + if( rc != FT_Err_Ok ) + return false; + + int nAngle = ApplyGlyphTransform( nGlyphFlags, pGlyphFT, true ); + + if( mbArtItalic ) + { + FT_Matrix aMatrix; + aMatrix.xx = aMatrix.yy = 0x10000L; + if( nFTVERSION >= 2102 ) // Freetype 2.1.2 API swapped xy with yx + aMatrix.xy = 0x6000L, aMatrix.yx = 0; + else + aMatrix.yx = 0x6000L, aMatrix.xy = 0; + FT_Glyph_Transform( pGlyphFT, &aMatrix, NULL ); + } + + // Check for zero area bounding boxes as this crashes some versions of FT. + // This also provides a handy short cut as much of the code following + // becomes an expensive nop when a glyph covers no pixels. + FT_BBox cbox; + FT_Glyph_Get_CBox(pGlyphFT, ft_glyph_bbox_unscaled, &cbox); + + if( (cbox.xMax - cbox.xMin) == 0 || (cbox.yMax - cbox.yMin == 0) ) + { + nAngle = 0; + memset(&rRawBitmap, 0, sizeof rRawBitmap); + FT_Done_Glyph( pGlyphFT ); + return true; + } + + if( pGlyphFT->format != FT_GLYPH_FORMAT_BITMAP ) + { + if( pGlyphFT->format == FT_GLYPH_FORMAT_OUTLINE ) + ((FT_OutlineGlyphRec*)pGlyphFT)->outline.flags |= FT_OUTLINE_HIGH_PRECISION; + // #i15743# freetype API 2.1.3 changed the FT_RENDER_MODE_MONO constant + FT_Render_Mode nRenderMode = (FT_Render_Mode)((nFTVERSION<2103) ? 1 : FT_RENDER_MODE_MONO); + + rc = FT_Glyph_To_Bitmap( &pGlyphFT, nRenderMode, NULL, sal_True ); + if( rc != FT_Err_Ok ) + { + FT_Done_Glyph( pGlyphFT ); + return false; + } + } + + const FT_BitmapGlyph pBmpGlyphFT = reinterpret_cast<const FT_BitmapGlyph>(pGlyphFT); + // NOTE: autohinting in FT<=2.0.2 miscalculates the offsets below by +-1 + rRawBitmap.mnXOffset = +pBmpGlyphFT->left; + rRawBitmap.mnYOffset = -pBmpGlyphFT->top; + + const FT_Bitmap& rBitmapFT = pBmpGlyphFT->bitmap; + rRawBitmap.mnHeight = rBitmapFT.rows; + rRawBitmap.mnBitCount = 1; + if( mbArtBold && !pFTEmbolden ) + { + rRawBitmap.mnWidth = rBitmapFT.width + 1; + int nLineBytes = (rRawBitmap.mnWidth + 7) >> 3; + rRawBitmap.mnScanlineSize = (nLineBytes > rBitmapFT.pitch) ? nLineBytes : rBitmapFT.pitch; + } + else + { + rRawBitmap.mnWidth = rBitmapFT.width; + rRawBitmap.mnScanlineSize = rBitmapFT.pitch; + } + + const sal_uLong nNeededSize = rRawBitmap.mnScanlineSize * rRawBitmap.mnHeight; + + if( rRawBitmap.mnAllocated < nNeededSize ) + { + delete[] rRawBitmap.mpBits; + rRawBitmap.mnAllocated = 2*nNeededSize; + rRawBitmap.mpBits = new unsigned char[ rRawBitmap.mnAllocated ]; + } + + if( !mbArtBold || pFTEmbolden ) + { + memcpy( rRawBitmap.mpBits, rBitmapFT.buffer, nNeededSize ); + } + else + { + memset( rRawBitmap.mpBits, 0, nNeededSize ); + const unsigned char* pSrcLine = rBitmapFT.buffer; + unsigned char* pDstLine = rRawBitmap.mpBits; + for( int h = rRawBitmap.mnHeight; --h >= 0; ) + { + memcpy( pDstLine, pSrcLine, rBitmapFT.pitch ); + pDstLine += rRawBitmap.mnScanlineSize; + pSrcLine += rBitmapFT.pitch; + } + + unsigned char* p = rRawBitmap.mpBits; + for( sal_uLong y=0; y < rRawBitmap.mnHeight; y++ ) + { + unsigned char nLastByte = 0; + for( sal_uLong x=0; x < rRawBitmap.mnScanlineSize; x++ ) + { + unsigned char nTmp = p[x] << 7; + p[x] |= (p[x] >> 1) | nLastByte; + nLastByte = nTmp; + } + p += rRawBitmap.mnScanlineSize; + } + } + + FT_Done_Glyph( pGlyphFT ); + + // special case for 0/90/180/270 degree orientation + switch( nAngle ) + { + case -900: + case +900: + case +1800: + case +2700: + rRawBitmap.Rotate( nAngle ); + break; + } + + return true; +} + +// ----------------------------------------------------------------------- + +bool ServerFont::GetGlyphBitmap8( int nGlyphIndex, RawBitmap& rRawBitmap ) const +{ + if( maSizeFT ) + pFTActivateSize( maSizeFT ); + + int nGlyphFlags; + SplitGlyphFlags( *this, nGlyphIndex, nGlyphFlags ); + + FT_Int nLoadFlags = mnLoadFlags; + + if( mbArtItalic ) + nLoadFlags |= FT_LOAD_NO_BITMAP; + +#if (FTVERSION <= 2004) && !defined(TT_CONFIG_OPTION_BYTECODE_INTERPRETER) + // autohinting in FT<=2.0.4 makes antialiased glyphs look worse + nLoadFlags |= FT_LOAD_NO_HINTING; +#else + if( (nGlyphFlags & GF_UNHINTED) || (mnPrioAutoHint < mnPrioAntiAlias) ) + nLoadFlags |= FT_LOAD_NO_HINTING; +#endif + + if( mnPrioEmbedded <= mnPrioAntiAlias ) + nLoadFlags |= FT_LOAD_NO_BITMAP; + + FT_Error rc = -1; +#if (FTVERSION <= 2008) + // #88364# freetype<=2005 prefers autohinting to embedded bitmaps + // => first we have to try without hinting + if( (nLoadFlags & (FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP)) == 0 ) + { + rc = FT_Load_Glyph( maFaceFT, nGlyphIndex, nLoadFlags|FT_LOAD_NO_HINTING ); + if( (rc==FT_Err_Ok) && (maFaceFT->glyph->format != FT_GLYPH_FORMAT_BITMAP) ) + rc = -1; // mark as "loading embedded bitmap" was unsuccessful + nLoadFlags |= FT_LOAD_NO_BITMAP; + } + + if( rc != FT_Err_Ok ) +#endif + rc = FT_Load_Glyph( maFaceFT, nGlyphIndex, nLoadFlags ); + + if( rc != FT_Err_Ok ) + return false; + + if( mbArtBold && pFTEmbolden ) + (*pFTEmbolden)( maFaceFT->glyph ); + + FT_Glyph pGlyphFT; + rc = FT_Get_Glyph( maFaceFT->glyph, &pGlyphFT ); + if( rc != FT_Err_Ok ) + return false; + + int nAngle = ApplyGlyphTransform( nGlyphFlags, pGlyphFT, true ); + + if( mbArtItalic ) + { + FT_Matrix aMatrix; + aMatrix.xx = aMatrix.yy = 0x10000L; + if( nFTVERSION >= 2102 ) // Freetype 2.1.2 API swapped xy with yx + aMatrix.xy = 0x6000L, aMatrix.yx = 0; + else + aMatrix.yx = 0x6000L, aMatrix.xy = 0; + FT_Glyph_Transform( pGlyphFT, &aMatrix, NULL ); + } + + if( pGlyphFT->format == FT_GLYPH_FORMAT_OUTLINE ) + ((FT_OutlineGlyph)pGlyphFT)->outline.flags |= FT_OUTLINE_HIGH_PRECISION; + + bool bEmbedded = (pGlyphFT->format == FT_GLYPH_FORMAT_BITMAP); + if( !bEmbedded ) + { + rc = FT_Glyph_To_Bitmap( &pGlyphFT, FT_RENDER_MODE_NORMAL, NULL, sal_True ); + if( rc != FT_Err_Ok ) + { + FT_Done_Glyph( pGlyphFT ); + return false; + } + } + + const FT_BitmapGlyph pBmpGlyphFT = reinterpret_cast<const FT_BitmapGlyph>(pGlyphFT); + rRawBitmap.mnXOffset = +pBmpGlyphFT->left; + rRawBitmap.mnYOffset = -pBmpGlyphFT->top; + + const FT_Bitmap& rBitmapFT = pBmpGlyphFT->bitmap; + rRawBitmap.mnHeight = rBitmapFT.rows; + rRawBitmap.mnWidth = rBitmapFT.width; + rRawBitmap.mnBitCount = 8; + rRawBitmap.mnScanlineSize = bEmbedded ? rBitmapFT.width : rBitmapFT.pitch; + if( mbArtBold && !pFTEmbolden ) + { + ++rRawBitmap.mnWidth; + ++rRawBitmap.mnScanlineSize; + } + rRawBitmap.mnScanlineSize = (rRawBitmap.mnScanlineSize + 3) & -4; + + const sal_uLong nNeededSize = rRawBitmap.mnScanlineSize * rRawBitmap.mnHeight; + if( rRawBitmap.mnAllocated < nNeededSize ) + { + delete[] rRawBitmap.mpBits; + rRawBitmap.mnAllocated = 2*nNeededSize; + rRawBitmap.mpBits = new unsigned char[ rRawBitmap.mnAllocated ]; + } + + const unsigned char* pSrc = rBitmapFT.buffer; + unsigned char* pDest = rRawBitmap.mpBits; + if( !bEmbedded ) + { + for( int y = rRawBitmap.mnHeight, x; --y >= 0 ; ) + { + for( x = 0; x < rBitmapFT.width; ++x ) + *(pDest++) = *(pSrc++); + for(; x < int(rRawBitmap.mnScanlineSize); ++x ) + *(pDest++) = 0; + } + } + else + { + for( int y = rRawBitmap.mnHeight, x; --y >= 0 ; ) + { + unsigned char nSrc = 0; + for( x = 0; x < rBitmapFT.width; ++x, nSrc+=nSrc ) + { + if( (x & 7) == 0 ) + nSrc = *(pSrc++); + *(pDest++) = (0x7F - nSrc) >> 8; + } + for(; x < int(rRawBitmap.mnScanlineSize); ++x ) + *(pDest++) = 0; + } + } + + if( mbArtBold && !pFTEmbolden ) + { + // overlay with glyph image shifted by one left pixel + unsigned char* p = rRawBitmap.mpBits; + for( sal_uLong y=0; y < rRawBitmap.mnHeight; y++ ) + { + unsigned char nLastByte = 0; + for( sal_uLong x=0; x < rRawBitmap.mnWidth; x++ ) + { + unsigned char nTmp = p[x]; + p[x] |= p[x] | nLastByte; + nLastByte = nTmp; + } + p += rRawBitmap.mnScanlineSize; + } + } + + if( !bEmbedded && mbUseGamma ) + { + unsigned char* p = rRawBitmap.mpBits; + for( sal_uLong y=0; y < rRawBitmap.mnHeight; y++ ) + { + for( sal_uLong x=0; x < rRawBitmap.mnWidth; x++ ) + { + p[x] = aGammaTable[ p[x] ]; + } + p += rRawBitmap.mnScanlineSize; + } + } + + FT_Done_Glyph( pGlyphFT ); + + // special case for 0/90/180/270 degree orientation + switch( nAngle ) + { + case -900: + case +900: + case +1800: + case +2700: + rRawBitmap.Rotate( nAngle ); + break; + } + + return true; +} + +// ----------------------------------------------------------------------- +// determine unicode ranges in font +// ----------------------------------------------------------------------- + +const ImplFontCharMap* ServerFont::GetImplFontCharMap( void ) const +{ + const ImplFontCharMap* pIFCMap = mpFontInfo->GetImplFontCharMap(); + return pIFCMap; +} + +const ImplFontCharMap* FtFontInfo::GetImplFontCharMap( void ) +{ + // check if the charmap is already cached + if( mpFontCharMap ) + return mpFontCharMap; + + // get the charmap and cache it + CmapResult aCmapResult; + bool bOK = GetFontCodeRanges( aCmapResult ); + if( bOK ) + mpFontCharMap = new ImplFontCharMap( aCmapResult ); + else + mpFontCharMap = ImplFontCharMap::GetDefaultMap(); + mpFontCharMap->AddReference(); + return mpFontCharMap; +} + +// TODO: merge into method GetFontCharMap() +bool FtFontInfo::GetFontCodeRanges( CmapResult& rResult ) const +{ + rResult.mbSymbolic = IsSymbolFont(); + + // TODO: is the full CmapResult needed on platforms calling this? + if( FT_IS_SFNT( maFaceFT ) ) + { + sal_uLong nLength = 0; + const unsigned char* pCmap = GetTable( "cmap", &nLength ); + if( pCmap && (nLength > 0) ) + if( ParseCMAP( pCmap, nLength, rResult ) ) + return true; + } + + typedef std::vector<sal_uInt32> U32Vector; + U32Vector aCodes; + + // FT's coverage is available since FT>=2.1.0 (OOo-baseline>=2.1.4 => ok) + aCodes.reserve( 0x1000 ); + FT_UInt nGlyphIndex; + for( sal_uInt32 cCode = FT_Get_First_Char( maFaceFT, &nGlyphIndex );; ) + { + if( !nGlyphIndex ) + break; + aCodes.push_back( cCode ); // first code inside range + sal_uInt32 cNext = cCode; + do cNext = FT_Get_Next_Char( maFaceFT, cCode, &nGlyphIndex ); while( cNext == ++cCode ); + aCodes.push_back( cCode ); // first code outside range + cCode = cNext; + } + + const int nCount = aCodes.size(); + if( !nCount) { + if( !rResult.mbSymbolic ) + return false; + + // we usually get here for Type1 symbol fonts + aCodes.push_back( 0xF020 ); + aCodes.push_back( 0xF100 ); + } + + sal_uInt32* pCodes = new sal_uInt32[ nCount ]; + for( int i = 0; i < nCount; ++i ) + pCodes[i] = aCodes[i]; + rResult.mpRangeCodes = pCodes; + rResult.mnRangeCount = nCount / 2; + return true; +} + +bool ServerFont::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const +{ + bool bRet = false; + + sal_uLong nLength = 0; + // load GSUB table + const FT_Byte* pGSUB = mpFontInfo->GetTable("GSUB", &nLength); + if (pGSUB) + vcl::getTTScripts(rFontCapabilities.maGSUBScriptTags, pGSUB, nLength); + + // load OS/2 table + const FT_Byte* pOS2 = mpFontInfo->GetTable("OS/2", &nLength); + if (pOS2) + { + bRet = vcl::getTTCoverage( + rFontCapabilities.maUnicodeRange, + rFontCapabilities.maCodePageRange, + pOS2, nLength); + } + + return bRet; +} + +// ----------------------------------------------------------------------- +// kerning stuff +// ----------------------------------------------------------------------- + +int ServerFont::GetGlyphKernValue( int nGlyphLeft, int nGlyphRight ) const +{ + // if no kerning info is available from Freetype + // then we may have to use extra info provided by e.g. psprint + if( !FT_HAS_KERNING( maFaceFT ) || !FT_IS_SFNT( maFaceFT ) ) + { + int nKernVal = mpFontInfo->GetExtraGlyphKernValue( nGlyphLeft, nGlyphRight ); + if( !nKernVal ) + return 0; + // scale the kern value to match the font size + const ImplFontSelectData& rFSD = GetFontSelData(); + nKernVal *= rFSD.mnWidth ? rFSD.mnWidth : rFSD.mnHeight; + return (nKernVal + 500) / 1000; + } + + // when font faces of different sizes share the same maFaceFT + // then we have to make sure that it uses the correct maSizeFT + if( maSizeFT ) + pFTActivateSize( maSizeFT ); + + // use Freetype's kerning info + FT_Vector aKernVal; + FT_Error rcFT = FT_Get_Kerning( maFaceFT, nGlyphLeft, nGlyphRight, + FT_KERNING_DEFAULT, &aKernVal ); + int nResult = (rcFT == FT_Err_Ok) ? (aKernVal.x + 32) >> 6 : 0; + return nResult; +} + +// ----------------------------------------------------------------------- + +sal_uLong ServerFont::GetKernPairs( ImplKernPairData** ppKernPairs ) const +{ + // if no kerning info is available in the font file + *ppKernPairs = NULL; + if( !FT_HAS_KERNING( maFaceFT ) || !FT_IS_SFNT( maFaceFT ) ) + { + // then we have may have extra kerning info from e.g. psprint + int nCount = mpFontInfo->GetExtraKernPairs( ppKernPairs ); + // scale the kern values to match the font size + const ImplFontSelectData& rFSD = GetFontSelData(); + int nFontWidth = rFSD.mnWidth ? rFSD.mnWidth : rFSD.mnHeight; + ImplKernPairData* pKernPair = *ppKernPairs; + for( int i = nCount; --i >= 0; ++pKernPair ) + { + long& rVal = pKernPair->mnKern; + rVal = ((rVal * nFontWidth) + 500) / 1000; + } + return nCount; + } + + // when font faces of different sizes share the same maFaceFT + // then we have to make sure that it uses the correct maSizeFT + if( maSizeFT ) + pFTActivateSize( maSizeFT ); + + // first figure out which glyph pairs are involved in kerning + sal_uLong nKernLength = 0; + const FT_Byte* const pKern = mpFontInfo->GetTable( "kern", &nKernLength ); + if( !pKern ) + return 0; + + // combine TTF/OTF tables from the font file to build a vector of + // unicode kerning pairs using Freetype's glyph kerning calculation + // for the kerning value + + // TODO: is it worth to share the glyph->unicode mapping between + // different instances of the same font face? + + typedef std::vector<ImplKernPairData> KernVector; + KernVector aKernGlyphVector; + ImplKernPairData aKernPair; + aKernPair.mnKern = 0; // To prevent "is used uninitialized" warning... + + const FT_Byte* pBuffer = pKern; + sal_uLong nVersion = GetUShort( pBuffer+0 ); + sal_uInt16 nTableCnt = GetUShort( pBuffer+2 ); + + // Microsoft/Old TrueType style kern table + if ( nVersion == 0 ) + { + pBuffer += 4; + + for( sal_uInt16 nTableIdx = 0; nTableIdx < nTableCnt; ++nTableIdx ) + { + // sal_uInt16 nSubVersion = GetUShort( pBuffer+0 ); + // sal_uInt16 nSubLength = GetUShort( pBuffer+2 ); + sal_uInt16 nSubCoverage = GetUShort( pBuffer+4 ); + pBuffer += 6; + if( (nSubCoverage&0x03) != 0x01 ) // no interest in minimum info here + continue; + switch( nSubCoverage >> 8 ) + { + case 0: // version 0, kerning format 0 + { + sal_uInt16 nPairs = GetUShort( pBuffer ); + pBuffer += 8; // skip search hints + aKernGlyphVector.reserve( aKernGlyphVector.size() + nPairs ); + for( int i = 0; i < nPairs; ++i ) + { + aKernPair.mnChar1 = GetUShort( pBuffer+0 ); + aKernPair.mnChar2 = GetUShort( pBuffer+2 ); + //long nUnscaledKern= GetSShort( pBuffer ); + pBuffer += 6; + aKernGlyphVector.push_back( aKernPair ); + } + } + break; + + case 2: // version 0, kerning format 2 + { + const FT_Byte* pSubTable = pBuffer; + //sal_uInt16 nRowWidth = GetUShort( pBuffer+0 ); + sal_uInt16 nOfsLeft = GetUShort( pBuffer+2 ); + sal_uInt16 nOfsRight = GetUShort( pBuffer+4 ); + sal_uInt16 nOfsArray = GetUShort( pBuffer+6 ); + pBuffer += 8; + + const FT_Byte* pTmp = pSubTable + nOfsLeft; + sal_uInt16 nFirstLeft = GetUShort( pTmp+0 ); + sal_uInt16 nLastLeft = GetUShort( pTmp+2 ) + nFirstLeft - 1; + + pTmp = pSubTable + nOfsRight; + sal_uInt16 nFirstRight = GetUShort( pTmp+0 ); + sal_uInt16 nLastRight = GetUShort( pTmp+2 ) + nFirstRight - 1; + + sal_uLong nPairs = (sal_uLong)(nLastLeft - nFirstLeft + 1) * (nLastRight - nFirstRight + 1); + aKernGlyphVector.reserve( aKernGlyphVector.size() + nPairs ); + + pTmp = pSubTable + nOfsArray; + for( int nLeft = nFirstLeft; nLeft < nLastLeft; ++nLeft ) + { + aKernPair.mnChar1 = nLeft; + for( int nRight = 0; nRight < nLastRight; ++nRight ) + { + if( GetUShort( pTmp ) != 0 ) + { + aKernPair.mnChar2 = nRight; + aKernGlyphVector.push_back( aKernPair ); + } + pTmp += 2; + } + } + } + break; + } + } + } + + // Apple New style kern table + pBuffer = pKern; + nVersion = NEXT_U32( pBuffer ); + nTableCnt = NEXT_U32( pBuffer ); + if ( nVersion == 0x00010000 ) + { + for( sal_uInt16 nTableIdx = 0; nTableIdx < nTableCnt; ++nTableIdx ) + { + /*sal_uLong nLength =*/ NEXT_U32( pBuffer ); + sal_uInt16 nCoverage = NEXT_U16( pBuffer ); + /*sal_uInt16 nTupleIndex =*/ NEXT_U16( pBuffer ); + + // Kerning sub-table format, 0 through 3 + sal_uInt8 nSubTableFormat = nCoverage & 0x00FF; + + switch( nSubTableFormat ) + { + case 0: // version 0, kerning format 0 + { + sal_uInt16 nPairs = NEXT_U16( pBuffer ); + pBuffer += 6; // skip search hints + aKernGlyphVector.reserve( aKernGlyphVector.size() + nPairs ); + for( int i = 0; i < nPairs; ++i ) + { + aKernPair.mnChar1 = NEXT_U16( pBuffer ); + aKernPair.mnChar2 = NEXT_U16( pBuffer ); + /*long nUnscaledKern=*/ NEXT_S16( pBuffer ); + aKernGlyphVector.push_back( aKernPair ); + } + } + break; + + case 2: // version 0, kerning format 2 + { + const FT_Byte* pSubTable = pBuffer; + /*sal_uInt16 nRowWidth =*/ NEXT_U16( pBuffer ); + sal_uInt16 nOfsLeft = NEXT_U16( pBuffer ); + sal_uInt16 nOfsRight = NEXT_U16( pBuffer ); + sal_uInt16 nOfsArray = NEXT_U16( pBuffer ); + + const FT_Byte* pTmp = pSubTable + nOfsLeft; + sal_uInt16 nFirstLeft = NEXT_U16( pTmp ); + sal_uInt16 nLastLeft = NEXT_U16( pTmp ) + nFirstLeft - 1; + + pTmp = pSubTable + nOfsRight; + sal_uInt16 nFirstRight = NEXT_U16( pTmp ); + sal_uInt16 nLastRight = NEXT_U16( pTmp ) + nFirstRight - 1; + + sal_uLong nPairs = (sal_uLong)(nLastLeft - nFirstLeft + 1) * (nLastRight - nFirstRight + 1); + aKernGlyphVector.reserve( aKernGlyphVector.size() + nPairs ); + + pTmp = pSubTable + nOfsArray; + for( int nLeft = nFirstLeft; nLeft < nLastLeft; ++nLeft ) + { + aKernPair.mnChar1 = nLeft; + for( int nRight = 0; nRight < nLastRight; ++nRight ) + { + if( NEXT_S16( pTmp ) != 0 ) + { + aKernPair.mnChar2 = nRight; + aKernGlyphVector.push_back( aKernPair ); + } + } + } + } + break; + + default: + fprintf( stderr, "gcach_ftyp.cxx: Found unsupported Apple-style kern subtable type %d.\n", nSubTableFormat ); + break; + } + } + } + + // now create VCL's ImplKernPairData[] format for all glyph pairs + sal_uLong nKernCount = aKernGlyphVector.size(); + if( nKernCount ) + { + // prepare glyphindex to character mapping + // TODO: this is needed to support VCL's existing kerning infrastructure, + // eliminate it up by redesigning kerning infrastructure to work with glyph indizes + typedef boost::unordered_multimap<sal_uInt16,sal_Unicode> Cmap; + Cmap aCmap; + for( sal_Unicode aChar = 0x0020; aChar < 0xFFFE; ++aChar ) + { + sal_uInt16 nGlyphIndex = GetGlyphIndex( aChar ); + if( nGlyphIndex ) + aCmap.insert( Cmap::value_type( nGlyphIndex, aChar ) ); + } + + // translate both glyph indizes in kerning pairs to characters + // problem is that these are 1:n mappings... + KernVector aKernCharVector; + aKernCharVector.reserve( nKernCount ); + KernVector::iterator it; + for( it = aKernGlyphVector.begin(); it != aKernGlyphVector.end(); ++it ) + { + FT_Vector aKernVal; + FT_Error rcFT = FT_Get_Kerning( maFaceFT, it->mnChar1, it->mnChar2, + FT_KERNING_DEFAULT, &aKernVal ); + aKernPair.mnKern = aKernVal.x >> 6; + if( (aKernPair.mnKern == 0) || (rcFT != FT_Err_Ok) ) + continue; + + typedef std::pair<Cmap::iterator,Cmap::iterator> CPair; + const CPair p1 = aCmap.equal_range( it->mnChar1 ); + const CPair p2 = aCmap.equal_range( it->mnChar2 ); + for( Cmap::const_iterator i1 = p1.first; i1 != p1.second; ++i1 ) + { + aKernPair.mnChar1 = (*i1).second; + for( Cmap::const_iterator i2 = p2.first; i2 != p2.second; ++i2 ) + { + aKernPair.mnChar2 = (*i2).second; + aKernCharVector.push_back( aKernPair ); + } + } + } + + // now move the resulting vector into VCL's ImplKernPairData[] format + nKernCount = aKernCharVector.size(); + ImplKernPairData* pTo = new ImplKernPairData[ nKernCount ]; + *ppKernPairs = pTo; + for( it = aKernCharVector.begin(); it != aKernCharVector.end(); ++it, ++pTo ) + { + pTo->mnChar1 = it->mnChar1; + pTo->mnChar2 = it->mnChar2; + pTo->mnKern = it->mnKern; + } + } + + return nKernCount; +} + +// ----------------------------------------------------------------------- +// outline stuff +// ----------------------------------------------------------------------- + +class PolyArgs +{ +public: + PolyArgs( PolyPolygon& rPolyPoly, sal_uInt16 nMaxPoints ); + ~PolyArgs(); + + void AddPoint( long nX, long nY, PolyFlags); + void ClosePolygon(); + + long GetPosX() const { return maPosition.x;} + long GetPosY() const { return maPosition.y;} + +private: + PolyPolygon& mrPolyPoly; + + Point* mpPointAry; + sal_uInt8* mpFlagAry; + + FT_Vector maPosition; + sal_uInt16 mnMaxPoints; + sal_uInt16 mnPoints; + sal_uInt16 mnPoly; + long mnHeight; + bool bHasOffline; +}; + +// ----------------------------------------------------------------------- + +PolyArgs::PolyArgs( PolyPolygon& rPolyPoly, sal_uInt16 nMaxPoints ) +: mrPolyPoly(rPolyPoly), + mnMaxPoints(nMaxPoints), + mnPoints(0), + mnPoly(0), + mnHeight(0), + bHasOffline(false) +{ + mpPointAry = new Point[ mnMaxPoints ]; + mpFlagAry = new sal_uInt8 [ mnMaxPoints ]; +} + +// ----------------------------------------------------------------------- + + +PolyArgs::~PolyArgs() +{ + delete[] mpFlagAry; + delete[] mpPointAry; +} + +// ----------------------------------------------------------------------- + +void PolyArgs::AddPoint( long nX, long nY, PolyFlags aFlag ) +{ + DBG_ASSERT( (mnPoints < mnMaxPoints), "FTGlyphOutline: AddPoint overflow!" ); + if( mnPoints >= mnMaxPoints ) + return; + + maPosition.x = nX; + maPosition.y = nY; + mpPointAry[ mnPoints ] = Point( nX, nY ); + mpFlagAry[ mnPoints++ ]= aFlag; + bHasOffline |= (aFlag != POLY_NORMAL); +} + +// ----------------------------------------------------------------------- + +void PolyArgs::ClosePolygon() +{ + if( !mnPoly++ ) + return; + + // freetype seems to always close the polygon with an ON_CURVE point + // PolyPoly wants to close the polygon itself => remove last point + DBG_ASSERT( (mnPoints >= 2), "FTGlyphOutline: PolyFinishNum failed!" ); + --mnPoints; + DBG_ASSERT( (mpPointAry[0]==mpPointAry[mnPoints]), "FTGlyphOutline: PolyFinishEq failed!" ); + DBG_ASSERT( (mpFlagAry[0]==POLY_NORMAL), "FTGlyphOutline: PolyFinishFE failed!" ); + DBG_ASSERT( (mpFlagAry[mnPoints]==POLY_NORMAL), "FTGlyphOutline: PolyFinishFS failed!" ); + + Polygon aPoly( mnPoints, mpPointAry, (bHasOffline ? mpFlagAry : NULL) ); + + // #i35928# + // This may be a invalid polygons, e.g. the last point is a control point. + // So close the polygon (and add the first point again) if the last point + // is a control point or different from first. + // #i48298# + // Now really duplicating the first point, to close or correct the + // polygon. Also no longer duplicating the flags, but enforcing + // POLY_NORMAL for the newly added last point. + const sal_uInt16 nPolySize(aPoly.GetSize()); + if(nPolySize) + { + if((aPoly.HasFlags() && POLY_CONTROL == aPoly.GetFlags(nPolySize - 1)) + || (aPoly.GetPoint(nPolySize - 1) != aPoly.GetPoint(0))) + { + aPoly.SetSize(nPolySize + 1); + aPoly.SetPoint(aPoly.GetPoint(0), nPolySize); + + if(aPoly.HasFlags()) + { + aPoly.SetFlags(nPolySize, POLY_NORMAL); + } + } + } + + mrPolyPoly.Insert( aPoly ); + mnPoints = 0; + bHasOffline = false; +} + +// ----------------------------------------------------------------------- + +extern "C" { + +// TODO: wait till all compilers accept that calling conventions +// for functions are the same independent of implementation constness, +// then uncomment the const-tokens in the function interfaces below +static int FT_move_to( FT_Vector_CPtr p0, void* vpPolyArgs ) +{ + PolyArgs& rA = *reinterpret_cast<PolyArgs*>(vpPolyArgs); + + // move_to implies a new polygon => finish old polygon first + rA.ClosePolygon(); + + rA.AddPoint( p0->x, p0->y, POLY_NORMAL ); + return 0; +} + +static int FT_line_to( FT_Vector_CPtr p1, void* vpPolyArgs ) +{ + PolyArgs& rA = *reinterpret_cast<PolyArgs*>(vpPolyArgs); + rA.AddPoint( p1->x, p1->y, POLY_NORMAL ); + return 0; +} + +static int FT_conic_to( FT_Vector_CPtr p1, FT_Vector_CPtr p2, void* vpPolyArgs ) +{ + PolyArgs& rA = *reinterpret_cast<PolyArgs*>(vpPolyArgs); + + // VCL's Polygon only knows cubic beziers + const long nX1 = (2 * rA.GetPosX() + 4 * p1->x + 3) / 6; + const long nY1 = (2 * rA.GetPosY() + 4 * p1->y + 3) / 6; + rA.AddPoint( nX1, nY1, POLY_CONTROL ); + + const long nX2 = (2 * p2->x + 4 * p1->x + 3) / 6; + const long nY2 = (2 * p2->y + 4 * p1->y + 3) / 6; + rA.AddPoint( nX2, nY2, POLY_CONTROL ); + + rA.AddPoint( p2->x, p2->y, POLY_NORMAL ); + return 0; +} + +static int FT_cubic_to( FT_Vector_CPtr p1, FT_Vector_CPtr p2, FT_Vector_CPtr p3, void* vpPolyArgs ) +{ + PolyArgs& rA = *reinterpret_cast<PolyArgs*>(vpPolyArgs); + rA.AddPoint( p1->x, p1->y, POLY_CONTROL ); + rA.AddPoint( p2->x, p2->y, POLY_CONTROL ); + rA.AddPoint( p3->x, p3->y, POLY_NORMAL ); + return 0; +} + +} // extern "C" + +// ----------------------------------------------------------------------- + +bool ServerFont::GetGlyphOutline( int nGlyphIndex, + ::basegfx::B2DPolyPolygon& rB2DPolyPoly ) const +{ + if( maSizeFT ) + pFTActivateSize( maSizeFT ); + + rB2DPolyPoly.clear(); + + int nGlyphFlags; + SplitGlyphFlags( *this, nGlyphIndex, nGlyphFlags ); + + FT_Int nLoadFlags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_TRANSFORM; + +#ifdef FT_LOAD_TARGET_LIGHT + // enable "light hinting" if available + if( nFTVERSION >= 2103 ) + nLoadFlags |= FT_LOAD_TARGET_LIGHT; +#endif + + FT_Error rc = FT_Load_Glyph( maFaceFT, nGlyphIndex, nLoadFlags ); + if( rc != FT_Err_Ok ) + return false; + + if( mbArtBold && pFTEmbolden ) + (*pFTEmbolden)( maFaceFT->glyph ); + + FT_Glyph pGlyphFT; + rc = FT_Get_Glyph( maFaceFT->glyph, &pGlyphFT ); + if( rc != FT_Err_Ok ) + return false; + + if( pGlyphFT->format != FT_GLYPH_FORMAT_OUTLINE ) + return false; + + if( mbArtItalic ) + { + FT_Matrix aMatrix; + aMatrix.xx = aMatrix.yy = 0x10000L; + if( nFTVERSION >= 2102 ) // Freetype 2.1.2 API swapped xy with yx + aMatrix.xy = 0x6000L, aMatrix.yx = 0; + else + aMatrix.yx = 0x6000L, aMatrix.xy = 0; + FT_Glyph_Transform( pGlyphFT, &aMatrix, NULL ); + } + + FT_Outline& rOutline = reinterpret_cast<FT_OutlineGlyphRec*>(pGlyphFT)->outline; + if( !rOutline.n_points ) // blank glyphs are ok + return true; + + long nMaxPoints = 1 + rOutline.n_points * 3; + PolyPolygon aToolPolyPolygon; + PolyArgs aPolyArg( aToolPolyPolygon, nMaxPoints ); + + /*int nAngle =*/ ApplyGlyphTransform( nGlyphFlags, pGlyphFT, false ); + + FT_Outline_Funcs aFuncs; + aFuncs.move_to = &FT_move_to; + aFuncs.line_to = &FT_line_to; + aFuncs.conic_to = &FT_conic_to; + aFuncs.cubic_to = &FT_cubic_to; + aFuncs.shift = 0; + aFuncs.delta = 0; + rc = FT_Outline_Decompose( &rOutline, &aFuncs, (void*)&aPolyArg ); + aPolyArg.ClosePolygon(); // close last polygon + FT_Done_Glyph( pGlyphFT ); + + // convert to basegfx polypolygon + // TODO: get rid of the intermediate tools polypolygon + rB2DPolyPoly = aToolPolyPolygon.getB2DPolyPolygon(); + rB2DPolyPoly.transform(basegfx::tools::createScaleB2DHomMatrix( +1.0/(1<<6), -1.0/(1<<6) )); + + return true; +} + +// ----------------------------------------------------------------------- + +bool ServerFont::ApplyGSUB( const ImplFontSelectData& rFSD ) +{ +#define MKTAG(s) ((((((s[0]<<8)+s[1])<<8)+s[2])<<8)+s[3]) + + typedef std::vector<sal_uLong> ReqFeatureTagList; + ReqFeatureTagList aReqFeatureTagList; + if( rFSD.mbVertical ) + aReqFeatureTagList.push_back( MKTAG("vert") ); + sal_uLong nRequestedScript = 0; //MKTAG("hani");//### TODO: where to get script? + sal_uLong nRequestedLangsys = 0; //MKTAG("ZHT"); //### TODO: where to get langsys? + // TODO: request more features depending on script and language system + + if( aReqFeatureTagList.empty()) // nothing to do + return true; + + // load GSUB table into memory + sal_uLong nLength = 0; + const FT_Byte* const pGsubBase = mpFontInfo->GetTable( "GSUB", &nLength ); + if( !pGsubBase ) + return false; + + // parse GSUB header + const FT_Byte* pGsubHeader = pGsubBase; + const sal_uInt16 nOfsScriptList = GetUShort( pGsubHeader+4 ); + const sal_uInt16 nOfsFeatureTable = GetUShort( pGsubHeader+6 ); + const sal_uInt16 nOfsLookupList = GetUShort( pGsubHeader+8 ); + pGsubHeader += 10; + + typedef std::vector<sal_uInt16> UshortList; + UshortList aFeatureIndexList; + UshortList aFeatureOffsetList; + + // parse Script Table + const FT_Byte* pScriptHeader = pGsubBase + nOfsScriptList; + const sal_uInt16 nCntScript = GetUShort( pScriptHeader+0 ); + pScriptHeader += 2; + for( sal_uInt16 nScriptIndex = 0; nScriptIndex < nCntScript; ++nScriptIndex ) + { + const sal_uLong nScriptTag = GetUInt( pScriptHeader+0 ); // e.g. hani/arab/kana/hang + const sal_uInt16 nOfsScriptTable= GetUShort( pScriptHeader+4 ); + pScriptHeader += 6; //### + if( (nScriptTag != nRequestedScript) && (nRequestedScript != 0) ) + continue; + + const FT_Byte* pScriptTable = pGsubBase + nOfsScriptList + nOfsScriptTable; + const sal_uInt16 nDefaultLangsysOfs = GetUShort( pScriptTable+0 ); + const sal_uInt16 nCntLangSystem = GetUShort( pScriptTable+2 ); + pScriptTable += 4; + sal_uInt16 nLangsysOffset = 0; + + for( sal_uInt16 nLangsysIndex = 0; nLangsysIndex < nCntLangSystem; ++nLangsysIndex ) + { + const sal_uLong nTag = GetUInt( pScriptTable+0 ); // e.g. KOR/ZHS/ZHT/JAN + const sal_uInt16 nOffset= GetUShort( pScriptTable+4 ); + pScriptTable += 6; + if( (nTag != nRequestedLangsys) && (nRequestedLangsys != 0) ) + continue; + nLangsysOffset = nOffset; + break; + } + + if( (nDefaultLangsysOfs != 0) && (nDefaultLangsysOfs != nLangsysOffset) ) + { + const FT_Byte* pLangSys = pGsubBase + nOfsScriptList + nOfsScriptTable + nDefaultLangsysOfs; + const sal_uInt16 nReqFeatureIdx = GetUShort( pLangSys+2 ); + const sal_uInt16 nCntFeature = GetUShort( pLangSys+4 ); + pLangSys += 6; + aFeatureIndexList.push_back( nReqFeatureIdx ); + for( sal_uInt16 i = 0; i < nCntFeature; ++i ) + { + const sal_uInt16 nFeatureIndex = GetUShort( pLangSys ); + pLangSys += 2; + aFeatureIndexList.push_back( nFeatureIndex ); + } + } + + if( nLangsysOffset != 0 ) + { + const FT_Byte* pLangSys = pGsubBase + nOfsScriptList + nOfsScriptTable + nLangsysOffset; + const sal_uInt16 nReqFeatureIdx = GetUShort( pLangSys+2 ); + const sal_uInt16 nCntFeature = GetUShort( pLangSys+4 ); + pLangSys += 6; + aFeatureIndexList.push_back( nReqFeatureIdx ); + for( sal_uInt16 i = 0; i < nCntFeature; ++i ) + { + const sal_uInt16 nFeatureIndex = GetUShort( pLangSys ); + pLangSys += 2; + aFeatureIndexList.push_back( nFeatureIndex ); + } + } + } + + if( aFeatureIndexList.empty() ) + return true; + + UshortList aLookupIndexList; + UshortList aLookupOffsetList; + + // parse Feature Table + const FT_Byte* pFeatureHeader = pGsubBase + nOfsFeatureTable; + const sal_uInt16 nCntFeature = GetUShort( pFeatureHeader ); + pFeatureHeader += 2; + for( sal_uInt16 nFeatureIndex = 0; nFeatureIndex < nCntFeature; ++nFeatureIndex ) + { + const sal_uLong nTag = GetUInt( pFeatureHeader+0 ); // e.g. locl/vert/trad/smpl/liga/fina/... + const sal_uInt16 nOffset= GetUShort( pFeatureHeader+4 ); + pFeatureHeader += 6; + + // short circuit some feature lookups + if( aFeatureIndexList[0] != nFeatureIndex ) // required feature? + { + const int nRequested = std::count( aFeatureIndexList.begin(), aFeatureIndexList.end(), nFeatureIndex); + if( !nRequested ) // ignore features that are not requested + continue; + const int nAvailable = std::count( aReqFeatureTagList.begin(), aReqFeatureTagList.end(), nTag); + if( !nAvailable ) // some fonts don't provide features they request! + continue; + } + + const FT_Byte* pFeatureTable = pGsubBase + nOfsFeatureTable + nOffset; + const sal_uInt16 nCntLookups = GetUShort( pFeatureTable+0 ); + pFeatureTable += 2; + for( sal_uInt16 i = 0; i < nCntLookups; ++i ) + { + const sal_uInt16 nLookupIndex = GetUShort( pFeatureTable ); + pFeatureTable += 2; + aLookupIndexList.push_back( nLookupIndex ); + } + if( nCntLookups == 0 ) //### hack needed by Mincho/Gothic/Mingliu/Simsun/... + aLookupIndexList.push_back( 0 ); + } + + // parse Lookup List + const FT_Byte* pLookupHeader = pGsubBase + nOfsLookupList; + const sal_uInt16 nCntLookupTable = GetUShort( pLookupHeader ); + pLookupHeader += 2; + for( sal_uInt16 nLookupIdx = 0; nLookupIdx < nCntLookupTable; ++nLookupIdx ) + { + const sal_uInt16 nOffset = GetUShort( pLookupHeader ); + pLookupHeader += 2; + if( std::count( aLookupIndexList.begin(), aLookupIndexList.end(), nLookupIdx ) ) + aLookupOffsetList.push_back( nOffset ); + } + + UshortList::const_iterator lookup_it = aLookupOffsetList.begin(); + for(; lookup_it != aLookupOffsetList.end(); ++lookup_it ) + { + const sal_uInt16 nOfsLookupTable = *lookup_it; + const FT_Byte* pLookupTable = pGsubBase + nOfsLookupList + nOfsLookupTable; + const sal_uInt16 eLookupType = GetUShort( pLookupTable+0 ); + const sal_uInt16 nCntLookupSubtable = GetUShort( pLookupTable+4 ); + pLookupTable += 6; + + // TODO: switch( eLookupType ) + if( eLookupType != 1 ) // TODO: once we go beyond SingleSubst + continue; + + for( sal_uInt16 nSubTableIdx = 0; nSubTableIdx < nCntLookupSubtable; ++nSubTableIdx ) + { + const sal_uInt16 nOfsSubLookupTable = GetUShort( pLookupTable ); + pLookupTable += 2; + const FT_Byte* pSubLookup = pGsubBase + nOfsLookupList + nOfsLookupTable + nOfsSubLookupTable; + + const sal_uInt16 nFmtSubstitution = GetUShort( pSubLookup+0 ); + const sal_uInt16 nOfsCoverage = GetUShort( pSubLookup+2 ); + pSubLookup += 4; + + typedef std::pair<sal_uInt16,sal_uInt16> GlyphSubst; + typedef std::vector<GlyphSubst> SubstVector; + SubstVector aSubstVector; + + const FT_Byte* pCoverage = pGsubBase + nOfsLookupList + nOfsLookupTable + nOfsSubLookupTable + nOfsCoverage; + const sal_uInt16 nFmtCoverage = GetUShort( pCoverage+0 ); + pCoverage += 2; + switch( nFmtCoverage ) + { + case 1: // Coverage Format 1 + { + const sal_uInt16 nCntGlyph = GetUShort( pCoverage ); + pCoverage += 2; + aSubstVector.reserve( nCntGlyph ); + for( sal_uInt16 i = 0; i < nCntGlyph; ++i ) + { + const sal_uInt16 nGlyphId = GetUShort( pCoverage ); + pCoverage += 2; + aSubstVector.push_back( GlyphSubst( nGlyphId, 0 ) ); + } + } + break; + + case 2: // Coverage Format 2 + { + const sal_uInt16 nCntRange = GetUShort( pCoverage ); + pCoverage += 2; + for( int i = nCntRange; --i >= 0; ) + { + const sal_uInt32 nGlyph0 = GetUShort( pCoverage+0 ); + const sal_uInt32 nGlyph1 = GetUShort( pCoverage+2 ); + const sal_uInt16 nCovIdx = GetUShort( pCoverage+4 ); + pCoverage += 6; + for( sal_uInt32 j = nGlyph0; j <= nGlyph1; ++j ) + aSubstVector.push_back( GlyphSubst( static_cast<sal_uInt16>(j + nCovIdx), 0 ) ); + } + } + break; + } + + SubstVector::iterator it( aSubstVector.begin() ); + + switch( nFmtSubstitution ) + { + case 1: // Single Substitution Format 1 + { + const sal_uInt16 nDeltaGlyphId = GetUShort( pSubLookup ); + pSubLookup += 2; + for(; it != aSubstVector.end(); ++it ) + (*it).second = (*it).first + nDeltaGlyphId; + } + break; + + case 2: // Single Substitution Format 2 + { + const sal_uInt16 nCntGlyph = GetUShort( pSubLookup ); + pSubLookup += 2; + for( int i = nCntGlyph; (it != aSubstVector.end()) && (--i>=0); ++it ) + { + const sal_uInt16 nGlyphId = GetUShort( pSubLookup ); + pSubLookup += 2; + (*it).second = nGlyphId; + } + } + break; + } + + DBG_ASSERT( (it == aSubstVector.end()), "lookup<->coverage table mismatch" ); + // now apply the glyph substitutions that have been collected in this subtable + for( it = aSubstVector.begin(); it != aSubstVector.end(); ++it ) + maGlyphSubstitution[ (*it).first ] = (*it).second; + } + } + + return true; +} + +const unsigned char* ServerFont::GetTable(const char* pName, sal_uLong* pLength) +{ + return mpFontInfo->GetTable( pName, pLength ); +} + +#ifdef ENABLE_GRAPHITE +GraphiteFaceWrapper* ServerFont::GetGraphiteFace() const +{ + return mpFontInfo->GetGraphiteFace(); +} +#endif + +// ======================================================================= + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/generic/glyphs/gcach_ftyp.hxx b/vcl/generic/glyphs/gcach_ftyp.hxx new file mode 100644 index 000000000000..7386bb3d1e82 --- /dev/null +++ b/vcl/generic/glyphs/gcach_ftyp.hxx @@ -0,0 +1,201 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * 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 + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef _SV_GCACHFTYP_HXX +#define _SV_GCACHFTYP_HXX + +#include "generic/glyphcache.hxx" + +#include <rtl/textcvt.h> + +class FreetypeServerFont; +#ifdef ENABLE_GRAPHITE +class GraphiteFaceWrapper; +#endif + +// ----------------------------------------------------------------------- + +// FtFontFile has the responsibility that a font file is only mapped once. +// (#86621#) the old directly ft-managed solution caused it to be mapped +// in up to nTTC*nSizes*nOrientation*nSynthetic times +class FtFontFile +{ +public: + static FtFontFile* FindFontFile( const ::rtl::OString& rNativeFileName ); + + bool Map(); + void Unmap(); + + const unsigned char* GetBuffer() const { return mpFileMap; } + int GetFileSize() const { return mnFileSize; } + const ::rtl::OString* GetFileName() const { return &maNativeFileName; } + int GetLangBoost() const { return mnLangBoost; } + +private: + FtFontFile( const ::rtl::OString& rNativeFileName ); + + const ::rtl::OString maNativeFileName; + const unsigned char* mpFileMap; + int mnFileSize; + int mnRefCount; + int mnLangBoost; +}; + +// ----------------------------------------------------------------------- + +// FtFontInfo corresponds to an unscaled font face +class FtFontInfo +{ +public: + FtFontInfo( const ImplDevFontAttributes&, + const ::rtl::OString& rNativeFileName, + int nFaceNum, sal_IntPtr nFontId, int nSynthetic, + const ExtraKernInfo* ); + ~FtFontInfo(); + + const unsigned char* GetTable( const char*, sal_uLong* pLength=0 ) const; + + FT_FaceRec_* GetFaceFT(); +#ifdef ENABLE_GRAPHITE + GraphiteFaceWrapper* GetGraphiteFace(); +#endif + void ReleaseFaceFT( FT_FaceRec_* ); + + const ::rtl::OString* GetFontFileName() const { return mpFontFile->GetFileName(); } + int GetFaceNum() const { return mnFaceNum; } + int GetSynthetic() const { return mnSynthetic; } + sal_IntPtr GetFontId() const { return mnFontId; } + bool IsSymbolFont() const { return maDevFontAttributes.IsSymbolFont(); } + const ImplFontAttributes& GetFontAttributes() const { return maDevFontAttributes; } + + void AnnounceFont( ImplDevFontList* ); + + int GetGlyphIndex( sal_UCS4 cChar ) const; + void CacheGlyphIndex( sal_UCS4 cChar, int nGI ) const; + + bool GetFontCodeRanges( CmapResult& ) const; + const ImplFontCharMap* GetImplFontCharMap( void ); + + bool HasExtraKerning() const; + int GetExtraKernPairs( ImplKernPairData** ) const; + int GetExtraGlyphKernValue( int nLeftGlyph, int nRightGlyph ) const; + +private: + FT_FaceRec_* maFaceFT; + FtFontFile* mpFontFile; + const int mnFaceNum; + int mnRefCount; + const int mnSynthetic; +#ifdef ENABLE_GRAPHITE + bool mbCheckedGraphite; + GraphiteFaceWrapper * mpGraphiteFace; +#endif + sal_IntPtr mnFontId; + ImplDevFontAttributes maDevFontAttributes; + + const ImplFontCharMap* mpFontCharMap; + + // cache unicode->glyphid mapping because looking it up is expensive + // TODO: change to boost::unordered_multimap when a use case requires a m:n mapping + typedef ::boost::unordered_map<int,int> Int2IntMap; + mutable Int2IntMap* mpChar2Glyph; + mutable Int2IntMap* mpGlyph2Char; + void InitHashes() const; + + const ExtraKernInfo* mpExtraKernInfo; +}; + +// these two inlines are very important for performance + +inline int FtFontInfo::GetGlyphIndex( sal_UCS4 cChar ) const +{ + if( !mpChar2Glyph ) + return -1; + Int2IntMap::const_iterator it = mpChar2Glyph->find( cChar ); + if( it == mpChar2Glyph->end() ) + return -1; + return it->second; +} + +inline void FtFontInfo::CacheGlyphIndex( sal_UCS4 cChar, int nIndex ) const +{ + if( !mpChar2Glyph ) + InitHashes(); + (*mpChar2Glyph)[ cChar ] = nIndex; + (*mpGlyph2Char)[ nIndex ] = cChar; +} + +// ----------------------------------------------------------------------- + +class FreetypeManager +{ +public: + FreetypeManager(); + ~FreetypeManager(); + + void AddFontFile( const rtl::OString& rNormalizedName, + int nFaceNum, sal_IntPtr nFontId, const ImplDevFontAttributes&, + const ExtraKernInfo* ); + void AnnounceFonts( ImplDevFontList* ) const; + void ClearFontList(); + + ServerFont* CreateFont( const ImplFontSelectData& ); + +private: + typedef ::boost::unordered_map<sal_IntPtr,FtFontInfo*> FontList; + FontList maFontList; + + sal_IntPtr mnMaxFontId; + sal_IntPtr mnNextFontId; +}; + +// ----------------------------------------------------------------------- + +class ImplFTSFontData : public ImplFontData +{ +private: + FtFontInfo* mpFtFontInfo; + enum { IFTSFONT_MAGIC = 0x1F150A1C }; + +public: + ImplFTSFontData( FtFontInfo*, const ImplDevFontAttributes& ); + + FtFontInfo* GetFtFontInfo() const { return mpFtFontInfo; } + + virtual ImplFontEntry* CreateFontInstance( ImplFontSelectData& ) const; + virtual ImplFontData* Clone() const { return new ImplFTSFontData( *this ); } + virtual sal_IntPtr GetFontId() const { return mpFtFontInfo->GetFontId(); } + + static bool CheckFontData( const ImplFontData& r ) { return r.CheckMagic( IFTSFONT_MAGIC ); } +}; + +// ----------------------------------------------------------------------- + +#endif // _SV_GCACHFTYP_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/generic/glyphs/gcach_layout.cxx b/vcl/generic/glyphs/gcach_layout.cxx new file mode 100644 index 000000000000..ae5ad511268d --- /dev/null +++ b/vcl/generic/glyphs/gcach_layout.cxx @@ -0,0 +1,669 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * 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 + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +#define ENABLE_ICU_LAYOUT +#include <gcach_ftyp.hxx> +#include <sallayout.hxx> +#include <salgdi.hxx> + +#include <vcl/svapp.hxx> + +#include <sal/alloca.h> + +#if OSL_DEBUG_LEVEL > 1 +#include <cstdio> +#endif +#include <rtl/instance.hxx> + +namespace { struct SimpleLayoutEngine : public rtl::Static< ServerFontLayoutEngine, SimpleLayoutEngine > {}; } + +// ======================================================================= +// layout implementation for ServerFont +// ======================================================================= + +ServerFontLayout::ServerFontLayout( ServerFont& rFont ) +: mrServerFont( rFont ) +{} + +void ServerFontLayout::DrawText( SalGraphics& rSalGraphics ) const +{ + rSalGraphics.DrawServerFontLayout( *this ); +} + +// ----------------------------------------------------------------------- + +bool ServerFontLayout::LayoutText( ImplLayoutArgs& rArgs ) +{ + ServerFontLayoutEngine* pLE = NULL; + if( !(rArgs.mnFlags & SAL_LAYOUT_COMPLEX_DISABLED) ) + pLE = mrServerFont.GetLayoutEngine(); + if( !pLE ) + pLE = &SimpleLayoutEngine::get(); + + bool bRet = (*pLE)( *this, rArgs ); + return bRet; +} + +// ----------------------------------------------------------------------- + +void ServerFontLayout::AdjustLayout( ImplLayoutArgs& rArgs ) +{ + GenericSalLayout::AdjustLayout( rArgs ); + + // apply asian kerning if the glyphs are not already formatted + if( (rArgs.mnFlags & SAL_LAYOUT_KERNING_ASIAN) + && !(rArgs.mnFlags & SAL_LAYOUT_VERTICAL) ) + if( (rArgs.mpDXArray != NULL) || (rArgs.mnLayoutWidth != 0) ) + ApplyAsianKerning( rArgs.mpStr, rArgs.mnLength ); + + // insert kashidas where requested by the formatting array + if( (rArgs.mnFlags & SAL_LAYOUT_KASHIDA_JUSTIFICATON) && rArgs.mpDXArray ) + { + int nKashidaIndex = mrServerFont.GetGlyphIndex( 0x0640 ); + if( nKashidaIndex != 0 ) + { + const GlyphMetric& rGM = mrServerFont.GetGlyphMetric( nKashidaIndex ); + KashidaJustify( nKashidaIndex, rGM.GetCharWidth() ); + // TODO: kashida-GSUB/GPOS + } + } +} + +// ======================================================================= + +bool ServerFontLayoutEngine::operator()( ServerFontLayout& rLayout, ImplLayoutArgs& rArgs ) +{ + ServerFont& rFont = rLayout.GetServerFont(); + + Point aNewPos( 0, 0 ); + int nOldGlyphId = -1; + int nGlyphWidth = 0; + GlyphItem aPrevItem; + bool bRightToLeft; + for( int nCharPos = -1; rArgs.GetNextPos( &nCharPos, &bRightToLeft ); ) + { + sal_UCS4 cChar = rArgs.mpStr[ nCharPos ]; + if( (cChar >= 0xD800) && (cChar <= 0xDFFF) ) + { + if( cChar >= 0xDC00 ) // this part of a surrogate pair was already processed + continue; + cChar = 0x10000 + ((cChar - 0xD800) << 10) + + (rArgs.mpStr[ nCharPos+1 ] - 0xDC00); + } + + if( bRightToLeft ) + cChar = GetMirroredChar( cChar ); + int nGlyphIndex = rFont.GetGlyphIndex( cChar ); + // when glyph fallback is needed update LayoutArgs + if( !nGlyphIndex ) { + rArgs.NeedFallback( nCharPos, bRightToLeft ); + if( cChar >= 0x10000 ) // handle surrogate pairs + rArgs.NeedFallback( nCharPos+1, bRightToLeft ); + } + + // apply pair kerning to prev glyph if requested + if( SAL_LAYOUT_KERNING_PAIRS & rArgs.mnFlags ) + { + int nKernValue = rFont.GetGlyphKernValue( nOldGlyphId, nGlyphIndex ); + nGlyphWidth += nKernValue; + aPrevItem.mnNewWidth = nGlyphWidth; + } + + // finish previous glyph + if( nOldGlyphId >= 0 ) + rLayout.AppendGlyph( aPrevItem ); + aNewPos.X() += nGlyphWidth; + + // prepare GlyphItem for appending it in next round + nOldGlyphId = nGlyphIndex; + const GlyphMetric& rGM = rFont.GetGlyphMetric( nGlyphIndex ); + nGlyphWidth = rGM.GetCharWidth(); + int nGlyphFlags = bRightToLeft ? GlyphItem::IS_RTL_GLYPH : 0; + aPrevItem = GlyphItem( nCharPos, nGlyphIndex, aNewPos, nGlyphFlags, nGlyphWidth ); + } + + // append last glyph item if any + if( nOldGlyphId >= 0 ) + rLayout.AppendGlyph( aPrevItem ); + + return true; +} + +// ======================================================================= +// bridge to ICU LayoutEngine +// ======================================================================= + +#ifdef ENABLE_ICU_LAYOUT + +#define bool_t signed char + +// disable warnings in icu layout headers +#if defined __SUNPRO_CC +#pragma disable_warn +#endif + +#include <layout/LayoutEngine.h> +#include <layout/LEFontInstance.h> +#include <layout/LEScripts.h> + +// enable warnings again +#if defined __SUNPRO_CC +#pragma enable_warn +#endif + +#include <unicode/uscript.h> +#include <unicode/ubidi.h> + +using namespace U_ICU_NAMESPACE; + +static const LEGlyphID ICU_DELETED_GLYPH = 0xFFFF; +static const LEGlyphID ICU_MARKED_GLYPH = 0xFFFE; + +// ----------------------------------------------------------------------- + +class IcuFontFromServerFont +: public LEFontInstance +{ +private: + ServerFont& mrServerFont; + +public: + IcuFontFromServerFont( ServerFont& rFont ) + : mrServerFont( rFont ) + {} + + virtual const void* getFontTable(LETag tableTag) const; + virtual le_int32 getUnitsPerEM() const; + virtual float getXPixelsPerEm() const; + virtual float getYPixelsPerEm() const; + virtual float getScaleFactorX() const; + virtual float getScaleFactorY() const; + + using LEFontInstance::mapCharToGlyph; + virtual LEGlyphID mapCharToGlyph( LEUnicode32 ch ) const; + + virtual le_int32 getAscent() const; + virtual le_int32 getDescent() const; + virtual le_int32 getLeading() const; + + virtual void getGlyphAdvance( LEGlyphID glyph, LEPoint &advance ) const; + virtual le_bool getGlyphPoint( LEGlyphID glyph, le_int32 pointNumber, LEPoint& point ) const; +}; + +// ----------------------------------------------------------------------- + +const void* IcuFontFromServerFont::getFontTable( LETag nICUTableTag ) const +{ + char pTagName[5]; + pTagName[0] = (char)(nICUTableTag >> 24); + pTagName[1] = (char)(nICUTableTag >> 16); + pTagName[2] = (char)(nICUTableTag >> 8); + pTagName[3] = (char)(nICUTableTag); + pTagName[4] = 0; + + sal_uLong nLength; + const unsigned char* pBuffer = mrServerFont.GetTable( pTagName, &nLength ); +#ifdef VERBOSE_DEBUG + fprintf(stderr,"IcuGetTable(\"%s\") => %p\n", pTagName, pBuffer); + int mnHeight = mrServerFont.GetFontSelData().mnHeight; + const char* pName = mrServerFont.GetFontFileName()->getStr(); + fprintf(stderr,"font( h=%d, \"%s\" )\n", mnHeight, pName ); +#endif + return (const void*)pBuffer; +} + +// ----------------------------------------------------------------------- + +le_int32 IcuFontFromServerFont::getUnitsPerEM() const +{ + return mrServerFont.GetEmUnits(); +} + +// ----------------------------------------------------------------------- + +float IcuFontFromServerFont::getXPixelsPerEm() const +{ + const ImplFontSelectData& r = mrServerFont.GetFontSelData(); + float fX = r.mnWidth ? r.mnWidth : r.mnHeight; + return fX; +} + +// ----------------------------------------------------------------------- + +float IcuFontFromServerFont::getYPixelsPerEm() const +{ + float fY = mrServerFont.GetFontSelData().mnHeight; + return fY; +} + +// ----------------------------------------------------------------------- + +float IcuFontFromServerFont::getScaleFactorX() const +{ + return 1.0; +} + +// ----------------------------------------------------------------------- + +float IcuFontFromServerFont::getScaleFactorY() const +{ + return 1.0; +} + +// ----------------------------------------------------------------------- + +LEGlyphID IcuFontFromServerFont::mapCharToGlyph( LEUnicode32 ch ) const +{ + LEGlyphID nGlyphIndex = mrServerFont.GetRawGlyphIndex( ch ); + return nGlyphIndex; +} + +// ----------------------------------------------------------------------- + +le_int32 IcuFontFromServerFont::getAscent() const +{ + const FT_Size_Metrics& rMetrics = mrServerFont.GetMetricsFT(); + le_int32 nAscent = (+rMetrics.ascender + 32) >> 6; + return nAscent; +} + +// ----------------------------------------------------------------------- + +le_int32 IcuFontFromServerFont::getDescent() const +{ + const FT_Size_Metrics& rMetrics = mrServerFont.GetMetricsFT(); + le_int32 nDescent = (-rMetrics.descender + 32) >> 6; + return nDescent; +} + +// ----------------------------------------------------------------------- + +le_int32 IcuFontFromServerFont::getLeading() const +{ + const FT_Size_Metrics& rMetrics = mrServerFont.GetMetricsFT(); + le_int32 nLeading = ((rMetrics.height - rMetrics.ascender + rMetrics.descender) + 32) >> 6; + return nLeading; +} + +// ----------------------------------------------------------------------- + +void IcuFontFromServerFont::getGlyphAdvance( LEGlyphID nGlyphIndex, + LEPoint &advance ) const +{ + if( (nGlyphIndex == ICU_MARKED_GLYPH) + || (nGlyphIndex == ICU_DELETED_GLYPH) ) + { + // deleted glyph or mark glyph has not advance + advance.fX = 0; + } + else + { + const GlyphMetric& rGM = mrServerFont.GetGlyphMetric( nGlyphIndex ); + advance.fX = rGM.GetCharWidth(); + } + + advance.fY = 0; +} + +// ----------------------------------------------------------------------- + +le_bool IcuFontFromServerFont::getGlyphPoint( LEGlyphID, + le_int32 +#if OSL_DEBUG_LEVEL > 1 +pointNumber +#endif + , + LEPoint& ) const +{ + //TODO: replace dummy implementation +#if OSL_DEBUG_LEVEL > 1 + fprintf(stderr,"getGlyphPoint(%d)\n", pointNumber ); +#endif + return false; +} + +// ======================================================================= + +class IcuLayoutEngine : public ServerFontLayoutEngine +{ +private: + IcuFontFromServerFont maIcuFont; + + le_int32 meScriptCode; + LayoutEngine* mpIcuLE; + +public: + IcuLayoutEngine( ServerFont& ); + virtual ~IcuLayoutEngine(); + + virtual bool operator()( ServerFontLayout&, ImplLayoutArgs& ); +}; + +// ----------------------------------------------------------------------- + +IcuLayoutEngine::IcuLayoutEngine( ServerFont& rServerFont ) +: maIcuFont( rServerFont ), + meScriptCode( USCRIPT_INVALID_CODE ), + mpIcuLE( NULL ) +{} + +// ----------------------------------------------------------------------- + +IcuLayoutEngine::~IcuLayoutEngine() +{ + if( mpIcuLE ) + delete mpIcuLE; +} + +// ----------------------------------------------------------------------- + +static bool lcl_CharIsJoiner(sal_Unicode cChar) +{ + return ((cChar == 0x200C) || (cChar == 0x200D)); +} + +//See https://bugs.freedesktop.org/show_bug.cgi?id=31016 +#define ARABIC_BANDAID + +bool IcuLayoutEngine::operator()( ServerFontLayout& rLayout, ImplLayoutArgs& rArgs ) +{ + LEUnicode* pIcuChars; + if( sizeof(LEUnicode) == sizeof(*rArgs.mpStr) ) + pIcuChars = (LEUnicode*)rArgs.mpStr; + else + { + // this conversion will only be needed when either + // ICU's or OOo's unicodes stop being unsigned shorts + // TODO: watch out for surrogates! + pIcuChars = (LEUnicode*)alloca( rArgs.mnLength * sizeof(LEUnicode) ); + for( xub_StrLen ic = 0; ic < rArgs.mnLength; ++ic ) + pIcuChars[ic] = static_cast<LEUnicode>( rArgs.mpStr[ic] ); + } + + // allocate temporary arrays, note: round to even + int nGlyphCapacity = (3 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos ) | 15) + 1; + + struct IcuPosition{ float fX, fY; }; + const int nAllocSize = sizeof(LEGlyphID) + sizeof(le_int32) + sizeof(IcuPosition); + LEGlyphID* pIcuGlyphs = (LEGlyphID*)alloca( (nGlyphCapacity * nAllocSize) + sizeof(IcuPosition) ); + le_int32* pCharIndices = (le_int32*)((char*)pIcuGlyphs + nGlyphCapacity * sizeof(LEGlyphID) ); + IcuPosition* pGlyphPositions = (IcuPosition*)((char*)pCharIndices + nGlyphCapacity * sizeof(le_int32) ); + + ServerFont& rFont = rLayout.GetServerFont(); + + UErrorCode rcI18n = U_ZERO_ERROR; + LEErrorCode rcIcu = LE_NO_ERROR; + Point aNewPos( 0, 0 ); + for( int nGlyphCount = 0;; ) + { + int nMinRunPos, nEndRunPos; + bool bRightToLeft; + if( !rArgs.GetNextRun( &nMinRunPos, &nEndRunPos, &bRightToLeft ) ) + break; + + // find matching script + // TODO: split up bidi run into script runs + le_int32 eScriptCode = -1; + for( int i = nMinRunPos; i < nEndRunPos; ++i ) + { + eScriptCode = uscript_getScript( pIcuChars[i], &rcI18n ); + if( (eScriptCode > 0) && (eScriptCode != latnScriptCode) ) + break; + } + if( eScriptCode < 0 ) // TODO: handle errors better + eScriptCode = latnScriptCode; + + // get layout engine matching to this script + // no engine change necessary if script is latin + if( !mpIcuLE || ((eScriptCode != meScriptCode) && (eScriptCode > USCRIPT_INHERITED)) ) + { + // TODO: cache multiple layout engines when multiple scripts are used + delete mpIcuLE; + meScriptCode = eScriptCode; + le_int32 eLangCode = 0; // TODO: get better value + mpIcuLE = LayoutEngine::layoutEngineFactory( &maIcuFont, eScriptCode, eLangCode, rcIcu ); + if( LE_FAILURE(rcIcu) ) + { + delete mpIcuLE; + mpIcuLE = NULL; + } + } + + // fall back to default layout if needed + if( !mpIcuLE ) + break; + + // run ICU layout engine + // TODO: get enough context, remove extra glyps below + int nRawRunGlyphCount = mpIcuLE->layoutChars( pIcuChars, + nMinRunPos, nEndRunPos - nMinRunPos, rArgs.mnLength, + bRightToLeft, aNewPos.X(), aNewPos.Y(), rcIcu ); + if( LE_FAILURE(rcIcu) ) + return false; + + // import layout info from icu + mpIcuLE->getGlyphs( pIcuGlyphs, rcIcu ); + mpIcuLE->getCharIndices( pCharIndices, rcIcu ); + mpIcuLE->getGlyphPositions( &pGlyphPositions->fX, rcIcu ); + mpIcuLE->reset(); // TODO: get rid of this, PROBLEM: crash at exit when removed + if( LE_FAILURE(rcIcu) ) + return false; + + // layout bidi/script runs and export them to a ServerFontLayout + // convert results to GlyphItems + int nLastCharPos = -1; + int nClusterMinPos = -1; + int nClusterMaxPos = -1; + bool bClusterStart = true; + int nFilteredRunGlyphCount = 0; + const IcuPosition* pPos = pGlyphPositions; + for( int i = 0; i < nRawRunGlyphCount; ++i, ++pPos ) + { + LEGlyphID nGlyphIndex = pIcuGlyphs[i]; + // ignore glyphs which were marked or deleted by ICU + if( (nGlyphIndex == ICU_MARKED_GLYPH) + || (nGlyphIndex == ICU_DELETED_GLYPH) ) + continue; + + // adjust the relative char pos + int nCharPos = pCharIndices[i]; + if( nCharPos >= 0 ) { + nCharPos += nMinRunPos; + // ICU seems to return bad pCharIndices + // for some combinations of ICU+font+text + // => better give up now than crash later + if( nCharPos >= nEndRunPos ) + continue; + } + + // if needed request glyph fallback by updating LayoutArgs + if( !nGlyphIndex ) + { + if( nCharPos >= 0 ) + { + rArgs.NeedFallback( nCharPos, bRightToLeft ); + if ( (nCharPos > 0) && lcl_CharIsJoiner(rArgs.mpStr[nCharPos-1]) ) + rArgs.NeedFallback( nCharPos-1, bRightToLeft ); + else if ( (nCharPos + 1 < nEndRunPos) && lcl_CharIsJoiner(rArgs.mpStr[nCharPos+1]) ) + rArgs.NeedFallback( nCharPos+1, bRightToLeft ); + } + + if( SAL_LAYOUT_FOR_FALLBACK & rArgs.mnFlags ) + continue; + } + + + // apply vertical flags, etc. + bool bDiacritic = false; + if( nCharPos >= 0 ) + { + sal_UCS4 aChar = rArgs.mpStr[ nCharPos ]; + nGlyphIndex = rFont.FixupGlyphIndex( nGlyphIndex, aChar ); + + // #i99367# HACK: try to detect all diacritics + if( aChar>=0x0300 && aChar<0x2100 ) + bDiacritic = IsDiacritic( aChar ); + } + + // get glyph position and its metrics + aNewPos = Point( (int)(pPos->fX+0.5), (int)(pPos->fY+0.5) ); + const GlyphMetric& rGM = rFont.GetGlyphMetric( nGlyphIndex ); + int nGlyphWidth = rGM.GetCharWidth(); + int nNewWidth = nGlyphWidth; + if( nGlyphWidth <= 0 ) + bDiacritic |= true; + // #i99367# force all diacritics to zero width + // TODO: we need mnOrigWidth/mnLogicWidth/mnNewWidth + else if( bDiacritic ) + nGlyphWidth = nNewWidth = 0; + else + { + // Hack, find next +ve width glyph and calculate current + // glyph width by substracting the two posituons + const IcuPosition* pNextPos = pPos+1; + for ( int j = i + 1; j <= nRawRunGlyphCount; ++j, ++pNextPos ) + { + if ( j == nRawRunGlyphCount ) + { + nNewWidth = static_cast<int>(pNextPos->fX - pPos->fX); + break; + } + + LEGlyphID nNextGlyphIndex = pIcuGlyphs[j]; + if( (nNextGlyphIndex == ICU_MARKED_GLYPH) + || (nNextGlyphIndex == ICU_DELETED_GLYPH) ) + continue; + + const GlyphMetric& rNextGM = rFont.GetGlyphMetric( nNextGlyphIndex ); + int nNextGlyphWidth = rNextGM.GetCharWidth(); + if ( nNextGlyphWidth > 0 ) + { + nNewWidth = static_cast<int>(pNextPos->fX - pPos->fX); + break; + } + } + } + + // heuristic to detect glyph clusters + bool bInCluster = true; + if( nLastCharPos == -1 ) + { + nClusterMinPos = nClusterMaxPos = nCharPos; + bInCluster = false; + } + else if( !bRightToLeft ) + { + // left-to-right case + if( nClusterMinPos > nCharPos ) + nClusterMinPos = nCharPos; // extend cluster + else if( nCharPos <= nClusterMaxPos ) + /*NOTHING*/; // inside cluster + else if( bDiacritic ) + nClusterMaxPos = nCharPos; // add diacritic to cluster + else { + nClusterMinPos = nClusterMaxPos = nCharPos; // new cluster + bInCluster = false; + } + } + else + { + // right-to-left case + if( nClusterMaxPos < nCharPos ) + nClusterMaxPos = nCharPos; // extend cluster + else if( nCharPos >= nClusterMinPos ) + /*NOTHING*/; // inside cluster + else if( bDiacritic ) + { + nClusterMinPos = nCharPos; // ICU often has [diacritic* baseglyph*] + if( bClusterStart ) { + nClusterMaxPos = nCharPos; + bInCluster = false; + } + } + else + { + nClusterMinPos = nClusterMaxPos = nCharPos; // new cluster + bInCluster = !bClusterStart; + } + } + + long nGlyphFlags = 0; + if( bInCluster ) + nGlyphFlags |= GlyphItem::IS_IN_CLUSTER; + if( bRightToLeft ) + nGlyphFlags |= GlyphItem::IS_RTL_GLYPH; + if( bDiacritic ) + nGlyphFlags |= GlyphItem::IS_DIACRITIC; + + // add resulting glyph item to layout + GlyphItem aGI( nCharPos, nGlyphIndex, aNewPos, nGlyphFlags, nGlyphWidth ); +#ifdef ARABIC_BANDAID + aGI.mnNewWidth = nNewWidth; +#endif + rLayout.AppendGlyph( aGI ); + ++nFilteredRunGlyphCount; + nLastCharPos = nCharPos; + bClusterStart = !aGI.IsDiacritic(); // TODO: only needed in RTL-codepath + } + aNewPos = Point( (int)(pPos->fX+0.5), (int)(pPos->fY+0.5) ); + nGlyphCount += nFilteredRunGlyphCount; + } + + // sort glyphs in visual order + // and then in logical order (e.g. diacritics after cluster start) + rLayout.SortGlyphItems(); + + // determine need for kashida justification + if( (rArgs.mpDXArray || rArgs.mnLayoutWidth) + && ((meScriptCode == arabScriptCode) || (meScriptCode == syrcScriptCode)) ) + rArgs.mnFlags |= SAL_LAYOUT_KASHIDA_JUSTIFICATON; + + return true; +} + +#endif // ENABLE_ICU_LAYOUT + +// ======================================================================= + +ServerFontLayoutEngine* ServerFont::GetLayoutEngine() +{ + // find best layout engine for font, platform, script and language +#ifdef ENABLE_ICU_LAYOUT + if( !mpLayoutEngine && FT_IS_SFNT( maFaceFT ) ) + mpLayoutEngine = new IcuLayoutEngine( *this ); +#endif // ENABLE_ICU_LAYOUT + + return mpLayoutEngine; +} + +// ======================================================================= + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/generic/glyphs/gcach_rbmp.cxx b/vcl/generic/glyphs/gcach_rbmp.cxx new file mode 100644 index 000000000000..a5dd5aebacf1 --- /dev/null +++ b/vcl/generic/glyphs/gcach_rbmp.cxx @@ -0,0 +1,277 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * 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 + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +#include "generic/glyphcache.hxx" +#include <string.h> + +//------------------------------------------------------------------------ + +RawBitmap::RawBitmap() +: mpBits(0), mnAllocated(0) +{} + +//------------------------------------------------------------------------ + +RawBitmap::~RawBitmap() +{ + delete[] mpBits; + mpBits = 0; + mnAllocated = 0; +} + +//------------------------------------------------------------------------ + +// used by 90 and 270 degree rotations on 8 bit deep bitmaps +static void ImplRotate8_90( unsigned char* p1, const unsigned char* p2, + int xmax, int ymax, int dx, int dy, int nPad ) +{ + for( int y = ymax; --y >= 0; p2 += dy ) + { + for( int x = xmax; --x >= 0; p2 += dx ) + *(p1++) = *p2; + for( int i = nPad; --i >= 0; ) + *(p1++) = 0; + } +} + +//------------------------------------------------------------------------ + +// used by inplace 180 degree rotation on 8 bit deep bitmaps +static void ImplRotate8_180( unsigned char* p1, int xmax, int ymax, int nPad ) +{ + unsigned char* p2 = p1 + ymax * (xmax + nPad); + for( int y = ymax/2; --y >= 0; ) + { + p2 -= nPad; + for( int x = xmax; --x >= 0; ) + { + unsigned char cTmp = *(--p2); + *p2 = *p1; + *(p1++) = cTmp; + } + p1 += nPad; + } + + // reverse middle line + p2 -= nPad; + while( p1 < p2 ) + { + unsigned char cTmp = *(--p2); + *p2 = *p1; + *(p1++) = cTmp; + } +} + +//------------------------------------------------------------------------ + +// used by 90 or 270 degree rotations on 1 bit deep bitmaps +static void ImplRotate1_90( unsigned char* p1, const unsigned char* p2, + int xmax, int ymax, int dx, int nShift, int nDeltaShift, int nPad ) +{ + for( int y = ymax; --y >= 0; ) + { + unsigned nTemp = 1; + const unsigned char* p20 = p2; + for( int x = xmax; --x >= 0; p2 += dx ) + { + // build bitwise and store when byte finished + nTemp += nTemp + ((*p2 >> nShift) & 1); + if( nTemp >= 0x100U ) + { + *(p1++) = (unsigned char)nTemp; + nTemp = 1; + } + } + p2 = p20; + + // store left aligned remainder if needed + if( nTemp > 1 ) + { + for(; nTemp < 0x100U; nTemp += nTemp ) ; + *(p1++) = (unsigned char)nTemp; + } + // pad scanline with zeroes + for( int i = nPad; --i >= 0;) + *(p1++) = 0; + + // increase/decrease shift, but keep bound inside 0 to 7 + nShift += nDeltaShift; + if( nShift != (nShift & 7) ) + p2 -= nDeltaShift; + nShift &= 7; + } +} + +//------------------------------------------------------------------------ + +// used by 180 degrees rotations on 1 bit deep bitmaps +static void ImplRotate1_180( unsigned char* p1, const unsigned char* p2, + int xmax, int ymax, int nPad ) +{ + --p2; + for( int y = ymax; --y >= 0; ) + { + p2 -= nPad; + + unsigned nTemp = 1; + unsigned nInp = (0x100 + *p2) >> (-xmax & 7); + for( int x = xmax; --x >= 0; ) + { + // build bitwise and store when byte finished + nTemp += nTemp + (nInp & 1); + if( nTemp >= 0x100 ) + { + *(p1++) = (unsigned char)nTemp; + nTemp = 1; + } + // update input byte if needed (and available) + if( (nInp >>= 1) <= 1 && ((y != 0) || (x != 0)) ) + nInp = 0x100 + *(--p2); + } + + // store left aligned remainder if needed + if( nTemp > 1 ) + { + for(; nTemp < 0x100; nTemp += nTemp ) ; + *(p1++) = (unsigned char)nTemp; + } + // scanline pad is already clean + p1 += nPad; + } +} + +//------------------------------------------------------------------------ + +bool RawBitmap::Rotate( int nAngle ) +{ + sal_uLong nNewScanlineSize = 0; + sal_uLong nNewHeight = 0; + sal_uLong nNewWidth = 0; + + // do inplace rotation or prepare double buffered rotation + switch( nAngle ) + { + case 0: // nothing to do + case 3600: + return true; + default: // non rectangular angles not allowed + return false; + case 1800: // rotate by 180 degrees + mnXOffset = -(mnXOffset + mnWidth); + mnYOffset = -(mnYOffset + mnHeight); + if( mnBitCount == 8 ) + { + ImplRotate8_180( mpBits, mnWidth, mnHeight, mnScanlineSize-mnWidth ); + return true; + } + nNewWidth = mnWidth; + nNewHeight = mnHeight; + nNewScanlineSize = mnScanlineSize; + break; + case +900: // left by 90 degrees + case -900: + case 2700: // right by 90 degrees + nNewWidth = mnHeight; + nNewHeight = mnWidth; + if( mnBitCount==1 ) + nNewScanlineSize = (nNewWidth + 7) / 8; + else + nNewScanlineSize = (nNewWidth + 3) & -4; + break; + } + + unsigned int nBufSize = nNewHeight * nNewScanlineSize; + unsigned char* pBuf = new unsigned char[ nBufSize ]; + if( !pBuf ) + return false; + + memset( pBuf, 0, nBufSize ); + int i; + + // dispatch non-inplace rotations + switch( nAngle ) + { + case 1800: // rotate by 180 degrees + // we know we only need to deal with 1 bit depth + ImplRotate1_180( pBuf, mpBits + mnHeight * mnScanlineSize, + mnWidth, mnHeight, mnScanlineSize - (mnWidth + 7) / 8 ); + break; + case +900: // rotate left by 90 degrees + i = mnXOffset; + mnXOffset = mnYOffset; + mnYOffset = -nNewHeight - i; + if( mnBitCount == 8 ) + ImplRotate8_90( pBuf, mpBits + mnWidth - 1, + nNewWidth, nNewHeight, +mnScanlineSize, -1-mnHeight*mnScanlineSize, + nNewScanlineSize - nNewWidth ); + else + ImplRotate1_90( pBuf, mpBits + (mnWidth - 1) / 8, + nNewWidth, nNewHeight, +mnScanlineSize, + (-mnWidth & 7), +1, nNewScanlineSize - (nNewWidth + 7) / 8 ); + break; + case 2700: // rotate right by 90 degrees + case -900: + i = mnXOffset; + mnXOffset = -(nNewWidth + mnYOffset); + mnYOffset = i; + if( mnBitCount == 8 ) + ImplRotate8_90( pBuf, mpBits + mnScanlineSize * (mnHeight-1), + nNewWidth, nNewHeight, -mnScanlineSize, +1+mnHeight*mnScanlineSize, + nNewScanlineSize - nNewWidth ); + else + ImplRotate1_90( pBuf, mpBits + mnScanlineSize * (mnHeight-1), + nNewWidth, nNewHeight, -mnScanlineSize, + +7, -1, nNewScanlineSize - (nNewWidth + 7) / 8 ); + break; + } + + mnWidth = nNewWidth; + mnHeight = nNewHeight; + mnScanlineSize = nNewScanlineSize; + + if( nBufSize < mnAllocated ) + { + memcpy( mpBits, pBuf, nBufSize ); + delete[] pBuf; + } + else + { + delete[] mpBits; + mpBits = pBuf; + mnAllocated = nBufSize; + } + + return true; +} + +//------------------------------------------------------------------------ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/generic/glyphs/glyphcache.cxx b/vcl/generic/glyphs/glyphcache.cxx new file mode 100644 index 000000000000..5322b6502310 --- /dev/null +++ b/vcl/generic/glyphs/glyphcache.cxx @@ -0,0 +1,503 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * 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 + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 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 <vcl/salbtype.hxx> +#include <gcach_ftyp.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/bitmap.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(); + for( FontList::iterator it = maFontList.begin(), end = maFontList.end(); it != end; ++it ) + { + ServerFont* pServerFont = it->second; + mrPeer.RemovingFont(*pServerFont); + delete pServerFont; + } + 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::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 ); +} + +// ----------------------------------------------------------------------- + +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 ); + + 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(); + } +} + +// ----------------------------------------------------------------------- + +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; +} + +// ----------------------------------------------------------------------- + +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(); + } + } +} + +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 ) +, mbGotFontOptions( 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; +} + +// ======================================================================= + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/generic/glyphs/graphite_serverfont.cxx b/vcl/generic/glyphs/graphite_serverfont.cxx new file mode 100644 index 000000000000..fd5babf5efe8 --- /dev/null +++ b/vcl/generic/glyphs/graphite_serverfont.cxx @@ -0,0 +1,154 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * 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 + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +// We need this to enable namespace support in libgrengine headers. +#define GR_NAMESPACE + +// Header files +// + +// Platform +#include <i18npool/mslangid.hxx> +#include <sallayout.hxx> +// Module +#include "gcach_ftyp.hxx" +#include "generic/glyphcache.hxx" +#include <graphite_features.hxx> +#include <graphite_serverfont.hxx> + +float freetypeServerFontAdvance(const void* appFontHandle, gr_uint16 glyphId) +{ + ServerFont * pServerFont = + const_cast<ServerFont*> + (reinterpret_cast<const ServerFont*>(appFontHandle)); + if (pServerFont) + { + return static_cast<float>(pServerFont->GetGlyphMetric(glyphId).GetCharWidth()); + } + return .0f; +} + +// +// An implementation of the GraphiteLayout interface to enable Graphite enabled fonts to be used. +// + +GraphiteServerFontLayout::GraphiteServerFontLayout(ServerFont& rServerFont) throw() + : ServerFontLayout(rServerFont), + maImpl(rServerFont.GetGraphiteFace()->face(), + rServerFont), + mpFeatures(NULL) +{ + gr_font * pFont = rServerFont.GetGraphiteFace()->font(rServerFont.GetFontSelData().mnHeight); + if (!pFont) + { + pFont = gr_make_font_with_advance_fn( + // need to use mnHeight here, mfExactHeight can give wrong values + static_cast<float>(rServerFont.GetFontSelData().mnHeight), + &rServerFont, + freetypeServerFontAdvance, + rServerFont.GetGraphiteFace()->face()); + rServerFont.GetGraphiteFace()->addFont(rServerFont.GetFontSelData().mnHeight, pFont); + } + maImpl.SetFont(pFont); + rtl::OString aLang(""); + if (rServerFont.GetFontSelData().meLanguage != LANGUAGE_DONTKNOW) + { + aLang = MsLangId::convertLanguageToIsoByteString( + rServerFont.GetFontSelData().meLanguage ); + } + rtl::OString name = rtl::OUStringToOString( + rServerFont.GetFontSelData().maTargetName, RTL_TEXTENCODING_UTF8 ); +#ifdef DEBUG + printf("GraphiteServerFontLayout %lx %s size %d %f\n", (long unsigned int)this, name.getStr(), + rServerFont.GetMetricsFT().x_ppem, + rServerFont.GetFontSelData().mfExactHeight); +#endif + sal_Int32 nFeat = name.indexOf(grutils::GrFeatureParser::FEAT_PREFIX) + 1; + if (nFeat > 0) + { + rtl::OString aFeat = name.copy(nFeat, name.getLength() - nFeat); + mpFeatures = new grutils::GrFeatureParser( + rServerFont.GetGraphiteFace()->face(), aFeat, aLang); +#ifdef DEBUG + if (mpFeatures) + printf("GraphiteServerFontLayout %s/%s/%s %x language %d features %d errors\n", + rtl::OUStringToOString( rServerFont.GetFontSelData().maName, + RTL_TEXTENCODING_UTF8 ).getStr(), + rtl::OUStringToOString( rServerFont.GetFontSelData().maTargetName, + RTL_TEXTENCODING_UTF8 ).getStr(), + rtl::OUStringToOString( rServerFont.GetFontSelData().maSearchName, + RTL_TEXTENCODING_UTF8 ).getStr(), + rServerFont.GetFontSelData().meLanguage, + (int)mpFeatures->numFeatures(), mpFeatures->parseErrors()); +#endif + } + else + { + mpFeatures = new grutils::GrFeatureParser( + rServerFont.GetGraphiteFace()->face(), aLang); + } + maImpl.SetFeatures(mpFeatures); +} + +GraphiteServerFontLayout::~GraphiteServerFontLayout() throw() +{ + delete mpFeatures; + mpFeatures = NULL; +} + +bool GraphiteServerFontLayout::IsGraphiteEnabledFont(ServerFont& rServerFont) +{ + if (rServerFont.GetGraphiteFace()) + { +#ifdef DEBUG + printf("IsGraphiteEnabledFont\n"); +#endif + return true; + } + return false; +} + +sal_GlyphId GraphiteLayoutImpl::getKashidaGlyph(int & width) +{ + int nKashidaIndex = mrServerFont.GetGlyphIndex( 0x0640 ); + if( nKashidaIndex != 0 ) + { + const GlyphMetric& rGM = mrServerFont.GetGlyphMetric( nKashidaIndex ); + width = rGM.GetCharWidth(); + } + else + { + width = 0; + } + return nKashidaIndex; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |