| /************************************************************** |
| * |
| * 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 <svsys.h> |
| #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 <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) |
| #if !defined(HPUX) |
| // PORTERS: dlfcn is used for getting symbols from FT versions newer than baseline |
| #include <dlfcn.h> |
| #endif |
| #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; |
| |
| 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<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; |
| |
| // 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<sal_IntPtr>( 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, vt=%d, sy=%d) => %d\n", |
| pFI->GetFontFileName()->getStr(), rFSD.mnHeight, rFSD.mnWidth, rFSD.mbVertical, 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<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; |
| #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<FT_BitmapGlyph>(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<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 ); |
| #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<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 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<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* 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<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; |
| } |
| |
| // ----------------------------------------------------------------------- |
| // 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<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 std::hash_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), |
| 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 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<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 FreetypeServerFont::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.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<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.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; |
| pFeatureTable += 2; // ignore FeatureParams |
| 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; |
| } |
| |
| // ======================================================================= |
| |