diff options
Diffstat (limited to 'vcl/source/glyphs')
-rw-r--r-- | vcl/source/glyphs/gcach_ftyp.cxx | 2667 | ||||
-rw-r--r-- | vcl/source/glyphs/gcach_ftyp.hxx | 280 | ||||
-rw-r--r-- | vcl/source/glyphs/gcach_layout.cxx | 669 | ||||
-rw-r--r-- | vcl/source/glyphs/gcach_rbmp.cxx | 277 | ||||
-rw-r--r-- | vcl/source/glyphs/gcach_vdev.cxx | 290 | ||||
-rw-r--r-- | vcl/source/glyphs/gcach_vdev.hxx | 60 | ||||
-rw-r--r-- | vcl/source/glyphs/glyphcache.cxx | 609 | ||||
-rw-r--r-- | vcl/source/glyphs/graphite_features.cxx | 288 | ||||
-rw-r--r-- | vcl/source/glyphs/graphite_layout.cxx | 1366 | ||||
-rw-r--r-- | vcl/source/glyphs/graphite_serverfont.cxx | 164 | ||||
-rw-r--r-- | vcl/source/glyphs/graphite_textsrc.cxx | 172 | ||||
-rw-r--r-- | vcl/source/glyphs/graphite_textsrc.hxx | 124 | ||||
-rw-r--r-- | vcl/source/glyphs/makefile.mk | 80 |
13 files changed, 7046 insertions, 0 deletions
diff --git a/vcl/source/glyphs/gcach_ftyp.cxx b/vcl/source/glyphs/gcach_ftyp.cxx new file mode 100644 index 000000000000..82fa925097ee --- /dev/null +++ b/vcl/source/glyphs/gcach_ftyp.cxx @@ -0,0 +1,2667 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +#ifdef WNT +#include <svsys.h> +#undef CreateFont +#endif + +#include "gcach_ftyp.hxx" + +#include "vcl/svapp.hxx" +#include "vcl/outfont.hxx" +#include "vcl/impfont.hxx" +#ifdef ENABLE_GRAPHITE +#include <graphite2/Font.h> +#include "vcl/graphite_layout.hxx" +#endif + +#include "tools/poly.hxx" +#include "basegfx/matrix/b2dhommatrix.hxx" +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include "basegfx/polygon/b2dpolypolygon.hxx" + +#include "osl/file.hxx" +#include "osl/thread.hxx" + +#include "sft.hxx" + +#include <ft2build.h> +#include FT_FREETYPE_H +#include FT_GLYPH_H +#include FT_OUTLINE_H +#include FT_TRUETYPE_TABLES_H +#include FT_TRUETYPE_TAGS_H +#include FT_TRUETYPE_IDS_H + +#ifndef FT_RENDER_MODE_MONO // happens in the MACOSX build + #define FT_RENDER_MODE_MONO ft_render_mode_mono +#endif +#include "rtl/instance.hxx" + +#ifndef FREETYPE_PATCH + // VERSION_MINOR in freetype.h is too coarse + // if patch-level is not available we need to fine-tune the version ourselves + #define FTVERSION 2005 +#else + #define FTVERSION (1000*FREETYPE_MAJOR + 100*FREETYPE_MINOR + FREETYPE_PATCH) +#endif +#if FTVERSION >= 2200 +typedef const FT_Vector* FT_Vector_CPtr; +#else // FTVERSION < 2200 +typedef FT_Vector* FT_Vector_CPtr; +#endif + +#include <vector> + +// TODO: move file mapping stuff to OSL +#if defined(UNX) + // PORTERS: dlfcn is used for getting symbols from FT versions newer than baseline + #include <dlfcn.h> + #include <unistd.h> + #include <fcntl.h> + #include <sys/stat.h> + #include <sys/mman.h> + #include "vcl/fontmanager.hxx" +#elif defined(WNT) + #include <io.h> + #define strncasecmp strnicmp +#endif + +typedef const unsigned char* CPU8; +inline sal_uInt16 NEXT_U16( CPU8& p ) { p+=2; return (p[-2]<<8)|p[-1]; } +inline sal_Int16 NEXT_S16( CPU8& p ) { return (sal_Int16)NEXT_U16(p); } +inline sal_uInt32 NEXT_U32( CPU8& p ) { p+=4; return (p[-4]<<24)|(p[-3]<<16)|(p[-2]<<8)|p[-1]; } +//inline sal_Int32 NEXT_S32( U8*& p ) { return (sal_Int32)NEXT_U32(p); } + +// ----------------------------------------------------------------------- + +// the gamma table makes artificial bold look better for CJK glyphs +static unsigned char aGammaTable[257]; + +static void InitGammaTable() +{ + static const int M_MAX = 255; + static const int M_X = 128; + static const int M_Y = 208; + + int x, a; + for( x = 0; x < 256; x++) + { + if ( x <= M_X ) + a = ( x * M_Y + M_X / 2) / M_X; + else + a = M_Y + ( ( x - M_X ) * ( M_MAX - M_Y ) + + ( M_MAX - M_X ) / 2 ) / ( M_MAX - M_X ); + + aGammaTable[x] = (unsigned char)a; + } +} + +// ----------------------------------------------------------------------- + +static FT_Library aLibFT = 0; + +// #110607# enable linking with old FT versions +static int nFTVERSION = 0; +static FT_Error (*pFTNewSize)(FT_Face,FT_Size*); +static FT_Error (*pFTActivateSize)(FT_Size); +static FT_Error (*pFTDoneSize)(FT_Size); +FT_Error (*pFTEmbolden)(FT_GlyphSlot); +FT_Error (*pFTOblique)(FT_GlyphSlot); +static bool bEnableSizeFT = false; + +struct EqStr{ bool operator()(const char* a, const char* b) const { return !strcmp(a,b); } }; +struct HashStr { size_t operator()( const char* s ) const { return rtl_str_hashCode(s); } }; +typedef ::boost::unordered_map<const char*,boost::shared_ptr<FtFontFile>,HashStr, EqStr> FontFileList; +namespace { struct vclFontFileList : public rtl::Static< FontFileList, vclFontFileList > {}; } + +// ----------------------------------------------------------------------- + +// TODO: remove when the priorities are selected by UI +// if (AH==0) => disable autohinting +// if (AA==0) => disable antialiasing +// if (EB==0) => disable embedded bitmaps +// if (AA prio <= AH prio) => antialias + autohint +// if (AH<AA) => do not autohint when antialiasing +// if (EB<AH) => do not autohint for monochrome +static int nDefaultPrioEmbedded = 2; +static int nDefaultPrioAutoHint = 1; +static int nDefaultPrioAntiAlias = 1; + +// ======================================================================= +// FreetypeManager +// ======================================================================= + +FtFontFile::FtFontFile( const ::rtl::OString& rNativeFileName ) +: maNativeFileName( rNativeFileName ), + mpFileMap( NULL ), + mnFileSize( 0 ), + mnRefCount( 0 ), + mnLangBoost( 0 ) +{ + // boost font preference if UI language is mentioned in filename + int nPos = maNativeFileName.lastIndexOf( '_' ); + if( nPos == -1 || maNativeFileName[nPos+1] == '.' ) + mnLangBoost += 0x1000; // no langinfo => good + else + { + static const char* pLangBoost = NULL; + static bool bOnce = true; + if( bOnce ) + { + bOnce = false; + LanguageType aLang = Application::GetSettings().GetUILanguage(); + switch( aLang ) + { + case LANGUAGE_JAPANESE: + pLangBoost = "jan"; + break; + case LANGUAGE_CHINESE: + case LANGUAGE_CHINESE_SIMPLIFIED: + case LANGUAGE_CHINESE_SINGAPORE: + pLangBoost = "zhs"; + break; + case LANGUAGE_CHINESE_TRADITIONAL: + case LANGUAGE_CHINESE_HONGKONG: + case LANGUAGE_CHINESE_MACAU: + pLangBoost = "zht"; + break; + case LANGUAGE_KOREAN: + case LANGUAGE_KOREAN_JOHAB: + pLangBoost = "kor"; + break; + } + } + + if( pLangBoost && !strncasecmp( pLangBoost, &maNativeFileName.getStr()[nPos+1], 3 ) ) + mnLangBoost += 0x2000; // matching langinfo => better + } +} + +// ----------------------------------------------------------------------- + +FtFontFile* FtFontFile::FindFontFile( const ::rtl::OString& rNativeFileName ) +{ + // font file already known? (e.g. for ttc, synthetic, aliased fonts) + const char* pFileName = rNativeFileName.getStr(); + FontFileList &rFontFileList = vclFontFileList::get(); + FontFileList::const_iterator it = rFontFileList.find( pFileName ); + if( it != rFontFileList.end() ) + return it->second.get(); + + // no => create new one + FtFontFile* pFontFile = new FtFontFile( rNativeFileName ); + pFileName = pFontFile->maNativeFileName.getStr(); + rFontFileList[pFileName].reset(pFontFile); + return pFontFile; +} + +// ----------------------------------------------------------------------- + +bool FtFontFile::Map() +{ + if( mnRefCount++ <= 0 ) + { + const char* pFileName = maNativeFileName.getStr(); +#if defined(UNX) + int nFile = open( pFileName, O_RDONLY ); + if( nFile < 0 ) + return false; + + struct stat aStat; + fstat( nFile, &aStat ); + mnFileSize = aStat.st_size; + mpFileMap = (const unsigned char*) + mmap( NULL, mnFileSize, PROT_READ, MAP_SHARED, nFile, 0 ); + if( mpFileMap == MAP_FAILED ) + mpFileMap = NULL; + close( nFile ); +#elif defined(WNT) + void* pFileDesc = ::CreateFile( pFileName, GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 ); + if( pFileDesc == INVALID_HANDLE_VALUE) + return false; + + mnFileSize = ::GetFileSize( pFileDesc, NULL ); + HANDLE aHandle = ::CreateFileMapping( pFileDesc, NULL, PAGE_READONLY, 0, mnFileSize, "TTF" ); + mpFileMap = (const unsigned char*)::MapViewOfFile( aHandle, FILE_MAP_READ, 0, 0, mnFileSize ); + ::CloseHandle( pFileDesc ); +#else + FILE* pFile = fopen( pFileName, "rb" ); + if( !pFile ) + return false; + + struct stat aStat; + stat( pFileName, &aStat ); + mnFileSize = aStat.st_size; + mpFileMap = new unsigned char[ mnFileSize ]; + if( mnFileSize != fread( mpFileMap, 1, mnFileSize, pFile ) ) + { + delete[] mpFileMap; + mpFileMap = NULL; + } + fclose( pFile ); +#endif + } + + return (mpFileMap != NULL); +} + +// ----------------------------------------------------------------------- + +void FtFontFile::Unmap() +{ + if( (--mnRefCount > 0) || (mpFileMap == NULL) ) + return; + +#if defined(UNX) + munmap( (char*)mpFileMap, mnFileSize ); +#elif defined(WNT) + UnmapViewOfFile( (LPCVOID)mpFileMap ); +#else + delete[] mpFileMap; +#endif + + mpFileMap = NULL; +} + +#ifdef ENABLE_GRAPHITE +// wrap FtFontInfo's table function +const void * graphiteFontTable(const void* appFaceHandle, unsigned int name, size_t *len) +{ + const FtFontInfo * pFontInfo = reinterpret_cast<const FtFontInfo*>(appFaceHandle); + typedef union { + char m_c[5]; + unsigned int m_id; + } TableId; + TableId tableId; + tableId.m_id = name; +#ifndef WORDS_BIGENDIAN + TableId swapped; + swapped.m_c[3] = tableId.m_c[0]; + swapped.m_c[2] = tableId.m_c[1]; + swapped.m_c[1] = tableId.m_c[2]; + swapped.m_c[0] = tableId.m_c[3]; + tableId.m_id = swapped.m_id; +#endif + tableId.m_c[4] = '\0'; + sal_uLong nLength = 0; + const void * pTable = static_cast<const void*>(pFontInfo->GetTable(tableId.m_c, &nLength)); + if (len) *len = static_cast<size_t>(nLength); + return pTable; +} +#endif + +// ======================================================================= + +FtFontInfo::FtFontInfo( const ImplDevFontAttributes& rDevFontAttributes, + const ::rtl::OString& rNativeFileName, int nFaceNum, sal_IntPtr nFontId, int nSynthetic, + const ExtraKernInfo* pExtraKernInfo ) +: + maFaceFT( NULL ), + mpFontFile( FtFontFile::FindFontFile( rNativeFileName ) ), + mnFaceNum( nFaceNum ), + mnRefCount( 0 ), + mnSynthetic( nSynthetic ), +#ifdef ENABLE_GRAPHITE + mbCheckedGraphite(false), + mpGraphiteFace(NULL), +#endif + mnFontId( nFontId ), + maDevFontAttributes( rDevFontAttributes ), + mpFontCharMap( NULL ), + mpChar2Glyph( NULL ), + mpGlyph2Char( NULL ), + mpExtraKernInfo( pExtraKernInfo ) +{ + // prefer font with low ID + maDevFontAttributes.mnQuality += 10000 - nFontId; + // prefer font with matching file names + maDevFontAttributes.mnQuality += mpFontFile->GetLangBoost(); + // prefer font with more external info + if( pExtraKernInfo ) + maDevFontAttributes.mnQuality += 100; +} + +// ----------------------------------------------------------------------- + +FtFontInfo::~FtFontInfo() +{ + if( mpFontCharMap ) + mpFontCharMap->DeReference(); + delete mpExtraKernInfo; + delete mpChar2Glyph; + delete mpGlyph2Char; +#ifdef ENABLE_GRAPHITE + if (mpGraphiteFace) + delete mpGraphiteFace; +#endif +} + +void FtFontInfo::InitHashes() const +{ + // TODO: avoid pointers when empty stl::hash_* objects become cheap + mpChar2Glyph = new Int2IntMap(); + mpGlyph2Char = new Int2IntMap(); +} + +// ----------------------------------------------------------------------- + +FT_FaceRec_* FtFontInfo::GetFaceFT() +{ + // get faceFT once/multiple depending on availability of SizeFT APIs + if( (mnRefCount++ <= 0) || !bEnableSizeFT ) + { + if( !mpFontFile->Map() ) + return NULL; + FT_Error rc = FT_New_Memory_Face( aLibFT, + (FT_Byte*)mpFontFile->GetBuffer(), + mpFontFile->GetFileSize(), mnFaceNum, &maFaceFT ); + if( (rc != FT_Err_Ok) || (maFaceFT->num_glyphs <= 0) ) + maFaceFT = NULL; + } + + return maFaceFT; +} + +#ifdef ENABLE_GRAPHITE +GraphiteFaceWrapper * FtFontInfo::GetGraphiteFace() +{ + if (mbCheckedGraphite) + return mpGraphiteFace; + // test for graphite here so that it is cached most efficiently + if (GetTable("Silf", 0)) + { + int graphiteSegCacheSize = 10000; + static const char* pGraphiteCacheStr = getenv( "SAL_GRAPHITE_CACHE_SIZE" ); + graphiteSegCacheSize = pGraphiteCacheStr ? (atoi(pGraphiteCacheStr)) : 0; + gr_face * pGraphiteFace; + if (graphiteSegCacheSize > 500) + pGraphiteFace = gr_make_face_with_seg_cache(this, graphiteFontTable, graphiteSegCacheSize, gr_face_cacheCmap); + else + pGraphiteFace = gr_make_face(this, graphiteFontTable, gr_face_cacheCmap); + if (pGraphiteFace) + mpGraphiteFace = new GraphiteFaceWrapper(pGraphiteFace); + } + mbCheckedGraphite = true; + return mpGraphiteFace; +} +#endif + +// ----------------------------------------------------------------------- + +void FtFontInfo::ReleaseFaceFT( FT_FaceRec_* pFaceFT ) +{ + // release last/each depending on SizeFT availability + if( (--mnRefCount <= 0) || !bEnableSizeFT ) + { + FT_Done_Face( pFaceFT ); + maFaceFT = NULL; + mpFontFile->Unmap(); + } +} + +// ----------------------------------------------------------------------- + +bool FtFontInfo::HasExtraKerning() const +{ + if( !mpExtraKernInfo ) + return false; + // TODO: how to enable the line below without getting #i29881# back? + // on the other hand being to optimistic doesn't cause problems + // return mpExtraKernInfo->HasKernPairs(); + return true; +} + +// ----------------------------------------------------------------------- + +int FtFontInfo::GetExtraKernPairs( ImplKernPairData** ppKernPairs ) const +{ + if( !mpExtraKernInfo ) + return 0; + return mpExtraKernInfo->GetUnscaledKernPairs( ppKernPairs ); +} + +// ----------------------------------------------------------------------- + +int FtFontInfo::GetExtraGlyphKernValue( int nLeftGlyph, int nRightGlyph ) const +{ + if( !mpExtraKernInfo ) + return 0; + if( !mpGlyph2Char ) + return 0; + sal_Unicode cLeftChar = (*mpGlyph2Char)[ nLeftGlyph ]; + sal_Unicode cRightChar = (*mpGlyph2Char)[ nRightGlyph ]; + return mpExtraKernInfo->GetUnscaledKernValue( cLeftChar, cRightChar ); +} + +// ----------------------------------------------------------------------- + +static unsigned GetUInt( const unsigned char* p ) { return((p[0]<<24)+(p[1]<<16)+(p[2]<<8)+p[3]);} +static unsigned GetUShort( const unsigned char* p ){ return((p[0]<<8)+p[1]);} +//static signed GetSShort( const unsigned char* p ){ return((short)((p[0]<<8)+p[1]));} + +// ----------------------------------------------------------------------- + +const unsigned char* FtFontInfo::GetTable( const char* pTag, sal_uLong* pLength ) const +{ + const unsigned char* pBuffer = mpFontFile->GetBuffer(); + int nFileSize = mpFontFile->GetFileSize(); + if( !pBuffer || nFileSize<1024 ) + return NULL; + + // we currently only handle TTF and TTC headers + unsigned nFormat = GetUInt( pBuffer ); + const unsigned char* p = pBuffer + 12; + if( nFormat == 0x74746366 ) // TTC_MAGIC + p += GetUInt( p + 4 * mnFaceNum ); + else if( (nFormat!=0x00010000) && (nFormat!=0x74727565) ) // TTF_MAGIC and Apple TTF Magic + return NULL; + + // walk table directory until match + int nTables = GetUShort( p - 8 ); + if( nTables >= 64 ) // something fishy? + return NULL; + for( int i = 0; i < nTables; ++i, p+=16 ) + { + if( p[0]==pTag[0] && p[1]==pTag[1] && p[2]==pTag[2] && p[3]==pTag[3] ) + { + sal_uLong nLength = GetUInt( p + 12 ); + if( pLength != NULL ) + *pLength = nLength; + const unsigned char* pTable = pBuffer + GetUInt( p + 8 ); + if( (pTable + nLength) <= (mpFontFile->GetBuffer() + nFileSize) ) + return pTable; + } + } + + return NULL; +} + +// ----------------------------------------------------------------------- + +void FtFontInfo::AnnounceFont( ImplDevFontList* pFontList ) +{ + ImplFTSFontData* pFD = new ImplFTSFontData( this, maDevFontAttributes ); + pFontList->Add( pFD ); +} + +// ======================================================================= + +FreetypeManager::FreetypeManager() +: mnMaxFontId( 0 ), mnNextFontId( 0x1000 ) +{ + /*FT_Error rcFT =*/ FT_Init_FreeType( &aLibFT ); + +#ifdef RTLD_DEFAULT // true if a good dlfcn.h header was included + // Get version of freetype library to enable workarounds. + // Freetype <= 2.0.9 does not have FT_Library_Version(). + // Using dl_sym() instead of osl_getSymbol() because latter + // isn't designed to work with oslModule=NULL + void (*pFTLibraryVersion)(FT_Library library, + FT_Int *amajor, FT_Int *aminor, FT_Int *apatch); + pFTLibraryVersion = (void (*)(FT_Library library, + FT_Int *amajor, FT_Int *aminor, FT_Int *apatch))(sal_IntPtr)dlsym( RTLD_DEFAULT, "FT_Library_Version" ); + + pFTNewSize = (FT_Error(*)(FT_Face,FT_Size*))(sal_IntPtr)dlsym( RTLD_DEFAULT, "FT_New_Size" ); + pFTActivateSize = (FT_Error(*)(FT_Size))(sal_IntPtr)dlsym( RTLD_DEFAULT, "FT_Activate_Size" ); + pFTDoneSize = (FT_Error(*)(FT_Size))(sal_IntPtr)dlsym( RTLD_DEFAULT, "FT_Done_Size" ); + pFTEmbolden = (FT_Error(*)(FT_GlyphSlot))(sal_IntPtr)dlsym( RTLD_DEFAULT, "FT_GlyphSlot_Embolden" ); + pFTOblique = (FT_Error(*)(FT_GlyphSlot))(sal_IntPtr)dlsym( RTLD_DEFAULT, "FT_GlyphSlot_Oblique" ); + + bEnableSizeFT = (pFTNewSize!=NULL) && (pFTActivateSize!=NULL) && (pFTDoneSize!=NULL); + + FT_Int nMajor = 0, nMinor = 0, nPatch = 0; + if( pFTLibraryVersion ) + pFTLibraryVersion( aLibFT, &nMajor, &nMinor, &nPatch ); + nFTVERSION = nMajor * 1000 + nMinor * 100 + nPatch; + + // disable embedded bitmaps for Freetype-2.1.3 unless explicitly + // requested by env var below because it crashes StarOffice on RH9 + // reason: double free in freetype's embedded bitmap handling + if( nFTVERSION == 2103 ) + nDefaultPrioEmbedded = 0; + // disable artificial emboldening with the Freetype API for older versions + if( nFTVERSION < 2110 ) + pFTEmbolden = NULL; + +#else // RTLD_DEFAULT + // assume systems where dlsym is not possible use supplied library + nFTVERSION = FTVERSION; +#endif + + // TODO: remove when the priorities are selected by UI + char* pEnv; + pEnv = ::getenv( "SAL_EMBEDDED_BITMAP_PRIORITY" ); + if( pEnv ) + nDefaultPrioEmbedded = pEnv[0] - '0'; + pEnv = ::getenv( "SAL_ANTIALIASED_TEXT_PRIORITY" ); + if( pEnv ) + nDefaultPrioAntiAlias = pEnv[0] - '0'; + pEnv = ::getenv( "SAL_AUTOHINTING_PRIORITY" ); + if( pEnv ) + nDefaultPrioAutoHint = pEnv[0] - '0'; + + InitGammaTable(); + vclFontFileList::get(); +} + +// ----------------------------------------------------------------------- + +void* FreetypeServerFont::GetFtFace() const +{ + if( maSizeFT ) + pFTActivateSize( maSizeFT ); + + return maFaceFT; +} + +// ----------------------------------------------------------------------- + +FreetypeManager::~FreetypeManager() +{ + ClearFontList(); +// This crashes on Solaris 10 +// TODO: check which versions have this problem +// +// FT_Error rcFT = FT_Done_FreeType( aLibFT ); +} + +// ----------------------------------------------------------------------- + +void FreetypeManager::AddFontFile( const rtl::OString& rNormalizedName, + int nFaceNum, sal_IntPtr nFontId, const ImplDevFontAttributes& rDevFontAttr, + const ExtraKernInfo* pExtraKernInfo ) +{ + if( !rNormalizedName.getLength() ) + return; + + if( maFontList.find( nFontId ) != maFontList.end() ) + return; + + FtFontInfo* pFontInfo = new FtFontInfo( rDevFontAttr, + rNormalizedName, nFaceNum, nFontId, 0, pExtraKernInfo ); + maFontList[ nFontId ] = pFontInfo; + if( mnMaxFontId < nFontId ) + mnMaxFontId = nFontId; +} + +// ----------------------------------------------------------------------- + +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(); + + 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( boost::shared_ptr<ImplFontOptions> pFontOptions) +{ + mpFontOptions = pFontOptions; + + if (!mpFontOptions) + return; + + FontAutoHint eHint = mpFontOptions->GetUseAutoHint(); + if( eHint == AUTOHINT_DONTKNOW ) + eHint = mbUseGamma ? AUTOHINT_TRUE : AUTOHINT_FALSE; + + if( eHint == AUTOHINT_TRUE ) + mnLoadFlags |= FT_LOAD_FORCE_AUTOHINT; + + if( (mnSin != 0) && (mnCos != 0) ) // hinting for 0/90/180/270 degrees only + mnLoadFlags |= FT_LOAD_NO_HINTING; + mnLoadFlags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; //#88334# + + if( mpFontOptions->DontUseAntiAlias() ) + mnPrioAntiAlias = 0; + if( mpFontOptions->DontUseEmbeddedBitmaps() ) + mnPrioEmbedded = 0; + if( mpFontOptions->DontUseHinting() ) + mnPrioAutoHint = 0; + +#if (FTVERSION >= 2005) || defined(TT_CONFIG_OPTION_BYTECODE_INTERPRETER) + if( mnPrioAutoHint <= 0 ) +#endif + mnLoadFlags |= FT_LOAD_NO_HINTING; + +#if defined(FT_LOAD_TARGET_LIGHT) && defined(FT_LOAD_TARGET_NORMAL) + if( !(mnLoadFlags & FT_LOAD_NO_HINTING) && (nFTVERSION >= 2103)) + { + mnLoadFlags |= FT_LOAD_TARGET_NORMAL; + switch( mpFontOptions->GetHintStyle() ) + { + case HINT_NONE: + mnLoadFlags |= FT_LOAD_NO_HINTING; + break; + case HINT_SLIGHT: + mnLoadFlags |= FT_LOAD_TARGET_LIGHT; + break; + case HINT_MEDIUM: + break; + case HINT_FULL: + default: + break; + } + } +#endif + + if( mnPrioEmbedded <= 0 ) + mnLoadFlags |= FT_LOAD_NO_BITMAP; +} + +boost::shared_ptr<ImplFontOptions> FreetypeServerFont::GetFontOptions() const +{ + return mpFontOptions; +} + +// ----------------------------------------------------------------------- + +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, int& nGlyphIndex, int& nGlyphFlags ) +{ + nGlyphFlags = nGlyphIndex & GF_FLAGMASK; + nGlyphIndex &= GF_IDXMASK; + + if( nGlyphIndex & GF_ISCHAR ) + nGlyphIndex = rFont.GetRawGlyphIndex( nGlyphIndex ); +} + +// ----------------------------------------------------------------------- + +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; +} + +// ----------------------------------------------------------------------- + +int 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 ); + } + mpFontInfo->CacheGlyphIndex( aChar, nGlyphIndex ); + } + + return nGlyphIndex; +} + +// ----------------------------------------------------------------------- + +int FreetypeServerFont::FixupGlyphIndex( int nGlyphIndex, sal_UCS4 aChar ) const +{ + int nGlyphFlags = GF_NONE; + + // do glyph substitution if necessary + // CJK vertical writing needs special treatment + if( GetFontSelData().mbVertical ) + { + // TODO: rethink when GSUB is used for non-vertical case + GlyphSubstitution::const_iterator it = maGlyphSubstitution.find( nGlyphIndex ); + if( it == maGlyphSubstitution.end() ) + { + int nTemp = GetVerticalChar( aChar ); + if( nTemp ) // is substitution possible + nTemp = GetRawGlyphIndex( nTemp ); + if( nTemp ) // substitute manually if sensible + nGlyphIndex = nTemp | (GF_GSUB | GF_ROTL); + else + nGlyphFlags |= GetVerticalFlags( aChar ); + } + else + { + // for vertical GSUB also compensate for nOrientation=2700 + nGlyphIndex = (*it).second; + nGlyphFlags |= GF_GSUB | GF_ROTL; + } + } + + if( nGlyphIndex != 0 ) + nGlyphIndex |= nGlyphFlags; + + return nGlyphIndex; +} + + +// ----------------------------------------------------------------------- + +int FreetypeServerFont::GetGlyphIndex( sal_UCS4 aChar ) const +{ + int nGlyphIndex = GetRawGlyphIndex( aChar ); + nGlyphIndex = FixupGlyphIndex( nGlyphIndex, aChar ); + return nGlyphIndex; +} + +// ----------------------------------------------------------------------- + +static int lcl_GetCharWidth( FT_FaceRec_* pFaceFT, double fStretch, int nGlyphFlags ) +{ + int nCharWidth = pFaceFT->glyph->metrics.horiAdvance; + + if( nGlyphFlags & GF_ROTMASK ) // for bVertical rotated glyphs + { + const FT_Size_Metrics& rMetrics = pFaceFT->size->metrics; +#if (FTVERSION < 2000) + nCharWidth = (int)((rMetrics.height - rMetrics.descender) * fStretch); +#else + nCharWidth = (int)((rMetrics.height + rMetrics.descender) * fStretch); +#endif + } + + return (nCharWidth + 32) >> 6; +} + +// ----------------------------------------------------------------------- + +void FreetypeServerFont::InitGlyphData( int nGlyphIndex, GlyphData& rGD ) const +{ + if( maSizeFT ) + pFTActivateSize( maSizeFT ); + + int nGlyphFlags; + SplitGlyphFlags( *this, nGlyphIndex, nGlyphFlags ); + + int nLoadFlags = mnLoadFlags; + +// if( mbArtItalic ) +// nLoadFlags |= FT_LOAD_NO_BITMAP; + + FT_Error rc = -1; +#if (FTVERSION <= 2008) + // #88364# freetype<=2005 prefers autohinting to embedded bitmaps + // => first we have to try without hinting + if( (nLoadFlags & (FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP)) == 0 ) + { + rc = FT_Load_Glyph( maFaceFT, nGlyphIndex, nLoadFlags|FT_LOAD_NO_HINTING ); + if( (rc==FT_Err_Ok) && (maFaceFT->glyph->format!=FT_GLYPH_FORMAT_BITMAP) ) + rc = -1; // mark as "loading embedded bitmap" was unsuccessful + nLoadFlags |= FT_LOAD_NO_BITMAP; + } + + if( rc != FT_Err_Ok ) +#endif + rc = FT_Load_Glyph( maFaceFT, nGlyphIndex, nLoadFlags ); + + if( rc != FT_Err_Ok ) + { + // we get here e.g. when a PS font lacks the default glyph + rGD.SetCharWidth( 0 ); + rGD.SetDelta( 0, 0 ); + rGD.SetOffset( 0, 0 ); + rGD.SetSize( Size( 0, 0 ) ); + return; + } + + const bool bOriginallyZeroWidth = (maFaceFT->glyph->metrics.horiAdvance == 0); + if( mbArtBold && pFTEmbolden ) + (*pFTEmbolden)( maFaceFT->glyph ); + + const int nCharWidth = bOriginallyZeroWidth ? 0 : lcl_GetCharWidth( maFaceFT, mfStretch, nGlyphFlags ); + rGD.SetCharWidth( nCharWidth ); + + FT_Glyph pGlyphFT; + rc = FT_Get_Glyph( maFaceFT->glyph, &pGlyphFT ); + + ApplyGlyphTransform( nGlyphFlags, pGlyphFT, false ); + if( mbArtBold && pFTEmbolden && (nFTVERSION < 2200) ) // #i71094# workaround staircase bug + pGlyphFT->advance.y = 0; + rGD.SetDelta( (pGlyphFT->advance.x + 0x8000) >> 16, -((pGlyphFT->advance.y + 0x8000) >> 16) ); + + FT_BBox aBbox; + FT_Glyph_Get_CBox( pGlyphFT, FT_GLYPH_BBOX_PIXELS, &aBbox ); + if( aBbox.yMin > aBbox.yMax ) // circumvent freetype bug + { + int t=aBbox.yMin; aBbox.yMin=aBbox.yMax, aBbox.yMax=t; + } + + rGD.SetOffset( aBbox.xMin, -aBbox.yMax ); + rGD.SetSize( Size( (aBbox.xMax-aBbox.xMin+1), (aBbox.yMax-aBbox.yMin) ) ); + + FT_Done_Glyph( pGlyphFT ); +} + +// ----------------------------------------------------------------------- + +bool FreetypeServerFont::GetAntialiasAdvice( void ) const +{ + if( GetFontSelData().mbNonAntialiased || (mnPrioAntiAlias<=0) ) + return false; + bool bAdviseAA = true; + // TODO: also use GASP info + return bAdviseAA; +} + +// ----------------------------------------------------------------------- + +bool FreetypeServerFont::GetGlyphBitmap1( int nGlyphIndex, RawBitmap& rRawBitmap ) const +{ + if( maSizeFT ) + pFTActivateSize( maSizeFT ); + + int nGlyphFlags; + SplitGlyphFlags( *this, nGlyphIndex, nGlyphFlags ); + + FT_Int nLoadFlags = mnLoadFlags; + // #i70930# force mono-hinting for monochrome text + if( nFTVERSION >= 2110 ) //#i71947# unless it looks worse + { + nLoadFlags &= ~0xF0000; + nLoadFlags |= FT_LOAD_TARGET_MONO; + } + + if( mbArtItalic ) + nLoadFlags |= FT_LOAD_NO_BITMAP; + +#if (FTVERSION >= 2002) + // for 0/90/180/270 degree fonts enable hinting even if not advisable + // non-hinted and non-antialiased bitmaps just look too ugly + if( (mnCos==0 || mnSin==0) && (mnPrioAutoHint > 0) ) + nLoadFlags &= ~FT_LOAD_NO_HINTING; +#endif + + if( mnPrioEmbedded <= mnPrioAutoHint ) + nLoadFlags |= FT_LOAD_NO_BITMAP; + + FT_Error rc = -1; +#if (FTVERSION <= 2008) + // #88364# freetype<=2005 prefers autohinting to embedded bitmaps + // => first we have to try without hinting + if( (nLoadFlags & (FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP)) == 0 ) + { + rc = FT_Load_Glyph( maFaceFT, nGlyphIndex, nLoadFlags|FT_LOAD_NO_HINTING ); + if( (rc==FT_Err_Ok) && (maFaceFT->glyph->format != FT_GLYPH_FORMAT_BITMAP) ) + rc = -1; // mark as "loading embedded bitmap" was unsuccessful + nLoadFlags |= FT_LOAD_NO_BITMAP; + } + + if( rc != FT_Err_Ok ) +#endif + rc = FT_Load_Glyph( maFaceFT, nGlyphIndex, nLoadFlags ); + if( rc != FT_Err_Ok ) + return false; + + if( mbArtBold && pFTEmbolden ) + (*pFTEmbolden)( maFaceFT->glyph ); + + FT_Glyph pGlyphFT; + rc = FT_Get_Glyph( maFaceFT->glyph, &pGlyphFT ); + if( rc != FT_Err_Ok ) + return false; + + int nAngle = ApplyGlyphTransform( nGlyphFlags, pGlyphFT, true ); + + if( mbArtItalic ) + { + FT_Matrix aMatrix; + aMatrix.xx = aMatrix.yy = 0x10000L; + if( nFTVERSION >= 2102 ) // Freetype 2.1.2 API swapped xy with yx + aMatrix.xy = 0x6000L, aMatrix.yx = 0; + else + aMatrix.yx = 0x6000L, aMatrix.xy = 0; + FT_Glyph_Transform( pGlyphFT, &aMatrix, NULL ); + } + + // Check for zero area bounding boxes as this crashes some versions of FT. + // This also provides a handy short cut as much of the code following + // becomes an expensive nop when a glyph covers no pixels. + FT_BBox cbox; + FT_Glyph_Get_CBox(pGlyphFT, ft_glyph_bbox_unscaled, &cbox); + + if( (cbox.xMax - cbox.xMin) == 0 || (cbox.yMax - cbox.yMin == 0) ) + { + nAngle = 0; + memset(&rRawBitmap, 0, sizeof rRawBitmap); + FT_Done_Glyph( pGlyphFT ); + return true; + } + + if( pGlyphFT->format != FT_GLYPH_FORMAT_BITMAP ) + { + if( pGlyphFT->format == FT_GLYPH_FORMAT_OUTLINE ) + ((FT_OutlineGlyphRec*)pGlyphFT)->outline.flags |= FT_OUTLINE_HIGH_PRECISION; + // #i15743# freetype API 2.1.3 changed the FT_RENDER_MODE_MONO constant + FT_Render_Mode nRenderMode = (FT_Render_Mode)((nFTVERSION<2103) ? 1 : FT_RENDER_MODE_MONO); + + rc = FT_Glyph_To_Bitmap( &pGlyphFT, nRenderMode, NULL, sal_True ); + if( rc != FT_Err_Ok ) + { + FT_Done_Glyph( pGlyphFT ); + return false; + } + } + + const FT_BitmapGlyph pBmpGlyphFT = reinterpret_cast<const FT_BitmapGlyph>(pGlyphFT); + // NOTE: autohinting in FT<=2.0.2 miscalculates the offsets below by +-1 + rRawBitmap.mnXOffset = +pBmpGlyphFT->left; + rRawBitmap.mnYOffset = -pBmpGlyphFT->top; + + const FT_Bitmap& rBitmapFT = pBmpGlyphFT->bitmap; + rRawBitmap.mnHeight = rBitmapFT.rows; + rRawBitmap.mnBitCount = 1; + if( mbArtBold && !pFTEmbolden ) + { + rRawBitmap.mnWidth = rBitmapFT.width + 1; + int nLineBytes = (rRawBitmap.mnWidth + 7) >> 3; + rRawBitmap.mnScanlineSize = (nLineBytes > rBitmapFT.pitch) ? nLineBytes : rBitmapFT.pitch; + } + else + { + rRawBitmap.mnWidth = rBitmapFT.width; + rRawBitmap.mnScanlineSize = rBitmapFT.pitch; + } + + const sal_uLong nNeededSize = rRawBitmap.mnScanlineSize * rRawBitmap.mnHeight; + + if( rRawBitmap.mnAllocated < nNeededSize ) + { + delete[] rRawBitmap.mpBits; + rRawBitmap.mnAllocated = 2*nNeededSize; + rRawBitmap.mpBits = new unsigned char[ rRawBitmap.mnAllocated ]; + } + + if( !mbArtBold || pFTEmbolden ) + { + memcpy( rRawBitmap.mpBits, rBitmapFT.buffer, nNeededSize ); + } + else + { + memset( rRawBitmap.mpBits, 0, nNeededSize ); + const unsigned char* pSrcLine = rBitmapFT.buffer; + unsigned char* pDstLine = rRawBitmap.mpBits; + for( int h = rRawBitmap.mnHeight; --h >= 0; ) + { + memcpy( pDstLine, pSrcLine, rBitmapFT.pitch ); + pDstLine += rRawBitmap.mnScanlineSize; + pSrcLine += rBitmapFT.pitch; + } + + unsigned char* p = rRawBitmap.mpBits; + for( sal_uLong y=0; y < rRawBitmap.mnHeight; y++ ) + { + unsigned char nLastByte = 0; + for( sal_uLong x=0; x < rRawBitmap.mnScanlineSize; x++ ) + { + unsigned char nTmp = p[x] << 7; + p[x] |= (p[x] >> 1) | nLastByte; + nLastByte = nTmp; + } + p += rRawBitmap.mnScanlineSize; + } + } + + FT_Done_Glyph( pGlyphFT ); + + // special case for 0/90/180/270 degree orientation + switch( nAngle ) + { + case -900: + case +900: + case +1800: + case +2700: + rRawBitmap.Rotate( nAngle ); + break; + } + + return true; +} + +// ----------------------------------------------------------------------- + +bool FreetypeServerFont::GetGlyphBitmap8( int nGlyphIndex, RawBitmap& rRawBitmap ) const +{ + if( maSizeFT ) + pFTActivateSize( maSizeFT ); + + int nGlyphFlags; + SplitGlyphFlags( *this, nGlyphIndex, nGlyphFlags ); + + FT_Int nLoadFlags = mnLoadFlags; + + if( mbArtItalic ) + nLoadFlags |= FT_LOAD_NO_BITMAP; + +#if (FTVERSION <= 2004) && !defined(TT_CONFIG_OPTION_BYTECODE_INTERPRETER) + // autohinting in FT<=2.0.4 makes antialiased glyphs look worse + nLoadFlags |= FT_LOAD_NO_HINTING; +#else + if( (nGlyphFlags & GF_UNHINTED) || (mnPrioAutoHint < mnPrioAntiAlias) ) + nLoadFlags |= FT_LOAD_NO_HINTING; +#endif + + if( mnPrioEmbedded <= mnPrioAntiAlias ) + nLoadFlags |= FT_LOAD_NO_BITMAP; + + FT_Error rc = -1; +#if (FTVERSION <= 2008) + // #88364# freetype<=2005 prefers autohinting to embedded bitmaps + // => first we have to try without hinting + if( (nLoadFlags & (FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP)) == 0 ) + { + rc = FT_Load_Glyph( maFaceFT, nGlyphIndex, nLoadFlags|FT_LOAD_NO_HINTING ); + if( (rc==FT_Err_Ok) && (maFaceFT->glyph->format != FT_GLYPH_FORMAT_BITMAP) ) + rc = -1; // mark as "loading embedded bitmap" was unsuccessful + nLoadFlags |= FT_LOAD_NO_BITMAP; + } + + if( rc != FT_Err_Ok ) +#endif + rc = FT_Load_Glyph( maFaceFT, nGlyphIndex, nLoadFlags ); + + if( rc != FT_Err_Ok ) + return false; + + if( mbArtBold && pFTEmbolden ) + (*pFTEmbolden)( maFaceFT->glyph ); + + FT_Glyph pGlyphFT; + rc = FT_Get_Glyph( maFaceFT->glyph, &pGlyphFT ); + if( rc != FT_Err_Ok ) + return false; + + int nAngle = ApplyGlyphTransform( nGlyphFlags, pGlyphFT, true ); + + if( mbArtItalic ) + { + FT_Matrix aMatrix; + aMatrix.xx = aMatrix.yy = 0x10000L; + if( nFTVERSION >= 2102 ) // Freetype 2.1.2 API swapped xy with yx + aMatrix.xy = 0x6000L, aMatrix.yx = 0; + else + aMatrix.yx = 0x6000L, aMatrix.xy = 0; + FT_Glyph_Transform( pGlyphFT, &aMatrix, NULL ); + } + + if( pGlyphFT->format == FT_GLYPH_FORMAT_OUTLINE ) + ((FT_OutlineGlyph)pGlyphFT)->outline.flags |= FT_OUTLINE_HIGH_PRECISION; + + bool bEmbedded = (pGlyphFT->format == FT_GLYPH_FORMAT_BITMAP); + if( !bEmbedded ) + { + rc = FT_Glyph_To_Bitmap( &pGlyphFT, FT_RENDER_MODE_NORMAL, NULL, sal_True ); + if( rc != FT_Err_Ok ) + { + FT_Done_Glyph( pGlyphFT ); + return false; + } + } + + const FT_BitmapGlyph pBmpGlyphFT = reinterpret_cast<const FT_BitmapGlyph>(pGlyphFT); + rRawBitmap.mnXOffset = +pBmpGlyphFT->left; + rRawBitmap.mnYOffset = -pBmpGlyphFT->top; + + const FT_Bitmap& rBitmapFT = pBmpGlyphFT->bitmap; + rRawBitmap.mnHeight = rBitmapFT.rows; + rRawBitmap.mnWidth = rBitmapFT.width; + rRawBitmap.mnBitCount = 8; + rRawBitmap.mnScanlineSize = bEmbedded ? rBitmapFT.width : rBitmapFT.pitch; + if( mbArtBold && !pFTEmbolden ) + { + ++rRawBitmap.mnWidth; + ++rRawBitmap.mnScanlineSize; + } + rRawBitmap.mnScanlineSize = (rRawBitmap.mnScanlineSize + 3) & -4; + + const sal_uLong nNeededSize = rRawBitmap.mnScanlineSize * rRawBitmap.mnHeight; + if( rRawBitmap.mnAllocated < nNeededSize ) + { + delete[] rRawBitmap.mpBits; + rRawBitmap.mnAllocated = 2*nNeededSize; + rRawBitmap.mpBits = new unsigned char[ rRawBitmap.mnAllocated ]; + } + + const unsigned char* pSrc = rBitmapFT.buffer; + unsigned char* pDest = rRawBitmap.mpBits; + if( !bEmbedded ) + { + for( int y = rRawBitmap.mnHeight, x; --y >= 0 ; ) + { + for( x = 0; x < rBitmapFT.width; ++x ) + *(pDest++) = *(pSrc++); + for(; x < int(rRawBitmap.mnScanlineSize); ++x ) + *(pDest++) = 0; + } + } + else + { + for( int y = rRawBitmap.mnHeight, x; --y >= 0 ; ) + { + unsigned char nSrc = 0; + for( x = 0; x < rBitmapFT.width; ++x, nSrc+=nSrc ) + { + if( (x & 7) == 0 ) + nSrc = *(pSrc++); + *(pDest++) = (0x7F - nSrc) >> 8; + } + for(; x < int(rRawBitmap.mnScanlineSize); ++x ) + *(pDest++) = 0; + } + } + + if( mbArtBold && !pFTEmbolden ) + { + // overlay with glyph image shifted by one left pixel + unsigned char* p = rRawBitmap.mpBits; + for( sal_uLong y=0; y < rRawBitmap.mnHeight; y++ ) + { + unsigned char nLastByte = 0; + for( sal_uLong x=0; x < rRawBitmap.mnWidth; x++ ) + { + unsigned char nTmp = p[x]; + p[x] |= p[x] | nLastByte; + nLastByte = nTmp; + } + p += rRawBitmap.mnScanlineSize; + } + } + + if( !bEmbedded && mbUseGamma ) + { + unsigned char* p = rRawBitmap.mpBits; + for( sal_uLong y=0; y < rRawBitmap.mnHeight; y++ ) + { + for( sal_uLong x=0; x < rRawBitmap.mnWidth; x++ ) + { + p[x] = aGammaTable[ p[x] ]; + } + p += rRawBitmap.mnScanlineSize; + } + } + + FT_Done_Glyph( pGlyphFT ); + + // special case for 0/90/180/270 degree orientation + switch( nAngle ) + { + case -900: + case +900: + case +1800: + case +2700: + rRawBitmap.Rotate( nAngle ); + break; + } + + return true; +} + +// ----------------------------------------------------------------------- +// determine unicode ranges in font +// ----------------------------------------------------------------------- + +const ImplFontCharMap* 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; +} + +bool FreetypeServerFont::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const +{ + bool bRet = false; + + sal_uLong nLength = 0; + // load GSUB table + const FT_Byte* pGSUB = mpFontInfo->GetTable("GSUB", &nLength); + if (pGSUB) + vcl::getTTScripts(rFontCapabilities.maGSUBScriptTags, pGSUB, nLength); + + // load OS/2 table + const FT_Byte* pOS2 = mpFontInfo->GetTable("OS/2", &nLength); + if (pOS2) + { + bRet = vcl::getTTCoverage( + rFontCapabilities.maUnicodeRange, + rFontCapabilities.maCodePageRange, + pOS2, nLength); + } + + return bRet; +} + +// ----------------------------------------------------------------------- +// kerning stuff +// ----------------------------------------------------------------------- + +int 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 boost::unordered_multimap<sal_uInt16,sal_Unicode> Cmap; + Cmap aCmap; + for( sal_Unicode aChar = 0x0020; aChar < 0xFFFE; ++aChar ) + { + sal_uInt16 nGlyphIndex = GetGlyphIndex( aChar ); + if( nGlyphIndex ) + aCmap.insert( Cmap::value_type( nGlyphIndex, aChar ) ); + } + + // translate both glyph indizes in kerning pairs to characters + // problem is that these are 1:n mappings... + KernVector aKernCharVector; + aKernCharVector.reserve( nKernCount ); + KernVector::iterator it; + for( it = aKernGlyphVector.begin(); it != aKernGlyphVector.end(); ++it ) + { + FT_Vector aKernVal; + FT_Error rcFT = FT_Get_Kerning( maFaceFT, it->mnChar1, it->mnChar2, + FT_KERNING_DEFAULT, &aKernVal ); + aKernPair.mnKern = aKernVal.x >> 6; + if( (aKernPair.mnKern == 0) || (rcFT != FT_Err_Ok) ) + continue; + + typedef std::pair<Cmap::iterator,Cmap::iterator> CPair; + const CPair p1 = aCmap.equal_range( it->mnChar1 ); + const CPair p2 = aCmap.equal_range( it->mnChar2 ); + for( Cmap::const_iterator i1 = p1.first; i1 != p1.second; ++i1 ) + { + aKernPair.mnChar1 = (*i1).second; + for( Cmap::const_iterator i2 = p2.first; i2 != p2.second; ++i2 ) + { + aKernPair.mnChar2 = (*i2).second; + aKernCharVector.push_back( aKernPair ); + } + } + } + + // now move the resulting vector into VCL's ImplKernPairData[] format + nKernCount = aKernCharVector.size(); + ImplKernPairData* pTo = new ImplKernPairData[ nKernCount ]; + *ppKernPairs = pTo; + for( it = aKernCharVector.begin(); it != aKernCharVector.end(); ++it, ++pTo ) + { + pTo->mnChar1 = it->mnChar1; + pTo->mnChar2 = it->mnChar2; + pTo->mnKern = it->mnKern; + } + } + + return nKernCount; +} + +// ----------------------------------------------------------------------- +// outline stuff +// ----------------------------------------------------------------------- + +class PolyArgs +{ +public: + PolyArgs( PolyPolygon& rPolyPoly, sal_uInt16 nMaxPoints ); + ~PolyArgs(); + + void AddPoint( long nX, long nY, PolyFlags); + void ClosePolygon(); + + long GetPosX() const { return maPosition.x;} + long GetPosY() const { return maPosition.y;} + +private: + PolyPolygon& mrPolyPoly; + + Point* mpPointAry; + sal_uInt8* mpFlagAry; + + FT_Vector maPosition; + sal_uInt16 mnMaxPoints; + sal_uInt16 mnPoints; + sal_uInt16 mnPoly; + long mnHeight; + bool bHasOffline; +}; + +// ----------------------------------------------------------------------- + +PolyArgs::PolyArgs( PolyPolygon& rPolyPoly, sal_uInt16 nMaxPoints ) +: mrPolyPoly(rPolyPoly), + mnMaxPoints(nMaxPoints), + mnPoints(0), + mnPoly(0), + mnHeight(0), + bHasOffline(false) +{ + mpPointAry = new Point[ mnMaxPoints ]; + mpFlagAry = new sal_uInt8 [ mnMaxPoints ]; +} + +// ----------------------------------------------------------------------- + + +PolyArgs::~PolyArgs() +{ + delete[] mpFlagAry; + delete[] mpPointAry; +} + +// ----------------------------------------------------------------------- + +void PolyArgs::AddPoint( long nX, long nY, PolyFlags aFlag ) +{ + DBG_ASSERT( (mnPoints < mnMaxPoints), "FTGlyphOutline: AddPoint overflow!" ); + if( mnPoints >= mnMaxPoints ) + return; + + maPosition.x = nX; + maPosition.y = nY; + mpPointAry[ mnPoints ] = Point( nX, nY ); + mpFlagAry[ mnPoints++ ]= aFlag; + bHasOffline |= (aFlag != POLY_NORMAL); +} + +// ----------------------------------------------------------------------- + +void PolyArgs::ClosePolygon() +{ + if( !mnPoly++ ) + return; + + // freetype seems to always close the polygon with an ON_CURVE point + // PolyPoly wants to close the polygon itself => remove last point + DBG_ASSERT( (mnPoints >= 2), "FTGlyphOutline: PolyFinishNum failed!" ); + --mnPoints; + DBG_ASSERT( (mpPointAry[0]==mpPointAry[mnPoints]), "FTGlyphOutline: PolyFinishEq failed!" ); + DBG_ASSERT( (mpFlagAry[0]==POLY_NORMAL), "FTGlyphOutline: PolyFinishFE failed!" ); + DBG_ASSERT( (mpFlagAry[mnPoints]==POLY_NORMAL), "FTGlyphOutline: PolyFinishFS failed!" ); + + Polygon aPoly( mnPoints, mpPointAry, (bHasOffline ? mpFlagAry : NULL) ); + + // #i35928# + // This may be a invalid polygons, e.g. the last point is a control point. + // So close the polygon (and add the first point again) if the last point + // is a control point or different from first. + // #i48298# + // Now really duplicating the first point, to close or correct the + // polygon. Also no longer duplicating the flags, but enforcing + // POLY_NORMAL for the newly added last point. + const sal_uInt16 nPolySize(aPoly.GetSize()); + if(nPolySize) + { + if((aPoly.HasFlags() && POLY_CONTROL == aPoly.GetFlags(nPolySize - 1)) + || (aPoly.GetPoint(nPolySize - 1) != aPoly.GetPoint(0))) + { + aPoly.SetSize(nPolySize + 1); + aPoly.SetPoint(aPoly.GetPoint(0), nPolySize); + + if(aPoly.HasFlags()) + { + aPoly.SetFlags(nPolySize, POLY_NORMAL); + } + } + } + + mrPolyPoly.Insert( aPoly ); + mnPoints = 0; + bHasOffline = false; +} + +// ----------------------------------------------------------------------- + +extern "C" { + +// TODO: wait till all compilers accept that calling conventions +// for functions are the same independent of implementation constness, +// then uncomment the const-tokens in the function interfaces below +static int FT_move_to( FT_Vector_CPtr p0, void* vpPolyArgs ) +{ + PolyArgs& rA = *reinterpret_cast<PolyArgs*>(vpPolyArgs); + + // move_to implies a new polygon => finish old polygon first + rA.ClosePolygon(); + + rA.AddPoint( p0->x, p0->y, POLY_NORMAL ); + return 0; +} + +static int FT_line_to( FT_Vector_CPtr p1, void* vpPolyArgs ) +{ + PolyArgs& rA = *reinterpret_cast<PolyArgs*>(vpPolyArgs); + rA.AddPoint( p1->x, p1->y, POLY_NORMAL ); + return 0; +} + +static int FT_conic_to( FT_Vector_CPtr p1, FT_Vector_CPtr p2, void* vpPolyArgs ) +{ + PolyArgs& rA = *reinterpret_cast<PolyArgs*>(vpPolyArgs); + + // VCL's Polygon only knows cubic beziers + const long nX1 = (2 * rA.GetPosX() + 4 * p1->x + 3) / 6; + const long nY1 = (2 * rA.GetPosY() + 4 * p1->y + 3) / 6; + rA.AddPoint( nX1, nY1, POLY_CONTROL ); + + const long nX2 = (2 * p2->x + 4 * p1->x + 3) / 6; + const long nY2 = (2 * p2->y + 4 * p1->y + 3) / 6; + rA.AddPoint( nX2, nY2, POLY_CONTROL ); + + rA.AddPoint( p2->x, p2->y, POLY_NORMAL ); + return 0; +} + +static int FT_cubic_to( FT_Vector_CPtr p1, FT_Vector_CPtr p2, FT_Vector_CPtr p3, void* vpPolyArgs ) +{ + PolyArgs& rA = *reinterpret_cast<PolyArgs*>(vpPolyArgs); + rA.AddPoint( p1->x, p1->y, POLY_CONTROL ); + rA.AddPoint( p2->x, p2->y, POLY_CONTROL ); + rA.AddPoint( p3->x, p3->y, POLY_NORMAL ); + return 0; +} + +} // extern "C" + +// ----------------------------------------------------------------------- + +bool FreetypeServerFont::GetGlyphOutline( int nGlyphIndex, + ::basegfx::B2DPolyPolygon& rB2DPolyPoly ) const +{ + if( maSizeFT ) + pFTActivateSize( maSizeFT ); + + rB2DPolyPoly.clear(); + + int nGlyphFlags; + SplitGlyphFlags( *this, nGlyphIndex, nGlyphFlags ); + + FT_Int nLoadFlags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_TRANSFORM; + +#ifdef FT_LOAD_TARGET_LIGHT + // enable "light hinting" if available + if( nFTVERSION >= 2103 ) + nLoadFlags |= FT_LOAD_TARGET_LIGHT; +#endif + + FT_Error rc = FT_Load_Glyph( maFaceFT, nGlyphIndex, nLoadFlags ); + if( rc != FT_Err_Ok ) + return false; + + if( mbArtBold && pFTEmbolden ) + (*pFTEmbolden)( maFaceFT->glyph ); + + FT_Glyph pGlyphFT; + rc = FT_Get_Glyph( maFaceFT->glyph, &pGlyphFT ); + if( rc != FT_Err_Ok ) + return false; + + if( pGlyphFT->format != FT_GLYPH_FORMAT_OUTLINE ) + return false; + + if( mbArtItalic ) + { + FT_Matrix aMatrix; + aMatrix.xx = aMatrix.yy = 0x10000L; + if( nFTVERSION >= 2102 ) // Freetype 2.1.2 API swapped xy with yx + aMatrix.xy = 0x6000L, aMatrix.yx = 0; + else + aMatrix.yx = 0x6000L, aMatrix.xy = 0; + FT_Glyph_Transform( pGlyphFT, &aMatrix, NULL ); + } + + FT_Outline& rOutline = reinterpret_cast<FT_OutlineGlyphRec*>(pGlyphFT)->outline; + if( !rOutline.n_points ) // blank glyphs are ok + return true; + + long nMaxPoints = 1 + rOutline.n_points * 3; + PolyPolygon aToolPolyPolygon; + PolyArgs aPolyArg( aToolPolyPolygon, nMaxPoints ); + + /*int nAngle =*/ ApplyGlyphTransform( nGlyphFlags, pGlyphFT, false ); + + FT_Outline_Funcs aFuncs; + aFuncs.move_to = &FT_move_to; + aFuncs.line_to = &FT_line_to; + aFuncs.conic_to = &FT_conic_to; + aFuncs.cubic_to = &FT_cubic_to; + aFuncs.shift = 0; + aFuncs.delta = 0; + rc = FT_Outline_Decompose( &rOutline, &aFuncs, (void*)&aPolyArg ); + aPolyArg.ClosePolygon(); // close last polygon + FT_Done_Glyph( pGlyphFT ); + + // convert to basegfx polypolygon + // TODO: get rid of the intermediate tools polypolygon + rB2DPolyPoly = aToolPolyPolygon.getB2DPolyPolygon(); + rB2DPolyPoly.transform(basegfx::tools::createScaleB2DHomMatrix( +1.0/(1<<6), -1.0/(1<<6) )); + + return true; +} + +// ----------------------------------------------------------------------- + +bool 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; + 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; +} + +// ======================================================================= + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/glyphs/gcach_ftyp.hxx b/vcl/source/glyphs/gcach_ftyp.hxx new file mode 100644 index 000000000000..6b461fe59143 --- /dev/null +++ b/vcl/source/glyphs/gcach_ftyp.hxx @@ -0,0 +1,280 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef _SV_GCACHFTYP_HXX +#define _SV_GCACHFTYP_HXX + +#include <vcl/glyphcache.hxx> +#include <rtl/textcvt.h> + +#include <ft2build.h> +#include FT_FREETYPE_H + +class FreetypeServerFont; +#ifdef ENABLE_GRAPHITE +class GraphiteFaceWrapper; +#endif + +struct FT_GlyphRec_; + +// ----------------------------------------------------------------------- + +// FtFontFile has the responsibility that a font file is only mapped once. +// (#86621#) the old directly ft-managed solution caused it to be mapped +// in up to nTTC*nSizes*nOrientation*nSynthetic times +class FtFontFile +{ +public: + static FtFontFile* FindFontFile( const ::rtl::OString& rNativeFileName ); + + bool Map(); + void Unmap(); + + const unsigned char* GetBuffer() const { return mpFileMap; } + int GetFileSize() const { return mnFileSize; } + const ::rtl::OString* GetFileName() const { return &maNativeFileName; } + int GetLangBoost() const { return mnLangBoost; } + +private: + FtFontFile( const ::rtl::OString& rNativeFileName ); + + const ::rtl::OString maNativeFileName; + const unsigned char* mpFileMap; + int mnFileSize; + int mnRefCount; + int mnLangBoost; +}; + +// ----------------------------------------------------------------------- + +// FtFontInfo corresponds to an unscaled font face +class FtFontInfo +{ +public: + FtFontInfo( const ImplDevFontAttributes&, + const ::rtl::OString& rNativeFileName, + int nFaceNum, sal_IntPtr nFontId, int nSynthetic, + const ExtraKernInfo* ); + ~FtFontInfo(); + + const unsigned char* GetTable( const char*, sal_uLong* pLength=0 ) const; + + FT_FaceRec_* GetFaceFT(); +#ifdef ENABLE_GRAPHITE + GraphiteFaceWrapper* GetGraphiteFace(); +#endif + void ReleaseFaceFT( FT_FaceRec_* ); + + const ::rtl::OString* GetFontFileName() const { return mpFontFile->GetFileName(); } + int GetFaceNum() const { return mnFaceNum; } + int GetSynthetic() const { return mnSynthetic; } + sal_IntPtr GetFontId() const { return mnFontId; } + bool IsSymbolFont() const { return maDevFontAttributes.IsSymbolFont(); } + const ImplFontAttributes& GetFontAttributes() const { return maDevFontAttributes; } + + void AnnounceFont( ImplDevFontList* ); + + int GetGlyphIndex( sal_UCS4 cChar ) const; + void CacheGlyphIndex( sal_UCS4 cChar, int nGI ) const; + + bool GetFontCodeRanges( CmapResult& ) const; + const ImplFontCharMap* GetImplFontCharMap( void ); + + bool HasExtraKerning() const; + int GetExtraKernPairs( ImplKernPairData** ) const; + int GetExtraGlyphKernValue( int nLeftGlyph, int nRightGlyph ) const; + +private: + FT_FaceRec_* maFaceFT; + FtFontFile* mpFontFile; + const int mnFaceNum; + int mnRefCount; + const int mnSynthetic; +#ifdef ENABLE_GRAPHITE + bool mbCheckedGraphite; + GraphiteFaceWrapper * mpGraphiteFace; +#endif + sal_IntPtr mnFontId; + ImplDevFontAttributes maDevFontAttributes; + + const ImplFontCharMap* mpFontCharMap; + + // cache unicode->glyphid mapping because looking it up is expensive + // TODO: change to boost::unordered_multimap when a use case requires a m:n mapping + typedef ::boost::unordered_map<int,int> Int2IntMap; + mutable Int2IntMap* mpChar2Glyph; + mutable Int2IntMap* mpGlyph2Char; + void InitHashes() const; + + const ExtraKernInfo* mpExtraKernInfo; +}; + +// these two inlines are very important for performance + +inline int FtFontInfo::GetGlyphIndex( sal_UCS4 cChar ) const +{ + if( !mpChar2Glyph ) + return -1; + Int2IntMap::const_iterator it = mpChar2Glyph->find( cChar ); + if( it == mpChar2Glyph->end() ) + return -1; + return it->second; +} + +inline void FtFontInfo::CacheGlyphIndex( sal_UCS4 cChar, int nIndex ) const +{ + if( !mpChar2Glyph ) + InitHashes(); + (*mpChar2Glyph)[ cChar ] = nIndex; + (*mpGlyph2Char)[ nIndex ] = cChar; +} + +// ----------------------------------------------------------------------- + +class FreetypeManager +{ +public: + FreetypeManager(); + ~FreetypeManager(); + + long AddFontDir( const String& rUrlName ); + void AddFontFile( const rtl::OString& rNormalizedName, + int nFaceNum, sal_IntPtr nFontId, const ImplDevFontAttributes&, + const ExtraKernInfo* ); + void AnnounceFonts( ImplDevFontList* ) const; + void ClearFontList(); + + FreetypeServerFont* CreateFont( const ImplFontSelectData& ); + +private: + typedef ::boost::unordered_map<sal_IntPtr,FtFontInfo*> FontList; + FontList maFontList; + + sal_IntPtr mnMaxFontId; + sal_IntPtr mnNextFontId; +}; + +// ----------------------------------------------------------------------- + +class FreetypeServerFont : public ServerFont +{ +public: + FreetypeServerFont( const ImplFontSelectData&, FtFontInfo* ); + virtual ~FreetypeServerFont(); + + virtual const ::rtl::OString* GetFontFileName() const { return mpFontInfo->GetFontFileName(); } + virtual int GetFontFaceNum() const { return mpFontInfo->GetFaceNum(); } + virtual bool TestFont() const; + virtual void* GetFtFace() const; + virtual void SetFontOptions( boost::shared_ptr<ImplFontOptions> ); + virtual boost::shared_ptr<ImplFontOptions> GetFontOptions() const; + virtual int GetLoadFlags() const { return (mnLoadFlags & ~FT_LOAD_IGNORE_TRANSFORM); } + virtual bool NeedsArtificialBold() const { return mbArtBold; } + virtual bool NeedsArtificialItalic() const { return mbArtItalic; } + + virtual void FetchFontMetric( ImplFontMetricData&, long& rFactor ) const; + virtual const ImplFontCharMap* GetImplFontCharMap( void ) const; + + virtual int GetGlyphIndex( sal_UCS4 ) const; + int GetRawGlyphIndex( sal_UCS4 ) const; + int FixupGlyphIndex( int nGlyphIndex, sal_UCS4 ) const; + + virtual bool GetAntialiasAdvice( void ) const; + virtual bool GetGlyphBitmap1( int nGlyphIndex, RawBitmap& ) const; + virtual bool GetGlyphBitmap8( int nGlyphIndex, RawBitmap& ) const; + virtual bool GetGlyphOutline( int nGlyphIndex, ::basegfx::B2DPolyPolygon& ) const; + virtual int GetGlyphKernValue( int nLeftGlyph, int nRightGlyph ) const; + virtual sal_uLong GetKernPairs( ImplKernPairData** ) const; + + const unsigned char* GetTable( const char* pName, sal_uLong* pLength ) + { return mpFontInfo->GetTable( pName, pLength ); } + int GetEmUnits() const; + const FT_Size_Metrics& GetMetricsFT() const { return maSizeFT->metrics; } +#ifdef ENABLE_GRAPHITE + GraphiteFaceWrapper* GetGraphiteFace() const { return mpFontInfo->GetGraphiteFace(); } +#endif + +protected: + friend class GlyphCache; + + int ApplyGlyphTransform( int nGlyphFlags, FT_GlyphRec_*, bool ) const; + virtual void InitGlyphData( int nGlyphIndex, GlyphData& ) const; + virtual bool GetFontCapabilities(vcl::FontCapabilities &) const; + bool ApplyGSUB( const ImplFontSelectData& ); + virtual ServerFontLayoutEngine* GetLayoutEngine(); + +private: + int mnWidth; + int mnPrioEmbedded; + int mnPrioAntiAlias; + int mnPrioAutoHint; + FtFontInfo* mpFontInfo; + FT_Int mnLoadFlags; + double mfStretch; + FT_FaceRec_* maFaceFT; + FT_SizeRec_* maSizeFT; + + boost::shared_ptr<ImplFontOptions> mpFontOptions; + + bool mbFaceOk; + bool mbArtItalic; + bool mbArtBold; + bool mbUseGamma; + + typedef ::boost::unordered_map<int,int> GlyphSubstitution; + GlyphSubstitution maGlyphSubstitution; + rtl_UnicodeToTextConverter maRecodeConverter; + + ServerFontLayoutEngine* mpLayoutEngine; +}; + +// ----------------------------------------------------------------------- + +class ImplFTSFontData : public ImplFontData +{ +private: + FtFontInfo* mpFtFontInfo; + enum { IFTSFONT_MAGIC = 0x1F150A1C }; + +public: + ImplFTSFontData( FtFontInfo*, const ImplDevFontAttributes& ); + + FtFontInfo* GetFtFontInfo() const { return mpFtFontInfo; } + + virtual ImplFontEntry* CreateFontInstance( ImplFontSelectData& ) const; + virtual ImplFontData* Clone() const { return new ImplFTSFontData( *this ); } + virtual sal_IntPtr GetFontId() const { return mpFtFontInfo->GetFontId(); } + + static bool CheckFontData( const ImplFontData& r ) { return r.CheckMagic( IFTSFONT_MAGIC ); } +}; + +// ----------------------------------------------------------------------- + +#endif // _SV_GCACHFTYP_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/glyphs/gcach_layout.cxx b/vcl/source/glyphs/gcach_layout.cxx new file mode 100644 index 000000000000..63a332869664 --- /dev/null +++ b/vcl/source/glyphs/gcach_layout.cxx @@ -0,0 +1,669 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +#define ENABLE_ICU_LAYOUT +#include <gcach_ftyp.hxx> +#include <vcl/sallayout.hxx> +#include <vcl/salgdi.hxx> + +#include <vcl/svapp.hxx> + +#include <sal/alloca.h> + +#if OSL_DEBUG_LEVEL > 1 +#include <cstdio> +#endif +#include <rtl/instance.hxx> + +namespace { struct SimpleLayoutEngine : public rtl::Static< ServerFontLayoutEngine, SimpleLayoutEngine > {}; } + +// ======================================================================= +// layout implementation for ServerFont +// ======================================================================= + +ServerFontLayout::ServerFontLayout( ServerFont& rFont ) +: mrServerFont( rFont ) +{} + +void ServerFontLayout::DrawText( SalGraphics& rSalGraphics ) const +{ + rSalGraphics.DrawServerFontLayout( *this ); +} + +// ----------------------------------------------------------------------- + +bool ServerFontLayout::LayoutText( ImplLayoutArgs& rArgs ) +{ + ServerFontLayoutEngine* pLE = NULL; + if( !(rArgs.mnFlags & SAL_LAYOUT_COMPLEX_DISABLED) ) + pLE = mrServerFont.GetLayoutEngine(); + if( !pLE ) + pLE = &SimpleLayoutEngine::get(); + + bool bRet = (*pLE)( *this, rArgs ); + return bRet; +} + +// ----------------------------------------------------------------------- + +void ServerFontLayout::AdjustLayout( ImplLayoutArgs& rArgs ) +{ + GenericSalLayout::AdjustLayout( rArgs ); + + // apply asian kerning if the glyphs are not already formatted + if( (rArgs.mnFlags & SAL_LAYOUT_KERNING_ASIAN) + && !(rArgs.mnFlags & SAL_LAYOUT_VERTICAL) ) + if( (rArgs.mpDXArray != NULL) || (rArgs.mnLayoutWidth != 0) ) + ApplyAsianKerning( rArgs.mpStr, rArgs.mnLength ); + + // insert kashidas where requested by the formatting array + if( (rArgs.mnFlags & SAL_LAYOUT_KASHIDA_JUSTIFICATON) && rArgs.mpDXArray ) + { + int nKashidaIndex = mrServerFont.GetGlyphIndex( 0x0640 ); + if( nKashidaIndex != 0 ) + { + const GlyphMetric& rGM = mrServerFont.GetGlyphMetric( nKashidaIndex ); + KashidaJustify( nKashidaIndex, rGM.GetCharWidth() ); + // TODO: kashida-GSUB/GPOS + } + } +} + +// ======================================================================= + +bool ServerFontLayoutEngine::operator()( ServerFontLayout& rLayout, ImplLayoutArgs& rArgs ) +{ + FreetypeServerFont& rFont = static_cast<FreetypeServerFont&>(rLayout.GetServerFont()); + + Point aNewPos( 0, 0 ); + int nOldGlyphId = -1; + int nGlyphWidth = 0; + GlyphItem aPrevItem; + bool bRightToLeft; + for( int nCharPos = -1; rArgs.GetNextPos( &nCharPos, &bRightToLeft ); ) + { + sal_UCS4 cChar = rArgs.mpStr[ nCharPos ]; + if( (cChar >= 0xD800) && (cChar <= 0xDFFF) ) + { + if( cChar >= 0xDC00 ) // this part of a surrogate pair was already processed + continue; + cChar = 0x10000 + ((cChar - 0xD800) << 10) + + (rArgs.mpStr[ nCharPos+1 ] - 0xDC00); + } + + if( bRightToLeft ) + cChar = GetMirroredChar( cChar ); + int nGlyphIndex = rFont.GetGlyphIndex( cChar ); + // when glyph fallback is needed update LayoutArgs + if( !nGlyphIndex ) { + rArgs.NeedFallback( nCharPos, bRightToLeft ); + if( cChar >= 0x10000 ) // handle surrogate pairs + rArgs.NeedFallback( nCharPos+1, bRightToLeft ); + } + + // apply pair kerning to prev glyph if requested + if( SAL_LAYOUT_KERNING_PAIRS & rArgs.mnFlags ) + { + int nKernValue = rFont.GetGlyphKernValue( nOldGlyphId, nGlyphIndex ); + nGlyphWidth += nKernValue; + aPrevItem.mnNewWidth = nGlyphWidth; + } + + // finish previous glyph + if( nOldGlyphId >= 0 ) + rLayout.AppendGlyph( aPrevItem ); + aNewPos.X() += nGlyphWidth; + + // prepare GlyphItem for appending it in next round + nOldGlyphId = nGlyphIndex; + const GlyphMetric& rGM = rFont.GetGlyphMetric( nGlyphIndex ); + nGlyphWidth = rGM.GetCharWidth(); + int nGlyphFlags = bRightToLeft ? GlyphItem::IS_RTL_GLYPH : 0; + aPrevItem = GlyphItem( nCharPos, nGlyphIndex, aNewPos, nGlyphFlags, nGlyphWidth ); + } + + // append last glyph item if any + if( nOldGlyphId >= 0 ) + rLayout.AppendGlyph( aPrevItem ); + + return true; +} + +// ======================================================================= +// bridge to ICU LayoutEngine +// ======================================================================= + +#ifdef ENABLE_ICU_LAYOUT + +#define bool_t signed char + +// disable warnings in icu layout headers +#if defined __SUNPRO_CC +#pragma disable_warn +#endif + +#include <layout/LayoutEngine.h> +#include <layout/LEFontInstance.h> +#include <layout/LEScripts.h> + +// enable warnings again +#if defined __SUNPRO_CC +#pragma enable_warn +#endif + +#include <unicode/uscript.h> +#include <unicode/ubidi.h> + +using namespace U_ICU_NAMESPACE; + +static const LEGlyphID ICU_DELETED_GLYPH = 0xFFFF; +static const LEGlyphID ICU_MARKED_GLYPH = 0xFFFE; + +// ----------------------------------------------------------------------- + +class IcuFontFromServerFont +: public LEFontInstance +{ +private: + FreetypeServerFont& mrServerFont; + +public: + IcuFontFromServerFont( FreetypeServerFont& rFont ) + : mrServerFont( rFont ) + {} + + virtual const void* getFontTable(LETag tableTag) const; + virtual le_int32 getUnitsPerEM() const; + virtual float getXPixelsPerEm() const; + virtual float getYPixelsPerEm() const; + virtual float getScaleFactorX() const; + virtual float getScaleFactorY() const; + + using LEFontInstance::mapCharToGlyph; + virtual LEGlyphID mapCharToGlyph( LEUnicode32 ch ) const; + + virtual le_int32 getAscent() const; + virtual le_int32 getDescent() const; + virtual le_int32 getLeading() const; + + virtual void getGlyphAdvance( LEGlyphID glyph, LEPoint &advance ) const; + virtual le_bool getGlyphPoint( LEGlyphID glyph, le_int32 pointNumber, LEPoint& point ) const; +}; + +// ----------------------------------------------------------------------- + +const void* IcuFontFromServerFont::getFontTable( LETag nICUTableTag ) const +{ + char pTagName[5]; + pTagName[0] = (char)(nICUTableTag >> 24); + pTagName[1] = (char)(nICUTableTag >> 16); + pTagName[2] = (char)(nICUTableTag >> 8); + pTagName[3] = (char)(nICUTableTag); + pTagName[4] = 0; + + sal_uLong nLength; + const unsigned char* pBuffer = mrServerFont.GetTable( pTagName, &nLength ); +#ifdef VERBOSE_DEBUG + fprintf(stderr,"IcuGetTable(\"%s\") => %p\n", pTagName, pBuffer); + int mnHeight = mrServerFont.GetFontSelData().mnHeight; + const char* pName = mrServerFont.GetFontFileName()->getStr(); + fprintf(stderr,"font( h=%d, \"%s\" )\n", mnHeight, pName ); +#endif + return (const void*)pBuffer; +} + +// ----------------------------------------------------------------------- + +le_int32 IcuFontFromServerFont::getUnitsPerEM() const +{ + return mrServerFont.GetEmUnits(); +} + +// ----------------------------------------------------------------------- + +float IcuFontFromServerFont::getXPixelsPerEm() const +{ + const ImplFontSelectData& r = mrServerFont.GetFontSelData(); + float fX = r.mnWidth ? r.mnWidth : r.mnHeight; + return fX; +} + +// ----------------------------------------------------------------------- + +float IcuFontFromServerFont::getYPixelsPerEm() const +{ + float fY = mrServerFont.GetFontSelData().mnHeight; + return fY; +} + +// ----------------------------------------------------------------------- + +float IcuFontFromServerFont::getScaleFactorX() const +{ + return 1.0; +} + +// ----------------------------------------------------------------------- + +float IcuFontFromServerFont::getScaleFactorY() const +{ + return 1.0; +} + +// ----------------------------------------------------------------------- + +LEGlyphID IcuFontFromServerFont::mapCharToGlyph( LEUnicode32 ch ) const +{ + LEGlyphID nGlyphIndex = mrServerFont.GetRawGlyphIndex( ch ); + return nGlyphIndex; +} + +// ----------------------------------------------------------------------- + +le_int32 IcuFontFromServerFont::getAscent() const +{ + const FT_Size_Metrics& rMetrics = mrServerFont.GetMetricsFT(); + le_int32 nAscent = (+rMetrics.ascender + 32) >> 6; + return nAscent; +} + +// ----------------------------------------------------------------------- + +le_int32 IcuFontFromServerFont::getDescent() const +{ + const FT_Size_Metrics& rMetrics = mrServerFont.GetMetricsFT(); + le_int32 nDescent = (-rMetrics.descender + 32) >> 6; + return nDescent; +} + +// ----------------------------------------------------------------------- + +le_int32 IcuFontFromServerFont::getLeading() const +{ + const FT_Size_Metrics& rMetrics = mrServerFont.GetMetricsFT(); + le_int32 nLeading = ((rMetrics.height - rMetrics.ascender + rMetrics.descender) + 32) >> 6; + return nLeading; +} + +// ----------------------------------------------------------------------- + +void IcuFontFromServerFont::getGlyphAdvance( LEGlyphID nGlyphIndex, + LEPoint &advance ) const +{ + if( (nGlyphIndex == ICU_MARKED_GLYPH) + || (nGlyphIndex == ICU_DELETED_GLYPH) ) + { + // deleted glyph or mark glyph has not advance + advance.fX = 0; + } + else + { + const GlyphMetric& rGM = mrServerFont.GetGlyphMetric( nGlyphIndex ); + advance.fX = rGM.GetCharWidth(); + } + + advance.fY = 0; +} + +// ----------------------------------------------------------------------- + +le_bool IcuFontFromServerFont::getGlyphPoint( LEGlyphID, + le_int32 +#if OSL_DEBUG_LEVEL > 1 +pointNumber +#endif + , + LEPoint& ) const +{ + //TODO: replace dummy implementation +#if OSL_DEBUG_LEVEL > 1 + fprintf(stderr,"getGlyphPoint(%d)\n", pointNumber ); +#endif + return false; +} + +// ======================================================================= + +class IcuLayoutEngine : public ServerFontLayoutEngine +{ +private: + IcuFontFromServerFont maIcuFont; + + le_int32 meScriptCode; + LayoutEngine* mpIcuLE; + +public: + IcuLayoutEngine( FreetypeServerFont& ); + virtual ~IcuLayoutEngine(); + + virtual bool operator()( ServerFontLayout&, ImplLayoutArgs& ); +}; + +// ----------------------------------------------------------------------- + +IcuLayoutEngine::IcuLayoutEngine( FreetypeServerFont& rServerFont ) +: maIcuFont( rServerFont ), + meScriptCode( USCRIPT_INVALID_CODE ), + mpIcuLE( NULL ) +{} + +// ----------------------------------------------------------------------- + +IcuLayoutEngine::~IcuLayoutEngine() +{ + if( mpIcuLE ) + delete mpIcuLE; +} + +// ----------------------------------------------------------------------- + +static bool lcl_CharIsJoiner(sal_Unicode cChar) +{ + return ((cChar == 0x200C) || (cChar == 0x200D)); +} + +//See https://bugs.freedesktop.org/show_bug.cgi?id=31016 +#define ARABIC_BANDAID + +bool IcuLayoutEngine::operator()( ServerFontLayout& rLayout, ImplLayoutArgs& rArgs ) +{ + LEUnicode* pIcuChars; + if( sizeof(LEUnicode) == sizeof(*rArgs.mpStr) ) + pIcuChars = (LEUnicode*)rArgs.mpStr; + else + { + // this conversion will only be needed when either + // ICU's or OOo's unicodes stop being unsigned shorts + // TODO: watch out for surrogates! + pIcuChars = (LEUnicode*)alloca( rArgs.mnLength * sizeof(LEUnicode) ); + for( xub_StrLen ic = 0; ic < rArgs.mnLength; ++ic ) + pIcuChars[ic] = static_cast<LEUnicode>( rArgs.mpStr[ic] ); + } + + // allocate temporary arrays, note: round to even + int nGlyphCapacity = (3 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos ) | 15) + 1; + + struct IcuPosition{ float fX, fY; }; + const int nAllocSize = sizeof(LEGlyphID) + sizeof(le_int32) + sizeof(IcuPosition); + LEGlyphID* pIcuGlyphs = (LEGlyphID*)alloca( (nGlyphCapacity * nAllocSize) + sizeof(IcuPosition) ); + le_int32* pCharIndices = (le_int32*)((char*)pIcuGlyphs + nGlyphCapacity * sizeof(LEGlyphID) ); + IcuPosition* pGlyphPositions = (IcuPosition*)((char*)pCharIndices + nGlyphCapacity * sizeof(le_int32) ); + + FreetypeServerFont& rFont = reinterpret_cast<FreetypeServerFont&>(rLayout.GetServerFont()); + + UErrorCode rcI18n = U_ZERO_ERROR; + LEErrorCode rcIcu = LE_NO_ERROR; + Point aNewPos( 0, 0 ); + for( int nGlyphCount = 0;; ) + { + int nMinRunPos, nEndRunPos; + bool bRightToLeft; + if( !rArgs.GetNextRun( &nMinRunPos, &nEndRunPos, &bRightToLeft ) ) + break; + + // find matching script + // TODO: split up bidi run into script runs + le_int32 eScriptCode = -1; + for( int i = nMinRunPos; i < nEndRunPos; ++i ) + { + eScriptCode = uscript_getScript( pIcuChars[i], &rcI18n ); + if( (eScriptCode > 0) && (eScriptCode != latnScriptCode) ) + break; + } + if( eScriptCode < 0 ) // TODO: handle errors better + eScriptCode = latnScriptCode; + + // get layout engine matching to this script + // no engine change necessary if script is latin + if( !mpIcuLE || ((eScriptCode != meScriptCode) && (eScriptCode > USCRIPT_INHERITED)) ) + { + // TODO: cache multiple layout engines when multiple scripts are used + delete mpIcuLE; + meScriptCode = eScriptCode; + le_int32 eLangCode = 0; // TODO: get better value + mpIcuLE = LayoutEngine::layoutEngineFactory( &maIcuFont, eScriptCode, eLangCode, rcIcu ); + if( LE_FAILURE(rcIcu) ) + { + delete mpIcuLE; + mpIcuLE = NULL; + } + } + + // fall back to default layout if needed + if( !mpIcuLE ) + break; + + // run ICU layout engine + // TODO: get enough context, remove extra glyps below + int nRawRunGlyphCount = mpIcuLE->layoutChars( pIcuChars, + nMinRunPos, nEndRunPos - nMinRunPos, rArgs.mnLength, + bRightToLeft, aNewPos.X(), aNewPos.Y(), rcIcu ); + if( LE_FAILURE(rcIcu) ) + return false; + + // import layout info from icu + mpIcuLE->getGlyphs( pIcuGlyphs, rcIcu ); + mpIcuLE->getCharIndices( pCharIndices, rcIcu ); + mpIcuLE->getGlyphPositions( &pGlyphPositions->fX, rcIcu ); + mpIcuLE->reset(); // TODO: get rid of this, PROBLEM: crash at exit when removed + if( LE_FAILURE(rcIcu) ) + return false; + + // layout bidi/script runs and export them to a ServerFontLayout + // convert results to GlyphItems + int nLastCharPos = -1; + int nClusterMinPos = -1; + int nClusterMaxPos = -1; + bool bClusterStart = true; + int nFilteredRunGlyphCount = 0; + const IcuPosition* pPos = pGlyphPositions; + for( int i = 0; i < nRawRunGlyphCount; ++i, ++pPos ) + { + LEGlyphID nGlyphIndex = pIcuGlyphs[i]; + // ignore glyphs which were marked or deleted by ICU + if( (nGlyphIndex == ICU_MARKED_GLYPH) + || (nGlyphIndex == ICU_DELETED_GLYPH) ) + continue; + + // adjust the relative char pos + int nCharPos = pCharIndices[i]; + if( nCharPos >= 0 ) { + nCharPos += nMinRunPos; + // ICU seems to return bad pCharIndices + // for some combinations of ICU+font+text + // => better give up now than crash later + if( nCharPos >= nEndRunPos ) + continue; + } + + // if needed request glyph fallback by updating LayoutArgs + if( !nGlyphIndex ) + { + if( nCharPos >= 0 ) + { + rArgs.NeedFallback( nCharPos, bRightToLeft ); + if ( (nCharPos > 0) && lcl_CharIsJoiner(rArgs.mpStr[nCharPos-1]) ) + rArgs.NeedFallback( nCharPos-1, bRightToLeft ); + else if ( (nCharPos + 1 < nEndRunPos) && lcl_CharIsJoiner(rArgs.mpStr[nCharPos+1]) ) + rArgs.NeedFallback( nCharPos+1, bRightToLeft ); + } + + if( SAL_LAYOUT_FOR_FALLBACK & rArgs.mnFlags ) + continue; + } + + + // apply vertical flags, etc. + bool bDiacritic = false; + if( nCharPos >= 0 ) + { + sal_UCS4 aChar = rArgs.mpStr[ nCharPos ]; + nGlyphIndex = rFont.FixupGlyphIndex( nGlyphIndex, aChar ); + + // #i99367# HACK: try to detect all diacritics + if( aChar>=0x0300 && aChar<0x2100 ) + bDiacritic = IsDiacritic( aChar ); + } + + // get glyph position and its metrics + aNewPos = Point( (int)(pPos->fX+0.5), (int)(pPos->fY+0.5) ); + const GlyphMetric& rGM = rFont.GetGlyphMetric( nGlyphIndex ); + int nGlyphWidth = rGM.GetCharWidth(); + int nNewWidth = nGlyphWidth; + if( nGlyphWidth <= 0 ) + bDiacritic |= true; + // #i99367# force all diacritics to zero width + // TODO: we need mnOrigWidth/mnLogicWidth/mnNewWidth + else if( bDiacritic ) + nGlyphWidth = nNewWidth = 0; + else + { + // Hack, find next +ve width glyph and calculate current + // glyph width by substracting the two posituons + const IcuPosition* pNextPos = pPos+1; + for ( int j = i + 1; j <= nRawRunGlyphCount; ++j, ++pNextPos ) + { + if ( j == nRawRunGlyphCount ) + { + nNewWidth = pNextPos->fX - pPos->fX; + break; + } + + LEGlyphID nNextGlyphIndex = pIcuGlyphs[j]; + if( (nNextGlyphIndex == ICU_MARKED_GLYPH) + || (nNextGlyphIndex == ICU_DELETED_GLYPH) ) + continue; + + const GlyphMetric& rNextGM = rFont.GetGlyphMetric( nNextGlyphIndex ); + int nNextGlyphWidth = rNextGM.GetCharWidth(); + if ( nNextGlyphWidth > 0 ) + { + nNewWidth = pNextPos->fX - pPos->fX; + break; + } + } + } + + // heuristic to detect glyph clusters + bool bInCluster = true; + if( nLastCharPos == -1 ) + { + nClusterMinPos = nClusterMaxPos = nCharPos; + bInCluster = false; + } + else if( !bRightToLeft ) + { + // left-to-right case + if( nClusterMinPos > nCharPos ) + nClusterMinPos = nCharPos; // extend cluster + else if( nCharPos <= nClusterMaxPos ) + /*NOTHING*/; // inside cluster + else if( bDiacritic ) + nClusterMaxPos = nCharPos; // add diacritic to cluster + else { + nClusterMinPos = nClusterMaxPos = nCharPos; // new cluster + bInCluster = false; + } + } + else + { + // right-to-left case + if( nClusterMaxPos < nCharPos ) + nClusterMaxPos = nCharPos; // extend cluster + else if( nCharPos >= nClusterMinPos ) + /*NOTHING*/; // inside cluster + else if( bDiacritic ) + { + nClusterMinPos = nCharPos; // ICU often has [diacritic* baseglyph*] + if( bClusterStart ) { + nClusterMaxPos = nCharPos; + bInCluster = false; + } + } + else + { + nClusterMinPos = nClusterMaxPos = nCharPos; // new cluster + bInCluster = !bClusterStart; + } + } + + long nGlyphFlags = 0; + if( bInCluster ) + nGlyphFlags |= GlyphItem::IS_IN_CLUSTER; + if( bRightToLeft ) + nGlyphFlags |= GlyphItem::IS_RTL_GLYPH; + if( bDiacritic ) + nGlyphFlags |= GlyphItem::IS_DIACRITIC; + + // add resulting glyph item to layout + GlyphItem aGI( nCharPos, nGlyphIndex, aNewPos, nGlyphFlags, nGlyphWidth ); +#ifdef ARABIC_BANDAID + aGI.mnNewWidth = nNewWidth; +#endif + rLayout.AppendGlyph( aGI ); + ++nFilteredRunGlyphCount; + nLastCharPos = nCharPos; + bClusterStart = !aGI.IsDiacritic(); // TODO: only needed in RTL-codepath + } + aNewPos = Point( (int)(pPos->fX+0.5), (int)(pPos->fY+0.5) ); + nGlyphCount += nFilteredRunGlyphCount; + } + + // sort glyphs in visual order + // and then in logical order (e.g. diacritics after cluster start) + rLayout.SortGlyphItems(); + + // determine need for kashida justification + if( (rArgs.mpDXArray || rArgs.mnLayoutWidth) + && ((meScriptCode == arabScriptCode) || (meScriptCode == syrcScriptCode)) ) + rArgs.mnFlags |= SAL_LAYOUT_KASHIDA_JUSTIFICATON; + + return true; +} + +#endif // ENABLE_ICU_LAYOUT + +// ======================================================================= + +ServerFontLayoutEngine* FreetypeServerFont::GetLayoutEngine() +{ + // find best layout engine for font, platform, script and language +#ifdef ENABLE_ICU_LAYOUT + if( !mpLayoutEngine && FT_IS_SFNT( maFaceFT ) ) + mpLayoutEngine = new IcuLayoutEngine( *this ); +#endif // ENABLE_ICU_LAYOUT + + return mpLayoutEngine; +} + +// ======================================================================= + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/glyphs/gcach_rbmp.cxx b/vcl/source/glyphs/gcach_rbmp.cxx new file mode 100644 index 000000000000..161e4c6314e7 --- /dev/null +++ b/vcl/source/glyphs/gcach_rbmp.cxx @@ -0,0 +1,277 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +#include <vcl/glyphcache.hxx> +#include <string.h> + +//------------------------------------------------------------------------ + +RawBitmap::RawBitmap() +: mpBits(0), mnAllocated(0) +{} + +//------------------------------------------------------------------------ + +RawBitmap::~RawBitmap() +{ + delete[] mpBits; + mpBits = 0; + mnAllocated = 0; +} + +//------------------------------------------------------------------------ + +// used by 90 and 270 degree rotations on 8 bit deep bitmaps +static void ImplRotate8_90( unsigned char* p1, const unsigned char* p2, + int xmax, int ymax, int dx, int dy, int nPad ) +{ + for( int y = ymax; --y >= 0; p2 += dy ) + { + for( int x = xmax; --x >= 0; p2 += dx ) + *(p1++) = *p2; + for( int i = nPad; --i >= 0; ) + *(p1++) = 0; + } +} + +//------------------------------------------------------------------------ + +// used by inplace 180 degree rotation on 8 bit deep bitmaps +static void ImplRotate8_180( unsigned char* p1, int xmax, int ymax, int nPad ) +{ + unsigned char* p2 = p1 + ymax * (xmax + nPad); + for( int y = ymax/2; --y >= 0; ) + { + p2 -= nPad; + for( int x = xmax; --x >= 0; ) + { + unsigned char cTmp = *(--p2); + *p2 = *p1; + *(p1++) = cTmp; + } + p1 += nPad; + } + + // reverse middle line + p2 -= nPad; + while( p1 < p2 ) + { + unsigned char cTmp = *(--p2); + *p2 = *p1; + *(p1++) = cTmp; + } +} + +//------------------------------------------------------------------------ + +// used by 90 or 270 degree rotations on 1 bit deep bitmaps +static void ImplRotate1_90( unsigned char* p1, const unsigned char* p2, + int xmax, int ymax, int dx, int nShift, int nDeltaShift, int nPad ) +{ + for( int y = ymax; --y >= 0; ) + { + unsigned nTemp = 1; + const unsigned char* p20 = p2; + for( int x = xmax; --x >= 0; p2 += dx ) + { + // build bitwise and store when byte finished + nTemp += nTemp + ((*p2 >> nShift) & 1); + if( nTemp >= 0x100U ) + { + *(p1++) = (unsigned char)nTemp; + nTemp = 1; + } + } + p2 = p20; + + // store left aligned remainder if needed + if( nTemp > 1 ) + { + for(; nTemp < 0x100U; nTemp += nTemp ) ; + *(p1++) = (unsigned char)nTemp; + } + // pad scanline with zeroes + for( int i = nPad; --i >= 0;) + *(p1++) = 0; + + // increase/decrease shift, but keep bound inside 0 to 7 + nShift += nDeltaShift; + if( nShift != (nShift & 7) ) + p2 -= nDeltaShift; + nShift &= 7; + } +} + +//------------------------------------------------------------------------ + +// used by 180 degrees rotations on 1 bit deep bitmaps +static void ImplRotate1_180( unsigned char* p1, const unsigned char* p2, + int xmax, int ymax, int nPad ) +{ + --p2; + for( int y = ymax; --y >= 0; ) + { + p2 -= nPad; + + unsigned nTemp = 1; + unsigned nInp = (0x100 + *p2) >> (-xmax & 7); + for( int x = xmax; --x >= 0; ) + { + // build bitwise and store when byte finished + nTemp += nTemp + (nInp & 1); + if( nTemp >= 0x100 ) + { + *(p1++) = (unsigned char)nTemp; + nTemp = 1; + } + // update input byte if needed (and available) + if( (nInp >>= 1) <= 1 && ((y != 0) || (x != 0)) ) + nInp = 0x100 + *(--p2); + } + + // store left aligned remainder if needed + if( nTemp > 1 ) + { + for(; nTemp < 0x100; nTemp += nTemp ) ; + *(p1++) = (unsigned char)nTemp; + } + // scanline pad is already clean + p1 += nPad; + } +} + +//------------------------------------------------------------------------ + +bool RawBitmap::Rotate( int nAngle ) +{ + sal_uLong nNewScanlineSize = 0; + sal_uLong nNewHeight = 0; + sal_uLong nNewWidth = 0; + + // do inplace rotation or prepare double buffered rotation + switch( nAngle ) + { + case 0: // nothing to do + case 3600: + return true; + default: // non rectangular angles not allowed + return false; + case 1800: // rotate by 180 degrees + mnXOffset = -(mnXOffset + mnWidth); + mnYOffset = -(mnYOffset + mnHeight); + if( mnBitCount == 8 ) + { + ImplRotate8_180( mpBits, mnWidth, mnHeight, mnScanlineSize-mnWidth ); + return true; + } + nNewWidth = mnWidth; + nNewHeight = mnHeight; + nNewScanlineSize = mnScanlineSize; + break; + case +900: // left by 90 degrees + case -900: + case 2700: // right by 90 degrees + nNewWidth = mnHeight; + nNewHeight = mnWidth; + if( mnBitCount==1 ) + nNewScanlineSize = (nNewWidth + 7) / 8; + else + nNewScanlineSize = (nNewWidth + 3) & -4; + break; + } + + unsigned int nBufSize = nNewHeight * nNewScanlineSize; + unsigned char* pBuf = new unsigned char[ nBufSize ]; + if( !pBuf ) + return false; + + memset( pBuf, 0, nBufSize ); + int i; + + // dispatch non-inplace rotations + switch( nAngle ) + { + case 1800: // rotate by 180 degrees + // we know we only need to deal with 1 bit depth + ImplRotate1_180( pBuf, mpBits + mnHeight * mnScanlineSize, + mnWidth, mnHeight, mnScanlineSize - (mnWidth + 7) / 8 ); + break; + case +900: // rotate left by 90 degrees + i = mnXOffset; + mnXOffset = mnYOffset; + mnYOffset = -nNewHeight - i; + if( mnBitCount == 8 ) + ImplRotate8_90( pBuf, mpBits + mnWidth - 1, + nNewWidth, nNewHeight, +mnScanlineSize, -1-mnHeight*mnScanlineSize, + nNewScanlineSize - nNewWidth ); + else + ImplRotate1_90( pBuf, mpBits + (mnWidth - 1) / 8, + nNewWidth, nNewHeight, +mnScanlineSize, + (-mnWidth & 7), +1, nNewScanlineSize - (nNewWidth + 7) / 8 ); + break; + case 2700: // rotate right by 90 degrees + case -900: + i = mnXOffset; + mnXOffset = -(nNewWidth + mnYOffset); + mnYOffset = i; + if( mnBitCount == 8 ) + ImplRotate8_90( pBuf, mpBits + mnScanlineSize * (mnHeight-1), + nNewWidth, nNewHeight, -mnScanlineSize, +1+mnHeight*mnScanlineSize, + nNewScanlineSize - nNewWidth ); + else + ImplRotate1_90( pBuf, mpBits + mnScanlineSize * (mnHeight-1), + nNewWidth, nNewHeight, -mnScanlineSize, + +7, -1, nNewScanlineSize - (nNewWidth + 7) / 8 ); + break; + } + + mnWidth = nNewWidth; + mnHeight = nNewHeight; + mnScanlineSize = nNewScanlineSize; + + if( nBufSize < mnAllocated ) + { + memcpy( mpBits, pBuf, nBufSize ); + delete[] pBuf; + } + else + { + delete[] mpBits; + mpBits = pBuf; + mnAllocated = nBufSize; + } + + return true; +} + +//------------------------------------------------------------------------ + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/glyphs/gcach_vdev.cxx b/vcl/source/glyphs/gcach_vdev.cxx new file mode 100644 index 000000000000..944d9aee16a3 --- /dev/null +++ b/vcl/source/glyphs/gcach_vdev.cxx @@ -0,0 +1,290 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +#include <gcach_vdev.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/bitmap.hxx> +#include <vcl/outfont.hxx> +#include <vcl/virdev.hxx> +#include <vcl/metric.hxx> + +// ======================================================================= +// VirtDevServerFont +// ======================================================================= + +// ----------------------------------------------------------------------- + +void VirtDevServerFont::AnnounceFonts( ImplDevFontList* pToAdd ) +{ + // TODO: get fonts on server but not on client, + // problem is that currently there is no serverside virtual device... + VirtualDevice vdev( 1 ); + long nCount = vdev.GetDevFontCount(); + + for( int i = 0; i < nCount; ++i ) + { + const FontInfo aFontInfo = vdev.GetDevFont( i ); + + ImplFontData& rData = *new ImplFontData; + rData.SetSysData( new FontSysData( (void*)SERVERFONT_MAGIC ) ); + + rData.maName = aFontInfo.GetName(); + rData.maStyleName = aFontInfo.GetStyleName(); + rData.mnWidth = aFontInfo.GetWidth(); + rData.mnHeight = aFontInfo.GetHeight(); + rData.meFamily = aFontInfo.GetFamily(); + rData.meCharSet = aFontInfo.GetCharSet(); + rData.mePitch = aFontInfo.GetPitch(); + rData.meWidthType = aFontInfo.GetWidthType(); + rData.meWeight = aFontInfo.GetWeight(); + rData.meItalic = aFontInfo.GetItalic(); + rData.meType = aFontInfo.GetType(); + rData.meFamily = aFontInfo.GetFamily(); + + rData.mbOrientation = true; // TODO: where to get this info? + rData.mbDevice = false; + rData.mnQuality = 0; // prefer client-side fonts if available + + pToAdd->Add( &rData ); + } +} + +// ----------------------------------------------------------------------- + +void VirtDevServerFont::ClearFontList() +{ + // TODO +} + +// ----------------------------------------------------------------------- + +VirtDevServerFont* VirtDevServerFont::CreateFont( const ImplFontSelectData& rFSD ) +{ + VirtDevServerFont* pServerFont = NULL; + // TODO: search list of VirtDevServerFonts, return NULL if not found + // pServerFont = new VirtDevServerFont( rFSD ); + return pServerFont; +} + +// ----------------------------------------------------------------------- + +VirtDevServerFont::VirtDevServerFont( const ImplFontSelectData& rFSD ) +: ServerFont( rFSD) +{} + +// ----------------------------------------------------------------------- + +void VirtDevServerFont::FetchFontMetric( ImplFontMetricData& rTo, long& rFactor ) const +{ + const ImplFontSelectData& aFSD = GetFontSelData(); + + Font aFont; + aFont.SetName ( aFSD.maName ); + aFont.SetStyleName ( aFSD.maStyleName ); + aFont.SetHeight ( aFSD.mnHeight ); + aFont.SetWidth ( aFSD.mnWidth ); + aFont.SetOrientation( aFSD.mnOrientation ); + aFont.SetVertical ( GetFontSelData().mbVertical ); + + VirtualDevice vdev( 1 ); + FontMetric aMetric( vdev.GetFontMetric( aFont ) ); + + rFactor = 0x100; + + rTo.mnAscent = aMetric.GetAscent(); + rTo.mnDescent = aMetric.GetDescent(); + rTo.mnIntLeading = aMetric.GetIntLeading(); + rTo.mnExtLeading = aMetric.GetExtLeading(); + rTo.mnSlant = aMetric.GetSlant(); + rTo.meType = aMetric.GetType(); + rTo.mnFirstChar = 0x0020; // TODO: where to get this info? + rTo.mnLastChar = 0xFFFE; // TODO: where to get this info? + + rTo.mnWidth = aFSD.mnWidth; + rTo.maName = aFSD.maName; + rTo.maStyleName = aFSD.maStyleName; + rTo.mnOrientation = aFSD.mnOrientation; + rTo.meFamily = aFSD.meFamily; + rTo.meCharSet = aFSD.meCharSet; + rTo.meWeight = aFSD.meWeight; + rTo.meItalic = aFSD.meItalic; + rTo.mePitch = aFSD.mePitch; + rTo.mbDevice = sal_False; +} + +// ----------------------------------------------------------------------- + +int VirtDevServerFont::GetGlyphIndex( sal_Unicode aChar ) const +{ + return aChar; +} + +// ----------------------------------------------------------------------- + +void VirtDevServerFont::InitGlyphData( int nGlyphIndex, GlyphData& rGD ) const +{ + Font aFont; + aFont.SetName ( GetFontSelData().maName ); + aFont.SetStyleName ( GetFontSelData().maStyleName ); + aFont.SetHeight ( GetFontSelData().mnHeight ); + aFont.SetWidth ( GetFontSelData().mnWidth ); + aFont.SetOrientation( GetFontSelData().mnOrientation ); + aFont.SetVertical ( GetFontSelData().mbVertical ); + + VirtualDevice vdev( 1 ); + vdev.SetFont( aFont ); + + // get glyph metrics + sal_Int32 nCharWidth = 10; +// TODO: vdev.GetCharWidth( nGlyphIndex, nGlyphIndex, &nCharWidth ); + rGD.SetCharWidth( nCharWidth ); + + sal_Unicode aChar = nGlyphIndex; + String aGlyphStr( &aChar, 1 ); + Rectangle aRect; + if( vdev.GetTextBoundRect( aRect, aGlyphStr, 0, 1 ) ) + { + rGD.SetOffset( aRect.Top(), aRect.Left() ); + rGD.SetDelta( vdev.GetTextWidth( nGlyphIndex ), 0 ); + rGD.SetSize( aRect.GetSize() ); + } +} + +// ----------------------------------------------------------------------- + +bool VirtDevServerFont::GetAntialiasAdvice( void ) const +{ + return false; +} + +// ----------------------------------------------------------------------- + +bool VirtDevServerFont::GetGlyphBitmap1( int nGlyphIndex, RawBitmap& ) const +{ + /* + sal_Unicode aChar = nGlyphIndex; + String aGlyphStr( &aChar, 1 ); + + // draw bitmap + vdev.SetOutputSizePixel( aSize, sal_True ); + vdev.DrawText( Point(0,0)-rGD.GetMetric().GetOffset(), aGlyphStr ); + + // create new glyph item + + const Bitmap& rBitmap = vdev.GetBitmap( Point(0,0), aSize ); + rGD.SetBitmap( new Bitmap( rBitmap ) ); + return true; + */ + return false; +} + +// ----------------------------------------------------------------------- + +bool VirtDevServerFont::GetGlyphBitmap8( int nGlyphIndex, RawBitmap& ) const +{ + return false; +} + +// ----------------------------------------------------------------------- + +int VirtDevServerFont::GetGlyphKernValue( int, int ) const +{ + return 0; +} + +// ----------------------------------------------------------------------- + +sal_uLong VirtDevServerFont::GetKernPairs( ImplKernPairData** ppImplKernPairs ) const +{ + Font aFont; + aFont.SetName ( GetFontSelData().maName ); + aFont.SetStyleName ( GetFontSelData().maStyleName ); + aFont.SetHeight ( GetFontSelData().mnHeight ); + aFont.SetWidth ( GetFontSelData().mnWidth ); + aFont.SetOrientation( GetFontSelData().mnOrientation ); + aFont.SetVertical ( GetFontSelData().mbVertical ); + + VirtualDevice vdev( 1 ); + vdev.SetFont( aFont ); + + sal_uLong nPairs = vdev.GetKerningPairCount(); + if( nPairs > 0 ) + { + KerningPair* const pKernPairs = new KerningPair[ nPairs ]; + vdev.GetKerningPairs( nPairs, pKernPairs ); + + *ppImplKernPairs = new ImplKernPairData[ nPairs ]; + ImplKernPairData* pTo = *ppImplKernPairs; + KerningPair* pFrom = pKernPairs; + for ( sal_uLong n = 0; n < nPairs; n++ ) + { + pTo->mnChar1 = pFrom->nChar1; + pTo->mnChar2 = pFrom->nChar2; + pTo->mnKern = pFrom->nKern; + ++pFrom; + ++pTo; + } + + delete[] pKernPairs; + } + + return nPairs; +} + +// ----------------------------------------------------------------------- + +bool VirtDevServerFont::GetGlyphOutline( int nGlyphIndex, PolyPolygon& rPolyPoly ) const +{ + return false; + /* + Font aFont; + aFont.SetName ( GetFontSelData().maName ); + aFont.SetStyleName ( GetFontSelData().maStyleName ); + aFont.SetHeight ( GetFontSelData().mnHeight ); + aFont.SetWidth ( GetFontSelData().mnWidth ); + aFont.SetOrientation( GetFontSelData().mnOrientation ); + aFont.SetVertical ( GetFontSelData().mbVertical ); + + VirtualDevice vdev( 1 ); + vdev.SetFont( aFont ); + + const bool bOptimize = true; + + sal_Unicode aChar = nGlyphIndex; + String aGlyphStr( &aChar, 1 ); + return vdev.GetTextOutline( rPolyPoly, aGlyphStr, 0, 1, bOptimize ); + */ +} + +// ======================================================================= + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/glyphs/gcach_vdev.hxx b/vcl/source/glyphs/gcach_vdev.hxx new file mode 100644 index 000000000000..61cd02b1f9cc --- /dev/null +++ b/vcl/source/glyphs/gcach_vdev.hxx @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include <vcl/glyphcache.hxx> + +// ----------------------------------------------------------------------- + +class VirtDevServerFont : public ServerFont +{ +public: + virtual bool GetAntialiasAdvice( void ) const; + virtual bool GetGlyphBitmap1( int nGlyphIndex, RawBitmap& ) const; + virtual bool GetGlyphBitmap8( int nGlyphIndex, RawBitmap& ) const; + virtual bool GetGlyphOutline( int nGlyphIndex, PolyPolygon& ) const; + +protected: + friend class GlyphCache; + static void AnnounceFonts( ImplDevFontList* ); + static void ClearFontList(); + + static VirtDevServerFont* CreateFont( const ImplFontSelectData& ); + virtual void FetchFontMetric( ImplFontMetricData&, long& rFactor ) const; + virtual sal_uLong GetKernPairs( ImplKernPairData** ) const; + virtual int GetGlyphKernValue( int, int ) const; + + virtual int GetGlyphIndex( sal_Unicode ) const; + virtual void InitGlyphData( int nGlyphIndex, GlyphData& ) const; + +private: + VirtDevServerFont( const ImplFontSelectData& ); +}; + +// ----------------------------------------------------------------------- + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/glyphs/glyphcache.cxx b/vcl/source/glyphs/glyphcache.cxx new file mode 100644 index 000000000000..ab56853dcf65 --- /dev/null +++ b/vcl/source/glyphs/glyphcache.cxx @@ -0,0 +1,609 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <vcl/salbtype.hxx> +#include <gcach_ftyp.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/bitmap.hxx> +#include <vcl/outfont.hxx> + +#ifdef ENABLE_GRAPHITE +#include <vcl/graphite_features.hxx> +#endif + +#include <rtl/ustring.hxx> // used only for string=>hashvalue +#include <osl/file.hxx> +#include <tools/debug.hxx> + +// ======================================================================= +// GlyphCache +// ======================================================================= + +static GlyphCache* pInstance = NULL; + +GlyphCache::GlyphCache( GlyphCachePeer& rPeer ) +: mrPeer( rPeer ), + mnMaxSize( 1500000 ), + mnBytesUsed(sizeof(GlyphCache)), + mnLruIndex(0), + mnGlyphCount(0), + mpCurrentGCFont(NULL), + mpFtManager(NULL) +{ + pInstance = this; + mpFtManager = new FreetypeManager; +} + +// ----------------------------------------------------------------------- + +GlyphCache::~GlyphCache() +{ + InvalidateAllGlyphs(); + for( FontList::iterator it = maFontList.begin(), end = maFontList.end(); it != end; ++it ) + { + ServerFont* pServerFont = it->second; + mrPeer.RemovingFont(*pServerFont); + delete pServerFont; + } + if( mpFtManager ) + delete mpFtManager; +} + +// ----------------------------------------------------------------------- + +void GlyphCache::InvalidateAllGlyphs() +{ + // an application about to exit can omit garbage collecting the heap + // since it makes things slower and introduces risks if the heap was not perfect + // for debugging, for memory grinding or leak checking the env allows to force GC + const char* pEnv = getenv( "SAL_FORCE_GC_ON_EXIT" ); + if( pEnv && (*pEnv != '0') ) + { + // uncache of all glyph shapes and metrics + for( FontList::iterator it = maFontList.begin(); it != maFontList.end(); ++it ) + delete const_cast<ServerFont*>( it->second ); + maFontList.clear(); + mpCurrentGCFont = NULL; + } +} + +// ----------------------------------------------------------------------- + +inline +size_t GlyphCache::IFSD_Hash::operator()( const ImplFontSelectData& rFontSelData ) const +{ + // TODO: is it worth to improve this hash function? + sal_IntPtr nFontId = reinterpret_cast<sal_IntPtr>( rFontSelData.mpFontData ); +#ifdef ENABLE_GRAPHITE + if (rFontSelData.maTargetName.Search(grutils::GrFeatureParser::FEAT_PREFIX) + != STRING_NOTFOUND) + { + rtl::OString aFeatName = rtl::OUStringToOString( rFontSelData.maTargetName, RTL_TEXTENCODING_UTF8 ); + nFontId ^= aFeatName.hashCode(); + } +#endif + size_t nHash = nFontId << 8; + nHash += rFontSelData.mnHeight; + nHash += rFontSelData.mnOrientation; + nHash += rFontSelData.mbVertical; + nHash += rFontSelData.meItalic; + nHash += rFontSelData.meWeight; +#ifdef ENABLE_GRAPHITE + nHash += rFontSelData.meLanguage; +#endif + return nHash; +} + +// ----------------------------------------------------------------------- + +bool GlyphCache::IFSD_Equal::operator()( const ImplFontSelectData& rA, const ImplFontSelectData& rB) const +{ + // check font ids + sal_IntPtr nFontIdA = reinterpret_cast<sal_IntPtr>( rA.mpFontData ); + sal_IntPtr nFontIdB = reinterpret_cast<sal_IntPtr>( rB.mpFontData ); + if( nFontIdA != nFontIdB ) + return false; + + // compare with the requested metrics + if( (rA.mnHeight != rB.mnHeight) + || (rA.mnOrientation != rB.mnOrientation) + || (rA.mbVertical != rB.mbVertical) + || (rA.mbNonAntialiased != rB.mbNonAntialiased) ) + return false; + + if( (rA.meItalic != rB.meItalic) + || (rA.meWeight != rB.meWeight) ) + return false; + + // NOTE: ignoring meFamily deliberately + + // compare with the requested width, allow default width + if( (rA.mnWidth != rB.mnWidth) + && ((rA.mnHeight != rB.mnWidth) || (rA.mnWidth != 0)) ) + return false; +#ifdef ENABLE_GRAPHITE + if (rA.meLanguage != rB.meLanguage) + return false; + // check for features + if ((rA.maTargetName.Search(grutils::GrFeatureParser::FEAT_PREFIX) + != STRING_NOTFOUND || + rB.maTargetName.Search(grutils::GrFeatureParser::FEAT_PREFIX) + != STRING_NOTFOUND) && rA.maTargetName != rB.maTargetName) + return false; +#endif + return true; +} + +// ----------------------------------------------------------------------- + +GlyphCache& GlyphCache::GetInstance() +{ + return *pInstance; +} + +// ----------------------------------------------------------------------- + +void GlyphCache::LoadFonts() +{ + if( const char* pFontPath = ::getenv( "SAL_FONTPATH_PRIVATE" ) ) + AddFontPath( String::CreateFromAscii( pFontPath ) ); + const String& rFontPath = Application::GetFontPath(); + if( rFontPath.Len() > 0 ) + AddFontPath( rFontPath ); +} + +// ----------------------------------------------------------------------- + +void GlyphCache::ClearFontPath() +{ + if( mpFtManager ) + mpFtManager->ClearFontList(); +} + +// ----------------------------------------------------------------------- + +void GlyphCache::AddFontPath( const String& rFontPath ) +{ + if( !mpFtManager ) + return; + + for( xub_StrLen nBreaker1 = 0, nBreaker2 = 0; nBreaker2 != STRING_LEN; nBreaker1 = nBreaker2 + 1 ) + { + nBreaker2 = rFontPath.Search( ';', nBreaker1 ); + if( nBreaker2 == STRING_NOTFOUND ) + nBreaker2 = STRING_LEN; + + ::rtl::OUString aUrlName; + osl::FileBase::getFileURLFromSystemPath( rFontPath.Copy( nBreaker1, nBreaker2 ), aUrlName ); + mpFtManager->AddFontDir( aUrlName ); + } +} + +// ----------------------------------------------------------------------- + +void GlyphCache::AddFontFile( const rtl::OString& rNormalizedName, int nFaceNum, + sal_IntPtr nFontId, const ImplDevFontAttributes& rDFA, const ExtraKernInfo* pExtraKern ) +{ + if( mpFtManager ) + mpFtManager->AddFontFile( rNormalizedName, nFaceNum, nFontId, rDFA, pExtraKern ); +} + +// ----------------------------------------------------------------------- + +void GlyphCache::AnnounceFonts( ImplDevFontList* pList ) const +{ + if( mpFtManager ) + mpFtManager->AnnounceFonts( pList ); + // VirtDevServerFont::AnnounceFonts( pList ); +} + +// ----------------------------------------------------------------------- + +ServerFont* GlyphCache::CacheFont( const ImplFontSelectData& rFontSelData ) +{ + // a serverfont request has pFontData + if( rFontSelData.mpFontData == NULL ) + return NULL; + // a serverfont request has a fontid > 0 + sal_IntPtr nFontId = rFontSelData.mpFontData->GetFontId(); + if( nFontId <= 0 ) + return NULL; + + // the FontList's key mpFontData member is reinterpreted as font id + ImplFontSelectData aFontSelData = rFontSelData; + aFontSelData.mpFontData = reinterpret_cast<ImplFontData*>( nFontId ); + FontList::iterator it = maFontList.find( aFontSelData ); + if( it != maFontList.end() ) + { + ServerFont* pFound = it->second; + if( pFound ) + pFound->AddRef(); + return pFound; + } + + // font not cached yet => create new font item + ServerFont* pNew = NULL; + if( mpFtManager ) + pNew = mpFtManager->CreateFont( aFontSelData ); + // TODO: pNew = VirtDevServerFont::CreateFont( aFontSelData ); + + if( pNew ) + { + maFontList[ aFontSelData ] = pNew; + mnBytesUsed += pNew->GetByteCount(); + + // enable garbage collection for new font + if( !mpCurrentGCFont ) + { + mpCurrentGCFont = pNew; + pNew->mpNextGCFont = pNew; + pNew->mpPrevGCFont = pNew; + } + else + { + pNew->mpNextGCFont = mpCurrentGCFont; + pNew->mpPrevGCFont = mpCurrentGCFont->mpPrevGCFont; + pNew->mpPrevGCFont->mpNextGCFont = pNew; + mpCurrentGCFont->mpPrevGCFont = pNew; + } + } + + return pNew; +} + +// ----------------------------------------------------------------------- + +void GlyphCache::UncacheFont( ServerFont& rServerFont ) +{ + // the interface for rServerFont must be const because a + // user who wants to release it only got const ServerFonts. + // The caching algorithm needs a non-const object + ServerFont* pFont = const_cast<ServerFont*>( &rServerFont ); + if( (pFont->Release() <= 0) + && (mnMaxSize <= (mnBytesUsed + mrPeer.GetByteCount())) ) + { + mpCurrentGCFont = pFont; + GarbageCollect(); + } +} + +// ----------------------------------------------------------------------- + +sal_uLong GlyphCache::CalcByteCount() const +{ + sal_uLong nCacheSize = sizeof(*this); + for( FontList::const_iterator it = maFontList.begin(); it != maFontList.end(); ++it ) + { + const ServerFont* pSF = it->second; + if( pSF ) + nCacheSize += pSF->GetByteCount(); + } + // TODO: also account something for hashtable management + return nCacheSize; +} + +// ----------------------------------------------------------------------- + +void GlyphCache::GarbageCollect() +{ + // when current GC font has been destroyed get another one + if( !mpCurrentGCFont ) + { + FontList::iterator it = maFontList.begin(); + if( it != maFontList.end() ) + mpCurrentGCFont = it->second; + } + + // unless there is no other font to collect + if( !mpCurrentGCFont ) + return; + + // prepare advance to next font for garbage collection + ServerFont* const pServerFont = mpCurrentGCFont; + mpCurrentGCFont = pServerFont->mpNextGCFont; + + if( (pServerFont == mpCurrentGCFont) // no other fonts + || (pServerFont->GetRefCount() > 0) ) // font still used + { + // try to garbage collect at least a few bytes + pServerFont->GarbageCollect( mnLruIndex - mnGlyphCount/2 ); + } + else // current GC font is unreferenced + { + DBG_ASSERT( (pServerFont->GetRefCount() == 0), + "GlyphCache::GC detected RefCount underflow" ); + + // free all pServerFont related data + pServerFont->GarbageCollect( mnLruIndex+0x10000000 ); + if( pServerFont == mpCurrentGCFont ) + mpCurrentGCFont = NULL; + const ImplFontSelectData& rIFSD = pServerFont->GetFontSelData(); + maFontList.erase( rIFSD ); + mrPeer.RemovingFont( *pServerFont ); + mnBytesUsed -= pServerFont->GetByteCount(); + + // remove font from list of garbage collected fonts + if( pServerFont->mpPrevGCFont ) + pServerFont->mpPrevGCFont->mpNextGCFont = pServerFont->mpNextGCFont; + if( pServerFont->mpNextGCFont ) + pServerFont->mpNextGCFont->mpPrevGCFont = pServerFont->mpPrevGCFont; + if( pServerFont == mpCurrentGCFont ) + mpCurrentGCFont = NULL; + + delete pServerFont; + } +} + +// ----------------------------------------------------------------------- + +inline void GlyphCache::UsingGlyph( ServerFont&, GlyphData& rGlyphData ) +{ + rGlyphData.SetLruValue( mnLruIndex++ ); +} + +// ----------------------------------------------------------------------- + +inline void GlyphCache::AddedGlyph( ServerFont& rServerFont, GlyphData& rGlyphData ) +{ + ++mnGlyphCount; + mnBytesUsed += sizeof( rGlyphData ); + UsingGlyph( rServerFont, rGlyphData ); + GrowNotify(); +} + +// ----------------------------------------------------------------------- + +void GlyphCache::GrowNotify() +{ + if( (mnBytesUsed + mrPeer.GetByteCount()) > mnMaxSize ) + GarbageCollect(); +} + +// ----------------------------------------------------------------------- + +inline void GlyphCache::RemovingGlyph( ServerFont& rSF, GlyphData& rGD, int nGlyphIndex ) +{ + mrPeer.RemovingGlyph( rSF, rGD, nGlyphIndex ); + mnBytesUsed -= sizeof( GlyphData ); + --mnGlyphCount; +} + +// ======================================================================= +// ServerFont +// ======================================================================= + +ServerFont::ServerFont( const ImplFontSelectData& rFSD ) +: maGlyphList( 0), + maFontSelData(rFSD), + mnExtInfo(0), + mnRefCount(1), + mnBytesUsed( sizeof(ServerFont) ), + mpPrevGCFont( NULL ), + mpNextGCFont( NULL ), + mnCos( 0x10000), + mnSin( 0 ), + mnZWJ( 0 ), + mnZWNJ( 0 ), + mbCollectedZW( false ) +{ + // TODO: move update of mpFontEntry into FontEntry class when + // it becomes reponsible for the ServerFont instantiation + ((ImplServerFontEntry*)rFSD.mpFontEntry)->SetServerFont( this ); + + if( rFSD.mnOrientation != 0 ) + { + const double dRad = rFSD.mnOrientation * ( F_2PI / 3600.0 ); + mnCos = static_cast<long>( 0x10000 * cos( dRad ) + 0.5 ); + mnSin = static_cast<long>( 0x10000 * sin( dRad ) + 0.5 ); + } +} + +// ----------------------------------------------------------------------- + +ServerFont::~ServerFont() +{ + ReleaseFromGarbageCollect(); +} + +// ----------------------------------------------------------------------- + +void ServerFont::ReleaseFromGarbageCollect() +{ + // remove from GC list + ServerFont* pPrev = mpPrevGCFont; + ServerFont* pNext = mpNextGCFont; + if( pPrev ) pPrev->mpNextGCFont = pNext; + if( pNext ) pNext->mpPrevGCFont = pPrev; + mpPrevGCFont = NULL; + mpNextGCFont = NULL; +} + +// ----------------------------------------------------------------------- + +long ServerFont::Release() const +{ + DBG_ASSERT( mnRefCount > 0, "ServerFont: RefCount underflow" ); + return --mnRefCount; +} + +// ----------------------------------------------------------------------- + +GlyphData& ServerFont::GetGlyphData( int nGlyphIndex ) +{ + // usually the GlyphData is cached + GlyphList::iterator it = maGlyphList.find( nGlyphIndex ); + if( it != maGlyphList.end() ) { + GlyphData& rGlyphData = it->second; + GlyphCache::GetInstance().UsingGlyph( *this, rGlyphData ); + return rGlyphData; + } + + // sometimes not => we need to create and initialize it ourselves + GlyphData& rGlyphData = maGlyphList[ nGlyphIndex ]; + mnBytesUsed += sizeof( GlyphData ); + InitGlyphData( nGlyphIndex, rGlyphData ); + GlyphCache::GetInstance().AddedGlyph( *this, rGlyphData ); + return rGlyphData; +} + +// ----------------------------------------------------------------------- + +void ServerFont::GarbageCollect( long nMinLruIndex ) +{ + GlyphList::iterator it_next = maGlyphList.begin(); + while( it_next != maGlyphList.end() ) + { + GlyphList::iterator it = it_next++; + GlyphData& rGD = it->second; + if( (nMinLruIndex - rGD.GetLruValue()) > 0 ) + { + OSL_ASSERT( mnBytesUsed >= sizeof(GlyphData) ); + mnBytesUsed -= sizeof( GlyphData ); + GlyphCache::GetInstance().RemovingGlyph( *this, rGD, it->first ); + maGlyphList.erase( it ); + it_next = maGlyphList.begin(); + } + } +} + +// ----------------------------------------------------------------------- + +Point ServerFont::TransformPoint( const Point& rPoint ) const +{ + if( mnCos == 0x10000 ) + return rPoint; + // TODO: use 32x32=>64bit intermediate + const double dCos = mnCos * (1.0 / 0x10000); + const double dSin = mnSin * (1.0 / 0x10000); + long nX = (long)(rPoint.X() * dCos + rPoint.Y() * dSin); + long nY = (long)(rPoint.Y() * dCos - rPoint.X() * dSin); + return Point( nX, nY ); +} + +bool ServerFont::IsGlyphInvisible( int nGlyphIndex ) +{ + if (!mbCollectedZW) + { + mnZWJ = GetGlyphIndex( 0x200D ); + mnZWNJ = GetGlyphIndex( 0x200C ); + mbCollectedZW = true; + } + + if( !nGlyphIndex ) // don't hide the NotDef glyph + return false; + if( (nGlyphIndex == mnZWNJ) || (nGlyphIndex == mnZWJ) ) + return true; + + return false; +} + +// ======================================================================= + +ImplServerFontEntry::ImplServerFontEntry( ImplFontSelectData& rFSD ) +: ImplFontEntry( rFSD ) +, mpServerFont( NULL ) +, mbGotFontOptions( false ) +{} + +// ----------------------------------------------------------------------- + +ImplServerFontEntry::~ImplServerFontEntry() +{ + // TODO: remove the ServerFont here instead of in the GlyphCache +} + +// ======================================================================= + +ExtraKernInfo::ExtraKernInfo( sal_IntPtr nFontId ) +: mbInitialized( false ), + mnFontId( nFontId ), + maUnicodeKernPairs( 0 ) +{} + +//-------------------------------------------------------------------------- + +bool ExtraKernInfo::HasKernPairs() const +{ + if( !mbInitialized ) + Initialize(); + return !maUnicodeKernPairs.empty(); +} + +//-------------------------------------------------------------------------- + +int ExtraKernInfo::GetUnscaledKernPairs( ImplKernPairData** ppKernPairs ) const +{ + if( !mbInitialized ) + Initialize(); + + // return early if no kerning available + if( maUnicodeKernPairs.empty() ) + return 0; + + // allocate kern pair table + int nKernCount = maUnicodeKernPairs.size(); + *ppKernPairs = new ImplKernPairData[ nKernCount ]; + + // fill in unicode kern pairs with the kern value scaled to the font width + ImplKernPairData* pKernData = *ppKernPairs; + UnicodeKernPairs::const_iterator it = maUnicodeKernPairs.begin(); + for(; it != maUnicodeKernPairs.end(); ++it ) + *(pKernData++) = *it; + + return nKernCount; +} + +//-------------------------------------------------------------------------- + +int ExtraKernInfo::GetUnscaledKernValue( sal_Unicode cLeft, sal_Unicode cRight ) const +{ + if( !mbInitialized ) + Initialize(); + + if( maUnicodeKernPairs.empty() ) + return 0; + + ImplKernPairData aKernPair = { cLeft, cRight, 0 }; + UnicodeKernPairs::const_iterator it = maUnicodeKernPairs.find( aKernPair ); + if( it == maUnicodeKernPairs.end() ) + return 0; + + int nUnscaledValue = (*it).mnKern; + return nUnscaledValue; +} + +// ======================================================================= + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/glyphs/graphite_features.cxx b/vcl/source/glyphs/graphite_features.cxx new file mode 100644 index 000000000000..175656e25515 --- /dev/null +++ b/vcl/source/glyphs/graphite_features.cxx @@ -0,0 +1,288 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// Description: +// Parse a string of features specified as & separated pairs. +// e.g. +// 1001=1&2002=2&fav1=0 + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +#include <sal/types.h> + +#ifdef WNT +#include <windows.h> +#endif + +#include <vcl/graphite_features.hxx> + +using namespace grutils; +// These mustn't conflict with font name lists which use ; and , +const char GrFeatureParser::FEAT_PREFIX = ':'; +const char GrFeatureParser::FEAT_SEPARATOR = '&'; +const char GrFeatureParser::FEAT_ID_VALUE_SEPARATOR = '='; + +GrFeatureParser::GrFeatureParser(const gr_face * pFace, const ::rtl::OString lang) + : mnNumSettings(0), mbErrors(false), mpSettings(NULL) +{ + maLang.label[0] = maLang.label[1] = maLang.label[2] = maLang.label[3] = '\0'; + setLang(pFace, lang); +} + +GrFeatureParser::GrFeatureParser(const gr_face * pFace, const ::rtl::OString features, const ::rtl::OString lang) + : mnNumSettings(0), mbErrors(false), mpSettings(NULL) +{ + sal_Int32 nEquals = 0; + sal_Int32 nFeatEnd = 0; + sal_Int32 pos = 0; + maLang.num = 0u; + setLang(pFace, lang); + while ((pos < features.getLength()) && (mnNumSettings < MAX_FEATURES)) + { + nEquals = features.indexOf(FEAT_ID_VALUE_SEPARATOR, pos); + if (nEquals == -1) + { + mbErrors = true; + break; + } + // check for a lang=xxx specification + const ::rtl::OString aLangPrefix("lang"); + if (features.match(aLangPrefix, pos )) + { + pos = nEquals + 1; + nFeatEnd = features.indexOf(FEAT_SEPARATOR, pos); + if (nFeatEnd == -1) + { + nFeatEnd = features.getLength(); + } + if (nFeatEnd - pos > 3) + mbErrors = true; + else + { + FeatId aLang = maLang; + aLang.num = 0; + for (sal_Int32 i = pos; i < nFeatEnd; i++) + aLang.label[i-pos] = features[i]; + + //ext_std::pair<gr::LanguageIterator,gr::LanguageIterator> aSupported + // = font.getSupportedLanguages(); + //gr::LanguageIterator iL = aSupported.first; + unsigned short i = 0; + for (; i < gr_face_n_languages(pFace); i++) + { + gr_uint32 nFaceLang = gr_face_lang_by_index(pFace, i); + FeatId aSupportedLang; + aSupportedLang.num = nFaceLang; +#ifdef __BIG_ENDIAN__ + // here we only expect full 3 letter codes + if (aLang.label[0] == aSupportedLang.label[0] && + aLang.label[1] == aSupportedLang.label[1] && + aLang.label[2] == aSupportedLang.label[2] && + aLang.label[3] == aSupportedLang.label[3]) +#else + if (aLang.label[0] == aSupportedLang.label[3] && + aLang.label[1] == aSupportedLang.label[2] && + aLang.label[2] == aSupportedLang.label[1] && + aLang.label[3] == aSupportedLang.label[0]) +#endif + { + maLang = aSupportedLang; + break; + } + } + if (i == gr_face_n_languages(pFace)) mbErrors = true; + else + { + mnHash = maLang.num; + mpSettings = gr_face_featureval_for_lang(pFace, maLang.num); + } + } + } + else + { + sal_uInt32 featId = 0; + if (isCharId(features, pos, nEquals - pos)) + { + featId = getCharId(features, pos, nEquals - pos); + } + else + { + featId = getIntValue(features, pos, nEquals - pos); + } + const gr_feature_ref * pFref = gr_face_find_fref(pFace, featId); + pos = nEquals + 1; + nFeatEnd = features.indexOf(FEAT_SEPARATOR, pos); + if (nFeatEnd == -1) + { + nFeatEnd = features.getLength(); + } + sal_Int16 featValue = 0; + featValue = getIntValue(features, pos, nFeatEnd - pos); + if (pFref && gr_fref_set_feature_value(pFref, featValue, mpSettings)) + { + mnHash = (mnHash << 16) ^ ((featId << 8) | featValue); + mnNumSettings++; + } + else + mbErrors = true; + } + pos = nFeatEnd + 1; + } +} + +void GrFeatureParser::setLang(const gr_face * pFace, const rtl::OString & lang) +{ + FeatId aLang; + aLang.num = 0; + if (lang.getLength() >= 2) + { + for (sal_Int32 i = 0; i < lang.getLength() && i < 3; i++) + { + if (lang[i] == '-') break; + aLang.label[i] = lang[i]; + } + unsigned short i = 0; + for (; i < gr_face_n_languages(pFace); i++) + { + gr_uint32 nFaceLang = gr_face_lang_by_index(pFace, i); + FeatId aSupportedLang; + aSupportedLang.num = nFaceLang; + // here we only expect full 2 & 3 letter codes +#ifdef __BIG_ENDIAN__ + if (aLang.label[0] == aSupportedLang.label[0] && + aLang.label[1] == aSupportedLang.label[1] && + aLang.label[2] == aSupportedLang.label[2] && + aLang.label[3] == aSupportedLang.label[3]) +#else + if (aLang.label[0] == aSupportedLang.label[3] && + aLang.label[1] == aSupportedLang.label[2] && + aLang.label[2] == aSupportedLang.label[1] && + aLang.label[3] == aSupportedLang.label[0]) +#endif + { + maLang = aSupportedLang; + break; + } + } + if (i != gr_face_n_languages(pFace)) + { + if (mpSettings) + gr_featureval_destroy(mpSettings); + mpSettings = gr_face_featureval_for_lang(pFace, maLang.num); + mnHash = maLang.num; + } + } + if (!mpSettings) + mpSettings = gr_face_featureval_for_lang(pFace, 0); +} + +GrFeatureParser::~GrFeatureParser() +{ + if (mpSettings) + { + gr_featureval_destroy(mpSettings); + mpSettings = NULL; + } +} + +bool GrFeatureParser::isCharId(const rtl::OString & id, size_t offset, size_t length) +{ + if (length > 4) return false; + for (size_t i = 0; i < length; i++) + { + if (i > 0 && id[offset+i] == '\0') continue; + if ((id[offset+i] < 0x20) || (id[offset+i] < 0)) + return false; + if (i==0 && (id[offset+i] < 0x41)) + return false; + } + return true; +} + +gr_uint32 GrFeatureParser::getCharId(const rtl::OString & id, size_t offset, size_t length) +{ + FeatId charId; + charId.num = 0; +#ifdef WORDS_BIGENDIAN + for (size_t i = 0; i < length; i++) + { + charId.label[i] = id[offset+i]; + } +#else + for (size_t i = 0; i < length; i++) + { + charId.label[3-i] = id[offset+i]; + } +#endif + return charId.num; +} + +short GrFeatureParser::getIntValue(const rtl::OString & id, size_t offset, size_t length) +{ + short value = 0; + int sign = 1; + for (size_t i = 0; i < length; i++) + { + switch (id[offset + i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + value *= 10; + if (sign < 0) + { + value = -(id[offset + i] - '0'); + sign = 1; + } + value += (id[offset + i] - '0'); + break; + case '-': + if (i == 0) + sign = -1; + else + { + mbErrors = true; + break; + } + default: + mbErrors = true; + break; + } + } + return value; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/glyphs/graphite_layout.cxx b/vcl/source/glyphs/graphite_layout.cxx new file mode 100644 index 000000000000..ab5b59025724 --- /dev/null +++ b/vcl/source/glyphs/graphite_layout.cxx @@ -0,0 +1,1366 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// Description: An implementation of the SalLayout interface that uses the +// Graphite engine. + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +// We need this to enable namespace support in libgrengine headers. +#define GR_NAMESPACE + +// Enable lots of debug info +#ifdef DEBUG +#include <cstdio> +//#define GRLAYOUT_DEBUG 1 +#undef NDEBUG +#endif + +// Header files +// +// Standard Library +#include <algorithm> +#include <cassert> +#include <functional> +#include <limits> +#include <numeric> +#include <deque> + +// Platform +#include <svsys.h> + +#include <vcl/salgdi.hxx> + +#include <unicode/uchar.h> +#include <unicode/ubidi.h> +#include <unicode/uscript.h> + +// Graphite Libraries (must be after vcl headers on windows) +#include <graphite2/Segment.h> + +#include "vcl/graphite_layout.hxx" +#include "vcl/graphite_features.hxx" + +// Module private type definitions and forward declarations. +// +// Module private names. +// + +#ifdef GRLAYOUT_DEBUG +static FILE * grLogFile = NULL; +static FILE * grLog() +{ +#ifdef WNT + std::string logFileName(getenv("TEMP")); + logFileName.append("/graphitelayout.log"); + if (grLogFile == NULL) grLogFile = fopen(logFileName.c_str(),"w"); + else fflush(grLogFile); + return grLogFile; +#else + fflush(stdout); + return stdout; +#endif +} +#endif + +namespace +{ + inline long round(const float n) { + return long(n + (n < 0 ? -0.5 : 0.5)); + } + + template<typename T> + inline bool in_range(const T i, const T b, const T e) { + return !(b > i) && i < e; + } + + template<typename T> + inline bool is_subrange(const T sb, const T se, const T b, const T e) { + return !(b > sb || se > e); + } + + template<typename T> + inline bool is_subrange(const std::pair<T, T> &s, const T b, const T e) { + return is_subrange(s.first, s.second, b, e); + } + + int findSameDirLimit(const xub_Unicode* buffer, int charCount, bool rtl) + { + UErrorCode status = U_ZERO_ERROR; + UBiDi *ubidi = ubidi_openSized(charCount, 0, &status); + int limit = 0; + ubidi_setPara(ubidi, reinterpret_cast<const UChar *>(buffer), charCount, + (rtl)?UBIDI_DEFAULT_RTL:UBIDI_DEFAULT_LTR, NULL, &status); + UBiDiLevel level = 0; + ubidi_getLogicalRun(ubidi, 0, &limit, &level); + ubidi_close(ubidi); + if ((rtl && !(level & 1)) || (!rtl && (level & 1))) + { + limit = 0; + } + return limit; + } + + template <typename T> + T maximum(T a, T b) + { + return (a > b)? a : b; + } + template <typename T> + T minimum(T a, T b) + { + return (a < b)? a : b; + } + +} // namespace + +// Impementation of the GraphiteLayout::Glyphs container class. +// This is an extended vector class with methods added to enable +// o Correctly filling with glyphs. +// o Querying clustering relationships. +// o manipulations that affect neighouring glyphs. + +const int GraphiteLayout::EXTRA_CONTEXT_LENGTH = 10; + +// find first slot of cluster and first slot of subsequent cluster +static void findFirstClusterSlot(const gr_slot* base, gr_slot const** first, gr_slot const** after, int * firstChar, int * lastChar, bool bRtl) +{ + if (gr_slot_attached_to(base) == NULL) + { + *first = base; + *after = (bRtl)? gr_slot_prev_in_segment(base) : + gr_slot_next_in_segment(base); + *firstChar = gr_slot_before(base); + *lastChar = gr_slot_after(base); + } + const gr_slot * attachment = gr_slot_first_attachment(base); + while (attachment) + { + if (gr_slot_origin_X(*first) > gr_slot_origin_X(attachment)) + *first = attachment; + const gr_slot* attachmentNext = (bRtl)? + gr_slot_prev_in_segment(attachment) : gr_slot_next_in_segment(attachment); + if (attachmentNext) + { + if (*after && (gr_slot_origin_X(*after) < gr_slot_origin_X(attachmentNext))) + *after = attachmentNext; + } + else + { + *after = NULL; + } + if (gr_slot_before(attachment) < *firstChar) + *firstChar = gr_slot_before(attachment); + if (gr_slot_after(attachment) > *lastChar) + *lastChar = gr_slot_after(attachment); + if (gr_slot_first_attachment(attachment)) + findFirstClusterSlot(attachment, first, after, firstChar, lastChar, bRtl); + attachment = gr_slot_next_sibling_attachment(attachment); + } +} + +// The Graphite glyph stream is really a sequence of glyph attachment trees +// each rooted at a non-attached base glyph. fill_from walks the glyph stream, +// finds each non-attached base glyph and calls append to record them as a +// sequence of clusters. +void +GraphiteLayout::fillFrom(gr_segment * pSegment, ImplLayoutArgs &rArgs, float fScaling) +{ + bool bRtl = (rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL); + int nCharRequested = rArgs.mnEndCharPos - rArgs.mnMinCharPos; + int nChar = gr_seg_n_cinfo(pSegment); + float fMinX = gr_seg_advance_X(pSegment); + float fMaxX = 0.0f; + long nDxOffset = 0; // from dropped glyphs + int nFirstCharInCluster = 0; + int nLastCharInCluster = 0; + unsigned int nGlyphs = gr_seg_n_slots(pSegment); + mvGlyph2Char.assign(nGlyphs, -1); + mvGlyphs.reserve(nGlyphs); + + if (bRtl) + { + const gr_slot* baseSlot = gr_seg_last_slot(pSegment); + // find first base + while (baseSlot && (gr_slot_attached_to(baseSlot) != NULL)) + baseSlot = gr_slot_prev_in_segment(baseSlot); + int iChar = nChar - 1; + int iNextChar = nChar - 1; + bool reordered = false; + int nBaseGlyphIndex = 0; + // now loop over bases + while (baseSlot) + { + bool bCluster = !reordered; + const gr_slot * clusterFirst = NULL; + const gr_slot * clusterAfter = NULL; + int firstChar = -1; + int lastChar = -1; + findFirstClusterSlot(baseSlot, &clusterFirst, &clusterAfter, &firstChar, &lastChar, bRtl); + iNextChar = minimum<int>(firstChar, iNextChar); + if (bCluster) + { + nBaseGlyphIndex = mvGlyphs.size(); + mvGlyph2Char[nBaseGlyphIndex] = iChar + mnSegCharOffset; + nFirstCharInCluster = firstChar; + nLastCharInCluster = lastChar; + } + else + { + mvGlyph2Char[mvGlyphs.size()] = firstChar + mnSegCharOffset; + nFirstCharInCluster = minimum<int>(firstChar, nFirstCharInCluster); + nLastCharInCluster = maximum<int>(firstChar, nLastCharInCluster); + } + float leftBoundary = gr_slot_origin_X(clusterFirst); + float rightBoundary = (clusterAfter)? + gr_slot_origin_X(clusterAfter) : gr_seg_advance_X(pSegment); + if ( + lastChar < iChar && + (gr_cinfo_after(gr_seg_cinfo(pSegment, iChar)) > + static_cast<int>(gr_slot_index(clusterAfter))) + ) + { + reordered = true; + } + else + { + reordered = false; + iChar = iNextChar - 1; + } + if (mnSegCharOffset + nFirstCharInCluster >= mnMinCharPos && + mnSegCharOffset + nFirstCharInCluster < mnEndCharPos) + { + fMinX = minimum<float>(fMinX, leftBoundary); + fMaxX = maximum<float>(fMaxX, rightBoundary); + if (!reordered) + { + for (int i = nFirstCharInCluster; i <= nLastCharInCluster; i++) + { + if (mnSegCharOffset + i >= mnEndCharPos) + break; + // from the point of view of the dx array, the xpos is + // the origin of the first glyph of the cluster rtl + mvCharDxs[mnSegCharOffset + i - mnMinCharPos] = + static_cast<int>(leftBoundary * fScaling) + nDxOffset; + mvCharBreaks[mnSegCharOffset + i - mnMinCharPos] = gr_cinfo_break_weight(gr_seg_cinfo(pSegment, i)); + } + mvChar2BaseGlyph[mnSegCharOffset + nFirstCharInCluster - mnMinCharPos] = nBaseGlyphIndex; + } + append(pSegment, rArgs, baseSlot, rightBoundary, fScaling, + nDxOffset, bCluster, mnSegCharOffset + firstChar); + } + if (mnSegCharOffset + nLastCharInCluster < mnMinCharPos) + break; + baseSlot = gr_slot_next_sibling_attachment(baseSlot); + } + } + else + { + const gr_slot* baseSlot = gr_seg_first_slot(pSegment); + // find first base + while (baseSlot && (gr_slot_attached_to(baseSlot) != NULL)) + baseSlot = gr_slot_next_in_segment(baseSlot); + int iChar = 0; // relative to segment + int iNextChar = 0; + bool reordered = false; + int nBaseGlyphIndex = 0; + // now loop over bases + while (baseSlot) + { + bool bCluster = !reordered; + const gr_slot * clusterFirst = NULL; + const gr_slot * clusterAfter = NULL; + int firstChar = -1; + int lastChar = -1; + findFirstClusterSlot(baseSlot, &clusterFirst, &clusterAfter, &firstChar, &lastChar, bRtl); + iNextChar = maximum<int>(lastChar, iNextChar); + if (bCluster) + { + nBaseGlyphIndex = mvGlyphs.size(); + mvGlyph2Char[nBaseGlyphIndex] = iChar + mnSegCharOffset; + nFirstCharInCluster = firstChar; + nLastCharInCluster = lastChar; + } + else + { + mvGlyph2Char[mvGlyphs.size()] = firstChar + mnSegCharOffset; + nFirstCharInCluster = minimum<int>(firstChar, nFirstCharInCluster); + nLastCharInCluster = maximum<int>(lastChar, nLastCharInCluster); + } + if ( + firstChar > iChar && + (gr_cinfo_before(gr_seg_cinfo(pSegment, iChar)) > + static_cast<int>(gr_slot_index(clusterFirst))) + ) + { + reordered = true; + } + else + { + reordered = false; + iChar = iNextChar + 1; + } + float leftBoundary = gr_slot_origin_X(clusterFirst); + float rightBoundary = (clusterAfter)? + gr_slot_origin_X(clusterAfter) : gr_seg_advance_X(pSegment); + if (mnSegCharOffset + nFirstCharInCluster >= mnMinCharPos && + mnSegCharOffset + nFirstCharInCluster < mnEndCharPos) + { + fMinX = minimum<float>(fMinX, leftBoundary); + fMaxX = maximum<float>(fMaxX, rightBoundary); + if (!reordered) + { + for (int i = nFirstCharInCluster; i <= nLastCharInCluster; i++) + { + if (mnSegCharOffset + i >= mnEndCharPos) + break; + // from the point of view of the dx array, the xpos is + // the origin of the first glyph of the next cluster ltr + mvCharDxs[mnSegCharOffset + i - mnMinCharPos] = + static_cast<int>(rightBoundary * fScaling) + nDxOffset; + mvCharBreaks[mnSegCharOffset + i - mnMinCharPos] = gr_cinfo_break_weight(gr_seg_cinfo(pSegment, i)); + } + // only set mvChar2BaseGlyph for first character of cluster + mvChar2BaseGlyph[mnSegCharOffset + nFirstCharInCluster - mnMinCharPos] = nBaseGlyphIndex; + } + append(pSegment, rArgs, baseSlot, rightBoundary, fScaling, + nDxOffset, true, mnSegCharOffset + firstChar); + } + if (mnSegCharOffset + nFirstCharInCluster >= mnEndCharPos) + break; + baseSlot = gr_slot_next_sibling_attachment(baseSlot); + } + } + long nXOffset = round(fMinX * fScaling); + mnWidth = round(fMaxX * fScaling) - nXOffset + nDxOffset; + if (mnWidth < 0) + { + // This can happen when there was no base inside the range + mnWidth = 0; + } + // fill up non-base char dx with cluster widths from previous base glyph + if (bRtl) + { + if (mvCharDxs[nCharRequested-1] == -1) + mvCharDxs[nCharRequested-1] = 0; + else + mvCharDxs[nCharRequested-1] -= nXOffset; + for (int i = nCharRequested - 2; i >= 0; i--) + { + if (mvCharDxs[i] == -1) mvCharDxs[i] = mvCharDxs[i+1]; + else mvCharDxs[i] -= nXOffset; + } + } + else + { + if (mvCharDxs[0] == -1) + mvCharDxs[0] = 0; + else + mvCharDxs[0] -= nXOffset; + for (int i = 1; i < nCharRequested; i++) + { + if (mvCharDxs[i] == -1) mvCharDxs[i] = mvCharDxs[i-1]; + else mvCharDxs[i] -= nXOffset; +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"%d,%d ", (int)i, (int)mvCharDxs[i]); +#endif + } + } + // remove offset due to context if there is one + if (nXOffset != 0) + { + for (size_t i = 0; i < mvGlyphs.size(); i++) + mvGlyphs[i].maLinearPos.X() -= nXOffset; + } +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(), "fillFrom %d glyphs offset %ld width %d\n", mvGlyphs.size(), nXOffset, mnWidth); +#endif +} + +// append walks an attachment tree, flattening it, and converting it into a +// sequence of GlyphItem objects which we can later manipulate. +void +GraphiteLayout::append(gr_segment *pSeg, ImplLayoutArgs &rArgs, + const gr_slot * gi, float nextGlyphOrigin, float scaling, long & rDXOffset, + bool bIsBase, int baseChar) +{ + bool bRtl = (rArgs.mnFlags & SAL_LAYOUT_BIDI_RTL); + float nextOrigin = nextGlyphOrigin; + assert(gi); + assert(gr_slot_before(gi) <= gr_slot_after(gi)); + int firstChar = gr_slot_before(gi) + mnSegCharOffset; + assert(mvGlyphs.size() < mvGlyph2Char.size()); + if (!bIsBase) mvGlyph2Char[mvGlyphs.size()] = baseChar;//firstChar; + // is the next glyph attached or in the next cluster? + //glyph_set_range_t iAttached = gi.attachedClusterGlyphs(); + const gr_slot * pFirstAttached = gr_slot_first_attachment(gi); + if (pFirstAttached) + { + nextOrigin = gr_slot_origin_X(pFirstAttached); + } + long glyphId = gr_slot_gid(gi); + long deltaOffset = 0; + int scaledGlyphPos = round(gr_slot_origin_X(gi) * scaling); + int glyphWidth = round(nextOrigin * scaling) - scaledGlyphPos; + if (glyphWidth < 0) + glyphWidth = 0; +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"c%d g%ld,X%d W%d nX%f ", firstChar, glyphId, + (int)(gr_slot_origin_X(gi) * scaling), glyphWidth, nextOrigin * scaling); +#endif + if (glyphId == 0) + { + rArgs.NeedFallback(firstChar, bRtl); + if( (SAL_LAYOUT_FOR_FALLBACK & rArgs.mnFlags )) + { + glyphId = GF_DROPPED; + deltaOffset -= glyphWidth; + glyphWidth = 0; + } + } + else if(rArgs.mnFlags & SAL_LAYOUT_FOR_FALLBACK) + { +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"fallback c%d %x in run %d\n", firstChar, rArgs.mpStr[firstChar], + rArgs.maRuns.PosIsInAnyRun(firstChar)); +#endif + // glyphs that aren't requested for fallback will be taken from base + // layout, so mark them as dropped (should this wait until Simplify(false) is called?) + if (!rArgs.maRuns.PosIsInAnyRun(firstChar) && + in_range(firstChar, rArgs.mnMinCharPos, rArgs.mnEndCharPos)) + { + glyphId = GF_DROPPED; + deltaOffset -= glyphWidth; + glyphWidth = 0; + } + } + // append this glyph. Set the cluster flag if this glyph is attached to another + long nGlyphFlags = bIsBase ? 0 : GlyphItem::IS_IN_CLUSTER; + nGlyphFlags |= (bRtl)? GlyphItem::IS_RTL_GLYPH : 0; + GlyphItem aGlyphItem(mvGlyphs.size(), + glyphId, + Point(scaledGlyphPos + rDXOffset, + round((-gr_slot_origin_Y(gi) * scaling))), + nGlyphFlags, + glyphWidth); + if (glyphId != static_cast<long>(GF_DROPPED)) + aGlyphItem.mnOrigWidth = round(gr_slot_advance_X(gi, mpFace, mpFont) * scaling); + mvGlyphs.push_back(aGlyphItem); + + // update the offset if this glyph was dropped + rDXOffset += deltaOffset; + + // Recursively append all the attached glyphs. + for (const gr_slot * agi = gr_slot_first_attachment(gi); agi != NULL; + agi = gr_slot_next_sibling_attachment(agi)) + { + if (gr_slot_next_sibling_attachment(agi) == NULL) + append(pSeg, rArgs, agi, nextGlyphOrigin, scaling, rDXOffset, + false, baseChar); + else + append(pSeg, rArgs, agi, gr_slot_origin_X(gr_slot_next_sibling_attachment(agi)), + scaling, rDXOffset, false, baseChar); + } +} + +// +// An implementation of the SalLayout interface to enable Graphite enabled fonts to be used. +// +GraphiteLayout::GraphiteLayout(const gr_face * face, gr_font * font, + const grutils::GrFeatureParser * pFeatures) throw() + : mpFace(face), + mpFont(font), + mnWidth(0), + mfScaling(1.0), + mpFeatures(pFeatures) +{ + +} + +GraphiteLayout::~GraphiteLayout() throw() +{ + clear(); + // the features and font are owned by the platform layers + mpFeatures = NULL; + mpFont = NULL; +} + +void GraphiteLayout::clear() +{ + // Destroy the segment and text source from any previous invocation of + // LayoutText + mvGlyphs.clear(); + mvCharDxs.clear(); + mvChar2BaseGlyph.clear(); + mvGlyph2Char.clear(); + + // Reset the state to the empty state. + mnWidth = 0; + // Don't reset the scaling, because it is set before LayoutText +} + +// This method shouldn't be called on windows, since it needs the dc reset +bool GraphiteLayout::LayoutText(ImplLayoutArgs & rArgs) +{ + gr_segment * pSegment = NULL; + bool success = true; + if (rArgs.mnMinCharPos < rArgs.mnEndCharPos) + { + pSegment = CreateSegment(rArgs); + if (!pSegment) + return false; + success = LayoutGlyphs(rArgs, pSegment); + if (pSegment) + { + gr_seg_destroy(pSegment); + pSegment = NULL; + } + } + else + { + clear(); + } + return success; +} + + +gr_segment * GraphiteLayout::CreateSegment(ImplLayoutArgs& rArgs) +{ + assert(rArgs.mnLength >= 0); + + gr_segment * pSegment = NULL; + + // Set the SalLayouts values to be the inital ones. + SalLayout::AdjustLayout(rArgs); + // TODO check if this is needed + if (mnUnitsPerPixel > 1) + mfScaling = 1.0f / mnUnitsPerPixel; + + // Clear out any previous buffers + clear(); + bool bRtl = mnLayoutFlags & SAL_LAYOUT_BIDI_RTL; + try + { + // Don't set RTL if font doesn't support it otherwise it forces rtl on + // everything + //if (bRtl && (mrFont.getSupportedScriptDirections() & gr::kfsdcHorizRtl)) + // maLayout.setRightToLeft(bRtl); + + // Context is often needed beyond the specified end, however, we don't + // want it if there has been a direction change, since it is hard + // to tell between reordering within one direction and multi-directional + // text. Extra context, can also cause problems with ligatures stradling + // a hyphenation point, so disable if CTL is disabled. + mnSegCharOffset = rArgs.mnMinCharPos; + int limit = rArgs.mnEndCharPos; + if (!(SAL_LAYOUT_COMPLEX_DISABLED & rArgs.mnFlags)) + { + const int nSegCharMin = maximum<int>(0, mnMinCharPos - EXTRA_CONTEXT_LENGTH); + const int nSegCharLimit = minimum(rArgs.mnLength, mnEndCharPos + EXTRA_CONTEXT_LENGTH); + if (nSegCharMin < mnSegCharOffset) + { + int sameDirEnd = findSameDirLimit(rArgs.mpStr + nSegCharMin, + rArgs.mnEndCharPos - nSegCharMin, bRtl); + if (sameDirEnd == rArgs.mnEndCharPos) + mnSegCharOffset = nSegCharMin; + } + if (nSegCharLimit > limit) + { + limit += findSameDirLimit(rArgs.mpStr + rArgs.mnEndCharPos, + nSegCharLimit - rArgs.mnEndCharPos, bRtl); + } + } + + if (mpFeatures) + pSegment = gr_make_seg(mpFont, mpFace, 0, mpFeatures->values(), gr_utf16, + rArgs.mpStr + mnSegCharOffset, limit - mnSegCharOffset, bRtl); + else + pSegment = gr_make_seg(mpFont, mpFace, 0, NULL, gr_utf16, + rArgs.mpStr + mnSegCharOffset, limit - mnSegCharOffset, bRtl); + + //pSegment = new gr::RangeSegment((gr::Font *)&mrFont, mpTextSrc, &maLayout, mnMinCharPos, limit); + if (pSegment != NULL) + { +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"Gr::LayoutText %d-%d, context %d,len%d rtl%d scaling %f\n", rArgs.mnMinCharPos, + rArgs.mnEndCharPos, limit, rArgs.mnLength, bRtl, mfScaling); +#endif + } + else + { +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(), "Gr::LayoutText failed: "); + for (int i = mnMinCharPos; i < limit; i++) + { + fprintf(grLog(), "%04x ", rArgs.mpStr[i]); + } + fprintf(grLog(), "\n"); +#endif + clear(); + return NULL; + } + } + catch (...) + { + clear(); // destroy the text source and any partially built segments. + return NULL; + } + return pSegment; +} + +bool GraphiteLayout::LayoutGlyphs(ImplLayoutArgs& rArgs, gr_segment * pSegment) +{ + // Calculate the initial character dxs. + mvCharDxs.assign(mnEndCharPos - mnMinCharPos, -1); + mvChar2BaseGlyph.assign(mnEndCharPos - mnMinCharPos, -1); + mvCharBreaks.assign(mnEndCharPos - mnMinCharPos, 0); + mnWidth = 0; + if (mvCharDxs.size() > 0) + { + // Discover all the clusters. + try + { + bool bRtl = mnLayoutFlags & SAL_LAYOUT_BIDI_RTL; + fillFrom(pSegment, rArgs, mfScaling); + + if (bRtl) + { + // not needed for adjacent differences, but for mouse clicks to char + std::transform(mvCharDxs.begin(), mvCharDxs.end(), mvCharDxs.begin(), + std::bind1st(std::minus<long>(), mnWidth)); + // fixup last dx to ensure it always equals the width + mvCharDxs[mvCharDxs.size() - 1] = mnWidth; + } + } + catch (std::exception e) + { +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"LayoutGlyphs failed %s\n", e.what()); +#endif + return false; + } + catch (...) + { +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"LayoutGlyphs failed with exception"); +#endif + return false; + } + } + else + { + mnWidth = 0; + } + return true; +} + +int GraphiteLayout::GetTextBreak(long maxmnWidth, long char_extra, int factor) const +{ +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"Gr::GetTextBreak c[%d-%d) maxWidth %ld char extra %ld factor %d\n", + mnMinCharPos, mnEndCharPos, maxmnWidth, char_extra, factor); +#endif + + // return quickly if this segment is narrower than the target width + if (maxmnWidth > mnWidth * factor + char_extra * (mnEndCharPos - mnMinCharPos - 1)) + return STRING_LEN; + + long nWidth = mvCharDxs[0] * factor; + long wLastBreak = 0; + int nLastBreak = -1; + int nEmergency = -1; + for (size_t i = 1; i < mvCharDxs.size(); i++) + { + nWidth += char_extra; + if (nWidth > maxmnWidth) break; + if (mvChar2BaseGlyph[i] != -1) + { + if ( + (mvCharBreaks[i] > -25 || (mvCharBreaks[i-1] > 0 && mvCharBreaks[i-1] < 25)) && + (mvCharBreaks[i-1] < 25 || (mvCharBreaks[i] < 0 && mvCharBreaks[i] > -25)) + ) + { + nLastBreak = static_cast<int>(i); + wLastBreak = nWidth; + } + nEmergency = static_cast<int>(i); + } + nWidth += (mvCharDxs[i] - mvCharDxs[i-1]) * factor; + } + int nBreak = mnMinCharPos; + if (wLastBreak > 9 * maxmnWidth / 10) + nBreak += nLastBreak; + else + if (nEmergency > -1) + nBreak += nEmergency; + +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(), "Gr::GetTextBreak break after %d, weights(%d, %d)\n", nBreak - mnMinCharPos, mvCharBreaks[nBreak - mnMinCharPos], mvCharBreaks[nBreak - mnMinCharPos - 1]); +#endif + + if (nBreak > mnEndCharPos) nBreak = STRING_LEN; + else if (nBreak < mnMinCharPos) nBreak = mnMinCharPos; + return nBreak; +} + +long GraphiteLayout::FillDXArray( sal_Int32* pDXArray ) const +{ + if (mnEndCharPos == mnMinCharPos) + // Then we must be zero width! + return 0; + + if (pDXArray) + { + for (size_t i = 0; i < mvCharDxs.size(); i++) + { + assert( (mvChar2BaseGlyph[i] == -1) || + ((signed)(mvChar2BaseGlyph[i]) < (signed)mvGlyphs.size())); + if (mvChar2BaseGlyph[i] != -1 && + mvGlyphs[mvChar2BaseGlyph[i]].mnGlyphIndex == GF_DROPPED) + { + // when used in MultiSalLayout::GetTextBreak dropped glyphs + // must have zero width + pDXArray[i] = 0; + } + else + { + pDXArray[i] = mvCharDxs[i]; + if (i > 0) pDXArray[i] -= mvCharDxs[i-1]; + } +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"%d,%d,%d ", (int)i, (int)mvCharDxs[i], pDXArray[i]); +#endif + } + //std::adjacent_difference(mvCharDxs.begin(), mvCharDxs.end(), pDXArray); + //for (size_t i = 0; i < mvCharDxs.size(); i++) + // fprintf(grLog(),"%d,%d,%d ", (int)i, (int)mvCharDxs[i], pDXArray[i]); + //fprintf(grLog(),"FillDX %ld,%d\n", mnWidth, std::accumulate(pDXArray, pDXArray + mvCharDxs.size(), 0)); + } +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"FillDXArray %d-%d=%ld\n", mnMinCharPos, mnEndCharPos, mnWidth); +#endif + return mnWidth; +} + +void GraphiteLayout::AdjustLayout(ImplLayoutArgs& rArgs) +{ + SalLayout::AdjustLayout(rArgs); + if(rArgs.mpDXArray) + { + std::vector<int> vDeltaWidths(mvGlyphs.size(), 0); + ApplyDXArray(rArgs, vDeltaWidths); + + if( (mnLayoutFlags & SAL_LAYOUT_BIDI_RTL) && + !(rArgs.mnFlags & SAL_LAYOUT_FOR_FALLBACK) ) + { + // check if this is a kashida script + bool bKashidaScript = false; + for (int i = rArgs.mnMinCharPos; i < rArgs.mnEndCharPos; i++) + { + UErrorCode aStatus = U_ZERO_ERROR; + UScriptCode scriptCode = uscript_getScript(rArgs.mpStr[i], &aStatus); + if (scriptCode == USCRIPT_ARABIC || scriptCode == USCRIPT_SYRIAC) + { + bKashidaScript = true; + break; + } + } + int nKashidaWidth = 0; + int nKashidaIndex = getKashidaGlyph(nKashidaWidth); + if( nKashidaIndex != 0 && bKashidaScript) + { + kashidaJustify( vDeltaWidths, nKashidaIndex, nKashidaWidth ); + } + } + } + else if (rArgs.mnLayoutWidth > 0) + { +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(), "AdjustLayout width %ld=>%ld\n", mnWidth, rArgs.mnLayoutWidth); +#endif + expandOrCondense(rArgs); + } +} + +void GraphiteLayout::expandOrCondense(ImplLayoutArgs &rArgs) +{ + int nDeltaWidth = rArgs.mnLayoutWidth - mnWidth; + if (nDeltaWidth > 0) // expand, just expand between clusters + { + // NOTE: for expansion we can use base glyphs (which have IsClusterStart set) + // even though they may have been reordered in which case they will have + // been placed in a bigger cluster for other purposes. + int nClusterCount = 0; + for (size_t j = 0; j < mvGlyphs.size(); j++) + { + if (mvGlyphs[j].IsClusterStart()) + { + ++nClusterCount; + } + } + if (nClusterCount > 1) + { + float fExtraPerCluster = static_cast<float>(nDeltaWidth) / static_cast<float>(nClusterCount - 1); + int nCluster = 0; + int nOffset = 0; + for (size_t i = 0; i < mvGlyphs.size(); i++) + { + if (mvGlyphs[i].IsClusterStart()) + { + nOffset = static_cast<int>(fExtraPerCluster * nCluster); + int nCharIndex = mvGlyph2Char[i]; + assert(nCharIndex > -1); + mvCharDxs[nCharIndex-mnMinCharPos] += nOffset; + // adjust char dxs for rest of characters in cluster + while (++nCharIndex < static_cast<int>(mvGlyph2Char.size())) + { + int nChar2Base = mvChar2BaseGlyph[nCharIndex-mnMinCharPos]; + if (nChar2Base == -1 || nChar2Base == static_cast<int>(i)) + mvCharDxs[nCharIndex-mnMinCharPos] += nOffset; + else + break; + } + ++nCluster; + } + mvGlyphs[i].maLinearPos.X() += nOffset; + } + } + } + else if (nDeltaWidth < 0)// condense - apply a factor to all glyph positions + { + if (mvGlyphs.size() == 0) return; + Glyphs::iterator iLastGlyph = mvGlyphs.begin() + (mvGlyphs.size() - 1); + // position last glyph using original width + float fXFactor = static_cast<float>(rArgs.mnLayoutWidth - iLastGlyph->mnOrigWidth) / static_cast<float>(iLastGlyph->maLinearPos.X()); +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(), "Condense by factor %f last x%ld\n", fXFactor, iLastGlyph->maLinearPos.X()); +#endif + if (fXFactor < 0) + return; // probably a bad mnOrigWidth value + iLastGlyph->maLinearPos.X() = rArgs.mnLayoutWidth - iLastGlyph->mnOrigWidth; + Glyphs::iterator iGlyph = mvGlyphs.begin(); + while (iGlyph != iLastGlyph) + { + iGlyph->maLinearPos.X() = static_cast<int>(static_cast<float>(iGlyph->maLinearPos.X()) * fXFactor); + ++iGlyph; + } + for (size_t i = 0; i < mvCharDxs.size(); i++) + { + mvCharDxs[i] = static_cast<int>(fXFactor * static_cast<float>(mvCharDxs[i])); + } + } + mnWidth = rArgs.mnLayoutWidth; +} + +void GraphiteLayout::ApplyDXArray(ImplLayoutArgs &args, std::vector<int> & rDeltaWidth) +{ + const size_t nChars = args.mnEndCharPos - args.mnMinCharPos; + if (nChars == 0) return; + +#ifdef GRLAYOUT_DEBUG + for (size_t iDx = 0; iDx < mvCharDxs.size(); iDx++) + fprintf(grLog(),"%d,%d,%d ", (int)iDx, (int)mvCharDxs[iDx], args.mpDXArray[iDx]); + fprintf(grLog(),"ApplyDx\n"); +#endif + bool bRtl = mnLayoutFlags & SAL_LAYOUT_BIDI_RTL; + int nXOffset = 0; + if (bRtl) + { + nXOffset = args.mpDXArray[nChars - 1] - mvCharDxs[nChars - 1]; + } + int nPrevClusterGlyph = (bRtl)? (signed)mvGlyphs.size() : -1; + int nPrevClusterLastChar = -1; + for (size_t i = 0; i < nChars; i++) + { + int nChar2Base = mvChar2BaseGlyph[i]; + if ((nChar2Base > -1) && (nChar2Base != nPrevClusterGlyph)) + { + assert((nChar2Base > -1) && (nChar2Base < (signed)mvGlyphs.size())); + GlyphItem & gi = mvGlyphs[nChar2Base]; + if (!gi.IsClusterStart()) + continue; + + // find last glyph of this cluster + size_t j = i + 1; + int nLastChar = i; + int nLastGlyph = nChar2Base; + int nChar2BaseJ = -1; + for (; j < nChars; j++) + { + nChar2BaseJ = mvChar2BaseGlyph[j]; + assert((nChar2BaseJ >= -1) && (nChar2BaseJ < (signed)mvGlyphs.size())); + if (nChar2BaseJ != -1 ) + { + nLastGlyph = nChar2BaseJ + ((bRtl)? +1 : -1); + nLastChar = j - 1; + break; + } + } + if (nLastGlyph < 0) + { + nLastGlyph = nChar2Base; + } + // Its harder to find the last glyph rtl, since the first of + // cluster is still on the left so we need to search towards + // the previous cluster to the right + if (bRtl) + { + nLastGlyph = nChar2Base; + while (nLastGlyph + 1 < (signed)mvGlyphs.size() && + !mvGlyphs[nLastGlyph+1].IsClusterStart()) + { + ++nLastGlyph; + } + } + if (j == nChars) + { + nLastChar = nChars - 1; + if (!bRtl) nLastGlyph = mvGlyphs.size() - 1; + } + int nBaseCount = 0; + // count bases within cluster - may be more than 1 with reordering + for (int k = nChar2Base; k <= nLastGlyph; k++) + { + if (mvGlyphs[k].IsClusterStart()) ++nBaseCount; + } + assert((nLastChar > -1) && (nLastChar < (signed)nChars)); + long nNewClusterWidth = args.mpDXArray[nLastChar]; + long nOrigClusterWidth = mvCharDxs[nLastChar]; + long nDGlyphOrigin = 0; + if (nPrevClusterLastChar > - 1) + { + assert(nPrevClusterLastChar < (signed)nChars); + nNewClusterWidth -= args.mpDXArray[nPrevClusterLastChar]; + nOrigClusterWidth -= mvCharDxs[nPrevClusterLastChar]; + nDGlyphOrigin = args.mpDXArray[nPrevClusterLastChar] - mvCharDxs[nPrevClusterLastChar]; + } + long nDWidth = nNewClusterWidth - nOrigClusterWidth; +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(), "c%lu last glyph %d/%lu\n", i, nLastGlyph, mvGlyphs.size()); +#endif + assert((nLastGlyph > -1) && (nLastGlyph < (signed)mvGlyphs.size())); + mvGlyphs[nLastGlyph].mnNewWidth += nDWidth; + if (gi.mnGlyphIndex != GF_DROPPED) + mvGlyphs[nLastGlyph].mnNewWidth += nDWidth; + else + nDGlyphOrigin += nDWidth; + long nDOriginPerBase = (nBaseCount > 0)? nDWidth / nBaseCount : 0; + nBaseCount = -1; + // update glyph positions + if (bRtl) + { + for (int n = nChar2Base; n <= nLastGlyph; n++) + { + if (mvGlyphs[n].IsClusterStart()) ++nBaseCount; + assert((n > - 1) && (n < (signed)mvGlyphs.size())); + mvGlyphs[n].maLinearPos.X() += -(nDGlyphOrigin + nDOriginPerBase * nBaseCount) + nXOffset; + } + } + else + { + for (int n = nChar2Base; n <= nLastGlyph; n++) + { + if (mvGlyphs[n].IsClusterStart()) ++nBaseCount; + assert((n > - 1) && (n < (signed)mvGlyphs.size())); + mvGlyphs[n].maLinearPos.X() += nDGlyphOrigin + (nDOriginPerBase * nBaseCount) + nXOffset; + } + } + rDeltaWidth[nChar2Base] = nDWidth; +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"c%d g%d-%d dW%ld-%ld=%ld dX%ld x%ld\t", (int)i, nChar2Base, nLastGlyph, nNewClusterWidth, nOrigClusterWidth, nDWidth, nDGlyphOrigin, mvGlyphs[nChar2Base].maLinearPos.X()); +#endif + nPrevClusterGlyph = nChar2Base; + nPrevClusterLastChar = nLastChar; + i = nLastChar; + } + } + // Update the dx vector with the new values. + std::copy(args.mpDXArray, args.mpDXArray + nChars, + mvCharDxs.begin() + (args.mnMinCharPos - mnMinCharPos)); +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"ApplyDx %d(%ld)\n", args.mpDXArray[nChars - 1], mnWidth); +#endif + mnWidth = args.mpDXArray[nChars - 1]; +} + +void GraphiteLayout::kashidaJustify(std::vector<int>& rDeltaWidths, sal_GlyphId nKashidaIndex, int nKashidaWidth) +{ + // skip if the kashida glyph in the font looks suspicious + if( nKashidaWidth <= 0 ) + return; + + // calculate max number of needed kashidas + Glyphs::iterator i = mvGlyphs.begin(); + int nKashidaCount = 0; + int nOrigGlyphIndex = -1; + int nGlyphIndex = -1; + while (i != mvGlyphs.end()) + { + nOrigGlyphIndex++; + nGlyphIndex++; + // only inject kashidas in RTL contexts + if( !(*i).IsRTLGlyph() ) + { + ++i; + continue; + } + // no kashida-injection for blank justified expansion either + if( IsSpacingGlyph( (*i).mnGlyphIndex ) ) + { + ++i; + continue; + } + // calculate gap, ignore if too small + int nGapWidth = rDeltaWidths[nOrigGlyphIndex]; + // worst case is one kashida even for mini-gaps + if( 3 * nGapWidth < nKashidaWidth ) + { + ++i; + continue; + } + nKashidaCount = 1 + (nGapWidth / nKashidaWidth); +#ifdef GRLAYOUT_DEBUG + printf("inserting %d kashidas at %u\n", nKashidaCount, (*i).mnGlyphIndex); +#endif + GlyphItem glyphItem = *i; + Point aPos(0, 0); + aPos.X() = (*i).maLinearPos.X(); + GlyphItem newGi(glyphItem.mnCharPos, nKashidaIndex, aPos, + GlyphItem::IS_IN_CLUSTER|GlyphItem::IS_RTL_GLYPH, nKashidaWidth); + mvGlyphs.reserve(mvGlyphs.size() + nKashidaCount); + i = mvGlyphs.begin() + nGlyphIndex; + mvGlyphs.insert(i, nKashidaCount, newGi); + i = mvGlyphs.begin() + nGlyphIndex; + nGlyphIndex += nKashidaCount; + // now fix up the kashida positions + for (int j = 0; j < nKashidaCount; j++) + { + (*(i)).maLinearPos.X() -= nGapWidth; + nGapWidth -= nKashidaWidth; + ++i; + } + + // fixup rightmost kashida for gap remainder + if( nGapWidth < 0 ) + { + if( nKashidaCount <= 1 ) + nGapWidth /= 2; // for small gap move kashida to middle + (*(i-1)).mnNewWidth += nGapWidth; // adjust kashida width to gap width + (*(i-1)).maLinearPos.X() += nGapWidth; + } + + (*i).mnNewWidth = (*i).mnOrigWidth; + ++i; + } + +} + +void GraphiteLayout::GetCaretPositions( int nArraySize, sal_Int32* pCaretXArray ) const +{ + // For each character except the last discover the caret positions + // immediatly before and after that character. + // This is used for underlines in the GUI amongst other things. + // It may be used from MultiSalLayout, in which case it must take into account + // glyphs that have been moved. + std::fill(pCaretXArray, pCaretXArray + nArraySize, -1); + // the layout method doesn't modify the layout even though it isn't + // const in the interface + bool bRtl = (mnLayoutFlags & SAL_LAYOUT_BIDI_RTL);//const_cast<GraphiteLayout*>(this)->maLayout.rightToLeft(); + int prevBase = -1; + long prevClusterWidth = 0; + for (int i = 0, nCharSlot = 0; i < nArraySize && nCharSlot < static_cast<int>(mvCharDxs.size()); ++nCharSlot, i+=2) + { + if (mvChar2BaseGlyph[nCharSlot] != -1) + { + int nChar2Base = mvChar2BaseGlyph[nCharSlot]; + assert((nChar2Base > -1) && (nChar2Base < (signed)mvGlyphs.size())); + GlyphItem gi = mvGlyphs[nChar2Base]; + if (gi.mnGlyphIndex == GF_DROPPED) + { + continue; + } + int nCluster = nChar2Base; + long origClusterWidth = gi.mnNewWidth; + long nMin = gi.maLinearPos.X(); + long nMax = gi.maLinearPos.X() + gi.mnNewWidth; + // attached glyphs are always stored after their base rtl or ltr + while (++nCluster < static_cast<int>(mvGlyphs.size()) && + !mvGlyphs[nCluster].IsClusterStart()) + { + origClusterWidth += mvGlyphs[nCluster].mnNewWidth; + if (mvGlyph2Char[nCluster] == nCharSlot) + { + nMin = minimum(nMin, mvGlyphs[nCluster].maLinearPos.X()); + nMax = maximum(nMax, mvGlyphs[nCluster].maLinearPos.X() + mvGlyphs[nCluster].mnNewWidth); + } + } + if (bRtl) + { + pCaretXArray[i+1] = nMin; + pCaretXArray[i] = nMax; + } + else + { + pCaretXArray[i] = nMin; + pCaretXArray[i+1] = nMax; + } + prevBase = nChar2Base; + prevClusterWidth = origClusterWidth; + } + else if (prevBase > -1) + { + // this could probably be improved + assert((prevBase > -1) && (prevBase < (signed)mvGlyphs.size())); + GlyphItem gi = mvGlyphs[prevBase]; + int nGlyph = prevBase + 1; + // try to find a better match, otherwise default to complete cluster + for (; nGlyph < static_cast<int>(mvGlyphs.size()) && + !mvGlyphs[nGlyph].IsClusterStart(); nGlyph++) + { + if (mvGlyph2Char[nGlyph] == nCharSlot) + { + gi = mvGlyphs[nGlyph]; + break; + } + } + // if no match position at end of cluster + if (nGlyph == static_cast<int>(mvGlyphs.size()) || + mvGlyphs[nGlyph].IsClusterStart()) + { + if (bRtl) + { + pCaretXArray[i+1] = gi.maLinearPos.X(); + pCaretXArray[i] = gi.maLinearPos.X(); + } + else + { + pCaretXArray[i] = gi.maLinearPos.X() + prevClusterWidth; + pCaretXArray[i+1] = gi.maLinearPos.X() + prevClusterWidth; + } + } + else + { + if (bRtl) + { + pCaretXArray[i+1] = gi.maLinearPos.X(); + pCaretXArray[i] = gi.maLinearPos.X() + gi.mnNewWidth; + } + else + { + pCaretXArray[i] = gi.maLinearPos.X(); + pCaretXArray[i+1] = gi.maLinearPos.X() + gi.mnNewWidth; + } + } + } + else + { + pCaretXArray[i] = pCaretXArray[i+1] = 0; + } +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"%d,%d-%d\t", nCharSlot, pCaretXArray[i], pCaretXArray[i+1]); +#endif + } +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"\n"); +#endif +} + +// GetNextGlyphs returns a contiguous sequence of glyphs that can be +// rendered together. It should never return a dropped glyph. +// The glyph_slot returned should be the index of the next visible +// glyph after the last glyph returned by this call. +// The char_index array should be filled with the characters corresponding +// to each glyph returned. +// glyph_adv array should be a virtual width such that if successive +// glyphs returned by this method are added one after the other they +// have the correct spacing. +// The logic in this method must match that expected in MultiSalLayout which +// is used when glyph fallback is in operation. +int GraphiteLayout::GetNextGlyphs( int length, sal_GlyphId * glyph_out, + ::Point & aPosOut, int &glyph_slot, sal_Int32 * glyph_adv, int *char_index) const +{ + // Sanity check on the slot index. + if (glyph_slot >= signed(mvGlyphs.size())) + { + glyph_slot = mvGlyphs.size(); + return 0; + } + assert(glyph_slot >= 0); + // Find the first glyph in the substring. + for (; glyph_slot < signed(mvGlyphs.size()) && + ((mvGlyphs.begin() + glyph_slot)->mnGlyphIndex == GF_DROPPED); + ++glyph_slot) {}; + + // Update the length + const int nGlyphSlotEnd = minimum(size_t(glyph_slot + length), mvGlyphs.size()); + + // We're all out of glyphs here. + if (glyph_slot == nGlyphSlotEnd) + { + return 0; + } + + // Find as many glyphs as we can which can be drawn in one go. + Glyphs::const_iterator glyph_itr = mvGlyphs.begin() + glyph_slot; + const int glyph_slot_begin = glyph_slot; + const int initial_y_pos = glyph_itr->maLinearPos.Y(); + + // Set the position to the position of the start glyph. + ::Point aStartPos = glyph_itr->maLinearPos; + //aPosOut = glyph_itr->maLinearPos; + aPosOut = GetDrawPosition(aStartPos); + + for (;;) // Forever + { + // last index of the range from glyph_to_chars does not include this glyph + if (char_index) + { + if (glyph_slot >= (signed)mvGlyph2Char.size()) + { + *char_index++ = mnMinCharPos + mvCharDxs.size(); + } + else + { + assert(glyph_slot > -1); + if (mvGlyph2Char[glyph_slot] == -1) + *char_index++ = mnMinCharPos + mvCharDxs.size(); + else + *char_index++ = mvGlyph2Char[glyph_slot]; + } + } + // Copy out this glyphs data. + ++glyph_slot; + *glyph_out++ = glyph_itr->mnGlyphIndex; + + // Find the actual advance - this must be correct if called from + // MultiSalLayout::AdjustLayout which requests one glyph at a time. + const long nGlyphAdvance = (glyph_slot == static_cast<int>(mvGlyphs.size()))? + glyph_itr->mnNewWidth : + ((glyph_itr+1)->maLinearPos.X() - glyph_itr->maLinearPos.X()); + +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"GetNextGlyphs g%d gid%d c%d x%ld,%ld adv%ld, pos %ld,%ld\n", + glyph_slot - 1, glyph_itr->mnGlyphIndex, + mvGlyph2Char[glyph_slot-1], glyph_itr->maLinearPos.X(), glyph_itr->maLinearPos.Y(), nGlyphAdvance, + aPosOut.X(), aPosOut.Y()); +#endif + + if (glyph_adv) // If we are returning advance store it. + *glyph_adv++ = nGlyphAdvance; + else // Stop when next advance is unexpected. + if (glyph_itr->mnOrigWidth != nGlyphAdvance) break; + + // Have fetched all the glyphs we need to + if (glyph_slot == nGlyphSlotEnd) + break; + + ++glyph_itr; + // Stop when next y position is unexpected. + if (initial_y_pos != glyph_itr->maLinearPos.Y()) + break; + + // Stop if glyph dropped + if (glyph_itr->mnGlyphIndex == GF_DROPPED) + break; + } + int numGlyphs = glyph_slot - glyph_slot_begin; + // move the next glyph_slot to a glyph that hasn't been dropped + while (glyph_slot < static_cast<int>(mvGlyphs.size()) && + (mvGlyphs.begin() + glyph_slot)->mnGlyphIndex == GF_DROPPED) + ++glyph_slot; + return numGlyphs; +} + +void GraphiteLayout::MoveGlyph( int nGlyphIndex, long nNewPos ) +{ + // TODO it might be better to actualy implement simplify properly, but this + // needs to be done carefully so the glyph/char maps are maintained + // If a glyph has been dropped then it wasn't returned by GetNextGlyphs, so + // the index here may be wrong + while ((mvGlyphs[nGlyphIndex].mnGlyphIndex == GF_DROPPED) && + (nGlyphIndex < (signed)mvGlyphs.size())) + { + nGlyphIndex++; + } + const long dx = nNewPos - mvGlyphs[nGlyphIndex].maLinearPos.X(); + + if (dx == 0) return; + // GenericSalLayout only changes maLinearPos, mvCharDxs doesn't change +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"Move %d (%ld,%ld) c%d by %ld\n", nGlyphIndex, mvGlyphs[nGlyphIndex].maLinearPos.X(), nNewPos, mvGlyph2Char[nGlyphIndex], dx); +#endif + for (size_t gi = nGlyphIndex; gi < mvGlyphs.size(); gi++) + { + mvGlyphs[gi].maLinearPos.X() += dx; + } + // width does need to be updated for correct fallback + mnWidth += dx; +} + +void GraphiteLayout::DropGlyph( int nGlyphIndex ) +{ + if(nGlyphIndex >= signed(mvGlyphs.size())) + return; + + GlyphItem & glyph = mvGlyphs[nGlyphIndex]; + glyph.mnGlyphIndex = GF_DROPPED; +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"Dropped %d\n", nGlyphIndex); +#endif +} + +void GraphiteLayout::Simplify( bool isBaseLayout ) +{ + const sal_GlyphId dropMarker = isBaseLayout ? GF_DROPPED : 0; + + Glyphs::iterator gi = mvGlyphs.begin(); + // TODO check whether we need to adjust positions here + // MultiSalLayout seems to move the glyphs itself, so it may not be needed. + long deltaX = 0; + while (gi != mvGlyphs.end()) + { + if (gi->mnGlyphIndex == dropMarker) + { + deltaX += gi->mnNewWidth; + gi->mnNewWidth = 0; + } + else + { + deltaX = 0; + } + ++gi; + } +#ifdef GRLAYOUT_DEBUG + fprintf(grLog(),"Simplify base%d dx=%ld newW=%ld\n", isBaseLayout, deltaX, mnWidth - deltaX); +#endif + // discard width from trailing dropped glyphs, but not those in the middle + mnWidth -= deltaX; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/glyphs/graphite_serverfont.cxx b/vcl/source/glyphs/graphite_serverfont.cxx new file mode 100644 index 000000000000..ec1388d9bb29 --- /dev/null +++ b/vcl/source/glyphs/graphite_serverfont.cxx @@ -0,0 +1,164 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +// We need this to enable namespace support in libgrengine headers. +#define GR_NAMESPACE + +// Header files +// + +// Platform +#include <i18npool/mslangid.hxx> +#include <vcl/sallayout.hxx> +// Module +#include "gcach_ftyp.hxx" +#include <vcl/glyphcache.hxx> +#include <vcl/graphite_features.hxx> +//#include "graphite_textsrc.hxx" +#include <vcl/graphite_serverfont.hxx> + +#ifndef WNT + +float freetypeServerFontAdvance(const void* appFontHandle, gr_uint16 glyphId) +{ + FreetypeServerFont * pServerFont = + const_cast<FreetypeServerFont*> + (reinterpret_cast<const FreetypeServerFont*>(appFontHandle)); + if (pServerFont) + { + return static_cast<float>(pServerFont->GetGlyphMetric(glyphId).GetCharWidth()); + } + return .0f; +} + +// +// An implementation of the GraphiteLayout interface to enable Graphite enabled fonts to be used. +// + +GraphiteServerFontLayout::GraphiteServerFontLayout(ServerFont & rServerFont) throw() + : ServerFontLayout(rServerFont), + maImpl(dynamic_cast<FreetypeServerFont&>(rServerFont).GetGraphiteFace()->face(), + rServerFont), + mpFeatures(NULL) +{ + FreetypeServerFont& rFTServerFont = dynamic_cast<FreetypeServerFont&>(rServerFont); + gr_font * pFont = rFTServerFont.GetGraphiteFace()->font(rServerFont.GetFontSelData().mnHeight); + if (!pFont) + { + pFont = gr_make_font_with_advance_fn( + // need to use mnHeight here, mfExactHeight can give wrong values + static_cast<float>(rServerFont.GetFontSelData().mnHeight), + &rFTServerFont, + freetypeServerFontAdvance, + rFTServerFont.GetGraphiteFace()->face()); + rFTServerFont.GetGraphiteFace()->addFont(rServerFont.GetFontSelData().mnHeight, pFont); + } + maImpl.SetFont(pFont); + rtl::OString aLang(""); + if (rServerFont.GetFontSelData().meLanguage != LANGUAGE_DONTKNOW) + { + aLang = MsLangId::convertLanguageToIsoByteString( + rServerFont.GetFontSelData().meLanguage ); + } + rtl::OString name = rtl::OUStringToOString( + rServerFont.GetFontSelData().maTargetName, RTL_TEXTENCODING_UTF8 ); +#ifdef DEBUG + printf("GraphiteServerFontLayout %lx %s size %d %f\n", (long unsigned int)this, name.getStr(), + rFTServerFont.GetMetricsFT().x_ppem, + rServerFont.GetFontSelData().mfExactHeight); +#endif + sal_Int32 nFeat = name.indexOf(grutils::GrFeatureParser::FEAT_PREFIX) + 1; + if (nFeat > 0) + { + rtl::OString aFeat = name.copy(nFeat, name.getLength() - nFeat); + mpFeatures = new grutils::GrFeatureParser( + rFTServerFont.GetGraphiteFace()->face(), aFeat, aLang); +#ifdef DEBUG + if (mpFeatures) + printf("GraphiteServerFontLayout %s/%s/%s %x language %d features %d errors\n", + rtl::OUStringToOString( rServerFont.GetFontSelData().maName, + RTL_TEXTENCODING_UTF8 ).getStr(), + rtl::OUStringToOString( rServerFont.GetFontSelData().maTargetName, + RTL_TEXTENCODING_UTF8 ).getStr(), + rtl::OUStringToOString( rServerFont.GetFontSelData().maSearchName, + RTL_TEXTENCODING_UTF8 ).getStr(), + rServerFont.GetFontSelData().meLanguage, + (int)mpFeatures->numFeatures(), mpFeatures->parseErrors()); +#endif + } + else + { + mpFeatures = new grutils::GrFeatureParser( + rFTServerFont.GetGraphiteFace()->face(), aLang); + } + maImpl.SetFeatures(mpFeatures); +} + +GraphiteServerFontLayout::~GraphiteServerFontLayout() throw() +{ + delete mpFeatures; + mpFeatures = NULL; +} + +bool GraphiteServerFontLayout::IsGraphiteEnabledFont(ServerFont * pServerFont) +{ + FreetypeServerFont * pFtServerFont = dynamic_cast<FreetypeServerFont*>(pServerFont); + if (pFtServerFont) + { + if (pFtServerFont->GetGraphiteFace()) + { +#ifdef DEBUG + printf("IsGraphiteEnabledFont\n"); +#endif + return true; + } + } + return false; +} + +sal_GlyphId GraphiteLayoutImpl::getKashidaGlyph(int & width) +{ + int nKashidaIndex = mrServerFont.GetGlyphIndex( 0x0640 ); + if( nKashidaIndex != 0 ) + { + const GlyphMetric& rGM = mrServerFont.GetGlyphMetric( nKashidaIndex ); + width = rGM.GetCharWidth(); + } + else + { + width = 0; + } + return nKashidaIndex; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/glyphs/graphite_textsrc.cxx b/vcl/source/glyphs/graphite_textsrc.cxx new file mode 100644 index 000000000000..33508ae34712 --- /dev/null +++ b/vcl/source/glyphs/graphite_textsrc.cxx @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +// We need this to enable namespace support in libgrengine headers. +#define GR_NAMESPACE + +// Header files +// +// Standard Library +#include <string> +#include <cassert> +#include "graphite_textsrc.hxx" +#include <vcl/graphite_features.hxx> + +// class TextSourceAdaptor implementation. +// +TextSourceAdaptor::~TextSourceAdaptor() +{ + delete mpFeatures; +} + +gr::UtfType TextSourceAdaptor::utfEncodingForm() { + return gr::kutf16; +} + + +size_t TextSourceAdaptor::getLength() +{ + return maLayoutArgs.mnLength; +} + + +size_t TextSourceAdaptor::fetch(gr::toffset, size_t, gr::utf32 *) +{ + assert(false); + return 0; +} + + +size_t TextSourceAdaptor::fetch(gr::toffset offset, size_t char_count, gr::utf16 * char_buffer) +{ + assert(char_buffer); + + size_t copy_count = std::min(size_t(maLayoutArgs.mnLength), char_count); + std::copy(maLayoutArgs.mpStr + offset, maLayoutArgs.mpStr + offset + copy_count, char_buffer); + + return copy_count; +} + + +size_t TextSourceAdaptor::fetch(gr::toffset, size_t, gr::utf8 *) +{ + assert(false); + return 0; +} + + +inline void TextSourceAdaptor::getCharProperties(const int nCharIdx, int & min, int & lim, size_t & depth) +{ + maLayoutArgs.ResetPos(); + bool rtl = maLayoutArgs.mnFlags & SAL_LAYOUT_BIDI_RTL; + for(depth = ((rtl)? 1:0); maLayoutArgs.maRuns.GetRun(&min, &lim, &rtl); maLayoutArgs.maRuns.NextRun()) + { + if (min > nCharIdx) + break; + // Only increase the depth when a change of direction occurs. + depth += int(rtl ^ bool(depth & 0x1)); + if (min <= nCharIdx && nCharIdx < lim) + break; + } + // If there is no run for this position increment the depth, but don't + // change if this is out of bounds context + if (lim > 0 && nCharIdx >= lim && nCharIdx < maLayoutArgs.mnEndCharPos) + depth++; +} + + +bool TextSourceAdaptor::getRightToLeft(gr::toffset nCharIdx) +{ + size_t depth; + int min, lim = 0; + getCharProperties(nCharIdx, min, lim, depth); + //printf("getRtl %d,%x=%d\n", nCharIdx, maLayoutArgs.mpStr[nCharIdx], depth & 0x1); + return depth & 0x1; +} + + +unsigned int TextSourceAdaptor::getDirectionDepth(gr::toffset nCharIdx) +{ + size_t depth; + int min, lim; + getCharProperties(nCharIdx, min, lim, depth); + //printf("getDirectionDepth %d,%x=%d\n", nCharIdx, maLayoutArgs.mpStr[nCharIdx], depth); + return depth; +} + + +float TextSourceAdaptor::getVerticalOffset(gr::toffset) +{ + return 0.0f; //TODO: Implement correctly +} + +gr::isocode TextSourceAdaptor::getLanguage(gr::toffset) +{ + if (mpFeatures && mpFeatures->hasLanguage()) + return mpFeatures->getLanguage(); + gr::isocode unknown = {{0,0,0,0}}; + return unknown; +} + +std::pair<gr::toffset, gr::toffset> TextSourceAdaptor::propertyRange(gr::toffset nCharIdx) +{ + + if (nCharIdx < unsigned(maLayoutArgs.mnMinCharPos)) + return std::make_pair(0, maLayoutArgs.mnMinCharPos); + + if (nCharIdx < mnEnd) + return std::make_pair(maLayoutArgs.mnMinCharPos, mnEnd); + + return std::make_pair(mnEnd, maLayoutArgs.mnLength); +} + +size_t TextSourceAdaptor::getFontFeatures(gr::toffset, gr::FeatureSetting * settings) +{ + if (mpFeatures) return mpFeatures->getFontFeatures(settings); + return 0; +} + + +bool TextSourceAdaptor::sameSegment(gr::toffset char_idx1, gr::toffset char_idx2) +{ + const std::pair<gr::toffset, gr::toffset> + range1 = propertyRange(char_idx1), + range2 = propertyRange(char_idx2); + + return range1 == range2; +} + +void TextSourceAdaptor::setFeatures(const grutils::GrFeatureParser * pFeatures) +{ + mpFeatures = new grutils::GrFeatureParser(*pFeatures); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/glyphs/graphite_textsrc.hxx b/vcl/source/glyphs/graphite_textsrc.hxx new file mode 100644 index 000000000000..b5251cff2922 --- /dev/null +++ b/vcl/source/glyphs/graphite_textsrc.hxx @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#ifndef _SV_GRAPHITETEXTSRC_HXX +#define _SV_GRAPHITETEXTSRC_HXX +// Description: Implements the Graphite interfaces IGrTextSource and +// IGrGraphics which provide Graphite with access to the +// app's text storage system and the platform's font and +// graphics systems. + +// We need this to enable namespace support in libgrengine headers. +#define GR_NAMESPACE + +// Standard Library +#include <stdexcept> +// Platform + +#ifdef WNT +#include <windows.h> +#endif + +#include <vcl/salgdi.hxx> + +#include <vcl/sallayout.hxx> + +// Module +#include "vcl/dllapi.h" + +// Libraries +#include <graphite/GrClient.h> +#include <graphite/Font.h> +#include <graphite/ITextSource.h> + +// Module type definitions and forward declarations. +// +namespace grutils +{ + class GrFeatureParser; +} +// Implements the Adaptor pattern to adapt the LayoutArgs and the ServerFont interfaces to the +// gr::IGrTextSource interface. +// @author tse +// +class TextSourceAdaptor : public gr::ITextSource +{ +public: + TextSourceAdaptor(ImplLayoutArgs &layout_args, const int nContextLen) throw(); + ~TextSourceAdaptor(); + virtual gr::UtfType utfEncodingForm(); + virtual size_t getLength(); + virtual size_t fetch(gr::toffset ichMin, size_t cch, gr::utf32 * prgchBuffer); + virtual size_t fetch(gr::toffset ichMin, size_t cch, gr::utf16 * prgchwBuffer); + virtual size_t fetch(gr::toffset ichMin, size_t cch, gr::utf8 * prgchsBuffer); + virtual bool getRightToLeft(gr::toffset ich); + virtual unsigned int getDirectionDepth(gr::toffset ich); + virtual float getVerticalOffset(gr::toffset ich); + virtual gr::isocode getLanguage(gr::toffset ich); + + virtual std::pair<gr::toffset, gr::toffset> propertyRange(gr::toffset ich); + virtual size_t getFontFeatures(gr::toffset ich, gr::FeatureSetting * prgfset); + virtual bool sameSegment(gr::toffset ich1, gr::toffset ich2); + virtual bool featureVariations() { return false; } + + operator ImplLayoutArgs & () throw(); + void setFeatures(const grutils::GrFeatureParser * pFeatures); + const ImplLayoutArgs & getLayoutArgs() const { return maLayoutArgs; } + size_t getContextLength() const { return mnEnd; }; + inline void switchLayoutArgs(ImplLayoutArgs & newArgs); +private: + // Prevent the generation of a default assignment operator. + TextSourceAdaptor & operator=(const TextSourceAdaptor &); + + void getCharProperties(const int, int &, int &, size_t &); + + ImplLayoutArgs maLayoutArgs; + size_t mnEnd; + const grutils::GrFeatureParser * mpFeatures; +}; + +inline TextSourceAdaptor::TextSourceAdaptor(ImplLayoutArgs &la, const int nContextLen) throw() + : maLayoutArgs(la), + mnEnd(std::min(la.mnLength, nContextLen)), + mpFeatures(NULL) +{ +} + +inline TextSourceAdaptor::operator ImplLayoutArgs & () throw() { + return maLayoutArgs; +} + +inline void TextSourceAdaptor::switchLayoutArgs(ImplLayoutArgs & aNewArgs) +{ + mnEnd += aNewArgs.mnMinCharPos - maLayoutArgs.mnMinCharPos; + maLayoutArgs = aNewArgs; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/glyphs/makefile.mk b/vcl/source/glyphs/makefile.mk new file mode 100644 index 000000000000..cbc9a38b4f9e --- /dev/null +++ b/vcl/source/glyphs/makefile.mk @@ -0,0 +1,80 @@ +#************************************************************************* +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# Copyright 2000, 2010 Oracle and/or its affiliates. +# +# OpenOffice.org - a multi-platform office productivity suite +# +# This file is part of OpenOffice.org. +# +# OpenOffice.org is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License version 3 +# only, as published by the Free Software Foundation. +# +# OpenOffice.org is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License version 3 for more details +# (a copy is included in the LICENSE file that accompanied this code). +# +# You should have received a copy of the GNU Lesser General Public License +# version 3 along with OpenOffice.org. If not, see +# <http://www.openoffice.org/license.html> +# for a copy of the LGPLv3 License. +# +#************************************************************************* + +PRJ=..$/.. + +PRJNAME=vcl +TARGET=glyphs + +ENABLE_EXCEPTIONS=true +# --- Settings ----------------------------------------------------- + +.INCLUDE : $(PRJ)$/util$/makefile.pmk +.INCLUDE : settings.mk +.INCLUDE : $(PRJ)$/util$/makefile2.pmk + +CFLAGS+= $(FREETYPE_CFLAGS) + +# --- Files -------------------------------------------------------- + +.IF "$(USE_BUILTIN_RASTERIZER)" != "" +# GlyphCache + FreeType support (only on UNX platforms currently) +SLOFILES=\ + $(SLO)$/glyphcache.obj \ + $(SLO)$/gcach_rbmp.obj \ + $(SLO)$/gcach_layout.obj \ + $(SLO)$/gcach_ftyp.obj + +.IF "$(ENABLE_GRAPHITE)" != "" +# Graphite support using the glyphcache infrastructure +CFLAGS+=-DENABLE_GRAPHITE +SLOFILES+=\ + $(SLO)$/graphite_features.obj \ + $(SLO)$/graphite_serverfont.obj \ + $(SLO)$/graphite_layout.obj + +.ENDIF + +.ELSE + +.IF "$(ENABLE_GRAPHITE)" == "TRUE" +# Graphite support on non-UNX platforms +SLOFILES=\ + $(SLO)$/graphite_features.obj \ + $(SLO)$/graphite_layout.obj + +.IF "$(SYSTEM_GRAPHITE)" != "YES" +CDEFS+=-DGR2_STATIC +.ENDIF + +.ENDIF +.ENDIF + +# --- Targets ------------------------------------------------------ + +.INCLUDE : target.mk + |