/************************************************************** * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * *************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_vcl.hxx" #ifdef WNT #include #undef CreateFont #endif #include "gcach_ftyp.hxx" #include "vcl/svapp.hxx" #include "outfont.hxx" #include "impfont.hxx" #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 #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 // TODO: move file mapping stuff to OSL #if defined(UNX) #if !defined(HPUX) // PORTERS: dlfcn is used for getting symbols from FT versions newer than baseline #include #endif #include #include #include #include #include "vcl/fontmanager.hxx" #elif defined(WNT) #include #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; typedef ::std::hash_map< const char*, FtFontFile*, rtl::CStringHash, rtl::CStringEqual> 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 do not autohint when antialiasing // if (EB 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; // no => create new one FtFontFile* pFontFile = new FtFontFile( rNativeFileName ); pFileName = pFontFile->maNativeFileName.getStr(); rFontFileList[ pFileName ] = 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; } // ======================================================================= 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 ), 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; } 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; } // ----------------------------------------------------------------------- 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(); } // ----------------------------------------------------------------------- void* FreetypeServerFont::GetFtFace() const { if( maSizeFT ) pFTActivateSize( maSizeFT ); return maFaceFT; } // ----------------------------------------------------------------------- FreetypeManager::~FreetypeManager() { // 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') ) { // cleanup container of fontinfos for( FontList::const_iterator it = maFontList.begin(); it != maFontList.end(); ++it ) { FtFontInfo* pInfo = (*it).second; delete pInfo; } maFontList.clear(); #if 0 // FT_Done_FreeType crashes on Solaris 10 // TODO: check which versions have this problem FT_Error rcFT = FT_Done_FreeType( aLibFT ); #endif } } // ----------------------------------------------------------------------- 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; } // ----------------------------------------------------------------------- long FreetypeManager::AddFontDir( const String& rUrlName ) { osl::Directory aDir( rUrlName ); osl::FileBase::RC rcOSL = aDir.open(); if( rcOSL != osl::FileBase::E_None ) return 0; long nCount = 0; osl::DirectoryItem aDirItem; rtl_TextEncoding theEncoding = osl_getThreadTextEncoding(); while( (rcOSL = aDir.getNextItem( aDirItem, 20 )) == osl::FileBase::E_None ) { osl::FileStatus aFileStatus( FileStatusMask_FileURL ); rcOSL = aDirItem.getFileStatus( aFileStatus ); ::rtl::OUString aUSytemPath; OSL_VERIFY( osl::FileBase::E_None == osl::FileBase::getSystemPathFromFileURL( aFileStatus.getFileURL(), aUSytemPath )); ::rtl::OString aCFileName = rtl::OUStringToOString( aUSytemPath, theEncoding ); const char* pszFontFileName = aCFileName.getStr(); FT_FaceRec_* aFaceFT = NULL; for( int nFaceNum = 0, nMaxFaces = 1; nFaceNum < nMaxFaces; ++nFaceNum ) { FT_Error rcFT = FT_New_Face( aLibFT, pszFontFileName, nFaceNum, &aFaceFT ); if( (rcFT != FT_Err_Ok) || (aFaceFT == NULL) ) break; if( !FT_IS_SCALABLE( aFaceFT ) ) // ignore non-scalabale fonts continue; nMaxFaces = aFaceFT->num_faces; ImplDevFontAttributes aDFA; // TODO: prefer unicode names if available // TODO: prefer locale specific names if available? if ( aFaceFT->family_name ) aDFA.maName = String::CreateFromAscii( aFaceFT->family_name ); if ( aFaceFT->style_name ) aDFA.maStyleName = String::CreateFromAscii( aFaceFT->style_name ); aDFA.mbSymbolFlag = false; for( int i = aFaceFT->num_charmaps; --i >= 0; ) { const FT_CharMap aCM = aFaceFT->charmaps[i]; #if (FTVERSION < 2000) if( aCM->encoding == FT_ENCODING_NONE ) #else if( (aCM->platform_id == TT_PLATFORM_MICROSOFT) && (aCM->encoding_id == TT_MS_ID_SYMBOL_CS) ) #endif aDFA.mbSymbolFlag = true; } // TODO: extract better font characterization data from font aDFA.meFamily = FAMILY_DONTKNOW; aDFA.mePitch = FT_IS_FIXED_WIDTH( aFaceFT ) ? PITCH_FIXED : PITCH_VARIABLE; aDFA.meWidthType = WIDTH_DONTKNOW; aDFA.meWeight = FT_STYLE_FLAG_BOLD & aFaceFT->style_flags ? WEIGHT_BOLD : WEIGHT_NORMAL; aDFA.meItalic = FT_STYLE_FLAG_ITALIC & aFaceFT->style_flags ? ITALIC_NORMAL : ITALIC_NONE; aDFA.mnQuality = 0; aDFA.mbOrientation= true; aDFA.mbDevice = true; aDFA.mbSubsettable= false; aDFA.mbEmbeddable = false; FT_Done_Face( aFaceFT ); AddFontFile( aCFileName, nFaceNum, ++mnNextFontId, aDFA, NULL ); ++nCount; } } aDir.close(); return nCount; } // ----------------------------------------------------------------------- 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(); } // ----------------------------------------------------------------------- FreetypeServerFont* FreetypeManager::CreateFont( const ImplFontSelectData& rFSD ) { FtFontInfo* pFontInfo = NULL; // find a FontInfo matching to the font id sal_IntPtr nFontId = reinterpret_cast( rFSD.mpFontData ); FontList::iterator it = maFontList.find( nFontId ); if( it != maFontList.end() ) pFontInfo = it->second; if( !pFontInfo ) return NULL; FreetypeServerFont* pNew = new FreetypeServerFont( 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; } // ======================================================================= // FreetypeServerFont // ======================================================================= FreetypeServerFont::FreetypeServerFont( const ImplFontSelectData& rFSD, FtFontInfo* pFI ) : ServerFont( rFSD ), mnPrioEmbedded(nDefaultPrioEmbedded), mnPrioAntiAlias(nDefaultPrioAntiAlias), mnPrioAutoHint(nDefaultPrioAutoHint), mpFontInfo( pFI ), maFaceFT( NULL ), maSizeFT( NULL ), mbFaceOk( false ), maRecodeConverter( NULL ), mpLayoutEngine( NULL ) { maFaceFT = pFI->GetFaceFT(); #ifdef HDU_DEBUG fprintf( stderr, "FTSF::FTSF(\"%s\", h=%d, w=%d, sy=%d) => %d\n", pFI->GetFontFileName()->getStr(), rFSD.mnHeight, rFSD.mnWidth, pFI->IsSymbolFont(), maFaceFT!=0 ); #endif 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 FreetypeServerFont::SetFontOptions( const ImplFontOptions& rFontOptions) { FontAutoHint eHint = rFontOptions.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( rFontOptions.DontUseAntiAlias() ) mnPrioAntiAlias = 0; if( rFontOptions.DontUseEmbeddedBitmaps() ) mnPrioEmbedded = 0; if( rFontOptions.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( rFontOptions.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; } // ----------------------------------------------------------------------- bool FreetypeServerFont::TestFont() const { return mbFaceOk; } // ----------------------------------------------------------------------- FreetypeServerFont::~FreetypeServerFont() { if( mpLayoutEngine ) delete mpLayoutEngine; if( maRecodeConverter ) rtl_destroyUnicodeToTextConverter( maRecodeConverter ); if( maSizeFT ) pFTDoneSize( maSizeFT ); mpFontInfo->ReleaseFaceFT( maFaceFT ); } // ----------------------------------------------------------------------- int FreetypeServerFont::GetEmUnits() const { return maFaceFT->units_per_EM; } // ----------------------------------------------------------------------- void FreetypeServerFont::FetchFontMetric( ImplFontMetricData& rTo, long& rFactor ) const { static_cast(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; #if (FTVERSION < 2000) rTo.mnDescent = (+rMetrics.descender + 32) >> 6; #else rTo.mnDescent = (-rMetrics.descender + 32) >> 6; #endif 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 ); const TT_HoriHeader* pHHEA = (const TT_HoriHeader*)FT_Get_Sfnt_Table( maFaceFT, ft_sfnt_hhea ); 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; } // #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! const double fScale = (double)GetFontSelData().mnHeight / maFaceFT->units_per_EM; 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; 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 FreetypeServerFont& rFont, sal_GlyphId& rGlyphId, int& nGlyphFlags ) { nGlyphFlags = rGlyphId & GF_FLAGMASK; rGlyphId &= GF_IDXMASK; if( rGlyphId & GF_ISCHAR ) rGlyphId = rFont.GetRawGlyphIndex( rGlyphId ); } // ----------------------------------------------------------------------- int FreetypeServerFont::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(pGlyphFT); pBmpGlyphFT->left += (aVector.x + 32) >> 6; pBmpGlyphFT->top += (aVector.y + 32) >> 6; } return nAngle; } // ----------------------------------------------------------------------- sal_GlyphId FreetypeServerFont::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(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 ); #if 0 // disabled for now because it introduced ae bad side-effect (#i88376#) // Finally try the postscript name table if (!nGlyphIndex) nGlyphIndex = psp::PrintFontManager::get().FreeTypeCharIndex( maFaceFT, aChar ); #endif } mpFontInfo->CacheGlyphIndex( aChar, nGlyphIndex ); } return sal_GlyphId( nGlyphIndex); } // ----------------------------------------------------------------------- sal_GlyphId FreetypeServerFont::FixupGlyphIndex( sal_GlyphId aGlyphId, 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( aGlyphId ); if( it == maGlyphSubstitution.end() ) { sal_GlyphId nTemp = GetVerticalChar( aChar ); if( nTemp ) // is substitution possible nTemp = GetRawGlyphIndex( nTemp ); if( nTemp ) // substitute manually if sensible aGlyphId = nTemp | (GF_GSUB | GF_ROTL); else nGlyphFlags |= GetVerticalFlags( aChar ); } else { // for vertical GSUB also compensate for nOrientation=2700 aGlyphId = (*it).second; nGlyphFlags |= GF_GSUB | GF_ROTL; } } #if 0 // #95556# autohinting not yet optimized for non-western glyph styles if( !(mnLoadFlags & (FT_LOAD_NO_HINTING | FT_LOAD_FORCE_AUTOHINT) ) && ( (aChar >= 0x0600 && aChar < 0x1E00) // south-east asian + arabic ||(aChar >= 0x2900 && aChar < 0xD800) // CJKV ||(aChar >= 0xF800) ) ) // presentation + symbols { nGlyphFlags |= GF_UNHINTED; } #endif if( aGlyphId != 0 ) aGlyphId |= nGlyphFlags; return aGlyphId; } // ----------------------------------------------------------------------- sal_GlyphId FreetypeServerFont::GetGlyphIndex( sal_UCS4 aChar ) const { sal_GlyphId aGlyphId = GetRawGlyphIndex( aChar ); aGlyphId = FixupGlyphIndex( aGlyphId, aChar ); return aGlyphId; } // ----------------------------------------------------------------------- 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 FreetypeServerFont::InitGlyphData( sal_GlyphId aGlyphId, GlyphData& rGD ) const { if( maSizeFT ) pFTActivateSize( maSizeFT ); int nGlyphFlags; SplitGlyphFlags( *this, aGlyphId, 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, aGlyphId, 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, aGlyphId, 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 FreetypeServerFont::GetAntialiasAdvice( void ) const { if( GetFontSelData().mbNonAntialiased || (mnPrioAntiAlias<=0) ) return false; bool bAdviseAA = true; // TODO: also use GASP info return bAdviseAA; } // ----------------------------------------------------------------------- bool FreetypeServerFont::GetGlyphBitmap1( sal_GlyphId aGlyphId, RawBitmap& rRawBitmap ) const { if( maSizeFT ) pFTActivateSize( maSizeFT ); int nGlyphFlags; SplitGlyphFlags( *this, aGlyphId, 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, aGlyphId, 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, aGlyphId, 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(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 FreetypeServerFont::GetGlyphBitmap8( sal_GlyphId aGlyphId, RawBitmap& rRawBitmap ) const { if( maSizeFT ) pFTActivateSize( maSizeFT ); int nGlyphFlags; SplitGlyphFlags( *this, aGlyphId, 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, aGlyphId, 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, aGlyphId, 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(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* FreetypeServerFont::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 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; } // ----------------------------------------------------------------------- // kerning stuff // ----------------------------------------------------------------------- int FreetypeServerFont::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 FreetypeServerFont::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 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 std::hash_multimap 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 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), 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(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(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(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(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 FreetypeServerFont::GetGlyphOutline( sal_GlyphId aGlyphId, ::basegfx::B2DPolyPolygon& rB2DPolyPoly ) const { if( maSizeFT ) pFTActivateSize( maSizeFT ); rB2DPolyPoly.clear(); int nGlyphFlags; SplitGlyphFlags( *this, aGlyphId, 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, aGlyphId, 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(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 FreetypeServerFont::ApplyGSUB( const ImplFontSelectData& rFSD ) { #define MKTAG(s) ((((((s[0]<<8)+s[1])<<8)+s[2])<<8)+s[3]) typedef std::vector 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.size() == 0) // 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 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.size() ) 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 GlyphSubst; typedef std::vector 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(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; } // =======================================================================